From 31b369ba4f761b43826b0d066c94f8a498ba9375 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Mon, 2 Sep 2024 21:48:11 +0000 Subject: [PATCH 1/3] chore: bump minimum version to 1.23 --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- go.mod | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d7361f602b..248ec99c19 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: name: Lint strategy: matrix: - go-version: [1.22.x, 1.23.x] + go-version: [1.23.x, 1.24.x] runs-on: ubuntu-latest steps: - uses: actions/setup-go@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff8ab1a271..824dee5965 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9d2ccfde8..fac3fb6ebe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: name: Test strategy: matrix: - go-version: [1.22.x, 1.23.x] + go-version: [1.23.x, 1.24.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{matrix.platform}} steps: diff --git a/go.mod b/go.mod index 3a4ef15117..8cc1a4e8d4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-task/task/v3 -go 1.22.0 +go 1.23.0 require ( github.com/Ladicle/tabwriter v1.0.0 From dd55d6e96b3c91ec5cfeba5addc7d61cc84e4557 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Mon, 9 Sep 2024 16:19:01 +0000 Subject: [PATCH 2/3] refactor: better sort package --- cmd/task/task.go | 6 ++-- help.go | 14 ++++---- internal/sort/sorter.go | 46 +++++++++++------------- internal/sort/sorter_test.go | 68 +++++++++++++++++------------------- task.go | 21 ++++++----- 5 files changed, 77 insertions(+), 78 deletions(-) diff --git a/cmd/task/task.go b/cmd/task/task.go index 439ca9c5a7..79e422c681 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -100,12 +100,12 @@ func run() error { dir = home } - var taskSorter sort.TaskSorter + var taskSorter sort.Sorter switch flags.TaskSort { case "none": - taskSorter = &sort.Noop{} + taskSorter = nil case "alphanumeric": - taskSorter = &sort.AlphaNumeric{} + taskSorter = sort.AlphaNumeric } e := task.Executor{ diff --git a/help.go b/help.go index 622ef17be5..6ee1f7e371 100644 --- a/help.go +++ b/help.go @@ -128,18 +128,20 @@ func (e *Executor) ListTaskNames(allTasks bool) error { w = e.Stdout } - // Get the list of tasks and sort them - tasks := e.Taskfile.Tasks.Values() - // Sort the tasks if e.TaskSorter == nil { - e.TaskSorter = &sort.AlphaNumericWithRootTasksFirst{} + e.TaskSorter = sort.AlphaNumericWithRootTasksFirst } - e.TaskSorter.Sort(tasks) + keys := e.Taskfile.Tasks.Keys() + e.TaskSorter(keys, nil) // Create a list of task names taskNames := make([]string, 0, e.Taskfile.Tasks.Len()) - for _, task := range tasks { + for _, key := range keys { + task, ok := e.Taskfile.Tasks.Get(key) + if !ok { + continue + } if (allTasks || task.Desc != "") && !task.Internal { taskNames = append(taskNames, strings.TrimRight(task.Task, ":")) for _, alias := range task.Aliases { diff --git a/internal/sort/sorter.go b/internal/sort/sorter.go index 4c1016cca1..e3b9842c12 100644 --- a/internal/sort/sorter.go +++ b/internal/sort/sorter.go @@ -3,42 +3,38 @@ package sort import ( "sort" "strings" - - "github.com/go-task/task/v3/taskfile/ast" ) -type TaskSorter interface { - Sort([]*ast.Task) -} - -type Noop struct{} +// A Sorter is any function that sorts a set of tasks. +type Sorter func(items []string, namespaces []string) []string -func (s *Noop) Sort(tasks []*ast.Task) {} - -type AlphaNumeric struct{} - -// Tasks that are not namespaced should be listed before tasks that are. -// We detect this by searching for a ':' in the task name. -func (s *AlphaNumeric) Sort(tasks []*ast.Task) { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Task < tasks[j].Task +// AlphaNumeric sorts the JSON output so that tasks are in alpha numeric order +// by task name. +func AlphaNumeric(items []string, namespaces []string) []string { + sort.Slice(items, func(i, j int) bool { + return items[i] < items[j] }) + return items } -type AlphaNumericWithRootTasksFirst struct{} - -// Tasks that are not namespaced should be listed before tasks that are. -// We detect this by searching for a ':' in the task name. -func (s *AlphaNumericWithRootTasksFirst) Sort(tasks []*ast.Task) { - sort.Slice(tasks, func(i, j int) bool { - iContainsColon := strings.Contains(tasks[i].Task, ":") - jContainsColon := strings.Contains(tasks[j].Task, ":") +// AlphaNumericWithRootTasksFirst sorts the JSON output so that tasks are in +// alpha numeric order by task name. It will also ensure that tasks that are not +// namespaced will be listed before tasks that are. We detect this by searching +// for a ':' in the task name. +func AlphaNumericWithRootTasksFirst(items []string, namespaces []string) []string { + if len(namespaces) > 0 { + return AlphaNumeric(items, namespaces) + } + sort.Slice(items, func(i, j int) bool { + iContainsColon := strings.Contains(items[i], ":") + jContainsColon := strings.Contains(items[j], ":") if iContainsColon == jContainsColon { - return tasks[i].Task < tasks[j].Task + return items[i] < items[j] } if !iContainsColon && jContainsColon { return true } return false }) + return items } diff --git a/internal/sort/sorter_test.go b/internal/sort/sorter_test.go index d788c545f8..e388c48e95 100644 --- a/internal/sort/sorter_test.go +++ b/internal/sort/sorter_test.go @@ -4,74 +4,70 @@ import ( "testing" "github.com/stretchr/testify/assert" - - "github.com/go-task/task/v3/taskfile/ast" ) func TestAlphaNumericWithRootTasksFirst_Sort(t *testing.T) { - task1 := &ast.Task{Task: "task1"} - task2 := &ast.Task{Task: "task2"} - task3 := &ast.Task{Task: "ns1:task3"} - task4 := &ast.Task{Task: "ns2:task4"} - task5 := &ast.Task{Task: "task5"} - task6 := &ast.Task{Task: "ns3:task6"} + item1 := "a-item1" + item2 := "m-item2" + item3 := "ns1:item3" + item4 := "ns2:item4" + item5 := "z-item5" + item6 := "ns3:item6" tests := []struct { name string - tasks []*ast.Task - want []*ast.Task + items []string + want []string }{ { - name: "no namespace tasks sorted alphabetically first", - tasks: []*ast.Task{task3, task2, task1}, - want: []*ast.Task{task1, task2, task3}, + name: "no namespace items sorted alphabetically first", + items: []string{item3, item2, item1}, + want: []string{item1, item2, item3}, }, { - name: "namespace tasks sorted alphabetically after non-namespaced tasks", - tasks: []*ast.Task{task3, task4, task5}, - want: []*ast.Task{task5, task3, task4}, + name: "namespace items sorted alphabetically after non-namespaced items", + items: []string{item3, item4, item5}, + want: []string{item5, item3, item4}, }, { - name: "all tasks sorted alphabetically with root tasks first", - tasks: []*ast.Task{task6, task5, task4, task3, task2, task1}, - want: []*ast.Task{task1, task2, task5, task3, task4, task6}, + name: "all items sorted alphabetically with root items first", + items: []string{item6, item5, item4, item3, item2, item1}, + want: []string{item1, item2, item5, item3, item4, item6}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &AlphaNumericWithRootTasksFirst{} - s.Sort(tt.tasks) - assert.Equal(t, tt.want, tt.tasks) + AlphaNumericWithRootTasksFirst(tt.items, nil) + assert.Equal(t, tt.want, tt.items) }) } } func TestAlphaNumeric_Sort(t *testing.T) { - task1 := &ast.Task{Task: "task1"} - task2 := &ast.Task{Task: "task2"} - task3 := &ast.Task{Task: "ns1:task3"} - task4 := &ast.Task{Task: "ns2:task4"} - task5 := &ast.Task{Task: "task5"} - task6 := &ast.Task{Task: "ns3:task6"} + item1 := "a-item1" + item2 := "m-item2" + item3 := "ns1:item3" + item4 := "ns2:item4" + item5 := "z-item5" + item6 := "ns3:item6" tests := []struct { name string - tasks []*ast.Task - want []*ast.Task + items []string + want []string }{ { - name: "all tasks sorted alphabetically", - tasks: []*ast.Task{task3, task2, task5, task1, task4, task6}, - want: []*ast.Task{task3, task4, task6, task1, task2, task5}, + name: "all items sorted alphabetically", + items: []string{item3, item2, item5, item1, item4, item6}, + want: []string{item1, item2, item3, item4, item6, item5}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &AlphaNumeric{} - s.Sort(tt.tasks) - assert.Equal(t, tt.tasks, tt.want) + AlphaNumeric(tt.items, nil) + assert.Equal(t, tt.want, tt.items) }) } } diff --git a/task.go b/task.go index dc6b7b09c1..2f956c0b7f 100644 --- a/task.go +++ b/task.go @@ -74,7 +74,7 @@ type Executor struct { Compiler *compiler.Compiler Output output.Output OutputStyle ast.Output - TaskSorter sort.TaskSorter + TaskSorter sort.Sorter UserWorkingDir string fuzzyModel *fuzzy.Model @@ -498,8 +498,19 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) { // Create an error group to wait for each task to be compiled var g errgroup.Group + // Sort the tasks + if e.TaskSorter == nil { + e.TaskSorter = sort.AlphaNumericWithRootTasksFirst + } + keys := e.Taskfile.Tasks.Keys() + e.TaskSorter(keys, nil) + // Filter tasks based on the given filter functions - for _, task := range e.Taskfile.Tasks.Values() { + for _, key := range keys { + task, ok := e.Taskfile.Tasks.Get(key) + if !ok { + continue + } var shouldFilter bool for _, filter := range filters { if filter(task) { @@ -528,12 +539,6 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) { return nil, err } - // Sort the tasks - if e.TaskSorter == nil { - e.TaskSorter = &sort.AlphaNumericWithRootTasksFirst{} - } - e.TaskSorter.Sort(tasks) - return tasks, nil } From 5ff80ea4756324e5f2f46ce3a0ad3c02eb3c4352 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Mon, 9 Sep 2024 19:00:16 +0000 Subject: [PATCH 3/3] feat: iterators --- help.go | 8 +--- internal/compiler/compiler.go | 36 +++++++++++------ internal/templater/templater.go | 5 +-- setup.go | 20 ++++------ task.go | 10 +---- taskfile/ast/graph.go | 6 +-- taskfile/ast/include.go | 33 +++++++++++++--- taskfile/ast/matrix.go | 32 +++++++++++---- taskfile/ast/tasks.go | 70 ++++++++++++++++++--------------- taskfile/ast/var.go | 42 ++++++++++++++------ taskfile/reader.go | 7 ++-- variables.go | 17 +++----- 12 files changed, 171 insertions(+), 115 deletions(-) diff --git a/help.go b/help.go index 6ee1f7e371..e7a024fb89 100644 --- a/help.go +++ b/help.go @@ -132,16 +132,10 @@ func (e *Executor) ListTaskNames(allTasks bool) error { if e.TaskSorter == nil { e.TaskSorter = sort.AlphaNumericWithRootTasksFirst } - keys := e.Taskfile.Tasks.Keys() - e.TaskSorter(keys, nil) // Create a list of task names taskNames := make([]string, 0, e.Taskfile.Tasks.Len()) - for _, key := range keys { - task, ok := e.Taskfile.Tasks.Get(key) - if !ok { - continue - } + for task := range e.Taskfile.Tasks.Values(e.TaskSorter) { if (allTasks || task.Desc != "") && !task.Internal { taskNames = append(taskNames, strings.TrimRight(task.Task, ":")) for _, alias := range task.Aliases { diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index ce2bed7576..225367a3c2 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -104,18 +104,26 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool taskRangeFunc = getRangeFunc(dir) } - if err := c.TaskfileEnv.Range(rangeFunc); err != nil { - return nil, err + for k, v := range c.TaskfileEnv.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } } - if err := c.TaskfileVars.Range(rangeFunc); err != nil { - return nil, err + for k, v := range c.TaskfileVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } } if t != nil { - if err := t.IncludeVars.Range(rangeFunc); err != nil { - return nil, err + for k, v := range t.IncludeVars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } } - if err := t.IncludedTaskfileVars.Range(taskRangeFunc); err != nil { - return nil, err + for k, v := range t.IncludedTaskfileVars.All() { + if err := taskRangeFunc(k, v); err != nil { + return nil, err + } } } @@ -123,11 +131,15 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool return result, nil } - if err := call.Vars.Range(rangeFunc); err != nil { - return nil, err + for k, v := range call.Vars.All() { + if err := rangeFunc(k, v); err != nil { + return nil, err + } } - if err := t.Vars.Range(taskRangeFunc); err != nil { - return nil, err + for k, v := range t.Vars.All() { + if err := taskRangeFunc(k, v); err != nil { + return nil, err + } } return result, nil diff --git a/internal/templater/templater.go b/internal/templater/templater.go index 41f2cc2868..9b253366ac 100644 --- a/internal/templater/templater.go +++ b/internal/templater/templater.go @@ -141,10 +141,9 @@ func ReplaceVarsWithExtra(vars *ast.Vars, cache *Cache, extra map[string]any) *a } var newVars ast.Vars - _ = vars.Range(func(k string, v ast.Var) error { + for k, v := range vars.All() { newVars.Set(k, ReplaceVarWithExtra(v, cache, extra)) - return nil - }) + } return &newVars } diff --git a/setup.go b/setup.go index 336e73b81b..f484d0e3dc 100644 --- a/setup.go +++ b/setup.go @@ -92,12 +92,9 @@ func (e *Executor) setupFuzzyModel() { model.SetThreshold(1) // because we want to build grammar based on every task name var words []string - for _, taskName := range e.Taskfile.Tasks.Keys() { - words = append(words, taskName) - - for _, task := range e.Taskfile.Tasks.Values() { - words = slices.Concat(words, task.Aliases) - } + for name, task := range e.Taskfile.Tasks.All(nil) { + words = append(words, name) + words = slices.Concat(words, task.Aliases) } model.Train(words) @@ -212,12 +209,11 @@ func (e *Executor) readDotEnvFiles() error { return err } - err = env.Range(func(key string, value ast.Var) error { - if _, ok := e.Taskfile.Env.Get(key); !ok { - e.Taskfile.Env.Set(key, value) + for k, v := range env.All() { + if _, ok := e.Taskfile.Env.Get(k); !ok { + e.Taskfile.Env.Set(k, v) } - return nil - }) + } return err } @@ -235,7 +231,7 @@ func (e *Executor) setupConcurrencyState() { e.taskCallCount = make(map[string]*int32, e.Taskfile.Tasks.Len()) e.mkdirMutexMap = make(map[string]*sync.Mutex, e.Taskfile.Tasks.Len()) - for _, k := range e.Taskfile.Tasks.Keys() { + for k := range e.Taskfile.Tasks.Keys(nil) { e.taskCallCount[k] = new(int32) e.mkdirMutexMap[k] = &sync.Mutex{} } diff --git a/task.go b/task.go index 2f956c0b7f..888ebc8666 100644 --- a/task.go +++ b/task.go @@ -462,7 +462,7 @@ func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) { // 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() { + for task := range e.Taskfile.Tasks.Values(nil) { if slices.Contains(task.Aliases, call.Task) { aliasedTasks = append(aliasedTasks, task.Task) matchingTask = task @@ -502,15 +502,9 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) { if e.TaskSorter == nil { e.TaskSorter = sort.AlphaNumericWithRootTasksFirst } - keys := e.Taskfile.Tasks.Keys() - e.TaskSorter(keys, nil) // Filter tasks based on the given filter functions - for _, key := range keys { - task, ok := e.Taskfile.Tasks.Get(key) - if !ok { - continue - } + for task := range e.Taskfile.Tasks.Values(e.TaskSorter) { var shouldFilter bool for _, filter := range filters { if filter(task) { diff --git a/taskfile/ast/graph.go b/taskfile/ast/graph.go index 31d189e0aa..2f52b19b3a 100644 --- a/taskfile/ast/graph.go +++ b/taskfile/ast/graph.go @@ -116,14 +116,14 @@ func (tfg *TaskfileGraph) Merge() (*Taskfile, error) { return nil, err } - _ = rootVertex.Taskfile.Tasks.Range(func(name string, task *Task) error { + // TODO: I don't think this is necessary anymore + for name, task := range rootVertex.Taskfile.Tasks.All(nil) { if task == nil { task = &Task{} rootVertex.Taskfile.Tasks.Set(name, task) } task.Task = name - return nil - }) + } return rootVertex.Taskfile, nil } diff --git a/taskfile/ast/include.go b/taskfile/ast/include.go index f78459aeee..915bb5ed1b 100644 --- a/taskfile/ast/include.go +++ b/taskfile/ast/include.go @@ -1,10 +1,13 @@ package ast import ( + "iter" + "github.com/elliotchance/orderedmap/v2" "gopkg.in/yaml.v3" "github.com/go-task/task/v3/errors" + "github.com/go-task/task/v3/internal/sort" ) // Include represents information about included taskfiles @@ -61,16 +64,34 @@ func (includes *Includes) Set(key string, value *Include) bool { return includes.om.Set(key, value) } -func (includes *Includes) Range(f func(k string, v *Include) error) error { +// All returns an iterator that loops over all task key-value pairs. +func (includes *Includes) All() iter.Seq2[string, *Include] { if includes == nil || includes.om == nil { - return nil + return func(yield func(string, *Include) bool) {} + } + return includes.om.Iterator() +} + +// Keys returns an iterator that loops over all task keys. +func (includes *Includes) Keys(sorter sort.Sorter) iter.Seq[string] { + return func(yield func(string) bool) { + for k := range includes.All() { + if !yield(k) { + return + } + } } - for pair := includes.om.Front(); pair != nil; pair = pair.Next() { - if err := f(pair.Key, pair.Value); err != nil { - return err +} + +// Values returns an iterator that loops over all task values. +func (includes *Includes) Values(sorter sort.Sorter) iter.Seq[*Include] { + return func(yield func(*Include) bool) { + for _, v := range includes.All() { + if !yield(v) { + return + } } } - return nil } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/taskfile/ast/matrix.go b/taskfile/ast/matrix.go index e5e2a8b6cb..12fc948e03 100644 --- a/taskfile/ast/matrix.go +++ b/taskfile/ast/matrix.go @@ -1,11 +1,14 @@ package ast import ( + "iter" + "github.com/elliotchance/orderedmap/v2" "gopkg.in/yaml.v3" "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/deepcopy" + "github.com/go-task/task/v3/internal/sort" ) type Matrix struct { @@ -48,16 +51,31 @@ func (matrix *Matrix) Set(key string, value []any) bool { return matrix.om.Set(key, value) } -func (matrix *Matrix) Range(f func(k string, v []any) error) error { - if matrix == nil || matrix.om == nil { - return nil +// All returns an iterator that loops over all task key-value pairs. +func (matrix *Matrix) All() iter.Seq2[string, []any] { + return matrix.om.Iterator() +} + +// Keys returns an iterator that loops over all task keys. +func (matrix *Matrix) Keys(sorter sort.Sorter) iter.Seq[string] { + return func(yield func(string) bool) { + for k := range matrix.All() { + if !yield(k) { + return + } + } } - for pair := matrix.om.Front(); pair != nil; pair = pair.Next() { - if err := f(pair.Key, pair.Value); err != nil { - return err +} + +// Values returns an iterator that loops over all task values. +func (matrix *Matrix) Values(sorter sort.Sorter) iter.Seq[[]any] { + return func(yield func([]any) bool) { + for _, v := range matrix.All() { + if !yield(v) { + return + } } } - return nil } func (matrix *Matrix) DeepCopy() *Matrix { diff --git a/taskfile/ast/tasks.go b/taskfile/ast/tasks.go index 2ecc4fbfa4..d2a201ead3 100644 --- a/taskfile/ast/tasks.go +++ b/taskfile/ast/tasks.go @@ -2,6 +2,7 @@ package ast import ( "fmt" + "iter" "slices" "strings" @@ -10,6 +11,7 @@ import ( "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/filepathext" + "github.com/go-task/task/v3/internal/sort" ) // Tasks represents a group of tasks @@ -53,38 +55,47 @@ func (tasks *Tasks) Set(key string, value *Task) bool { return tasks.om.Set(key, value) } -func (tasks *Tasks) Range(f func(k string, v *Task) error) error { - if tasks == nil || tasks.om == nil { - return nil +// All returns an iterator that loops over all task key-value pairs in the order +// specified by the sorter. +func (t *Tasks) All(sorter sort.Sorter) iter.Seq2[string, *Task] { + if t == nil || t.om == nil { + return func(yield func(string, *Task) bool) {} + } + if sorter == nil { + return t.om.Iterator() } - for pair := tasks.om.Front(); pair != nil; pair = pair.Next() { - if err := f(pair.Key, pair.Value); err != nil { - return err + return func(yield func(string, *Task) bool) { + for _, key := range sorter(t.om.Keys(), nil) { + el := t.om.GetElement(key) + if !yield(el.Key, el.Value) { + return + } } } - return nil } -func (tasks *Tasks) Keys() []string { - if tasks == nil { - return nil - } - var keys []string - for pair := tasks.om.Front(); pair != nil; pair = pair.Next() { - keys = append(keys, pair.Key) +// Keys returns an iterator that loops over all task keys in the order specified +// by the sorter. +func (t *Tasks) Keys(sorter sort.Sorter) iter.Seq[string] { + return func(yield func(string) bool) { + for k := range t.All(sorter) { + if !yield(k) { + return + } + } } - return keys } -func (tasks *Tasks) Values() []*Task { - if tasks == nil { - return nil - } - var values []*Task - for pair := tasks.om.Front(); pair != nil; pair = pair.Next() { - values = append(values, pair.Value) +// Values returns an iterator that loops over all task values in the order +// specified by the sorter. +func (t *Tasks) Values(sorter sort.Sorter) iter.Seq[*Task] { + return func(yield func(*Task) bool) { + for _, v := range t.All(sorter) { + if !yield(v) { + return + } + } } - return values } type MatchingTask struct { @@ -104,20 +115,19 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask { } // Attempt a wildcard match // For now, we can just nil check the task before each loop - _ = t.Range(func(key string, value *Task) error { + for _, value := range t.All(nil) { if match, wildcards := value.WildcardMatch(call.Task); match { matchingTasks = append(matchingTasks, &MatchingTask{ Task: value, Wildcards: wildcards, }) } - return nil - }) + } return matchingTasks } func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) error { - err := t2.Range(func(name string, v *Task) error { + for name, v := range t2.All(nil) { // We do a deep copy of the task struct here to ensure that no data can // be changed elsewhere once the taskfile is merged. task := v.DeepCopy() @@ -177,9 +187,7 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) } // Add the task to the merged taskfile t1.Set(taskName, task) - - return nil - }) + } // If the included Taskfile has a default task, is not flattened and the // parent namespace has no task with a matching name, we can add an alias so @@ -197,7 +205,7 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) } } - return err + return nil } func (t *Tasks) UnmarshalYAML(node *yaml.Node) error { diff --git a/taskfile/ast/var.go b/taskfile/ast/var.go index d40682a510..7bb3459366 100644 --- a/taskfile/ast/var.go +++ b/taskfile/ast/var.go @@ -1,6 +1,7 @@ package ast import ( + "iter" "strings" "github.com/elliotchance/orderedmap/v2" @@ -9,6 +10,7 @@ import ( "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/experiments" + "github.com/go-task/task/v3/internal/sort" ) // Vars is a string[string] variables map. @@ -52,32 +54,50 @@ func (vs *Vars) Set(key string, value Var) bool { return vs.om.Set(key, value) } -func (vs *Vars) Range(f func(k string, v Var) error) error { +// All returns an iterator that loops over all task key-value pairs. +func (vs *Vars) All() iter.Seq2[string, Var] { if vs == nil || vs.om == nil { - return nil + return func(yield func(string, Var) bool) {} } - for pair := vs.om.Front(); pair != nil; pair = pair.Next() { - if err := f(pair.Key, pair.Value); err != nil { - return err + return vs.om.Iterator() +} + +// Keys returns an iterator that loops over all task keys. +func (vs *Vars) Keys(sorter sort.Sorter) iter.Seq[string] { + return func(yield func(string) bool) { + for k := range vs.All() { + if !yield(k) { + return + } + } + } +} + +// Values returns an iterator that loops over all task values. +func (vs *Vars) Values(sorter sort.Sorter) iter.Seq[Var] { + return func(yield func(Var) bool) { + for _, v := range vs.All() { + if !yield(v) { + return + } } } - return nil } // ToCacheMap converts Vars to a map containing only the static // variables func (vs *Vars) ToCacheMap() (m map[string]any) { m = make(map[string]any, vs.Len()) - for pair := vs.om.Front(); pair != nil; pair = pair.Next() { - if pair.Value.Sh != "" { + for k, v := range vs.All() { + if v.Sh != "" { // Dynamic variable is not yet resolved; trigger // to be used in templates. return nil } - if pair.Value.Live != nil { - m[pair.Key] = pair.Value.Live + if v.Live != nil { + m[k] = v.Live } else { - m[pair.Key] = pair.Value.Value + m[k] = v.Value } } return diff --git a/taskfile/reader.go b/taskfile/reader.go index 1f1fefe2a1..53f1efa419 100644 --- a/taskfile/reader.go +++ b/taskfile/reader.go @@ -97,7 +97,7 @@ func (r *Reader) include(node Node) error { var g errgroup.Group // Loop over each included taskfile - _ = vertex.Taskfile.Includes.Range(func(namespace string, include *ast.Include) error { + for _, include := range vertex.Taskfile.Includes.All() { vars := compiler.GetEnviron() vars.Merge(vertex.Taskfile.Vars, nil) // Start a goroutine to process each included Taskfile @@ -173,8 +173,7 @@ func (r *Reader) include(node Node) error { } return err }) - return nil - }) + } // Wait for all the go routines to finish return g.Wait() @@ -282,7 +281,7 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) { // Set the taskfile/task's locations tf.Location = node.Location() - for _, task := range tf.Tasks.Values() { + for task := range tf.Tasks.Values(nil) { // If the task is not defined, create a new one if task == nil { task = &ast.Task{} diff --git a/variables.go b/variables.go index 3d6289e579..516a8f8d8c 100644 --- a/variables.go +++ b/variables.go @@ -109,21 +109,17 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task, new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache), nil) new.Env.Merge(templater.ReplaceVars(origTask.Env, cache), nil) if evaluateShVars { - err = new.Env.Range(func(k string, v ast.Var) error { + for k, v := range new.Env.All() { // If the variable is not dynamic, we can set it and return if v.Value != nil || v.Sh == "" { new.Env.Set(k, ast.Var{Value: v.Value}) - return nil + continue } static, err := e.Compiler.HandleDynamicVar(v, new.Dir) if err != nil { - return err + return nil, err } new.Env.Set(k, ast.Var{Value: static}) - return nil - }) - if err != nil { - return nil, err } } @@ -300,7 +296,7 @@ func itemsFromFor( // If the variable is dynamic, then it hasn't been resolved yet // and we can't use it as a list. This happens when fast compiling a task // for use in --list or --list-all etc. - if ok && v.Sh == "" { + if ok && v.Value != nil && v.Sh == "" { switch value := v.Value.(type) { case string: if f.Split != "" { @@ -337,7 +333,7 @@ func product(inputMap *ast.Matrix) []map[string]any { result := []map[string]any{{}} // Iterate over each slice in the slices - _ = inputMap.Range(func(key string, slice []any) error { + for key, slice := range inputMap.All() { var newResult []map[string]any // For each combination in the current result @@ -357,8 +353,7 @@ func product(inputMap *ast.Matrix) []map[string]any { // Update result with the new combinations result = newResult - return nil - }) + } return result }