diff --git a/CHANGELOG.md b/CHANGELOG.md index 3821c354..dcdb24b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Add support for delegating CLI arguments to commands with `--` and a + special `CLI_ARGS` variable + ([#327](https://github.com/go-task/task/issues/327)). - Add a `--concurrency` (alias `-C`) flag, to limit the number of tasks that run concurrently. This is useful for heavy workloads. ([#345](https://github.com/go-task/task/pull/345)). diff --git a/args/args.go b/args/args.go index b172f41f..3562ee94 100644 --- a/args/args.go +++ b/args/args.go @@ -9,7 +9,7 @@ import ( // ParseV3 parses command line argument: tasks and global variables func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) { var calls []taskfile.Call - var globals *taskfile.Vars + var globals = &taskfile.Vars{} for _, arg := range args { if !strings.Contains(arg, "=") { @@ -17,9 +17,6 @@ func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) { continue } - if globals == nil { - globals = &taskfile.Vars{} - } name, value := splitVar(arg) globals.Set(name, taskfile.Var{Static: value}) } @@ -34,7 +31,7 @@ func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) { // ParseV2 parses command line argument: tasks and vars of each task func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) { var calls []taskfile.Call - var globals *taskfile.Vars + var globals = &taskfile.Vars{} for _, arg := range args { if !strings.Contains(arg, "=") { @@ -43,9 +40,6 @@ func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) { } if len(calls) < 1 { - if globals == nil { - globals = &taskfile.Vars{} - } name, value := splitVar(arg) globals.Set(name, taskfile.Var{Static: value}) } else { diff --git a/args/args_test.go b/args/args_test.go index e6ca73ce..80638c55 100644 --- a/args/args_test.go +++ b/args/args_test.go @@ -96,7 +96,9 @@ func TestArgsV3(t *testing.T) { t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) { calls, globals := args.ParseV3(test.Args...) assert.Equal(t, test.ExpectedCalls, calls) - assert.Equal(t, test.ExpectedGlobals, globals) + if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 { + assert.Equal(t, test.ExpectedGlobals, globals) + } }) } } @@ -198,7 +200,10 @@ func TestArgsV2(t *testing.T) { t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) { calls, globals := args.ParseV2(test.Args...) assert.Equal(t, test.ExpectedCalls, calls) - assert.Equal(t, test.ExpectedGlobals, globals) + if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 { + assert.Equal(t, test.ExpectedGlobals, globals) + } + }) } } diff --git a/cmd/task/task.go b/cmd/task/task.go index 936d7fc6..11d7ffab 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "syscall" "github.com/spf13/pflag" @@ -157,14 +158,18 @@ func main() { } var ( - calls []taskfile.Call - globals *taskfile.Vars + calls []taskfile.Call + globals *taskfile.Vars + tasksAndVars, cliArgs = getArgs() ) + if v >= 3.0 { - calls, globals = args.ParseV3(pflag.Args()...) + calls, globals = args.ParseV3(tasksAndVars...) } else { - calls, globals = args.ParseV2(pflag.Args()...) + calls, globals = args.ParseV2(tasksAndVars...) } + + globals.Set("CLI_ARGS", taskfile.Var{Static: strings.Join(cliArgs, " ")}) e.Taskfile.Vars.Merge(globals) ctx := context.Background() @@ -185,6 +190,22 @@ func main() { } } +func getArgs() (tasksAndVars, cliArgs []string) { + var ( + args = pflag.Args() + doubleDashPos = pflag.CommandLine.ArgsLenAtDash() + ) + + if doubleDashPos != -1 { + tasksAndVars = args[:doubleDashPos] + cliArgs = args[doubleDashPos:] + } else { + tasksAndVars = args + } + + return +} + func getSignalContext() context.Context { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM) diff --git a/docs/usage.md b/docs/usage.md index 46b1ac36..9e8d1a62 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -520,6 +520,27 @@ tasks: This works for all types of variables. +## Forwarding CLI arguments to commands + +If `--` is given in the CLI, all following paramaters are added to a +special `.CLI_ARGS` variable. This is useful to forward arguments to another +command. + +The below example will run `yarn install`. + +```bash +$ task yarn -- install +``` + +```yaml +version: '3' + +tasks: + yarn: + cmds: + - yarn {{.CLI_ARGS}} +``` + ## Go's template engine Task parse commands as [Go's template engine][gotemplate] before executing diff --git a/internal/templater/templater.go b/internal/templater/templater.go index 4f13c3ed..f1617148 100644 --- a/internal/templater/templater.go +++ b/internal/templater/templater.go @@ -63,7 +63,7 @@ func (r *Templater) ReplaceSlice(strs []string) []string { } func (r *Templater) ReplaceVars(vars *taskfile.Vars) *taskfile.Vars { - if r.err != nil || vars == nil || len(vars.Keys) == 0 { + if r.err != nil || vars.Len() == 0 { return nil } diff --git a/taskfile/var.go b/taskfile/var.go index bd7ea28f..294343f2 100644 --- a/taskfile/var.go +++ b/taskfile/var.go @@ -75,7 +75,7 @@ func (vs *Vars) Range(yield func(key string, value Var) error) error { // ToCacheMap converts Vars to a map containing only the static // variables func (vs *Vars) ToCacheMap() (m map[string]interface{}) { - m = make(map[string]interface{}, len(vs.Keys)) + m = make(map[string]interface{}, vs.Len()) vs.Range(func(k string, v Var) error { if v.Sh != "" { // Dynamic variable is not yet resolved; trigger @@ -93,6 +93,14 @@ func (vs *Vars) ToCacheMap() (m map[string]interface{}) { return } +// Len returns the size of the map +func (vs *Vars) Len() int { + if vs == nil { + return 0 + } + return len(vs.Keys) +} + // Var represents either a static or dynamic variable. type Var struct { Static string