-
Notifications
You must be signed in to change notification settings - Fork 2
/
cli.go
316 lines (292 loc) · 8.67 KB
/
cli.go
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
package main
import (
"errors"
"fmt"
"strconv"
"strings"
docopt "github.com/docopt/docopt-go"
b "github.com/barsanuphe/endive/book"
en "github.com/barsanuphe/endive/endive"
"github.com/barsanuphe/helpers"
)
const (
incorrectInput = "Incorrect input, check endive -h for complete help."
incorrectIDValue = "Incorrect ID."
noBookFound = "Book with ID %d cannot be found"
numberOfBooksHeader = "# of Books"
incorrectFlag = "--first and --last only support integer values"
directoryDoesNotExist = "Directory %s does not exist"
invalidLimit = -1
infoTags = "Tags"
infoSeries = "Series"
infoPublishers = "Publishers"
infoAuthors = "Authors"
infoBook = "Book"
infoGeneral = "General"
endiveVersion = "Endive -- CLI Epub collection manager -- v1.0."
endiveUsage = `
Endive.
This is an epub collection manager.
The main commands are:
config Display current configuration
collection Do some maintenance on the collection
import, i Import epubs to the collection
export, x Export epubs to ereader
info Display information
edit Edit metadata
progress, p Set book reading progress
list, ls List books
search, s Search for specific books
Searching / Exporting:
A list of strings can be given as input to search for books.
It is also possible to restrict a value to a specific field: field:value.
Valid fields are:
author, title, year, language, series, tag, publisher, category,
type, genre, description, exported, progress, review.
Examples:
'author:XX title:YY' will give results satifsying any of the two conditions.
'author:XX +title:YY' will give results satifsying both conditions.
'author:XX -title:YY' will give results satifsying the first condition excluding the second.
Usage:
endive config
endive collection (check|refresh|rebuild-index|check-index)
endive (import|i) ((retail|r)|(nonretail|nr)) [--list] [<epub>...]
endive (export|x) (all|(id <ID>...)|<search-criteria>...) [--dir=DIRECTORY]
endive info [tags|series|authors|publishers] [<ID>]
endive (list|ls) [--incomplete|--nonretail|--retail] [--first=N|--last=N] [--sort=SORT]
endive (search|s) <search-criteria>... [--first=N|--last=N] [--sort=SORT]
endive review <ID> <rating> [<review>]
endive set (unread|read|reading|shortlisted|(field <field_name> <value>)) <ID>...
endive edit [(field <field_name>)] <ID>...
endive reset [(field <field_name>)] <ID>...
endive -h | --help
endive --version
Options:
-h --help Show this screen.
--version Show version.
--list List importable epubs only.
--dir=DIRECTORY Override the export directory in the configuration file.
-f N --first=N Filter only the n first books.
-l N --last=N Filter only the n last books.
-s SORT --sort=SORT Sort results [default: id].
--incomplete Filter books with incomplete metadata.
--retail Only show retail books.
--nonretail Only show non-retail books.`
)
// CLI sorts and checks user input
type CLI struct {
builtInCommand bool
// argument values
books []*b.Book
collection en.Collection
collectionMap map[string]int
epubs []string
searchTerms []string
field string
value string
// flags
lastN int
firstN int
sortBy string
// config
showConfig bool
// collection
checkCollection bool
checkIndex bool
refreshCollection bool
rebuildIndex bool
// import
importRetail bool
importEpubs bool
listImport bool
// export
export bool
exportDirectory string
// info
info string
// search
search bool
// review
review bool
rating string
reviewText string
// list
list bool
// edit, set, reset
edit bool
set bool
reset bool
progress string
}
func (o *CLI) parseArgs(e *Endive, osArgs []string) error {
// parse arguments and options
args, err := docopt.Parse(endiveUsage, osArgs, true, endiveVersion, false, false)
if err != nil {
return err
}
if len(args) == 0 {
// builtin command, nothing to do.
o.builtInCommand = true
return nil
}
// init
o.collection = e.Library.Collection
var ok bool
// checking if IDs were given, getting relevant *Book-s
if args["<ID>"] != nil {
// test if string or []string
idsString, ok := args["<ID>"].([]string)
if !ok {
return errors.New(incorrectInput)
}
// if [<ID>], idsString can be an empty slice
if len(idsString) != 0 {
// convert to int
ids := []int{}
for _, i := range idsString {
id, err := strconv.Atoi(i)
if err != nil {
return errors.New(incorrectInput)
}
ids = append(ids, id)
}
if len(ids) == 0 {
return errors.New(incorrectIDValue)
}
// get the relevant Books
for _, id := range ids {
bk, err := o.collection.FindByID(id)
if err != nil {
return fmt.Errorf(noBookFound, id)
}
o.books = append(o.books, bk.(*b.Book))
}
// get the equivalent collection
o.collection = o.collection.WithID(ids...)
if len(o.books) == 0 {
return errors.New("No valid book found.")
}
}
}
// checking other common flags
o.firstN = invalidLimit
if args["--first"] != nil {
o.firstN, err = strconv.Atoi(args["--first"].(string))
if err != nil {
return errors.New(incorrectFlag)
}
}
o.lastN = invalidLimit
if args["--last"] != nil {
o.lastN, err = strconv.Atoi(args["--last"].(string))
if err != nil {
return errors.New(incorrectFlag)
}
}
o.sortBy = strings.ToLower(args["--sort"].(string))
if args["--dir"] != nil {
exportDir := args["--dir"].(string)
// check it exists, do not create in case of error
if !helpers.DirectoryExists(exportDir) {
return fmt.Errorf(directoryDoesNotExist, o.exportDirectory)
}
o.exportDirectory = exportDir
}
// commands
o.showConfig = args["config"].(bool)
if args["collection"].(bool) {
o.checkCollection = args["check"].(bool)
o.rebuildIndex = args["rebuild-index"].(bool)
o.refreshCollection = args["refresh"].(bool)
o.checkIndex = args["check-index"].(bool)
}
if args["import"].(bool) || args["i"].(bool) {
o.importEpubs = true
// if not retail, non-retail.
o.importRetail = args["retail"].(bool) || args["r"].(bool)
o.listImport = args["--list"].(bool)
o.epubs = args["<epub>"].([]string)
// cheking they are existing epubs
for _, epub := range o.epubs {
// assert epub exists
if _, err := helpers.FileExists(epub); err != nil {
return errors.New("Epub does not exist!")
}
// assert it's an epub
if !strings.HasSuffix(strings.ToLower(epub), en.EpubExtension) {
return errors.New(epub + " is not an epub")
}
}
}
o.search = args["search"].(bool) || args["s"].(bool)
o.searchTerms, ok = args["<search-criteria>"].([]string)
if ok && o.search && len(o.searchTerms) == 0 {
return errors.New("No search terms found.")
}
// if export all: o.collection is set to complete collection by default
// if export ids: o.collection is already set
// if export search: same for o.searchTerms
o.export = args["export"].(bool) || args["x"].(bool)
if args["info"].(bool) {
if args["tags"].(bool) {
o.info = infoTags
o.collectionMap = o.collection.Tags()
} else if args["series"].(bool) {
o.info = infoSeries
o.collectionMap = o.collection.Series()
} else if args["authors"].(bool) {
o.info = infoAuthors
o.collectionMap = o.collection.Authors()
} else if args["publishers"].(bool) {
o.info = infoPublishers
o.collectionMap = o.collection.Publishers()
} else {
if len(o.books) != 0 {
o.info = infoBook
} else {
o.info = infoGeneral
}
}
}
o.review = args["review"].(bool)
o.rating, ok = args["<rating>"].(string)
if ok {
// checking rating is between 0 and 5
if r, err := strconv.ParseFloat(o.rating, 32); err != nil || r > 5 || r < 0 {
return errors.New("Rating must be between 0 and 5.")
}
}
o.reviewText, _ = args["<review>"].(string)
o.list = args["list"].(bool) || args["ls"].(bool)
if args["--incomplete"].(bool) {
o.collection = o.collection.Incomplete()
}
if args["--retail"].(bool) {
o.collection = o.collection.Retail()
}
if args["--nonretail"].(bool) {
o.collection = o.collection.NonRetailOnly()
}
o.field, ok = args["<field_name>"].(string)
if ok {
o.field = strings.ToLower(o.field)
}
// check it's a valid field name
if ok && !b.CheckValidField(o.field) {
return errors.New("Invalid field!")
}
o.value, _ = args["<value>"].(string)
o.edit = args["edit"].(bool)
o.reset = args["reset"].(bool)
o.set = args["set"].(bool)
for _, p := range []string{"unread", "read", "reading", "shortlisted"} {
if args[p].(bool) {
o.progress = p
break
}
}
if o.set && !args["field"].(bool) && o.progress == "" {
return errors.New("Invalid progress")
}
return nil
}