From ea9b2aba2fb08423492d08cd9cc2b5a731291782 Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:29:49 +0200 Subject: [PATCH 1/8] add validPath --- utils.go | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/utils.go b/utils.go index fced335..484b421 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,7 @@ package main import ( "io/fs" + "os" "path/filepath" "slices" "strings" @@ -22,21 +23,6 @@ func addPathRecursively(root string, watcher *fsnotify.Watcher) error { return err } -func parseCommands(cmd string) [][]string { - cmd = strings.TrimSpace(cmd) - cmds := strings.Split(cmd, ";") - var res [][]string - for _, cmd := range cmds { - cmd = strings.TrimSpace(cmd) - cmds = strings.Split(cmd, " ") - if len(cmds) < 1 || cmds[0] == "" { - continue - } - res = append(res, cmds) - } - return res -} - var excludedFolders = []string{ "node_modules", "vendor", @@ -58,3 +44,8 @@ var excludedFolders = []string{ "tmp", "build", } + +func validPath(path string) bool { + _, err := os.Stat(path) + return err == nil +} From 4605320ae8a444b7b041dab4e5ad15336671f778 Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:30:07 +0200 Subject: [PATCH 2/8] create parseCommand(cmd string) *exec.Cmd function --- utils.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utils.go b/utils.go index 484b421..e17f06e 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "io/fs" "os" + "os/exec" "path/filepath" "slices" "strings" @@ -23,6 +24,15 @@ func addPathRecursively(root string, watcher *fsnotify.Watcher) error { return err } +func parseCommand(cmd string) *exec.Cmd { + cmd = strings.TrimSpace(cmd) + parts := strings.Split(cmd, " ") + if len(parts) < 2 { + return nil + } + return exec.Command(parts[0], parts[1:]...) +} + var excludedFolders = []string{ "node_modules", "vendor", From bb7fcf3bddc96f36ba9b70d0c48091824f604ee0 Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:40:14 +0200 Subject: [PATCH 3/8] update watchEvents --- watcher.go | 96 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/watcher.go b/watcher.go index db41313..517459b 100644 --- a/watcher.go +++ b/watcher.go @@ -3,39 +3,17 @@ package main import ( "fmt" "os" - "os/exec" - "strings" "time" "github.com/fsnotify/fsnotify" ) -type watcherOptions struct { - path string - commands [][]string - registredEvents []fsnotify.Op - recursive bool -} - -func (opt *watcherOptions) print() { - fmt.Println("👀 Watcher v0.1.0") - fmt.Printf("📂 Path: %s\n", opt.path) - fmt.Printf("🔍 Events: %v\n", opt.registredEvents) - fmt.Printf("🔄 Recursive: %v\n", opt.recursive) - - if len(opt.commands) > 0 { - fmt.Println("🚀 Commands to run:") - for _, command := range opt.commands { - fmt.Printf(" %v\n", strings.Join(command, " ")) - } - } else { - fmt.Println("⚠️ No commands specified to run on events. Events will be printed to stdout.") - } - - fmt.Println("\nlistening for events...") -} +// fmt.Println("👀 Watcher v0.1.0") +// fmt.Printf("📂 Path: %s\n", opt.path) +// fmt.Printf("🔍 Events: %v\n", opt.registredEvents) +// fmt.Printf("🔄 Recursive: %v\n", opt.recursive) -func watchEvents(watcher *fsnotify.Watcher, options watcherOptions) { +func watchEvents(watcher *fsnotify.Watcher, cf CommandsFile) { if watcher == nil { panic("watcher is nil!") } @@ -47,27 +25,65 @@ func watchEvents(watcher *fsnotify.Watcher, options watcherOptions) { case event, ok := <-watcher.Events: if !ok { return + + } + if !(time.Since(eventTime) > (time.Millisecond*400) || lastEvent != event.Op) { + continue } - for _, op := range options.registredEvents { - if event.Has(op) && (time.Since(eventTime) > (time.Millisecond*400) || lastEvent != event.Op) { - if len(options.commands) == 0 { - fmt.Printf("%s %s\n", time.Now().Format("2006-01-02 15:04:05"), event) - continue + switch event.Op.String() { + case fsnotify.Write.String(): + for _, v := range cf.Write { + if cmd := wrapCmd(parseCommand(v)); cmd != nil { + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running command %q: %s\n", v, err) + continue + } + } + } + case fsnotify.Create.String(): + for _, v := range cf.Create { + if cmd := wrapCmd(parseCommand(v)); cmd != nil { + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running command %q: %s\n", v, err) + continue + } + } + } + case fsnotify.Remove.String(): + for _, v := range cf.Remove { + if cmd := wrapCmd(parseCommand(v)); cmd != nil { + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running command %q: %s\n", v, err) + continue + } } - for _, s := range options.commands { - cmd := exec.Command(s[0], s[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Start() + } + case fsnotify.Rename.String(): + for _, v := range cf.Rename { + if cmd := wrapCmd(parseCommand(v)); cmd != nil { + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "error running command %q: %s\n", v, err) + continue + } + } + } + case fsnotify.Chmod.String(): + for _, v := range cf.Chmod { + if cmd := wrapCmd(parseCommand(v)); cmd != nil { + err := cmd.Run() if err != nil { - fmt.Fprintf(os.Stderr, "error: can't start command: %s\n", err.Error()) + fmt.Fprintf(os.Stderr, "error running command %q: %s\n", v, err) continue } - eventTime = time.Now() - lastEvent = event.Op } } } + eventTime = time.Now() + lastEvent = event.Op case err, ok := <-watcher.Errors: if !ok { return From 35b34d2cce88eadbae9caf158b2d7ac0f07049cb Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:40:38 +0200 Subject: [PATCH 4/8] add wrapCmd func --- utils.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/utils.go b/utils.go index e17f06e..afe869d 100644 --- a/utils.go +++ b/utils.go @@ -33,6 +33,14 @@ func parseCommand(cmd string) *exec.Cmd { return exec.Command(parts[0], parts[1:]...) } +func wrapCmd(cmd *exec.Cmd) *exec.Cmd { + if cmd != nil { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + return cmd +} + var excludedFolders = []string{ "node_modules", "vendor", From 17dab1422230200178676b731f1b788ea09634da Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:41:19 +0200 Subject: [PATCH 5/8] add package for parsing yaml files --- go.mod | 7 ++++++- go.sum | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f1aeb7b..7182bae 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,9 @@ go 1.21.4 require github.com/fsnotify/fsnotify v1.7.0 -require golang.org/x/sys v0.4.0 // indirect +require ( + github.com/alexflint/go-arg v1.4.3 // indirect + github.com/alexflint/go-scalar v1.2.0 // indirect + golang.org/x/sys v0.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index ccd7ce9..c684857 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,19 @@ +github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= +github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= +github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b346908c5bfcc0098a75fa4e843618c95c6a92db Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:41:34 +0200 Subject: [PATCH 6/8] create args type --- args.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 args.go diff --git a/args.go b/args.go new file mode 100644 index 0000000..f3529dc --- /dev/null +++ b/args.go @@ -0,0 +1,24 @@ +package main + +import ( + "gopkg.in/yaml.v3" +) + +type args struct { + Path string `arg:"-p,--path"` + File string `arg:"-f,--file, required"` + Recursive bool `arg:"-r,--recursive"` +} + +type CommandsFile struct { + Write []string `yaml:"write"` + Chmod []string `yaml:"chmod"` + Rename []string `yaml:"rename"` + Remove []string `yaml:"remove"` + Create []string `yaml:"create"` + Common []string `yaml:"common"` +} + +func (c *CommandsFile) Parse(data []byte) error { + return yaml.Unmarshal(data, c) +} From 41bc053c017627a0b2e9e04b47ae1b6fbd6f89cf Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:41:50 +0200 Subject: [PATCH 7/8] update main func --- main.go | 93 ++++++++++++++------------------------------------------- 1 file changed, 23 insertions(+), 70 deletions(-) diff --git a/main.go b/main.go index e8c5751..f57afd0 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,10 @@ package main import ( - "flag" "fmt" "os" - "strings" + "github.com/alexflint/go-arg" "github.com/fsnotify/fsnotify" ) @@ -20,8 +19,8 @@ func init() { } func main() { - var path, command, events string - var recursive bool + var args args + arg.MustParse(&args) wd, err := os.Getwd() if err != nil { @@ -29,83 +28,37 @@ func main() { os.Exit(1) } - flag.StringVar(&command, "cmd", "", "semicolon separated commands to run when new event occur") - flag.StringVar(&path, "path", wd, "path to the directory to watch for events on") - flag.StringVar(&events, "events", "all", "events to watch for (write, create, chmod, remove, rename, all)") - flag.BoolVar(&recursive, "r", false, "watch subdirectories recursively") - flag.Parse() - - options := validateAndParseFlags( - command, - path, - events, - recursive, - ) - options.print() - watcher, _ := fsnotify.NewWatcher() - defer watcher.Close() + if args.Path != "" && !validPath(args.Path) { + fmt.Fprintf(os.Stderr, "err: %s\n", "invalid path: "+args.Path) + os.Exit(1) + } - if options.recursive { - addPathRecursively(options.path, watcher) - } else { - watcher.Add(options.path) + if args.Path == "" { + args.Path = wd } - go watchEvents(watcher, options) - <-make(chan struct{}) -} + c := &CommandsFile{} -// validateAndParseFlags function to validate the flag and build the watcherOptions struct -func validateAndParseFlags( - commands string, - path string, - events string, - recursive bool, -) (opt watcherOptions) { - fileInfo, err := os.Stat(path) + data, err := os.ReadFile(args.File) if err != nil { - fmt.Fprintf(os.Stderr, "err: %v%s\n", "invalid path: ", path) + fmt.Fprintf(os.Stderr, "err: %s\n", err.Error()) os.Exit(1) } - opt.path = path - if recursive && !fileInfo.IsDir() { - fmt.Fprintf(os.Stderr, "err: you can't use recursive flag with a file\n") + c.Parse(data) + + watcher, err := fsnotify.NewWatcher() + if err != nil { + fmt.Fprintf(os.Stderr, "err: %s\n", err.Error()) os.Exit(1) } - opt.recursive = recursive + defer watcher.Close() - // parse events - if events == "all" { - opt.registredEvents = []fsnotify.Op{ - fsnotify.Write, - fsnotify.Create, - fsnotify.Chmod, - fsnotify.Remove, - fsnotify.Rename, - } + if args.Recursive { + addPathRecursively(args.Path, watcher) } else { - events = strings.ToLower(events) - eventsList := strings.Split(events, ",") - for _, event := range eventsList { - event = strings.TrimSpace(event) - switch event { - case "write": - opt.registredEvents = append(opt.registredEvents, fsnotify.Write) - case "create": - opt.registredEvents = append(opt.registredEvents, fsnotify.Create) - case "chmod": - opt.registredEvents = append(opt.registredEvents, fsnotify.Chmod) - case "remove": - opt.registredEvents = append(opt.registredEvents, fsnotify.Remove) - case "rename": - opt.registredEvents = append(opt.registredEvents, fsnotify.Rename) - default: - fmt.Fprintf(os.Stderr, "err: %v%s\n", "invalid event: ", event) - os.Exit(1) - } - } + watcher.Add(args.Path) } - opt.commands = parseCommands(commands) - return + + watchEvents(watcher, *c) } From 4f42fec166d34b04991efd948aa519e13f8c6405 Mon Sep 17 00:00:00 2001 From: knbr13 Date: Thu, 28 Mar 2024 19:42:53 +0200 Subject: [PATCH 8/8] add example commands.yaml --- commands.txt | 2 -- commands.yaml | 12 ++++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) delete mode 100644 commands.txt create mode 100644 commands.yaml diff --git a/commands.txt b/commands.txt deleted file mode 100644 index d506047..0000000 --- a/commands.txt +++ /dev/null @@ -1,2 +0,0 @@ -sh -c ./script.sh; -echo --------------------------------------------------------------------------- diff --git a/commands.yaml b/commands.yaml new file mode 100644 index 0000000..1a97522 --- /dev/null +++ b/commands.yaml @@ -0,0 +1,12 @@ +write: + - echo i am write event +rename: + - echo i am rename event +remove: + - echo i am remove event +chmod: + - echo i am chmod event +create: + - echo i am create event +common: + - echo i am common event