From 376a6182ebed222ae30bbafd661f66b63c395772 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Sat, 1 Oct 2022 22:39:44 +0000 Subject: [PATCH] feat: aliases feat: add aliases to --list and --list-all flags feat: add aliases to --summary feat: enable aliases for included tasks tests: added alias unit tests --- errors.go | 10 ++++++ go.mod | 3 +- go.sum | 9 ++--- help.go | 3 +- internal/summary/summary.go | 13 +++++++ task.go | 53 +++++++++++++++++++++++----- task_test.go | 56 ++++++++++++++++++++++++++++-- taskfile/merge.go | 3 ++ taskfile/task.go | 3 ++ testdata/alias/Taskfile.yml | 18 ++++++++++ testdata/alias/Taskfile2.yml | 7 ++++ testdata/alias/alias-duplicate.txt | 1 + testdata/alias/alias-summary.txt | 11 ++++++ testdata/alias/alias.txt | 6 ++++ variables.go | 8 ++--- 15 files changed, 183 insertions(+), 21 deletions(-) create mode 100644 testdata/alias/Taskfile.yml create mode 100644 testdata/alias/Taskfile2.yml create mode 100644 testdata/alias/alias-duplicate.txt create mode 100644 testdata/alias/alias-summary.txt create mode 100644 testdata/alias/alias.txt diff --git a/errors.go b/errors.go index cc2e65ed..1be4c15e 100644 --- a/errors.go +++ b/errors.go @@ -3,6 +3,7 @@ package task import ( "errors" "fmt" + "strings" "mvdan.cc/sh/v3/interp" ) @@ -20,6 +21,15 @@ func (err *taskNotFoundError) Error() string { return fmt.Sprintf(`task: Task %q not found`, err.taskName) } +type multipleTasksWithAliasError struct { + aliasName string + taskNames []string +} + +func (err *multipleTasksWithAliasError) Error() string { + return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.taskNames, ", "), err.aliasName) +} + type taskInternalError struct { taskName string } diff --git a/go.mod b/go.mod index acb991e6..e8cec71f 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/radovskyb/watcher v1.0.7 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/yaml.v3 v3.0.1 mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899 @@ -19,7 +20,7 @@ require ( github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index e6ab020a..7a4d3b13 100644 --- a/go.sum +++ b/go.sum @@ -7,7 +7,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -34,16 +34,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/help.go b/help.go index b47350e2..262ab11c 100644 --- a/help.go +++ b/help.go @@ -44,8 +44,9 @@ func (e *Executor) printTasks(listAll bool) { // Format in tab-separated columns with a tab stop of 8. w := tabwriter.NewWriter(e.Stdout, 0, 8, 6, ' ', 0) for _, task := range tasks { + taskNames := append([]string{task.Task}, task.Aliases...) e.Logger.FOutf(w, logger.Yellow, "* ") - e.Logger.FOutf(w, logger.Green, task.Task) + e.Logger.FOutf(w, logger.Green, strings.Join(taskNames, "|")) e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc) fmt.Fprint(w, "\n") } diff --git a/internal/summary/summary.go b/internal/summary/summary.go index 13e19f01..0e58831c 100644 --- a/internal/summary/summary.go +++ b/internal/summary/summary.go @@ -28,6 +28,7 @@ func PrintTask(l *logger.Logger, t *taskfile.Task) { printTaskName(l, t) printTaskDescribingText(t, l) printTaskDependencies(l, t) + printTaskAliases(l, t) printTaskCommands(l, t) } @@ -61,6 +62,18 @@ func printTaskName(l *logger.Logger, t *taskfile.Task) { l.Outf(logger.Default, "") } +func printTaskAliases(l *logger.Logger, t *taskfile.Task) { + if len(t.Aliases) == 0 { + return + } + l.Outf(logger.Default, "") + l.Outf(logger.Default, "aliases:") + for _, alias := range t.Aliases { + l.FOutf(l.Stdout, logger.Default, " - ") + l.Outf(logger.Cyan, alias) + } +} + func hasDescription(t *taskfile.Task) bool { return t.Desc != "" } diff --git a/task.go b/task.go index 59cde1f1..d2cca73a 100644 --- a/task.go +++ b/task.go @@ -16,6 +16,7 @@ import ( "github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/taskfile" + "golang.org/x/exp/slices" "golang.org/x/sync/errgroup" ) @@ -63,16 +64,15 @@ type Executor struct { // Run runs Task func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error { // check if given tasks exist - for _, c := range calls { - t, ok := e.Taskfile.Tasks[c.Task] - if !ok { - // FIXME: move to the main package + for _, call := range calls { + task, err := e.GetTask(call) + if err != nil { e.ListTasksWithDesc() - return &taskNotFoundError{taskName: c.Task} + return err } - if t.Internal { + if task.Internal { e.ListTasksWithDesc() - return &taskInternalError{taskName: c.Task} + return &taskInternalError{taskName: call.Task} } } @@ -112,8 +112,8 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { if err != nil { return err } - if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall { - return &MaximumTaskCallExceededError{task: call.Task} + if !e.Watch && atomic.AddInt32(e.taskCallCount[t.Task], 1) >= MaximumTaskCall { + return &MaximumTaskCallExceededError{task: t.Task} } release := e.acquireConcurrencyLimit() @@ -330,3 +330,38 @@ func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute return execute(ctx) } + +// GetTask will return the task with the name matching the given call from the taskfile. +// If no task is found, it will search for tasks with a matching alias. +// If multiple tasks contain the same alias or no matches are found an error is returned. +func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) { + // Search for a matching task + matchingTask, ok := e.Taskfile.Tasks[call.Task] + if ok { + return matchingTask, nil + } + + // If didn't find one, search for a task with a matching alias + var aliasedTasks []string + for _, task := range e.Taskfile.Tasks { + if slices.Contains(task.Aliases, call.Task) { + aliasedTasks = append(aliasedTasks, task.Task) + matchingTask = task + } + } + // If we found multiple tasks + if len(aliasedTasks) > 1 { + return nil, &multipleTasksWithAliasError{ + aliasName: call.Task, + taskNames: aliasedTasks, + } + } + // If we found no tasks + if len(aliasedTasks) == 0 { + return nil, &taskNotFoundError{ + taskName: call.Task, + } + } + + return matchingTask, nil +} diff --git a/task_test.go b/task_test.go index 9602a6c0..9a48e206 100644 --- a/task_test.go +++ b/task_test.go @@ -476,6 +476,58 @@ func TestStatusChecksum(t *testing.T) { assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String()) } +func TestAlias(t *testing.T) { + const dir = "testdata/alias" + + data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias.txt")) + assert.NoError(t, err) + + var buff bytes.Buffer + e := task.Executor{ + Dir: dir, + Stdout: &buff, + Stderr: &buff, + } + assert.NoError(t, e.Setup()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"})) + assert.Equal(t, string(data), buff.String()) +} + +func TestDuplicateAlias(t *testing.T) { + const dir = "testdata/alias" + + data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-duplicate.txt")) + assert.NoError(t, err) + + var buff bytes.Buffer + e := task.Executor{ + Dir: dir, + Stdout: &buff, + Stderr: &buff, + } + assert.NoError(t, e.Setup()) + assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "x"})) + assert.Equal(t, string(data), buff.String()) +} + +func TestAliasSummary(t *testing.T) { + const dir = "testdata/alias" + + data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-summary.txt")) + assert.NoError(t, err) + + var buff bytes.Buffer + e := task.Executor{ + Dir: dir, + Summary: true, + Stdout: &buff, + Stderr: &buff, + } + assert.NoError(t, e.Setup()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"})) + assert.Equal(t, string(data), buff.String()) +} + func TestLabelUpToDate(t *testing.T) { const dir = "testdata/label_uptodate" @@ -989,7 +1041,7 @@ func TestIncludesInternal(t *testing.T) { err := e.Run(context.Background(), taskfile.Call{Task: test.task}) if test.expectedErr { - assert.Error(t, err, test.expectedErr) + assert.Error(t, err) } else { assert.NoError(t, err) } @@ -1029,7 +1081,7 @@ func TestInternalTask(t *testing.T) { err := e.Run(context.Background(), taskfile.Call{Task: test.task}) if test.expectedErr { - assert.Error(t, err, test.expectedErr) + assert.Error(t, err) } else { assert.NoError(t, err) } diff --git a/taskfile/merge.go b/taskfile/merge.go index baebad0e..ecf58e41 100644 --- a/taskfile/merge.go +++ b/taskfile/merge.go @@ -55,6 +55,9 @@ func Merge(t1, t2 *Taskfile, internal bool, namespaces ...string) error { cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...) } } + for i, alias := range v.Aliases { + v.Aliases[i] = taskNameWithNamespace(alias, namespaces...) + } } return nil diff --git a/taskfile/task.go b/taskfile/task.go index 46548bbf..5e9f1008 100644 --- a/taskfile/task.go +++ b/taskfile/task.go @@ -11,6 +11,7 @@ type Task struct { Label string Desc string Summary string + Aliases []string Sources []string Generates []string Status []string @@ -56,6 +57,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { Label string Desc string Summary string + Aliases []string Sources []string Generates []string Status []string @@ -78,6 +80,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { t.Deps = task.Deps t.Label = task.Label t.Desc = task.Desc + t.Aliases = task.Aliases t.Summary = task.Summary t.Sources = task.Sources t.Generates = task.Generates diff --git a/testdata/alias/Taskfile.yml b/testdata/alias/Taskfile.yml new file mode 100644 index 00000000..ed5743ae --- /dev/null +++ b/testdata/alias/Taskfile.yml @@ -0,0 +1,18 @@ +version: '3' + +includes: + included: + taskfile: Taskfile2.yml + +tasks: + foo: + aliases: [f, x] + cmds: + - echo "foo" + - task: b + + bar: + aliases: [b, x] + cmds: + - echo "bar" + - task: included:q diff --git a/testdata/alias/Taskfile2.yml b/testdata/alias/Taskfile2.yml new file mode 100644 index 00000000..72de3b32 --- /dev/null +++ b/testdata/alias/Taskfile2.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + qux: + aliases: [q, x] + cmds: + - echo "qux" diff --git a/testdata/alias/alias-duplicate.txt b/testdata/alias/alias-duplicate.txt new file mode 100644 index 00000000..56e8128e --- /dev/null +++ b/testdata/alias/alias-duplicate.txt @@ -0,0 +1 @@ +task: No tasks with description available. Try --list-all to list all tasks diff --git a/testdata/alias/alias-summary.txt b/testdata/alias/alias-summary.txt new file mode 100644 index 00000000..335e562e --- /dev/null +++ b/testdata/alias/alias-summary.txt @@ -0,0 +1,11 @@ +task: foo + +(task does not have description or summary) + +aliases: + - f + - x + +commands: + - echo "foo" + - Task: b diff --git a/testdata/alias/alias.txt b/testdata/alias/alias.txt new file mode 100644 index 00000000..afef2a62 --- /dev/null +++ b/testdata/alias/alias.txt @@ -0,0 +1,6 @@ +task: [foo] echo "foo" +foo +task: [bar] echo "bar" +bar +task: [included:qux] echo "qux" +qux diff --git a/variables.go b/variables.go index fd2124f0..872b81ed 100644 --- a/variables.go +++ b/variables.go @@ -22,13 +22,12 @@ func (e *Executor) FastCompiledTask(call taskfile.Call) (*taskfile.Task, error) } func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskfile.Task, error) { - origTask, ok := e.Taskfile.Tasks[call.Task] - if !ok { - return nil, &taskNotFoundError{call.Task} + origTask, err := e.GetTask(call) + if err != nil { + return nil, err } var vars *taskfile.Vars - var err error if evaluateShVars { vars, err = e.Compiler.GetVariables(origTask, call) } else { @@ -50,6 +49,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf Label: r.Replace(origTask.Label), Desc: r.Replace(origTask.Desc), Summary: r.Replace(origTask.Summary), + Aliases: origTask.Aliases, Sources: r.ReplaceSlice(origTask.Sources), Generates: r.ReplaceSlice(origTask.Generates), Dir: r.Replace(origTask.Dir),