1
0
mirror of https://github.com/go-task/task.git synced 2025-06-23 00:38:19 +02:00

feat: error when multiple wildcard matches are found

This commit is contained in:
Pete Davison
2024-02-22 20:52:05 +00:00
parent beb9f42215
commit 38a06dad8e
7 changed files with 68 additions and 33 deletions

View File

@ -1249,8 +1249,8 @@ $ task run-foo-bar
foo bar foo bar
``` ```
If multiple matching tasks are found, the first one listed in the Taskfile will If multiple matching tasks are found, an error occurs. If you are using included
be used. If you are using included Taskfiles, Taskfiles, tasks in parent files will be considered first.
## Doing task cleanup with `defer` ## Doing task cleanup with `defer`

View File

@ -65,15 +65,15 @@ func (err *TaskInternalError) Code() int {
return CodeTaskInternal return CodeTaskInternal
} }
// TaskNameConflictError is returned when multiple tasks with the same name or // TaskNameConflictError is returned when multiple tasks with a matching name or
// alias are found. // alias are found.
type TaskNameConflictError struct { type TaskNameConflictError struct {
AliasName string Call string
TaskNames []string TaskNames []string
} }
func (err *TaskNameConflictError) Error() string { func (err *TaskNameConflictError) Error() string {
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.TaskNames, ", "), err.AliasName) return fmt.Sprintf(`task: Found multiple tasks (%s) that match %q`, strings.Join(err.TaskNames, ", "), err.Call)
} }
func (err *TaskNameConflictError) Code() int { func (err *TaskNameConflictError) Code() int {

View File

@ -10,7 +10,7 @@ import (
func PrintTasks(l *logger.Logger, t *ast.Taskfile, c []*ast.Call) { func PrintTasks(l *logger.Logger, t *ast.Taskfile, c []*ast.Call) {
for i, call := range c { for i, call := range c {
PrintSpaceBetweenSummaries(l, i) PrintSpaceBetweenSummaries(l, i)
PrintTask(l, t.Tasks.Get(call)) PrintTask(l, t.Tasks.Get(call.Task))
} }
} }

24
task.go
View File

@ -415,12 +415,28 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
// If multiple tasks contain the same alias or no matches are found an error is returned. // If multiple tasks contain the same alias or no matches are found an error is returned.
func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) { func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) {
// Search for a matching task // Search for a matching task
matchingTask := e.Taskfile.Tasks.Get(call) matchingTasks := e.Taskfile.Tasks.FindMatchingTasks(call)
if matchingTask != nil { switch len(matchingTasks) {
return matchingTask, nil case 0: // Carry on
case 1:
if call.Vars == nil {
call.Vars = &ast.Vars{}
}
call.Vars.Set("MATCH", ast.Var{Value: matchingTasks[0].Wildcards})
return matchingTasks[0].Task, nil
default:
taskNames := make([]string, len(matchingTasks))
for i, matchingTask := range matchingTasks {
taskNames[i] = matchingTask.Task.Task
}
return nil, &errors.TaskNameConflictError{
Call: call.Task,
TaskNames: taskNames,
}
} }
// If didn't find one, search for a task with a matching alias // If didn't find one, search for a task with a matching alias
var matchingTask *ast.Task
var aliasedTasks []string var aliasedTasks []string
for _, task := range e.Taskfile.Tasks.Values() { for _, task := range e.Taskfile.Tasks.Values() {
if slices.Contains(task.Aliases, call.Task) { if slices.Contains(task.Aliases, call.Task) {
@ -431,7 +447,7 @@ func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) {
// If we found multiple tasks // If we found multiple tasks
if len(aliasedTasks) > 1 { if len(aliasedTasks) > 1 {
return nil, &errors.TaskNameConflictError{ return nil, &errors.TaskNameConflictError{
AliasName: call.Task, Call: call.Task,
TaskNames: aliasedTasks, TaskNames: aliasedTasks,
} }
} }

View File

@ -2253,33 +2253,44 @@ func TestFor(t *testing.T) {
func TestWildcard(t *testing.T) { func TestWildcard(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
call string
expectedOutput string expectedOutput string
wantErr bool wantErr bool
}{ }{
{ {
name: "wildcard-foo", name: "basic wildcard",
call: "wildcard-foo",
expectedOutput: "Hello foo\n", expectedOutput: "Hello foo\n",
}, },
{ {
name: "foo-wildcard-bar", name: "double wildcard",
call: "foo-wildcard-bar",
expectedOutput: "Hello foo bar\n", expectedOutput: "Hello foo bar\n",
}, },
{ {
name: "start-foo", name: "store wildcard",
call: "start-foo",
expectedOutput: "Starting foo\n", expectedOutput: "Starting foo\n",
}, },
{ {
name: "matches-exactly-*", name: "matches exactly",
expectedOutput: "I don't consume matches: \n", call: "matches-exactly-*",
expectedOutput: "I don't consume matches: []\n",
}, },
{ {
name: "no-match", name: "no matches",
call: "no-match",
wantErr: true,
},
{
name: "multiple matches",
call: "wildcard-foo-bar",
wantErr: true, wantErr: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.call, func(t *testing.T) {
var buff bytes.Buffer var buff bytes.Buffer
e := task.Executor{ e := task.Executor{
Dir: "testdata/wildcards", Dir: "testdata/wildcards",
@ -2290,10 +2301,10 @@ func TestWildcard(t *testing.T) {
} }
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
if test.wantErr { if test.wantErr {
require.Error(t, e.Run(context.Background(), &ast.Call{Task: test.name})) require.Error(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
return return
} }
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.name})) require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
assert.Equal(t, test.expectedOutput, buff.String()) assert.Equal(t, test.expectedOutput, buff.String())
}) })
} }

View File

@ -14,29 +14,34 @@ type Tasks struct {
omap.OrderedMap[string, *Task] omap.OrderedMap[string, *Task]
} }
func (t *Tasks) Get(call *Call) *Task { type MatchingTask struct {
Task *Task
Wildcards []string
}
func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
if call == nil { if call == nil {
return nil return nil
} }
var task *Task var task *Task
var matchingTasks []*MatchingTask
// If there is a direct match, return it // If there is a direct match, return it
if task = t.OrderedMap.Get(call.Task); task != nil { if task = t.OrderedMap.Get(call.Task); task != nil {
return task matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
} return matchingTasks
if call.Vars == nil {
call.Vars = &Vars{}
} }
// Attempt a wildcard match // Attempt a wildcard match
// TODO: We need to add a yield func to the Range method so that we can stop looping when we find a match
// For now, we can just nil check the task before each loop // For now, we can just nil check the task before each loop
_ = t.Range(func(key string, value *Task) error { _ = t.Range(func(key string, value *Task) error {
if match, wildcards := value.WildcardMatch(call.Task); match && task == nil { if match, wildcards := value.WildcardMatch(call.Task); match {
task = value matchingTasks = append(matchingTasks, &MatchingTask{
call.Vars.Set("MATCH", Var{Value: wildcards}) Task: value,
Wildcards: wildcards,
})
} }
return nil return nil
}) })
return task return matchingTasks
} }
func (t1 *Tasks) Merge(t2 Tasks, include *Include) { func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
@ -86,11 +91,10 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
// run the included Taskfile's default task without specifying its full // run the included Taskfile's default task without specifying its full
// name. If the parent namespace has aliases, we add another alias for each // name. If the parent namespace has aliases, we add another alias for each
// of them. // of them.
if t2.Get(&Call{Task: "default"}) != nil && t1.Get(&Call{Task: include.Namespace}) == nil { if t2.Get("default") != nil && t1.Get(include.Namespace) == nil {
defaultTaskName := fmt.Sprintf("%s:default", include.Namespace) defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
defaultTaskCall := &Call{Task: defaultTaskName} t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Namespace)
t1.Get(defaultTaskCall).Aliases = append(t1.Get(defaultTaskCall).Aliases, include.Namespace) t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Aliases...)
t1.Get(defaultTaskCall).Aliases = append(t1.Get(defaultTaskCall).Aliases, include.Aliases...)
} }
} }

View File

@ -5,6 +5,10 @@ tasks:
cmds: cmds:
- echo "Hello {{index .MATCH 0}}" - echo "Hello {{index .MATCH 0}}"
wildcard-*-*:
cmds:
- echo "Hello {{index .MATCH 0}}"
'*-wildcard-*': '*-wildcard-*':
cmds: cmds:
- echo "Hello {{index .MATCH 0}} {{index .MATCH 1}}" - echo "Hello {{index .MATCH 0}} {{index .MATCH 1}}"