feat: iterators (#1798)

* feat: update to github.com/elliotchance/orderedmap/v3

* refactor: better sort package

* feat: iterators

* chore: remove unnecessary code
This commit is contained in:
Pete Davison
2025-02-22 16:22:03 +00:00
committed by GitHub
parent 25f9299d0a
commit daf39a04bf
18 changed files with 229 additions and 246 deletions

View File

@@ -116,14 +116,5 @@ func (tfg *TaskfileGraph) Merge() (*Taskfile, error) {
return nil, err
}
_ = rootVertex.Taskfile.Tasks.Range(func(name string, task *Task) error {
if task == nil {
task = &Task{}
rootVertex.Taskfile.Tasks.Set(name, task)
}
task.Task = name
return nil
})
return rootVertex.Taskfile, nil
}

View File

@@ -1,9 +1,10 @@
package ast
import (
"iter"
"sync"
"github.com/elliotchance/orderedmap/v2"
"github.com/elliotchance/orderedmap/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
@@ -84,19 +85,31 @@ func (includes *Includes) Set(key string, value *Include) bool {
return includes.om.Set(key, value)
}
// All returns an iterator that loops over all task key-value pairs.
// Range calls the provided function for each include in the map. The function
// receives the include's key and value as arguments. If the function returns
// an error, the iteration stops and the error is returned.
func (includes *Includes) Range(f func(k string, v *Include) error) error {
func (includes *Includes) All() iter.Seq2[string, *Include] {
if includes == nil || includes.om == nil {
return nil
return func(yield func(string, *Include) bool) {}
}
for pair := includes.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
return includes.om.AllFromFront()
}
// Keys returns an iterator that loops over all task keys.
func (includes *Includes) Keys() iter.Seq[string] {
if includes == nil || includes.om == nil {
return func(yield func(string) bool) {}
}
return nil
return includes.om.Keys()
}
// Values returns an iterator that loops over all task values.
func (includes *Includes) Values() iter.Seq[*Include] {
if includes == nil || includes.om == nil {
return func(yield func(*Include) bool) {}
}
return includes.om.Values()
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.

View File

@@ -1,7 +1,9 @@
package ast
import (
"github.com/elliotchance/orderedmap/v2"
"iter"
"github.com/elliotchance/orderedmap/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
@@ -48,16 +50,28 @@ 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 {
// All returns an iterator that loops over all task key-value pairs.
func (matrix *Matrix) All() iter.Seq2[string, []any] {
if matrix == nil || matrix.om == nil {
return nil
return func(yield func(string, []any) bool) {}
}
for pair := matrix.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
return matrix.om.AllFromFront()
}
// Keys returns an iterator that loops over all task keys.
func (matrix *Matrix) Keys() iter.Seq[string] {
if matrix == nil || matrix.om == nil {
return func(yield func(string) bool) {}
}
return nil
return matrix.om.Keys()
}
// Values returns an iterator that loops over all task values.
func (matrix *Matrix) Values() iter.Seq[[]any] {
if matrix == nil || matrix.om == nil {
return func(yield func([]any) bool) {}
}
return matrix.om.Values()
}
func (matrix *Matrix) DeepCopy() *Matrix {

View File

@@ -2,15 +2,17 @@ package ast
import (
"fmt"
"iter"
"slices"
"strings"
"sync"
"github.com/elliotchance/orderedmap/v2"
"github.com/elliotchance/orderedmap/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/sort"
)
type (
@@ -79,47 +81,47 @@ func (tasks *Tasks) Set(key string, value *Task) bool {
return tasks.om.Set(key, value)
}
// Range calls the provided function for each task in the map. The function
// receives the task's key and value as arguments. If the function returns an
// error, the iteration stops and the error is returned.
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) {}
}
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
if sorter == nil {
return t.om.AllFromFront()
}
return func(yield func(string, *Task) bool) {
for _, key := range sorter(slices.Collect(t.om.Keys()), nil) {
el := t.om.GetElement(key)
if !yield(el.Key, el.Value) {
return
}
}
}
return nil
}
// Keys returns a slice of all the keys in the Tasks map.
func (tasks *Tasks) Keys() []string {
if tasks == nil {
return nil
// 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
}
}
}
defer tasks.mutex.RUnlock()
tasks.mutex.RLock()
var keys []string
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
keys = append(keys, pair.Key)
}
return keys
}
// Values returns a slice of all the values in the Tasks map.
func (tasks *Tasks) Values() []*Task {
if tasks == nil {
return nil
// 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
}
}
}
defer tasks.mutex.RUnlock()
tasks.mutex.RLock()
var values []*Task
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
values = append(values, pair.Value)
}
return values
}
// FindMatchingTasks returns a list of tasks that match the given call. A task
@@ -138,22 +140,21 @@ 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 {
defer t2.mutex.RUnlock()
t2.mutex.RLock()
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()
@@ -162,9 +163,9 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars)
task.Internal = task.Internal || (include != nil && include.Internal)
taskName := name
// if the task is in the exclude list, don't add it to the merged taskfile and early return
// if the task is in the exclude list, don't add it to the merged taskfile
if slices.Contains(include.Excludes, name) {
return nil
continue
}
if !include.Flatten {
@@ -219,9 +220,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
@@ -239,7 +238,7 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars)
}
}
return err
return nil
}
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {

View File

@@ -1,9 +1,10 @@
package ast
import (
"iter"
"sync"
"github.com/elliotchance/orderedmap/v2"
"github.com/elliotchance/orderedmap/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
@@ -70,19 +71,28 @@ func (vars *Vars) Set(key string, value Var) bool {
return vars.om.Set(key, value)
}
// Range calls the provided function for each variable in the map. The function
// receives the variable's key and value as arguments. If the function returns
// an error, the iteration stops and the error is returned.
func (vars *Vars) Range(f func(k string, v Var) error) error {
// All returns an iterator that loops over all task key-value pairs.
func (vars *Vars) All() iter.Seq2[string, Var] {
if vars == nil || vars.om == nil {
return nil
return func(yield func(string, Var) bool) {}
}
for pair := vars.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
return vars.om.AllFromFront()
}
// Keys returns an iterator that loops over all task keys.
func (vars *Vars) Keys() iter.Seq[string] {
if vars == nil || vars.om == nil {
return func(yield func(string) bool) {}
}
return nil
return vars.om.Keys()
}
// Values returns an iterator that loops over all task values.
func (vars *Vars) Values() iter.Seq[Var] {
if vars == nil || vars.om == nil {
return func(yield func(Var) bool) {}
}
return vars.om.Values()
}
// ToCacheMap converts Vars to an unordered map containing only the static
@@ -91,16 +101,16 @@ func (vars *Vars) ToCacheMap() (m map[string]any) {
defer vars.mutex.RUnlock()
vars.mutex.RLock()
m = make(map[string]any, vars.Len())
for pair := vars.om.Front(); pair != nil; pair = pair.Next() {
if pair.Value.Sh != nil && *pair.Value.Sh != "" {
for k, v := range vars.All() {
if v.Sh != nil && *v.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> 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

View File

@@ -187,7 +187,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
@@ -264,8 +264,7 @@ func (r *Reader) include(node Node) error {
}
return err
})
return nil
})
}
// Wait for all the go routines to finish
return g.Wait()
@@ -299,7 +298,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{}