1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
// Copyright (C) 2021 nytpu
// SPDX-License-Identifier: AGPL-3.0-or-later
// For more license details, see LICENSE or <https://www.gnu.org/licenses/agpl-3.0.html>.
// 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)
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)
}
case "remove":
argCheck(c.Args(), 1, true, "Please provide a URL to remove.")
// TODO
fmt.Fprintln(os.Stderr, "Not Yet Implemented")
}
err = core.Data.WriteJSON(dataPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error saving subscriptions: %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 <command> [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")
}
|