// Copyright (C) 2021 nytpu // SPDX-License-Identifier: AGPL-3.0-or-later // For more license details, see LICENSE or . // comitium is a feed aggregator for gemini. see README.md for more details and // doc/comitium.1.scd for a manual page. package main import ( "fmt" "net/url" "os" "path/filepath" "github.com/mitchellh/go-homedir" "golang.nytpu.com/comitium/core" "golang.nytpu.com/comitium/fetch" ) func main() { argCheck(os.Args, 2, true, "Please provide a command.") // these are special cases where we shouldn't bother to do anything else if os.Args[1] == "help" { usage() os.Exit(0) } if os.Args[1] == "version" { fmt.Printf("comitium v%v, commit %v\n", Version, Commit) os.Exit(0) } c, ok := commands[os.Args[1]] if !ok { fmt.Fprint(os.Stderr, "Unrecognized Command.\n\n") usage() os.Exit(1) } c.AddFlagSet(globalFs) c.Parse(os.Args[2:]) // dataPath is the location where all the data (list of subs, cached hashes, // etcetera) are stored dataPath := getDataPath() // make sure it exists so we can start working with files in it os.MkdirAll(dataPath, os.ModePerm) err := core.Init(dataPath) if err != nil { fmt.Fprintf(os.Stderr, "Error in initialization: %v\n", err) os.Exit(1) } switch os.Args[1] { case "refresh": argCheck(c.Args(), 0, false, "'refresh' doesn't take an argument!") fetch.RefreshAll(&core.Data, *refreshWorkersFlag) err = core.Data.WriteSubs(dataPath) if err != nil { fmt.Fprintf(os.Stderr, "Error saving subscriptions: %v\n", err) os.Exit(1) } case "list": argCheck(c.Args(), 0, false, "'list' doesn't take an argument!") core.Data.Export(os.Stdout) case "add": argCheck(c.Args(), 1, true, "Please provide a URL to add.") remote, err := url.Parse(c.Arg(0)) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing URL: %v\n", err) os.Exit(1) } if *addPageFlag { err = fetch.Page(&core.Data, remote, c.Arg(1)) } else { err = fetch.Feed(&core.Data, remote, c.Arg(1)) } if err != nil { fmt.Fprintf(os.Stderr, "Error adding URL: %v\n", err) os.Exit(1) } err = core.Data.WriteSubs(dataPath) if err != nil { fmt.Fprintf(os.Stderr, "Error saving subscriptions: %v\n", err) os.Exit(1) } case "remove": argCheck(c.Args(), 1, true, "Please provide a URL to remove.") delete(core.Data.Feeds, c.Arg(0)) delete(core.Data.Pages, c.Arg(0)) } err = core.Data.WriteJSON(dataPath) if err != nil { fmt.Fprintf(os.Stderr, "Error saving subscriptions json: %v\n", err) os.Exit(1) } } // argCheck makes sure that the target number of arguments are given. If // not, msg is displayed alongside usage information and then the program exits // with failure. func argCheck(args []string, target int, lt bool, msg string) { if (lt && len(args) < target) || (!lt && len(args) != target) { fmt.Fprint(os.Stderr, msg, "\n\n") usage() os.Exit(1) } } // verifyPath will expand ~ if neccessary and will clean the path. func verifyPath(p string) string { if n, err := homedir.Expand(p); err != nil { return filepath.Clean(p) } else { return filepath.Clean(n) } } // getDataPath will return a data path in a gracefully degrading tree, picking // the first that exists: // --data flag, $COMITIUM_DATA, $XDG_DATA_HOME/comitium, // $HOME/.local/share/comitium, $HOME/.comitium, $PWD (last resort) func getDataPath() string { if *dataFlag != "" { return verifyPath(*dataFlag) } if env := os.Getenv("COMITIUM_DATA"); env != "" { return verifyPath(env) } if env := os.Getenv("XDG_DATA_HOME"); env != "" { return verifyPath(env + "/comitium") } home, err := homedir.Dir() if err == nil { if _, err := os.Stat(verifyPath(home + "/.local")); !os.IsNotExist(err) { return verifyPath(home + "/.local/share/comitium") } return verifyPath(home + "/.comitium") } fmt.Fprintln(os.Stderr, "Error finding a suitable directory! Falling back to the current working directory. See \"comitium help\" or comitium(1) for more information.") return verifyPath("comitium") } func usage() { fmt.Fprintf(os.Stderr, "Usage:\n") fmt.Fprintf(os.Stderr, " %v [FLAGS] [ARGUMENTS]\n\n", os.Args[0]) fmt.Fprint(os.Stderr, "Commands:\n") for _, v := range commandInfo { // FIXME: find a way to dynamically match the spacing that // flag.PrintDefaults() outputs (instead of hardcoding "-20") fmt.Fprintf(os.Stderr, " %-20s%s\n", v.Name, v.Usage) } fmt.Fprintf(os.Stderr, "\nGlobal Flags:\n%v", globalFs.FlagUsages()) // command-specific flags should be printed here fmt.Fprintf(os.Stderr, "Flags for \"add\":\n%v", commands["add"].FlagUsages()) fmt.Fprintf(os.Stderr, "Flags for \"refresh\":\n%v\n", commands["refresh"].FlagUsages()) fmt.Fprint(os.Stderr, "For more help, see comitium(1).\n") }