package main import ( "crypto/sha256" "fmt" "io" "io/fs" "log" "os" "path/filepath" "time" flags "github.com/jessevdk/go-flags" ) const VERSION string = "v0.1.0" var opts struct { Version bool `short:"v" long:"version" description:"Displays version info and exits"` Quiet bool `short:"q" long:"quiet" description:"Suppresses all non-error output"` Files []string `short:"f" long:"file" description:"File(s) to listen to (watch)" value-name:"FILE"` Condition string `short:"w" long:"when" description:"If multiple files are specified, choose if any file or all files specified are needed to trigger COMMAND" default:"any" choice:"any" choice:"all"` Checksum bool `short:"c" long:"checksum" description:"Use checksum to determine when file(s) are changed instead of writes/modification time"` Interval string `short:"i" long:"interval" description:"Use this time interval (ex. 5m30s, 1s) between filesystem checks instead of watching kernel events. If the interval is effectively 0 (the default), kernel events are used" default:"0s"` } func validateArgs(files []string, interval string) error { // file checks if len(files) <= 0 { fmt.Println("listen: at least one file (-f) is required") return flags.ErrCommandRequired } for _, file := range files { info, err := os.Stat(file) if err != nil { fmt.Printf("listen: %s\n", err) return err } if info.IsDir() { fmt.Printf("listen: %s: not a file\n", file) return fs.ErrInvalid } _, err = os.Open(file) if err != nil { fmt.Printf("listen: %s\n", err) return err } } // interval checks if _, err := time.ParseDuration(interval); err != nil { fmt.Printf("listen: %s\n", err) return err } return nil } func main() { parser := flags.NewParser(&opts, flags.Default) parser.Usage = "[OPTIONS] -- [COMMAND]" remaining, err := parser.Parse() if err == nil { if opts.Version { fmt.Printf("listen %s\n", VERSION) } else if err := validateArgs(opts.Files, opts.Interval); err == nil { var filesMap map[string]bool = make(map[string]bool) var cksumMap map[string][]byte = make(map[string][]byte) var intervalMap map[string]time.Time = make(map[string]time.Time) intervalDuration, _ := time.ParseDuration(opts.Interval) success := true for _, file := range opts.Files { hasher := sha256.New() f, err := os.Open(file) if err != nil { success = false break } defer f.Close() s, err := os.Stat(file) if err != nil { success = false break } if _, err := io.Copy(hasher, f); err != nil { log.Fatal(err) } abs, err := filepath.Abs(file) if err == nil { filesMap[abs] = false cksumMap[abs] = hasher.Sum(nil) intervalMap[abs] = s.ModTime() } else { success = false break } } if success { l := Listen{filesMap, cksumMap, intervalMap, opts.Condition, opts.Checksum, intervalDuration, remaining, opts.Quiet} l.Run() } } } }