diff --git a/task.go b/task.go index 0b762c6140..4d443861ed 100644 --- a/task.go +++ b/task.go @@ -400,19 +400,40 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func } // FindMatchingTasks returns a list of tasks that match the given call. A task -// matches a call if its name is equal to the call's task name or if it matches +// matches a call if its name is equal to the call's task name, or one of aliases, or if it matches // a wildcard pattern. The function returns a list of MatchingTask structs, each // containing a task and a list of wildcards that were matched. -func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask { +// If multiple tasks match due to aliases, a TaskNameConflictError is returned. +func (e *Executor) FindMatchingTasks(call *Call) ([]*MatchingTask, error) { if call == nil { - return nil + return nil, nil } var matchingTasks []*MatchingTask // If there is a direct match, return it if task, ok := e.Taskfile.Tasks.Get(call.Task); ok { matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil}) - return matchingTasks + return matchingTasks, nil + } + var aliasedTasks []string + for task := range e.Taskfile.Tasks.Values(nil) { + if slices.Contains(task.Aliases, call.Task) { + aliasedTasks = append(aliasedTasks, task.Task) + matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil}) + } + } + + if len(aliasedTasks) == 1 { + return matchingTasks, nil + } + + // If we found multiple tasks + if len(aliasedTasks) > 1 { + return nil, &errors.TaskNameConflictError{ + Call: call.Task, + TaskNames: aliasedTasks, + } } + // Attempt a wildcard match for _, value := range e.Taskfile.Tasks.All(nil) { if match, wildcards := value.WildcardMatch(call.Task); match { @@ -422,7 +443,7 @@ func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask { }) } } - return matchingTasks + return matchingTasks, nil } // GetTask will return the task with the name matching the given call from the taskfile. @@ -430,7 +451,11 @@ func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask { // If multiple tasks contain the same alias or no matches are found an error is returned. func (e *Executor) GetTask(call *Call) (*ast.Task, error) { // Search for a matching task - matchingTasks := e.FindMatchingTasks(call) + matchingTasks, err := e.FindMatchingTasks(call) + if err != nil { + return nil, err + } + if len(matchingTasks) > 0 { if call.Vars == nil { call.Vars = ast.NewVars() @@ -439,35 +464,15 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) { return matchingTasks[0].Task, nil } - // If didn't find one, search for a task with a matching alias - var matchingTask *ast.Task - var aliasedTasks []string - for task := range e.Taskfile.Tasks.Values(nil) { - 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, &errors.TaskNameConflictError{ - Call: call.Task, - TaskNames: aliasedTasks, - } - } // If we found no tasks - if len(aliasedTasks) == 0 { - didYouMean := "" - if e.fuzzyModel != nil { - didYouMean = e.fuzzyModel.SpellCheck(call.Task) - } - return nil, &errors.TaskNotFoundError{ - TaskName: call.Task, - DidYouMean: didYouMean, - } + didYouMean := "" + if e.fuzzyModel != nil { + didYouMean = e.fuzzyModel.SpellCheck(call.Task) + } + return nil, &errors.TaskNotFoundError{ + TaskName: call.Task, + DidYouMean: didYouMean, } - - return matchingTask, nil } type FilterFunc func(task *ast.Task) bool diff --git a/task_test.go b/task_test.go index 711d672416..d55d29112b 100644 --- a/task_test.go +++ b/task_test.go @@ -2500,6 +2500,11 @@ func TestWildcard(t *testing.T) { call: "start-foo", expectedOutput: "Starting foo\n", }, + { + name: "alias", + call: "s-foo", + expectedOutput: "Starting foo\n", + }, { name: "matches exactly", call: "matches-exactly-*", diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index 17fa976ae5..045033a9f2 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -64,26 +64,29 @@ func (t *Task) LocalName() string { // WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values. func (t *Task) WildcardMatch(name string) (bool, []string) { - // Convert the name into a regex string - regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(t.Task, "*", "(.*)")) - regex := regexp.MustCompile(regexStr) - wildcards := regex.FindStringSubmatch(name) - wildcardCount := strings.Count(t.Task, "*") - - // If there are no wildcards, return false - if len(wildcards) == 0 { - return false, nil - } + names := append([]string{t.Task}, t.Aliases...) + + for _, taskName := range names { + regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)")) + regex := regexp.MustCompile(regexStr) + wildcards := regex.FindStringSubmatch(name) + + if len(wildcards) == 0 { + continue + } + + // Remove the first match, which is the full string + wildcards = wildcards[1:] + wildcardCount := strings.Count(taskName, "*") - // Remove the first match, which is the full string - wildcards = wildcards[1:] + if len(wildcards) != wildcardCount { + continue + } - // If there are more/less wildcards than matches, return false - if len(wildcards) != wildcardCount { - return false, wildcards + return true, wildcards } - return true, wildcards + return false, nil } func (t *Task) UnmarshalYAML(node *yaml.Node) error { diff --git a/testdata/wildcards/Taskfile.yml b/testdata/wildcards/Taskfile.yml index 5b0d9765af..0ec2ae2895 100644 --- a/testdata/wildcards/Taskfile.yml +++ b/testdata/wildcards/Taskfile.yml @@ -19,6 +19,8 @@ tasks: - "echo \"I don't consume matches: {{.MATCH}}\"" start-*: + aliases: + - s-* vars: SERVICE: "{{index .MATCH 0}}" cmds: diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx index eda391a093..24d2a68cda 100644 --- a/website/docs/usage.mdx +++ b/website/docs/usage.mdx @@ -1748,6 +1748,27 @@ $ task start:foo:3 Starting foo with 3 replicas ``` +Using wildcards with aliases +Wildcards also work with aliases. If a task has an alias, you can use the alias name with wildcards to capture arguments. For example: + +```yaml +version: '3' + +tasks: + start:*: + aliases: [run:*] + vars: + SERVICE: "{{index .MATCH 0}}" + cmds: + - echo "Running {{.SERVICE}}" +``` +In this example, you can call the task using the alias run:*: + +```shell +$ task run:foo +Running foo +``` + ## Doing task cleanup with `defer` With the `defer` keyword, it's possible to schedule cleanup to be run once the