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:
57
task.go
57
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
|
// 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
|
// a wildcard pattern. The function returns a list of MatchingTask structs, each
|
||||||
// containing a task and a list of wildcards that were matched.
|
// 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 {
|
if call == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var matchingTasks []*MatchingTask
|
var matchingTasks []*MatchingTask
|
||||||
// If there is a direct match, return it
|
// If there is a direct match, return it
|
||||||
if task, ok := e.Taskfile.Tasks.Get(call.Task); ok {
|
if task, ok := e.Taskfile.Tasks.Get(call.Task); ok {
|
||||||
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
|
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
|
// Attempt a wildcard match
|
||||||
for _, value := range e.Taskfile.Tasks.All(nil) {
|
for _, value := range e.Taskfile.Tasks.All(nil) {
|
||||||
if match, wildcards := value.WildcardMatch(call.Task); match {
|
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.
|
// 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.
|
// 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) {
|
func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||||
// Search for a matching task
|
// 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 len(matchingTasks) > 0 {
|
||||||
if call.Vars == nil {
|
if call.Vars == nil {
|
||||||
call.Vars = ast.NewVars()
|
call.Vars = ast.NewVars()
|
||||||
@@ -439,24 +464,7 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
|||||||
return matchingTasks[0].Task, nil
|
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 we found no tasks
|
||||||
if len(aliasedTasks) == 0 {
|
|
||||||
didYouMean := ""
|
didYouMean := ""
|
||||||
if e.fuzzyModel != nil {
|
if e.fuzzyModel != nil {
|
||||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||||
@@ -465,9 +473,6 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
|||||||
TaskName: call.Task,
|
TaskName: call.Task,
|
||||||
DidYouMean: didYouMean,
|
DidYouMean: didYouMean,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return matchingTask, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterFunc func(task *ast.Task) bool
|
type FilterFunc func(task *ast.Task) bool
|
||||||
|
@@ -2500,6 +2500,11 @@ func TestWildcard(t *testing.T) {
|
|||||||
call: "start-foo",
|
call: "start-foo",
|
||||||
expectedOutput: "Starting foo\n",
|
expectedOutput: "Starting foo\n",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "alias",
|
||||||
|
call: "s-foo",
|
||||||
|
expectedOutput: "Starting foo\n",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "matches exactly",
|
name: "matches exactly",
|
||||||
call: "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.
|
// 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) {
|
func (t *Task) WildcardMatch(name string) (bool, []string) {
|
||||||
// Convert the name into a regex string
|
names := append([]string{t.Task}, t.Aliases...)
|
||||||
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(t.Task, "*", "(.*)"))
|
|
||||||
|
for _, taskName := range names {
|
||||||
|
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)"))
|
||||||
regex := regexp.MustCompile(regexStr)
|
regex := regexp.MustCompile(regexStr)
|
||||||
wildcards := regex.FindStringSubmatch(name)
|
wildcards := regex.FindStringSubmatch(name)
|
||||||
wildcardCount := strings.Count(t.Task, "*")
|
|
||||||
|
|
||||||
// If there are no wildcards, return false
|
|
||||||
if len(wildcards) == 0 {
|
if len(wildcards) == 0 {
|
||||||
return false, nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the first match, which is the full string
|
// Remove the first match, which is the full string
|
||||||
wildcards = wildcards[1:]
|
wildcards = wildcards[1:]
|
||||||
|
wildcardCount := strings.Count(taskName, "*")
|
||||||
|
|
||||||
// If there are more/less wildcards than matches, return false
|
|
||||||
if len(wildcards) != wildcardCount {
|
if len(wildcards) != wildcardCount {
|
||||||
return false, wildcards
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, wildcards
|
return true, wildcards
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
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}}\""
|
- "echo \"I don't consume matches: {{.MATCH}}\""
|
||||||
|
|
||||||
start-*:
|
start-*:
|
||||||
|
aliases:
|
||||||
|
- s-*
|
||||||
vars:
|
vars:
|
||||||
SERVICE: "{{index .MATCH 0}}"
|
SERVICE: "{{index .MATCH 0}}"
|
||||||
cmds:
|
cmds:
|
||||||
|
@@ -1748,6 +1748,27 @@ $ task start:foo:3
|
|||||||
Starting foo with 3 replicas
|
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`
|
## Doing task cleanup with `defer`
|
||||||
|
|
||||||
With the `defer` keyword, it's possible to schedule cleanup to be run once the
|
With the `defer` keyword, it's possible to schedule cleanup to be run once the
|
||||||
|
Reference in New Issue
Block a user