commit 8c92ace89a0279b153432be26870b7974b5eaffb Author: sloum Date: Fri Mar 20 22:54:38 2020 -0700 Initial commit diff --git a/main.go b/main.go new file mode 100644 index 0000000..3081a86 --- /dev/null +++ b/main.go @@ -0,0 +1,244 @@ +package main + +import ( + "bufio" + "encoding/csv" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" +) + +var swPath, fPath string + +/////////////////////////////// +// Startup & helpers +////////////////////////////// + +func ifErrExit(e error, msg string) { + if e != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", msg, e.Error()) + os.Exit(1) + } +} + +func earlyExit(msg string) { + fmt.Fprintf(os.Stderr, "Error: %s\n", msg) + os.Exit(1) +} + +// validateDataPaths makes sure that the necessary data folders exist +// for the user running spacewalk. It builds them if they are not +// present and does an error exit if there is an error. +func validateDataPaths() { + xdgDataHome := os.Getenv("XDG_DATA_HOME") + if xdgDataHome == "" { + usr, _ := user.Current() + xdgDataHome = filepath.Join(usr.HomeDir, "/.local/share") + } + neededFolders := filepath.Join(xdgDataHome, "spacewalk") + neededFolders, _ = filepath.Abs(neededFolders) + swPath = neededFolders + neededFolders = filepath.Join(neededFolders, "flights") + fPath = neededFolders + err := os.MkdirAll(neededFolders, 0755) + ifErrExit(err, "Error validation data paths") + p := filepath.Join(swPath, "flight-manifest") + f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + ifErrExit(err, "Could not create flight manifest") + f.Close() +} + +func displayUsage() { + fmt.Println("spacewalk [stuff] [things]") + os.Exit(0) +} + +// parseArgs is the main entry point into scbm and will run the appropriate functions +// based on the arguments passed in at run time. +func parseArgs() { + a := os.Args + if len(a) == 1 { + displayUsage() + } + + switch a[1] { + case "create": + if len(a) == 3 { + create(a[2]) + } else { + earlyExit("Missing flight name.\n- spacewalk create \033[3mflight\033[0m") + } + case "update": + if len(a) == 3 { + update(a[2]) + } else { + earlyExit("Missing flight name.\n- spacewalk update \033[3mflight\033[0m") + } + case "remove": + if len(a) == 3 { + remove(a[2]) + } else { + earlyExit("Missing flight name.\n- spacewalk remove \033[3mflight\033[0m") + } + case "add": + if len(a) == 5 { + add(a[2], a[3], a[4]) + } else { + earlyExit("Incorrect syntax.\n- spacewalk add \033[3mflight url title\033[0m") + } + case "delete": + if len(a) == 4 { + del(a[2], a[3]) + } else { + earlyExit("Incorrect syntax.\n- spacewalk delete \033[3mflight url|title\033[0m") + } + case "launch": + if len(a) == 2 { + launchFlights() + } else if len(a) == 3 { + launchFlight(a[2]) + } else { + earlyExit("Incorrect syntax.\n- spacewalk launch [\033[3mflight\033[0m]") + } + case "show": + if len(a) == 2 { + showFlights("") + } else if len(a) == 3 { + showFlights(a[2]) + } else { + earlyExit("Incorrect syntax.\n- spacewalk show [\033[3mflight\033[0m]") + } + default: + earlyExit(fmt.Sprintf("Unknown command %q", a[1])) + } +} + +func getLine(prefix string) (string, error) { + reader := bufio.NewReader(os.Stdin) + fmt.Print(prefix) + text, err := reader.ReadString('\n') + if err != nil { + return "", err + } + + return text[:len(text)-1], nil +} + +func readManifest() [][]string { + p := filepath.Join(swPath, "flight-manifest") + f, err := os.Open(p) + ifErrExit(err, "Could not open flight manifest") + defer f.Close() + r := csv.NewReader(f) + records, err := r.ReadAll() + ifErrExit(err, "Could not read from flight manifest") + return records +} + +/////////////////////////////// +// Command Execution +////////////////////////////// + +func create(flight string) { + var lp, hp, fp string + var err error + records := readManifest() + for _, r := range records { + if r[0] == flight { + earlyExit(fmt.Sprintf("There is already a flight with the name %q", flight)) + } + } + + fmt.Printf("Creating flight: %q\n", flight) + + happy := false + for !happy { + lp, err = getLine("Enter the launch path (output path including file name): ") + ifErrExit(err, "Error reading from stdin") + if lp == "" { + fmt.Println("Launch path cannot be empty") + continue + } + hp, err = getLine("Enter the header path, or leave blank for none: ") + ifErrExit(err, "Error reading from stdin") + if hp == "" { + hp = "none" + } + fp, err = getLine("Enter the footer path, or leave blank for none: ") + ifErrExit(err, "Error reading from stdin") + if fp == "" { + fp = "none" + } + fmt.Println("Are you happy with the following:") + fmt.Printf("Launch path: %s\nHeader path: %s\nFooter path: %s\n", lp, hp, fp) + yesNo, err := getLine("Type 'yes' to accept, anything else to redo: ") + ifErrExit(err, "Error reading from stdin") + + if strings.ToLower(yesNo) == "yes" { + happy = true + } + } + + p := filepath.Join(swPath, "flight-manifest") + f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + ifErrExit(err, "Could not open flight manifest") + defer f.Close() + ln := fmt.Sprintf("%s, %s, %s, %s\n", flight, lp, hp, fp) + _, err = f.WriteString(ln) + ifErrExit(err, "Unable to save new flight to data file") + fpath := filepath.Join(fPath, flight) + ff, err := os.OpenFile(fpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + ifErrExit(err, fmt.Sprintf("Could not create flight log for %s", flight)) + ff.Close() +} + +func update(flight string) { + fmt.Println(flight) +} + +func remove(flight string) { + fmt.Println(flight) +} + +func add(flight, url, title string) { + p := filepath.Join(fPath, "flights", flight) + f, err := os.OpenFile(p, os.O_APPEND|os.O_WRONLY, 0644) + ifErrExit(err, fmt.Sprintf("Could not open flight log for %s", flight)) + defer f.Close() + ln := fmt.Sprintf("%s, %s, %s, %s\n", title, url, "nil", "0") + _, err = f.WriteString(ln) + ifErrExit(err, "Unable to save new capsule to data file") + fmt.Printf("Capsule %q added to %s's flight log", title, flight) +} + +func del(flight, item string) { + fmt.Println(flight, item) +} + +func launchFlights() { + fmt.Println("Hello") +} + +func launchFlight(flight string) { + fmt.Println(flight) +} + +func showFlights(flight string) { + records := readManifest() + for _, row := range records { + if flight != "" && row[0] != flight { + continue + } + fmt.Printf("\033[1mFlight Name:\033[0m %s\n", row[0]) + fmt.Printf("\033[1mLaunch Path:\033[0m %s\n", row[1]) + fmt.Printf("\033[1mHeader Path:\033[0m %s\n", row[2]) + fmt.Printf("\033[1mFooter Path:\033[0m %s\n--\n", row[3]) + } +} + +func main() { + validateDataPaths() + parseArgs() +} diff --git a/spacewalk b/spacewalk new file mode 100755 index 0000000..3513675 Binary files /dev/null and b/spacewalk differ diff --git a/spacewalk.md b/spacewalk.md new file mode 100644 index 0000000..dbe7048 --- /dev/null +++ b/spacewalk.md @@ -0,0 +1,72 @@ +# spacewalk + +This is intended to be a golang based moku-pona-_like_ program for gemini. + + +## commands + +### create + +Adds a new flight to the system's spacewalk flight list. + +- spacewalk create _flight_ + +### update + +Updates the settings for a flight + +- spacewalk update _flight_ + +### remove + +Removes an existing flight. + +- spacewalk remove _flight_ + +### add + +Adds a capsule to the a flight's feed. Requires the URL and a display name. + +- spacewalk add flight _url_ _title_ + +### delete + +Removes a capsule from the feed. Pass either a url or a display name. + +- spacewalk delete flight _url|title_ + +### show + +When called without further arguments, will list the available flights. When called with a flight as an argument will list the capsules for that flight. + +- spacewalk show [flight] + +### launch + +Checks for updates and builds the directory capsule. Takes a flight name to launch just that flight or will launch all flights when not passed a flight name. + +- spacewalk launch [flight] + + +## files + +### flight config + +Paths: $XDG\_DATA\_HOME/spacewalk/flight-config || ~/.local/share/spacewalk/flight-config +Format: csv +Structure: flight-name, flight-slug, launch-path, header-path, footer-path + +### flight data + +Paths: $XDG\_DATA\_HOME/spacewalk/flights/{flight-slug} || ~/.local/share/spacewalk/flights/{flight-slug} +Format: csv +Structure: capsule-name, capsule-url, checksum, timestamp + + +## glossary + +capsule : a gemini site/page (a gemini URL) +flight : a collection of capsules for which spacewalk can generate directory capsules. + + +