From 230a2cba59ff56be6ab8e9215f06b50077133591 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 16 Jul 2019 14:50:24 -0400 Subject: [PATCH] Command line + readme --- README.md | 42 +++++++++++++++++++++++++++++- main.go | 76 ++++++++++++++++++++++++++++++++++++++++++++----------- util.go | 7 +++-- 3 files changed, 107 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e68dbb1..1fda9cb 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,44 @@ [![Build Status](https://ci.simon987.net/buildStatus/icon?job=beemer_builds)](https://ci.simon987.net/job/beemer_builds/) [![CodeFactor](https://www.codefactor.io/repository/github/simon987/beemer/badge)](https://www.codefactor.io/repository/github/simon987/beemer) -wip +**beemer** executes a custom command on files written in the watched directory and deletes it. + +### Usage + +``` +NAME: + beemer - Execute a command on a file after a delay of inactivity + +GLOBAL OPTIONS: + --command value, -c value Will be executed on file write. You can use %file and %dir. Example: "rclone move %file remote:/beem/%dir" + --wait DELAY, -w DELAY Files will be beemed after DELAY of inactivity (default: 10s) + --directory DIRECTORY, -d DIRECTORY DIRECTORY to watch. If non-empty, its current files & subdirectories will be ignored + --help, -h show help + --version, -v print the version +``` + +### Examples + +Upload file to an rclone remote when it has been inactive for at least 30s, +keeps the directory structure +```bash +./beemer -w 30s -d ./test -c "rclone move %file remote:/beem/%dir" +``` + +Send file via SSH, ignoring the local directory structure +```bash +./beemer -d ./test -c "scp %file worker@StagingServer:flatdir/" +``` + +Upload file to transfer.sh, store URLs in `urls.txt` +```bash +./beemer -w 1s -d ./test -c "bash -c \"curl -s -w '\\n' --upload-file %file https://transfer.sh/%name &>> urls.txt\"" +``` + +### Beem command template + +| Special sequence | Description | Example | +| :--- | :--- | :--- | +| `%file` | Full path of the modified file | `/tmp/beemer/test/a/myFile.txt` | +| `%name` | Name of the modified file | `myFile.txt` | +| `%dir` | Directory of the modified file, relative to the watched dir. | `test/a` | diff --git a/main.go b/main.go index a2817ef..307bb10 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,9 @@ package main import ( "github.com/fsnotify/fsnotify" + "github.com/pkg/errors" "github.com/sirupsen/logrus" + "github.com/urfave/cli" "os" "os/exec" "path/filepath" @@ -21,8 +23,7 @@ type File struct { BeemLock bool } -var CmdString = "rclone move %file remote:/beem/%dir" -var InactiveDelay = time.Second * 5 +var InactiveDelay time.Duration var ctx = Ctx{ FileMap: make(map[string]*File, 0), @@ -31,26 +32,71 @@ var ctx = Ctx{ func main() { logrus.SetLevel(logrus.TraceLevel) - initTempDir() - ctx.BeemCommand = parseCommand(CmdString) + app := cli.NewApp() + app.Name = "beemer" + app.Usage = "Execute a command on a file after a delay of inactivity" + app.Email = "me@simon987.net" + app.Author = "simon987" + app.Version = "1.0" - watcher, err := fsnotify.NewWatcher() - if err != nil { - logrus.Fatal(err) + var cmdString string + var watchDir string + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "command, c", + Usage: "Will be executed on file write. You can use %file, %name and %dir. " + + "Example: \"rclone move %file remote:/beem/%dir\"", + Destination: &cmdString, + }, + cli.DurationFlag{ + Name: "wait, w", + Usage: "Files will be beemed after `DELAY` of inactivity", + Destination: &InactiveDelay, + Value: time.Second * 10, + }, + cli.StringFlag{ + Name: "directory, d", + Usage: "`DIRECTORY` to watch. If non-empty, its current files & subdirectories will be ignored", + Destination: &watchDir, + }, } - defer watcher.Close() + app.Action = func(c *cli.Context) error { - go handleFileChange(watcher) + if !c.IsSet("directory") { + return errors.New("Directory must be specified") + } - err = watcher.Add("./test") - if err != nil { - logrus.Fatal(err) + initTempDir() + ctx.BeemCommand = parseCommand(cmdString) + + watcher, err := fsnotify.NewWatcher() + if err != nil { + logrus.Fatal(err) + } + + defer watcher.Close() + + go handleFileChange(watcher) + + logrus.WithField("dir", watchDir).Info("Watching directory for changes") + err = watcher.Add(watchDir) + if err != nil { + logrus.Fatal(err) + } + + //TODO gracefully handle SIGINT + done := make(chan bool) + <-done + + return nil } - //TODO gracefully handle SIGINT - done := make(chan bool) - <-done + err := app.Run(os.Args) + if err != nil { + logrus.Fatal(app.OnUsageError) + } } func getAndResetTimer(name string) *time.Timer { diff --git a/util.go b/util.go index 4a56138..feae90f 100644 --- a/util.go +++ b/util.go @@ -84,13 +84,16 @@ func copyFile(src string, dst string) error { func parseCommand(command string) func(string, string) (string, []string) { args, _ := argv.Argv([]rune(command), argv.ParseEnv(os.Environ()), argv.Run) + logrus.WithField("cmd", args[0]).Info("Parsed beem command") + return func(name string, dir string) (string, []string) { newTokens := make([]string, len(args[0])) copy(newTokens, args[0]) for i := range newTokens { - newTokens[i] = strings.Replace(newTokens[i], "%file", name,-1) - newTokens[i] = strings.Replace(newTokens[i], "%dir", dir,-1) + newTokens[i] = strings.Replace(newTokens[i], "%file", name, -1) + newTokens[i] = strings.Replace(newTokens[i], "%dir", dir, -1) + newTokens[i] = strings.Replace(newTokens[i], "%name", filepath.Base(name), -1) } return args[0][0], newTokens[1:] }