@@ -20,11 +20,22 @@ import (
2020 "github.com/natefinch/lumberjack"
2121 "github.com/vartanbeno/go-reddit/reddit"
2222 "go.uber.org/zap"
23- "go.uber.org/zap/zapcore"
2423 kingpin "gopkg.in/alecthomas/kingpin.v2"
2524 "gopkg.in/yaml.v2"
2625)
2726
27+ // Version will be overwritten at build time
28+ var Version = "devVersion"
29+
30+ // Commit will be overwritten at build time
31+ var Commit = "devCommit"
32+
33+ // Date will be overwritten at build time
34+ var Date = "devDate"
35+
36+ // BuiltBy will be overwritten at build time
37+ var BuiltBy = "devBuiltBy"
38+
2839type subreddit struct {
2940 Name string
3041 Destination string
@@ -91,16 +102,10 @@ func genFilePath(destinationDir string, title string, fullURL string) (string, e
91102 return fileName , nil
92103}
93104
94- func readConfig (configPath string ) (* lumberjack.Logger , []subreddit , error ) {
95-
96- configBytes , err := ioutil .ReadFile (configPath )
97- if err != nil {
98- // It's ok to not have a config (first run, for example)
99- return nil , nil , errors .WithStack (err )
100- }
105+ func readConfig (configBytes []byte ) (* lumberjack.Logger , []subreddit ) {
101106
102107 cfg := config {}
103- err = yaml .UnmarshalStrict (configBytes , & cfg )
108+ err : = yaml .UnmarshalStrict (configBytes , & cfg )
104109 if err != nil {
105110 // not ok to get invalid YAML
106111 log .Panicf ("readConfig: yaml decode: %+v\n " , errors .WithStack (err ))
@@ -128,7 +133,14 @@ func readConfig(configPath string) (*lumberjack.Logger, []subreddit, error) {
128133 subreddits = append (subreddits , sr )
129134 }
130135
131- return lumberjackLogger , subreddits , nil
136+ return lumberjackLogger , subreddits
137+ }
138+
139+ func isImage (URL string ) error {
140+ if strings .HasSuffix (URL , ".jpg" ) {
141+ return nil
142+ }
143+ return errors .New ("string doesn't end in .jpg" )
132144}
133145
134146func grab (sugar * zap.SugaredLogger , subreddits []subreddit ) error {
@@ -149,6 +161,7 @@ func grab(sugar *zap.SugaredLogger, subreddits []subreddit) error {
149161 for _ , subreddit := range subreddits {
150162
151163 info , err := os .Stat (subreddit .Destination )
164+ // NOTE: should I move these checks to config parsing time?
152165 if err != nil {
153166 logAndPrint (sugar , os .Stderr ,
154167 "destination error - skipping all posts" ,
@@ -187,56 +200,59 @@ func grab(sugar *zap.SugaredLogger, subreddits []subreddit) error {
187200 }
188201
189202 for _ , post := range posts {
190- if strings .HasSuffix (post .URL , ".jpg" ) {
191-
192- filePath , err := genFilePath (subreddit .Destination , post .Title , post .URL )
193- if err != nil {
194- logAndPrint (sugar , os .Stderr ,
195- "genFilePath err" ,
196- "subreddit" , subreddit .Name ,
197- "url" , post .URL ,
198- "err" , errors .WithStack (err ),
199- )
200- continue
201- }
202- if fileExists (filePath ) {
203- logAndPrint (sugar , os .Stdout ,
204- "file already exists!" ,
205- "filePath" , filePath ,
206- )
207- continue
208- }
209- err = downloadFile (post .URL , filePath )
210- if err != nil {
211- logAndPrint (sugar , os .Stderr ,
212- "downloadFile error" ,
213- "subreddit" , subreddit .Name ,
214- "url" , post .URL ,
215- "err" , errors .WithStack (err ),
216- )
217-
218- } else {
219- logAndPrint (sugar , os .Stdout ,
220- "downloaded file" ,
221- "subreddit" , subreddit .Name ,
222- "filePath" , filePath ,
223- "url" , post .URL ,
224- )
225- }
226- } else {
203+ err = isImage (post .URL )
204+ if err != nil {
205+ logAndPrint (sugar , os .Stderr ,
206+ "can't download image" ,
207+ "subreddit" , subreddit .Name ,
208+ "url" , post .URL ,
209+ "err" , err ,
210+ )
211+ continue
212+ }
213+
214+ filePath , err := genFilePath (subreddit .Destination , post .Title , post .URL )
215+ if err != nil {
227216 logAndPrint (sugar , os .Stderr ,
228- "Could not download " ,
217+ "genFilePath err " ,
229218 "subreddit" , subreddit .Name ,
230219 "url" , post .URL ,
220+ "err" , errors .WithStack (err ),
231221 )
222+ continue
232223 }
224+ if fileExists (filePath ) {
225+ logAndPrint (sugar , os .Stdout ,
226+ "file already exists!" ,
227+ "filePath" , filePath ,
228+ )
229+ continue
230+ }
231+ err = downloadFile (post .URL , filePath )
232+ if err != nil {
233+ logAndPrint (sugar , os .Stderr ,
234+ "downloadFile error" ,
235+ "subreddit" , subreddit .Name ,
236+ "url" , post .URL ,
237+ "err" , errors .WithStack (err ),
238+ )
239+ continue
240+
241+ }
242+ logAndPrint (sugar , os .Stdout ,
243+ "downloaded file" ,
244+ "subreddit" , subreddit .Name ,
245+ "filePath" , filePath ,
246+ "url" , post .URL ,
247+ )
233248 }
234249 }
235-
236250 return nil
237251}
238252
239253func editConfig (sugar * zap.SugaredLogger , configPath string , editor string ) error {
254+ // TODO: make this a serialized config struct
255+ // so I get a compile warning if there's problems
240256 emptyConfigContent := []byte (`version: 2.0.0
241257# make lumberjacklogger nil to not log to file
242258lumberjacklogger:
@@ -258,6 +274,11 @@ subreddits:
258274 _ , err := os .Stat (configPath )
259275 if os .IsNotExist (err ) {
260276 err = ioutil .WriteFile (configPath , emptyConfigContent , 0644 )
277+ logAndPrint (
278+ sugar , os .Stdout ,
279+ "wrote default config" ,
280+ "configPath" , configPath ,
281+ )
261282 if err != nil {
262283 logAndPrint (
263284 sugar , os .Stderr ,
@@ -299,8 +320,9 @@ subreddits:
299320 return err
300321 }
301322
302- fmt .Printf ("Executing: %s %s\n " , executable , configPath )
303- sugar .Infow ("Executing" ,
323+ logAndPrint (
324+ sugar , os .Stdout ,
325+ "Opening editor" ,
304326 "editor" , executable ,
305327 "configPath" , configPath ,
306328 )
@@ -322,62 +344,8 @@ subreddits:
322344 return nil
323345}
324346
325- // newLogger builds a logger. if lumberjackLogger or fp are nil, then that respective sink won't be made
326- func newLogger (lumberjackLogger * lumberjack.Logger , fp * os.File , lvl zapcore.LevelEnabler ) * zap.Logger {
327- encoderConfig := zapcore.EncoderConfig {
328- // Keys can be anything except the empty string.
329- TimeKey : "timestamp" ,
330- LevelKey : "level" ,
331- NameKey : "name" , // TODO: what is this?
332- CallerKey : "caller" ,
333- FunctionKey : "function" , // zapcore.OmitKey,
334- MessageKey : "msg" ,
335- StacktraceKey : "stacktrace" ,
336- LineEnding : zapcore .DefaultLineEnding ,
337- EncodeLevel : zapcore .CapitalLevelEncoder ,
338- EncodeTime : zapcore .ISO8601TimeEncoder ,
339- EncodeDuration : zapcore .StringDurationEncoder ,
340- EncodeCaller : zapcore .ShortCallerEncoder ,
341- }
342-
343- coreSlice := make ([]zapcore.Core , 0 , 2 )
344-
345- if lumberjackLogger != nil {
346- jsonCore := zapcore .NewCore (
347- zapcore .NewJSONEncoder (encoderConfig ),
348- zapcore .AddSync (lumberjackLogger ),
349- lvl ,
350- )
351- coreSlice = append (coreSlice , jsonCore )
352- }
353-
354- if fp != nil {
355- encoderConfig .EncodeLevel = zapcore .CapitalColorLevelEncoder
356- fpCore := zapcore .NewCore (
357- zapcore .NewConsoleEncoder (encoderConfig ),
358- zapcore .Lock (fp ),
359- lvl ,
360- )
361- coreSlice = append (coreSlice , fpCore )
362- }
363-
364- combinedCore := zapcore .NewTee (
365- coreSlice ... ,
366- )
367-
368- logger := zap .New (
369- combinedCore ,
370- zap .AddCaller (),
371- // Using errors package to get better stack traces
372- // zap.AddStacktrace(stackTraceLvl),
373- // TODO: replace with version (goreleaser embeds it)
374- zap .Fields (zap .Int ("pid" , os .Getpid ())),
375- )
376-
377- return logger
378- }
379-
380347func run () error {
348+
381349 // cli and go!
382350 app := kingpin .New ("grabbit" , "Get top images from subreddits" ).UsageTemplate (kingpin .DefaultUsageTemplate )
383351 app .HelpFlag .Short ('h' )
@@ -389,6 +357,8 @@ func run() error {
389357
390358 grabCmd := app .Command ("grab" , "Grab images. Use `edit-config` first to create a config" )
391359
360+ versionCmd := app .Command ("version" , "print grabbit build and version information" )
361+
392362 cmd := kingpin .MustParse (app .Parse (os .Args [1 :]))
393363
394364 configPath , err := homedir .Expand (* appConfigPathFlag )
@@ -398,11 +368,15 @@ func run() error {
398368 panic (err )
399369 }
400370
401- lumberjackLogger , subreddits , cfgErr := readConfig (configPath )
371+ configBytes , cfgErr := ioutil .ReadFile (configPath )
372+
373+ // if configBytes == []byte{}, then this will be
374+ // defaulted to nothing and the logging won't work
375+ // Stuff will still be printed out with logAndPrint though
376+ lumberjackLogger , subreddits := readConfig (configBytes )
402377
403378 logger := newLogger (
404379 lumberjackLogger ,
405- // os.Stdout, // TODO: get an os.File from readConfig to put in here - os.Stderr
406380 nil ,
407381 zap .DebugLevel ,
408382 )
@@ -415,55 +389,32 @@ func run() error {
415389 return editConfig (sugar , configPath , * editConfigCmdEditorFlag )
416390 case grabCmd .FullCommand ():
417391 if cfgErr != nil {
418- fmt .Fprintf (os .Stderr , "Config error: maybe try `edit-config`: %v\n " , cfgErr )
392+ logAndPrint (
393+ sugar , os .Stderr ,
394+ "Config error - try `edit-config`" ,
395+ "cfgErr" , cfgErr ,
396+ "cfgErrMsg" , cfgErr .Error (),
397+ )
419398 return cfgErr
420399 }
421400 return grab (sugar , subreddits )
401+ case versionCmd .FullCommand ():
402+ logAndPrint (
403+ sugar , os .Stdout ,
404+ "Version and build information" ,
405+ "BuiltBy" , BuiltBy ,
406+ "Commit" , Commit ,
407+ "Date" , Date ,
408+ "Version" , Version ,
409+ )
422410 }
423411
424412 return nil
425413}
426414
427- // logAndPrint produces a more human readable error message for the console.
428- // It's really only designed for simple keys/value messages It prints Info if
429- // fp == os.Stdout, Error if fp == os.Stderr, and panics otherwise.
430- func logAndPrint (sugar * zap.SugaredLogger , fp * os.File , msg string , keysAndValues ... interface {}) {
431-
432- switch fp {
433- case os .Stdout :
434- sugar .Infow (msg , keysAndValues ... )
435- msg = "INFO: " + msg
436- case os .Stderr :
437- sugar .Errorw (msg , keysAndValues ... )
438- msg = "ERROR: " + msg
439- default :
440- log .Panicf ("logAndPrint: fp not os.Stderr or os.Stdout: %#v\n " , fp )
441- }
442-
443- length := len (keysAndValues )
444- if length % 2 != 0 {
445- log .Panicf ("printMsgKeysAndValues: len() not even: %#v\n " , keysAndValues ... )
446- }
447-
448- keys := make ([]string , length / 2 )
449- values := make ([]interface {}, length / 2 )
450- for i := 0 ; i < length / 2 ; i ++ {
451- keys [i ] = keysAndValues [i * 2 ].(string )
452- values [i ] = keysAndValues [i * 2 + 1 ]
453- }
454-
455- fmtStr := msg + "\n "
456- for i := 0 ; i < length / 2 ; i ++ {
457- fmtStr += (" " + keys [i ] + ": %#v\n " )
458- }
459-
460- fmtStr += "\n "
461- fmt .Fprintf (fp , fmtStr , values ... )
462- }
463-
464415func main () {
465416 err := run ()
466417 if err != nil {
467- panic ( err )
418+ os . Exit ( 1 )
468419 }
469420}
0 commit comments