Last active
January 27, 2022 11:59
-
-
Save soypat/56e8db7a30baff25c06d01020c96b2b3 to your computer and use it in GitHub Desktop.
fsnotify tool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"io/fs" | |
stdlog "log" | |
"os" | |
"os/exec" | |
"os/signal" | |
"path/filepath" | |
"strings" | |
"time" | |
"github.com/fsnotify/fsnotify" | |
"github.com/spf13/pflag" | |
) | |
var ( | |
dir = "." | |
recursive = false | |
ignoreExt = []string{} | |
cmdPeriod = time.Second | |
hidden = false | |
) | |
var log *stdlog.Logger | |
func main() { | |
pflag.Usage = func() { | |
fmt.Fprintf(os.Stderr, "fsnotify - run a command when file writes occur\nUsage of fsnotify:\n") | |
pflag.PrintDefaults() | |
} | |
// Disable log | |
fp, _ := os.Open(os.DevNull) | |
stdlog.SetOutput(fp) | |
log = stdlog.New(os.Stdout, "[fs]", stdlog.Ltime) | |
pflag.StringVarP(&dir, "dir", "d", dir, "Directory to observe") | |
pflag.StringArrayVarP(&ignoreExt, "ignore-ext", "i", ignoreExt, "array of ignored file extensions") | |
pflag.BoolVarP(&recursive, "recursive", "r", recursive, "Recursive directory observation.") | |
pflag.DurationVarP(&cmdPeriod, "period", "", cmdPeriod, "Minimum time between subsequent command runs") | |
pflag.BoolVarP(&hidden, "hidden", "", hidden, "Ignore hidden directories.") | |
pflag.Parse() | |
args := pflag.Args() | |
if len(ignoreExt) > 0 { | |
log.Println("ignoring extensions:", ignoreExt) | |
} | |
if len(args) < 1 { | |
log.Fatal("no arguments found!") | |
} | |
ctx, cancel := context.WithCancel(context.Background()) | |
intChan := make(chan os.Signal) | |
signal.Notify(intChan, os.Interrupt) | |
go func() { | |
log.Println("listening for interrupts") | |
<-intChan | |
cancel() | |
log.Println("got interrupt, context cancelled") | |
}() | |
watcher, err := fsnotify.NewWatcher() | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer watcher.Close() | |
go func() { | |
lastCmd := time.Now() | |
for { | |
select { | |
case <-ctx.Done(): | |
log.Println("context done. ending listening.") | |
return | |
case event, ok := <-watcher.Events: | |
if !ok { | |
return | |
} | |
ignore := false | |
event.Name = strings.TrimSpace(event.Name) | |
for i := range ignoreExt { | |
if strings.HasSuffix(event.Name, ignoreExt[i]) { | |
ignore = true | |
break | |
} | |
} | |
if ignore { | |
break // break switch | |
} | |
if event.Op&fsnotify.Write == fsnotify.Write && time.Since(lastCmd) > cmdPeriod { | |
lastCmd = time.Now() | |
log.Println("event:", event) | |
log.Println("The modified file:", event.Name) | |
err = ExecCMD(ctx) | |
if err != nil { | |
log.Printf("error running command: %v", err) | |
} | |
} | |
case err, ok := <-watcher.Errors: | |
if !ok { | |
return | |
} | |
log.Println("watcher error:", err) | |
} | |
} | |
}() | |
if !recursive { | |
err = watcher.Add(".") | |
} else { | |
err = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { | |
if err != nil { | |
return nil | |
} | |
if !info.IsDir() { | |
return nil | |
} | |
dirs := filepath.SplitList(path) | |
for i := 0; !hidden && i < len(dirs); i++ { // start at 1 to include base dir | |
if strings.HasPrefix(dirs[i], ".") && dirs[i] != "." { | |
return nil // hidden parent directory | |
} | |
} | |
totalPath := filepath.Join(dir, path) | |
log.Println("watch: adding " + totalPath) | |
return watcher.Add(totalPath) | |
}) | |
} | |
if err != nil { | |
log.Fatal(err) | |
} | |
<-ctx.Done() | |
time.Sleep(time.Millisecond * 400) | |
} | |
func ExecCMD(ctx context.Context) error { | |
if ctx.Err() != nil { | |
panic("context done entering execCMD") | |
} | |
ctx, cancel := context.WithCancel(ctx) | |
defer cancel() | |
args := pflag.Args() | |
if len(args) < 1 { | |
panic("no arguments!") | |
} | |
var cmd *exec.Cmd | |
out := &strings.Builder{} | |
if len(args) > 1 { | |
cmd = exec.CommandContext(ctx, args[0], args[1:]...) | |
} else { | |
cmd = exec.CommandContext(ctx, args[0]) | |
} | |
cmd.Stderr = out | |
cmd.Stdout = out | |
log.Println("running: ", cmd.String()) | |
err := cmd.Run() | |
if err != nil { | |
return fmt.Errorf("%s: %s", err, out) | |
} | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment