From c8b8149be09aaf31687d8020807161c1d2b8f489 Mon Sep 17 00:00:00 2001 From: Bryson Steck Date: Fri, 11 Jul 2025 00:44:19 -0600 Subject: [PATCH] make checksum default behavior, pass ^C to running command --- listen.go | 80 +++++++++++++++++++++++++++++++++---------------- main.go | 26 ++++++++++------ man/listen.1.md | 9 ++++++ 3 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 man/listen.1.md diff --git a/listen.go b/listen.go index 9ebb05f..cae78d4 100644 --- a/listen.go +++ b/listen.go @@ -17,12 +17,14 @@ import ( "github.com/fsnotify/fsnotify" ) +var triggered bool = false + type Listen struct { FileMap map[string]bool CksumMap map[string][]byte IntervalMap map[string]time.Time Condition string - Cksum bool + NoCksum bool Interval time.Duration Command []string Quiet bool @@ -74,6 +76,7 @@ func allCheck(l Listen) bool { } func runTrigger(l Listen, f string, r bool, quit chan bool) bool { + triggered = true if !l.Quiet && !r { if l.Condition == "any" { printStatus(fmt.Sprintf("& File %s modified. ", f)) @@ -94,12 +97,32 @@ func runTrigger(l Listen, f string, r bool, quit chan bool) bool { log.Fatalln(err) } - if err := cmd.Wait(); err != nil { - if rc, ok := err.(*exec.ExitError); ok { - if !l.Quiet { - color.New(color.FgYellow).Fprintf(os.Stderr, "& WARNING: Command exited with code %d.\n", rc.ExitCode()) + exit := make(chan bool) + s := make(chan os.Signal, 1) + signal.Notify(s, os.Interrupt) + + go func() { + if err := cmd.Wait(); err != nil { + if rc, ok := err.(*exec.ExitError); ok { + if !l.Quiet { + switch rc.ExitCode() { + case -1: + color.New(color.FgYellow).Fprintf(os.Stderr, "\n& WARNING: Command interrupted with ^C.\n") + default: + color.New(color.FgYellow).Fprintf(os.Stderr, "& WARNING: Command exited with code %d.\n", rc.ExitCode()) + } + } } } + + exit <- true + }() + + select { + case <-s: + cmd.Process.Signal(os.Interrupt) + <-exit + case <-exit: } if !l.Quiet { @@ -111,9 +134,11 @@ func runTrigger(l Listen, f string, r bool, quit chan bool) bool { } quit <- true + triggered = false return true } + triggered = false return false } @@ -140,13 +165,7 @@ func loopFsnotify(l Listen, quit chan bool) { for key := range l.FileMap { if event.Name == key { - if l.Cksum { - // return value indicates a break is needed - if cksumCheck(l, key, &trigger) { - fileMod = key - break - } - } else { + if l.NoCksum { if event.Has(fsnotify.Write) { if l.Condition == "any" { fileMod = key @@ -169,6 +188,12 @@ func loopFsnotify(l Listen, quit chan bool) { l.FileMap[key] = true } + } else { + // return value indicates a break is needed + if cksumCheck(l, key, &trigger) { + fileMod = key + break + } } } } // end for @@ -206,13 +231,7 @@ func loopInterval(l Listen, quit chan bool) { trigger := false for key := range l.FileMap { - if l.Cksum { - // return value indicates a break is needed - if cksumCheck(l, key, &trigger) { - fileMod = key - break - } - } else { + if l.NoCksum { s, err := os.Stat(key) if err != nil { log.Fatal() @@ -228,7 +247,12 @@ func loopInterval(l Listen, quit chan bool) { l.FileMap[key] = true } - + } else { + // return value indicates a break is needed + if cksumCheck(l, key, &trigger) { + fileMod = key + break + } } } // end for key @@ -262,7 +286,7 @@ func startMessage(l Listen) { printStatus("& listen will exit when ") } - if l.Cksum { + if !l.NoCksum { printStatus("the checksum of ") } @@ -278,10 +302,10 @@ func startMessage(l Listen) { printStatus("this file has ") } - if l.Cksum { - printStatus("changed:\n") - } else { + if l.NoCksum { printStatus("been modified:\n") + } else { + printStatus("changed:\n") } for key := range l.FileMap { @@ -317,7 +341,13 @@ func (l Listen) Run() { select { case <-c: - fmt.Println() + for { + if !triggered { + fmt.Println() + break + } + <-c + } case <-quit: } diff --git a/main.go b/main.go index 72638ff..f35ee45 100644 --- a/main.go +++ b/main.go @@ -13,16 +13,16 @@ import ( flags "github.com/jessevdk/go-flags" ) -const VERSION string = "v0.3.1" +const VERSION string = "v0.4.0" type Options struct { - Version bool `short:"v" long:"version" description:"Displays version info and exits"` - Quiet bool `short:"q" long:"quiet" description:"Suppresses status messages (stderr lines beginning with '&')"` - 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"` - RunFirst bool `short:"r" long:"run" description:"Runs COMMAND (if specified) before starting listen"` + Version bool `short:"v" long:"version" description:"Displays version info and exits"` + Quiet bool `short:"q" long:"quiet" description:"Suppresses status messages (stderr lines beginning with '&')"` + 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"` + NoChecksum bool `short:"x" long:"no-checksum" description:"Do not calculate checksum as an additional check for file changes (see MODES section in man page for more info)"` + 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"` + RunFirst bool `short:"r" long:"run" description:"Runs COMMAND (if specified) before starting listen"` } func validateArgs(opts Options, commandLen int) error { @@ -103,7 +103,7 @@ func setup(opts Options, command []string) { } if success { - l := Listen{filesMap, cksumMap, intervalMap, opts.Condition, opts.Checksum, intervalDuration, command, opts.Quiet, opts.RunFirst} + l := Listen{filesMap, cksumMap, intervalMap, opts.Condition, opts.NoChecksum, intervalDuration, command, opts.Quiet, opts.RunFirst} l.Run() } } @@ -114,6 +114,13 @@ func main() { parser.Usage = "[OPTIONS] -- [COMMAND]" remaining, err := parser.Parse() + defer func() { + if err := recover(); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + }() + if err == nil { if opts.Version { fmt.Printf("listen %s\n", VERSION) @@ -121,4 +128,5 @@ func main() { setup(opts, remaining) } } + } diff --git a/man/listen.1.md b/man/listen.1.md new file mode 100644 index 0000000..b0e0207 --- /dev/null +++ b/man/listen.1.md @@ -0,0 +1,9 @@ +% listen | General Commands Manual + +# NAME + +listen - A simple watcher program that runs commands when specified files are modified + +# SYNOPSIS + +listen [OPTIONS] -- [COMMAND]