mirror of
https://github.com/go-task/task.git
synced 2025-08-10 22:42:19 +02:00
feat: wildcard match aliases
This commit is contained in:
71
task.go
71
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
|
||||
|
@@ -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-*",
|
||||
|
@@ -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, "*")
|
||||
names := append([]string{t.Task}, t.Aliases...)
|
||||
|
||||
// If there are no wildcards, return false
|
||||
if len(wildcards) == 0 {
|
||||
return false, nil
|
||||
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, "*")
|
||||
|
||||
if len(wildcards) != wildcardCount {
|
||||
continue
|
||||
}
|
||||
|
||||
return true, wildcards
|
||||
}
|
||||
|
||||
// Remove the first match, which is the full string
|
||||
wildcards = wildcards[1:]
|
||||
|
||||
// If there are more/less wildcards than matches, return false
|
||||
if len(wildcards) != wildcardCount {
|
||||
return false, wildcards
|
||||
}
|
||||
|
||||
return true, wildcards
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
|
2
testdata/wildcards/Taskfile.yml
vendored
2
testdata/wildcards/Taskfile.yml
vendored
@@ -19,6 +19,8 @@ tasks:
|
||||
- "echo \"I don't consume matches: {{.MATCH}}\""
|
||||
|
||||
start-*:
|
||||
aliases:
|
||||
- s-*
|
||||
vars:
|
||||
SERVICE: "{{index .MATCH 0}}"
|
||||
cmds:
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user