From f3097845b44afd2b8acb15e6ceba9df387f04dc8 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Thu, 7 Sep 2017 13:57:06 -0300 Subject: [PATCH] allow assigning variables to tasks at run time via CLI using a similar syntax than setting env variables to command in bash, but used right after the task: ```bash task print MESSAGE=Hello! ``` closes #33 --- README.md | 9 ++++++ Taskfile.yml | 1 + args/args.go | 34 +++++++++++++++++++++++ args/args_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/task/task.go | 14 +++++++--- task.go | 14 +++++----- task_test.go | 24 ++++++++-------- watch.go | 28 +++++++++++-------- 8 files changed, 159 insertions(+), 35 deletions(-) create mode 100644 args/args.go create mode 100644 args/args_test.go diff --git a/README.md b/README.md index eab8d39c..4b61bf7b 100644 --- a/README.md +++ b/README.md @@ -279,6 +279,15 @@ Example of overriding with environment variables: $ TASK_VARIABLE=a-value task do-something ``` +Since some shells don't support above syntax to set environment variables +(Windows) tasks also accepts a similar style when not in the beginning of +the command. Variables given in this form are only visible to the task called +right before. + +```bash +$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!" +``` + Example of locally declared vars: ```yml diff --git a/Taskfile.yml b/Taskfile.yml index 04d107b1..e446aa28 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -34,6 +34,7 @@ test: desc: Runs test suite deps: [install] cmds: + - go test ./args - go test # https://github.com/goreleaser/goreleaser diff --git a/args/args.go b/args/args.go new file mode 100644 index 00000000..0e29ef64 --- /dev/null +++ b/args/args.go @@ -0,0 +1,34 @@ +package args + +import ( + "errors" + "strings" + + "github.com/go-task/task" +) + +var ( + ErrVariableWithoutTask = errors.New("task: variable given before any task") +) + +func Parse(args ...string) ([]task.Call, error) { + var calls []task.Call + + for _, arg := range args { + if !strings.Contains(arg, "=") { + calls = append(calls, task.Call{Task: arg}) + continue + } + if len(calls) < 1 { + return nil, ErrVariableWithoutTask + } + + if calls[len(calls)-1].Vars == nil { + calls[len(calls)-1].Vars = make(task.Vars) + } + + pair := strings.SplitN(arg, "=", 2) + calls[len(calls)-1].Vars[pair[0]] = task.Var{Static: pair[1]} + } + return calls, nil +} diff --git a/args/args_test.go b/args/args_test.go new file mode 100644 index 00000000..a1964d58 --- /dev/null +++ b/args/args_test.go @@ -0,0 +1,70 @@ +package args_test + +import ( + "fmt" + "testing" + + "github.com/go-task/task" + "github.com/go-task/task/args" + + "github.com/stretchr/testify/assert" +) + +func TestArgs(t *testing.T) { + tests := []struct { + Args []string + Expected []task.Call + Err error + }{ + { + Args: []string{"task-a", "task-b", "task-c"}, + Expected: []task.Call{ + {Task: "task-a"}, + {Task: "task-b"}, + {Task: "task-c"}, + }, + }, + { + Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"}, + Expected: []task.Call{ + { + Task: "task-a", + Vars: task.Vars{ + "FOO": task.Var{Static: "bar"}, + }, + }, + {Task: "task-b"}, + { + Task: "task-c", + Vars: task.Vars{ + "BAR": task.Var{Static: "baz"}, + "BAZ": task.Var{Static: "foo"}, + }, + }, + }, + }, + { + Args: []string{"task-a", "CONTENT=with some spaces"}, + Expected: []task.Call{ + { + Task: "task-a", + Vars: task.Vars{ + "CONTENT": task.Var{Static: "with some spaces"}, + }, + }, + }, + }, + { + Args: []string{"FOO=bar", "task-a"}, + Err: args.ErrVariableWithoutTask, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) { + calls, err := args.Parse(test.Args...) + assert.Equal(t, test.Err, err) + assert.Equal(t, test.Expected, calls) + }) + } +} diff --git a/cmd/task/task.go b/cmd/task/task.go index 421d1a03..f3d47ebb 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -6,6 +6,7 @@ import ( "os" "github.com/go-task/task" + "github.com/go-task/task/args" "github.com/spf13/pflag" ) @@ -99,13 +100,18 @@ func main() { return } - args := pflag.Args() - if len(args) == 0 { + arguments := pflag.Args() + if len(arguments) == 0 { log.Println("task: No argument given, trying default task") - args = []string{"default"} + arguments = []string{"default"} } - if err := e.Run(args...); err != nil { + calls, err := args.Parse(arguments...) + if err != nil { + log.Fatal(err) + } + + if err := e.Run(calls...); err != nil { log.Fatal(err) } } diff --git a/task.go b/task.go index c0adc319..bbf49397 100644 --- a/task.go +++ b/task.go @@ -63,7 +63,7 @@ type Task struct { } // Run runs Task -func (e *Executor) Run(args ...string) error { +func (e *Executor) Run(calls ...Call) error { if e.Stdin == nil { e.Stdin = os.Stdin } @@ -84,23 +84,23 @@ func (e *Executor) Run(args ...string) error { } // check if given tasks exist - for _, a := range args { - if _, ok := e.Tasks[a]; !ok { + for _, c := range calls { + if _, ok := e.Tasks[c.Task]; !ok { // FIXME: move to the main package e.PrintTasksHelp() - return &taskNotFoundError{taskName: a} + return &taskNotFoundError{taskName: c.Task} } } if e.Watch { - if err := e.watchTasks(args...); err != nil { + if err := e.watchTasks(calls...); err != nil { return err } return nil } - for _, a := range args { - if err := e.RunTask(context.Background(), Call{Task: a, Vars: nil}); err != nil { + for _, c := range calls { + if err := e.RunTask(context.TODO(), c); err != nil { return err } } diff --git a/task_test.go b/task_test.go index c8db2f06..071ea8c1 100644 --- a/task_test.go +++ b/task_test.go @@ -38,7 +38,7 @@ func (fct fileContentTest) Run(t *testing.T) { Stderr: ioutil.Discard, } assert.NoError(t, e.ReadTaskfile(), "e.ReadTaskfile()") - assert.NoError(t, e.Run(fct.Target), "e.Run(target)") + assert.NoError(t, e.Run(task.Call{Task: fct.Target}), "e.Run(target)") for name, expectContent := range fct.Files { t.Run(fct.name(name), func(t *testing.T) { @@ -137,7 +137,7 @@ func TestVarsInvalidTmpl(t *testing.T) { Stderr: ioutil.Discard, } assert.NoError(t, e.ReadTaskfile(), "e.ReadTaskfile()") - assert.EqualError(t, e.Run(target), expectError, "e.Run(target)") + assert.EqualError(t, e.Run(task.Call{Task: target}), expectError, "e.Run(target)") } func TestParams(t *testing.T) { @@ -189,7 +189,7 @@ func TestDeps(t *testing.T) { Stderr: ioutil.Discard, } assert.NoError(t, e.ReadTaskfile()) - assert.NoError(t, e.Run("default")) + assert.NoError(t, e.Run(task.Call{Task: "default"})) for _, f := range files { f = filepath.Join(dir, f) @@ -217,7 +217,7 @@ func TestTaskCall(t *testing.T) { Stderr: ioutil.Discard, } assert.NoError(t, e.ReadTaskfile()) - assert.NoError(t, e.Run("default")) + assert.NoError(t, e.Run(task.Call{Task: "default"})) for _, f := range files { if _, err := os.Stat(filepath.Join(dir, f)); err != nil { @@ -242,7 +242,7 @@ func TestStatus(t *testing.T) { Stderr: ioutil.Discard, } assert.NoError(t, e.ReadTaskfile()) - assert.NoError(t, e.Run("gen-foo")) + assert.NoError(t, e.Run(task.Call{Task: "gen-foo"})) if _, err := os.Stat(file); err != nil { t.Errorf("File should exists: %v", err) @@ -250,7 +250,7 @@ func TestStatus(t *testing.T) { buff := bytes.NewBuffer(nil) e.Stdout, e.Stderr = buff, buff - assert.NoError(t, e.Run("gen-foo")) + assert.NoError(t, e.Run(task.Call{Task: "gen-foo"})) if buff.String() != `task: Task "gen-foo" is up to date`+"\n" { t.Errorf("Wrong output message: %s", buff.String()) @@ -283,13 +283,13 @@ func TestGenerates(t *testing.T) { } assert.NoError(t, e.ReadTaskfile()) - for _, task := range []string{relTask, absTask} { - var destFile = filepath.Join(dir, task) + for _, theTask := range []string{relTask, absTask} { + var destFile = filepath.Join(dir, theTask) var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) + - fmt.Sprintf("task: Task \"%s\" is up to date\n", task) + fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask) // Run task for the first time. - assert.NoError(t, e.Run(task)) + assert.NoError(t, e.Run(task.Call{Task: theTask})) if _, err := os.Stat(srcFile); err != nil { t.Errorf("File should exists: %v", err) @@ -304,7 +304,7 @@ func TestGenerates(t *testing.T) { buff.Reset() // Re-run task to ensure it's now found to be up-to-date. - assert.NoError(t, e.Run(task)) + assert.NoError(t, e.Run(task.Call{Task: theTask})) if buff.String() != upToDate { t.Errorf("Wrong output message: %s", buff.String()) } @@ -339,5 +339,5 @@ func TestCyclicDep(t *testing.T) { Stderr: ioutil.Discard, } assert.NoError(t, e.ReadTaskfile()) - assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run("task-1")) + assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(task.Call{Task: "task-1"})) } diff --git a/watch.go b/watch.go index 4bbf761a..24050882 100644 --- a/watch.go +++ b/watch.go @@ -15,14 +15,18 @@ var watchIgnoredDirs = []string{ } // watchTasks start watching the given tasks -func (e *Executor) watchTasks(args ...string) error { - e.printfln("task: Started watching for tasks: %s", strings.Join(args, ", ")) +func (e *Executor) watchTasks(calls ...Call) error { + tasks := make([]string, len(calls)) + for i, c := range calls { + tasks[i] = c.Task + } + e.printfln("task: Started watching for tasks: %s", strings.Join(tasks, ", ")) ctx, cancel := context.WithCancel(context.Background()) - for _, a := range args { - a := a + for _, c := range calls { + c := c go func() { - if err := e.RunTask(ctx, Call{Task: a}); err != nil && !isContextError(err) { + if err := e.RunTask(ctx, c); err != nil && !isContextError(err) { e.println(err) } }() @@ -43,10 +47,10 @@ func (e *Executor) watchTasks(args ...string) error { cancel() ctx, cancel = context.WithCancel(context.Background()) - for _, a := range args { - a := a + for _, c := range calls { + c := c go func() { - if err := e.RunTask(ctx, Call{Task: a}); err != nil && !isContextError(err) { + if err := e.RunTask(ctx, c); err != nil && !isContextError(err) { e.println(err) } }() @@ -69,7 +73,7 @@ func (e *Executor) watchTasks(args ...string) error { go func() { // re-register each second because we can have new files for { - if err := e.registerWatchedFiles(w, args); err != nil { + if err := e.registerWatchedFiles(w, tasks); err != nil { e.println(err) } time.Sleep(time.Second) @@ -79,7 +83,7 @@ func (e *Executor) watchTasks(args ...string) error { return w.Start(time.Second) } -func (e *Executor) registerWatchedFiles(w *watcher.Watcher, args []string) error { +func (e *Executor) registerWatchedFiles(w *watcher.Watcher, tasks []string) error { oldWatchedFiles := make(map[string]struct{}) for f := range w.WatchedFiles() { oldWatchedFiles[f] = struct{}{} @@ -121,8 +125,8 @@ func (e *Executor) registerWatchedFiles(w *watcher.Watcher, args []string) error return nil } - for _, a := range args { - if err := registerTaskFiles(a); err != nil { + for _, t := range tasks { + if err := registerTaskFiles(t); err != nil { return err } }