Skip to content

Commit

Permalink
Merge pull request #6 from knbr13/templating
Browse files Browse the repository at this point in the history
Templating
  • Loading branch information
knbr13 authored Mar 28, 2024
2 parents 6f33269 + 4f42fec commit b073878
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 124 deletions.
24 changes: 24 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 0 additions & 2 deletions commands.txt

This file was deleted.

12 changes: 12 additions & 0 deletions commands.yaml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
93 changes: 23 additions & 70 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package main

import (
"flag"
"fmt"
"os"
"strings"

"github.com/alexflint/go-arg"
"github.com/fsnotify/fsnotify"
)

Expand All @@ -20,92 +19,46 @@ 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 {
fmt.Fprintf(os.Stderr, "err: %s\n", err.Error())
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)
}
31 changes: 20 additions & 11 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"io/fs"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
Expand All @@ -22,19 +24,21 @@ func addPathRecursively(root string, watcher *fsnotify.Watcher) error {
return err
}

func parseCommands(cmd string) [][]string {
func parseCommand(cmd string) *exec.Cmd {
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)
parts := strings.Split(cmd, " ")
if len(parts) < 2 {
return nil
}
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 res
return cmd
}

var excludedFolders = []string{
Expand All @@ -58,3 +62,8 @@ var excludedFolders = []string{
"tmp",
"build",
}

func validPath(path string) bool {
_, err := os.Stat(path)
return err == nil
}
96 changes: 56 additions & 40 deletions watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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!")
}
Expand All @@ -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
Expand Down

0 comments on commit b073878

Please sign in to comment.