Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: use external package for ordered maps
Browse files Browse the repository at this point in the history
pd93 committed Sep 9, 2024

Verified

This commit was signed with the committer’s verified signature.
pellared Robert Pająk
1 parent 8ab5fe0 commit 3396992
Showing 25 changed files with 500 additions and 472 deletions.
2 changes: 1 addition & 1 deletion args/args.go
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import (
// Parse parses command line argument: tasks and global variables
func Parse(args ...string) ([]*ast.Call, *ast.Vars) {
calls := []*ast.Call{}
globals := &ast.Vars{}
globals := ast.NewVars()

for _, arg := range args {
if !strings.Contains(arg, "=") {
82 changes: 48 additions & 34 deletions args/args_test.go
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/assert"

"github.com/go-task/task/v3/args"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/taskfile/ast"
)

@@ -32,45 +31,55 @@ func TestArgs(t *testing.T) {
{Task: "task-b"},
{Task: "task-c"},
},
ExpectedGlobals: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"FOO": {Value: "bar"},
"BAR": {Value: "baz"},
"BAZ": {Value: "foo"},
ExpectedGlobals: ast.NewVars(
&ast.VarElement{
Key: "FOO",
Value: ast.Var{
Value: "bar",
},
[]string{"FOO", "BAR", "BAZ"},
),
},
},
&ast.VarElement{
Key: "BAR",
Value: ast.Var{
Value: "baz",
},
},
&ast.VarElement{
Key: "BAZ",
Value: ast.Var{
Value: "foo",
},
},
),
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []*ast.Call{
{Task: "task-a"},
},
ExpectedGlobals: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"CONTENT": {Value: "with some spaces"},
ExpectedGlobals: ast.NewVars(
&ast.VarElement{
Key: "CONTENT",
Value: ast.Var{
Value: "with some spaces",
},
[]string{"CONTENT"},
),
},
},
),
},
{
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []*ast.Call{
{Task: "task-a"},
{Task: "task-b"},
},
ExpectedGlobals: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"FOO": {Value: "bar"},
ExpectedGlobals: ast.NewVars(
&ast.VarElement{
Key: "FOO",
Value: ast.Var{
Value: "bar",
},
[]string{"FOO"},
),
},
},
),
},
{
Args: nil,
@@ -83,15 +92,20 @@ func TestArgs(t *testing.T) {
{
Args: []string{"FOO=bar", "BAR=baz"},
ExpectedCalls: []*ast.Call{},
ExpectedGlobals: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"FOO": {Value: "bar"},
"BAR": {Value: "baz"},
ExpectedGlobals: ast.NewVars(
&ast.VarElement{
Key: "FOO",
Value: ast.Var{
Value: "bar",
},
[]string{"FOO", "BAR"},
),
},
},
&ast.VarElement{
Key: "BAR",
Value: ast.Var{
Value: "baz",
},
},
),
},
}

@@ -100,8 +114,8 @@ func TestArgs(t *testing.T) {
calls, globals := args.Parse(test.Args...)
assert.Equal(t, test.ExpectedCalls, calls)
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
assert.Equal(t, test.ExpectedGlobals.Keys(), globals.Keys())
assert.Equal(t, test.ExpectedGlobals.Values(), globals.Values())
assert.Equal(t, test.ExpectedGlobals, globals)
assert.Equal(t, test.ExpectedGlobals, globals)
}
})
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ require (
github.com/alecthomas/chroma/v2 v2.14.0
github.com/davecgh/go-spew v1.1.1
github.com/dominikbraun/graph v0.23.0
github.com/elliotchance/orderedmap/v2 v2.4.0
github.com/fatih/color v1.17.0
github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.1.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/elliotchance/orderedmap/v2 v2.4.0 h1:6tUmMwD9F998FNpwFxA5E6NQvSpk2PVw7RKsVq3+2Cw=
github.com/elliotchance/orderedmap/v2 v2.4.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
2 changes: 1 addition & 1 deletion internal/compiler/compiler.go
Original file line number Diff line number Diff line change
@@ -171,7 +171,7 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
return result, nil
}

// ResetCache clear the dymanic variables cache
// ResetCache clear the dynamic variables cache
func (c *Compiler) ResetCache() {
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
2 changes: 1 addition & 1 deletion internal/compiler/env.go
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import (
// GetEnviron the all return all environment variables encapsulated on a
// ast.Vars
func GetEnviron() *ast.Vars {
m := &ast.Vars{}
m := ast.NewVars()
for _, e := range os.Environ() {
keyVal := strings.SplitN(e, "=", 2)
key, val := keyVal[0], keyVal[1]
17 changes: 17 additions & 0 deletions internal/deepcopy/deepcopy.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@ package deepcopy

import (
"reflect"

"github.com/elliotchance/orderedmap/v2"
)

type Copier[T any] interface {
@@ -38,6 +40,21 @@ func Map[K comparable, V any](orig map[K]V) map[K]V {
return c
}

func OrderedMap[K comparable, V any](orig *orderedmap.OrderedMap[K, V]) *orderedmap.OrderedMap[K, V] {
if orig.Len() == 0 {
return orderedmap.NewOrderedMap[K, V]()
}
c := orderedmap.NewOrderedMap[K, V]()
for pair := orig.Front(); pair != nil; pair = pair.Next() {
if copyable, ok := any(pair.Value).(Copier[V]); ok {
c.Set(pair.Key, copyable.DeepCopy())
} else {
c.Set(pair.Key, pair.Value)
}
}
return c
}

// TraverseStringsFunc runs the given function on every string in the given
// value by traversing it recursively. If the given value is a string, the
// function will run on a copy of the string and return it. If the value is a
164 changes: 0 additions & 164 deletions internal/omap/orderedmap.go

This file was deleted.

121 changes: 0 additions & 121 deletions internal/omap/orderedmap_test.go

This file was deleted.

12 changes: 6 additions & 6 deletions internal/output/output_test.go
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@ import (
"github.com/stretchr/testify/require"

"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
@@ -49,11 +48,12 @@ func TestGroup(t *testing.T) {

func TestGroupWithBeginEnd(t *testing.T) {
tmpl := templater.Cache{
Vars: &ast.Vars{
OrderedMap: omap.FromMap(map[string]ast.Var{
"VAR1": {Value: "example-value"},
}),
},
Vars: ast.NewVars(
&ast.VarElement{
Key: "VAR1",
Value: ast.Var{Value: "example-value"},
},
),
}

var o output.Output = output.Group{
4 changes: 3 additions & 1 deletion internal/summary/summary.go
Original file line number Diff line number Diff line change
@@ -10,7 +10,9 @@ import (
func PrintTasks(l *logger.Logger, t *ast.Taskfile, c []*ast.Call) {
for i, call := range c {
PrintSpaceBetweenSummaries(l, i)
PrintTask(l, t.Tasks.Get(call.Task))
if task, ok := t.Tasks.Get(call.Task); ok {
PrintTask(l, task)
}
}
}

2 changes: 1 addition & 1 deletion internal/summary/summary_test.go
Original file line number Diff line number Diff line change
@@ -156,7 +156,7 @@ func TestPrintAllWithSpaces(t *testing.T) {
t2 := &ast.Task{Task: "t2"}
t3 := &ast.Task{Task: "t3"}

tasks := ast.Tasks{}
tasks := ast.NewTasks()
tasks.Set("t1", t1)
tasks.Set("t2", t2)
tasks.Set("t3", t3)
2 changes: 1 addition & 1 deletion requires.go
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ func (e *Executor) areTaskRequiredVarsSet(t *ast.Task, call *ast.Call) error {

var missingVars []string
for _, requiredVar := range t.Requires.Vars {
if !vars.Exists(requiredVar) {
if _, ok := vars.Get(requiredVar); !ok {
missingVars = append(missingVars, requiredVar)
}
}
2 changes: 1 addition & 1 deletion setup.go
Original file line number Diff line number Diff line change
@@ -213,7 +213,7 @@ func (e *Executor) readDotEnvFiles() error {
}

err = env.Range(func(key string, value ast.Var) error {
if ok := e.Taskfile.Env.Exists(key); !ok {
if _, ok := e.Taskfile.Env.Get(key); !ok {
e.Taskfile.Env.Set(key, value)
}
return nil
2 changes: 1 addition & 1 deletion task.go
Original file line number Diff line number Diff line change
@@ -444,7 +444,7 @@ func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) {
case 0: // Carry on
case 1:
if call.Vars == nil {
call.Vars = &ast.Vars{}
call.Vars = ast.NewVars()
}
call.Vars.Set("MATCH", ast.Var{Value: matchingTasks[0].Wildcards})
return matchingTasks[0].Task, nil
2 changes: 1 addition & 1 deletion task_test.go
Original file line number Diff line number Diff line change
@@ -2205,7 +2205,7 @@ func TestSplitArgs(t *testing.T) {
}
require.NoError(t, e.Setup())

vars := &ast.Vars{}
vars := ast.NewVars()
vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"})

err := e.Run(context.Background(), &ast.Call{Task: "default", Vars: vars})
5 changes: 2 additions & 3 deletions taskfile/ast/for.go
Original file line number Diff line number Diff line change
@@ -5,13 +5,12 @@ import (

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
"github.com/go-task/task/v3/internal/omap"
)

type For struct {
From string
List []any
Matrix omap.OrderedMap[string, []any]
Matrix *Matrix
Var string
Split string
As string
@@ -38,7 +37,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {

case yaml.MappingNode:
var forStruct struct {
Matrix omap.OrderedMap[string, []any]
Matrix *Matrix
Var string
Split string
As string
81 changes: 59 additions & 22 deletions taskfile/ast/include.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package ast

import (
"github.com/elliotchance/orderedmap/v2"
"gopkg.in/yaml.v3"

"github.com/go-task/task/v3/errors"
omap "github.com/go-task/task/v3/internal/omap"
)

// Include represents information about included taskfiles
@@ -20,27 +20,80 @@ type Include struct {
Flatten bool
}

// Includes represents information about included tasksfiles
// Includes represents information about included taskfiles
type Includes struct {
omap.OrderedMap[string, *Include]
om *orderedmap.OrderedMap[string, *Include]
}

type IncludeElement orderedmap.Element[string, *Include]

func NewIncludes(els ...*IncludeElement) *Includes {
includes := &Includes{
om: orderedmap.NewOrderedMap[string, *Include](),
}
for _, el := range els {
includes.Set(el.Key, el.Value)
}
return includes
}

func (includes *Includes) Len() int {
if includes == nil || includes.om == nil {
return 0
}
return includes.om.Len()
}

func (includes *Includes) Get(key string) (*Include, bool) {
if includes == nil || includes.om == nil {
return &Include{}, false
}
return includes.om.Get(key)
}

func (includes *Includes) Set(key string, value *Include) bool {
if includes == nil {
includes = NewIncludes()
}
if includes.om == nil {
includes.om = orderedmap.NewOrderedMap[string, *Include]()
}
return includes.om.Set(key, value)
}

func (includes *Includes) Range(f func(k string, v *Include) error) error {
if includes == nil || includes.om == nil {
return nil
}
for pair := includes.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
}
return nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
// NOTE(@andreynering): on this style of custom unmarshalling,
// even number contains the keys, while odd numbers contains
// the values.
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
// the map manually. We increment over 2 values at a time and assign
// them as a key-value pair.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]

// Decode the value node into an Include struct
var v Include
if err := valueNode.Decode(&v); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}

// Set the include namespace
v.Namespace = keyNode.Value

// Add the include to the ordered map
includes.Set(keyNode.Value, &v)
}
return nil
@@ -49,22 +102,6 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("includes")
}

// Len returns the length of the map
func (includes *Includes) Len() int {
if includes == nil {
return 0
}
return includes.OrderedMap.Len()
}

// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
func (includes *Includes) Range(f func(k string, v *Include) error) error {
if includes == nil {
return nil
}
return includes.OrderedMap.Range(f)
}

func (include *Include) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {

95 changes: 95 additions & 0 deletions taskfile/ast/matrix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package ast

import (
"github.com/elliotchance/orderedmap/v2"
"gopkg.in/yaml.v3"

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)

type Matrix struct {
om *orderedmap.OrderedMap[string, []any]
}

type MatrixElement orderedmap.Element[string, []any]

func NewMatrix(els ...*MatrixElement) *Matrix {
matrix := &Matrix{
om: orderedmap.NewOrderedMap[string, []any](),
}
for _, el := range els {
matrix.Set(el.Key, el.Value)
}
return matrix
}

func (matrix *Matrix) Len() int {
if matrix == nil || matrix.om == nil {
return 0
}
return matrix.om.Len()
}

func (matrix *Matrix) Get(key string) ([]any, bool) {
if matrix == nil || matrix.om == nil {
return nil, false
}
return matrix.om.Get(key)
}

func (matrix *Matrix) Set(key string, value []any) bool {
if matrix == nil {
matrix = NewMatrix()
}
if matrix.om == nil {
matrix.om = orderedmap.NewOrderedMap[string, []any]()
}
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
}
for pair := matrix.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
}
return nil
}

func (matrix *Matrix) DeepCopy() *Matrix {
if matrix == nil {
return nil
}
return &Matrix{
om: deepcopy.OrderedMap(matrix.om),
}
}

func (matrix *Matrix) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
// the map manually. We increment over 2 values at a time and assign
// them as a key-value pair.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]

// Decode the value node into a Matrix struct
var v []any
if err := valueNode.Decode(&v); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}

// Add the task to the ordered map
matrix.Set(keyNode.Value, v)
}
return nil
}

return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("matrix")
}
24 changes: 18 additions & 6 deletions taskfile/ast/taskfile.go
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ type Taskfile struct {
Shopt []string
Vars *Vars
Env *Vars
Tasks Tasks
Tasks *Tasks
Silent bool
Dotenv []string
Run string
@@ -47,11 +47,17 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
if t2.Output.IsSet() {
t1.Output = t2.Output
}
if t1.Includes == nil {
t1.Includes = NewIncludes()
}
if t1.Vars == nil {
t1.Vars = &Vars{}
t1.Vars = NewVars()
}
if t1.Env == nil {
t1.Env = &Vars{}
t1.Env = NewVars()
}
if t1.Tasks == nil {
t1.Tasks = NewTasks()
}
t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include)
@@ -70,7 +76,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
Shopt []string
Vars *Vars
Env *Vars
Tasks Tasks
Tasks *Tasks
Silent bool
Dotenv []string
Run string
@@ -92,11 +98,17 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run
tf.Interval = taskfile.Interval
if tf.Includes == nil {
tf.Includes = NewIncludes()
}
if tf.Vars == nil {
tf.Vars = &Vars{}
tf.Vars = NewVars()
}
if tf.Env == nil {
tf.Env = &Vars{}
tf.Env = NewVars()
}
if tf.Tasks == nil {
tf.Tasks = NewTasks()
}
return nil
}
60 changes: 36 additions & 24 deletions taskfile/ast/taskfile_test.go
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/taskfile/ast"
)

@@ -38,15 +37,21 @@ vars:
yamlTaskCall,
&ast.Cmd{},
&ast.Cmd{
Task: "another-task", Vars: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"PARAM1": {Value: "VALUE1"},
"PARAM2": {Value: "VALUE2"},
Task: "another-task",
Vars: ast.NewVars(
&ast.VarElement{
Key: "PARAM1",
Value: ast.Var{
Value: "VALUE1",
},
[]string{"PARAM1", "PARAM2"},
),
},
},
&ast.VarElement{
Key: "PARAM2",
Value: ast.Var{
Value: "VALUE2",
},
},
),
},
},
{
@@ -58,14 +63,15 @@ vars:
yamlDeferredCall,
&ast.Cmd{},
&ast.Cmd{
Task: "some_task", Vars: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"PARAM1": {Value: "var"},
Task: "some_task",
Vars: ast.NewVars(
&ast.VarElement{
Key: "PARAM1",
Value: ast.Var{
Value: "var",
},
[]string{"PARAM1"},
),
},
},
),
Defer: true,
},
},
@@ -78,15 +84,21 @@ vars:
yamlTaskCall,
&ast.Dep{},
&ast.Dep{
Task: "another-task", Vars: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{
"PARAM1": {Value: "VALUE1"},
"PARAM2": {Value: "VALUE2"},
Task: "another-task",
Vars: ast.NewVars(
&ast.VarElement{
Key: "PARAM1",
Value: ast.Var{
Value: "VALUE1",
},
},
&ast.VarElement{
Key: "PARAM2",
Value: ast.Var{
Value: "VALUE2",
},
[]string{"PARAM1", "PARAM2"},
),
},
},
),
},
},
}
151 changes: 109 additions & 42 deletions taskfile/ast/tasks.go
Original file line number Diff line number Diff line change
@@ -5,16 +5,86 @@ import (
"slices"
"strings"

"github.com/elliotchance/orderedmap/v2"
"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/omap"
)

// Tasks represents a group of tasks
type Tasks struct {
omap.OrderedMap[string, *Task]
om *orderedmap.OrderedMap[string, *Task]
}

type TaskElement orderedmap.Element[string, *Task]

func NewTasks(els ...*TaskElement) *Tasks {
tasks := &Tasks{
om: orderedmap.NewOrderedMap[string, *Task](),
}
for _, el := range els {
tasks.Set(el.Key, el.Value)
}
return tasks
}

func (tasks *Tasks) Len() int {
if tasks == nil || tasks.om == nil {
return 0
}
return tasks.om.Len()
}

func (tasks *Tasks) Get(key string) (*Task, bool) {
if tasks == nil || tasks.om == nil {
return &Task{}, false
}
return tasks.om.Get(key)
}

func (tasks *Tasks) Set(key string, value *Task) bool {
if tasks == nil {
tasks = NewTasks()
}
if tasks.om == nil {
tasks.om = orderedmap.NewOrderedMap[string, *Task]()
}
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
}
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
}
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)
}
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)
}
return values
}

type MatchingTask struct {
@@ -26,10 +96,9 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
if call == nil {
return nil
}
var task *Task
var matchingTasks []*MatchingTask
// If there is a direct match, return it
if task = t.OrderedMap.Get(call.Task); task != nil {
if task, ok := t.Get(call.Task); ok {
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
return matchingTasks
}
@@ -47,7 +116,7 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
return matchingTasks
}

func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) error {
func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) error {
err := t2.Range(func(name string, v *Task) error {
// We do a deep copy of the task struct here to ensure that no data can
// be changed elsewhere once the taskfile is merged.
@@ -94,13 +163,13 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) e
if include.AdvancedImport {
task.Dir = filepathext.SmartJoin(include.Dir, task.Dir)
if task.IncludeVars == nil {
task.IncludeVars = &Vars{}
task.IncludeVars = NewVars()
}
task.IncludeVars.Merge(include.Vars, nil)
task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
}

if t1.Get(taskName) != nil {
if _, ok := t1.Get(taskName); ok {
return &errors.TaskNameFlattenConflictError{
TaskName: taskName,
Include: include.Namespace,
@@ -112,52 +181,50 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) e
return nil
})

// If the included Taskfile has a default task, being not flattened and the parent namespace has
// no task with a matching name, we can add an alias so that the user can
// run the included Taskfile's default task without specifying its full
// name. If the parent namespace has aliases, we add another alias for each
// of them.
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil && !include.Flatten {
// 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
// that the user can run the included Taskfile's default task without
// specifying its full name. If the parent namespace has aliases, we add
// another alias for each of them.
_, t2DefaultExists := t2.Get("default")
_, t1NamespaceExists := t1.Get(include.Namespace)
if t2DefaultExists && !t1NamespaceExists && !include.Flatten {
defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Namespace)
t1.Get(defaultTaskName).Aliases = slices.Concat(t1.Get(defaultTaskName).Aliases, include.Aliases)
t1DefaultTask, ok := t1.Get(defaultTaskName)
if ok {
t1DefaultTask.Aliases = append(t1DefaultTask.Aliases, include.Namespace)
t1DefaultTask.Aliases = slices.Concat(t1DefaultTask.Aliases, include.Aliases)
}
}

return err
}

func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
tasks := omap.New[string, *Task]()
if err := node.Decode(&tasks); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}

// nolint: errcheck
tasks.Range(func(name string, task *Task) error {
// Set the task's name
if task == nil {
task = &Task{
Task: name,
}
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
// the map manually. We increment over 2 values at a time and assign
// them as a key-value pair.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]

// Decode the value node into a Task struct
var v Task
if err := valueNode.Decode(&v); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
task.Task = name

// Set the task's location
for _, keys := range node.Content {
if keys.Value == name {
task.Location = &Location{
Line: keys.Line,
Column: keys.Column,
}
}

// Set the task name and location
v.Task = keyNode.Value
v.Location = &Location{
Line: keyNode.Line,
Column: keyNode.Column,
}
tasks.Set(name, task)
return nil
})

*t = Tasks{
OrderedMap: tasks,
// Add the task to the ordered map
t.Set(keyNode.Value, &v)
}
return nil
}
120 changes: 88 additions & 32 deletions taskfile/ast/var.go
Original file line number Diff line number Diff line change
@@ -3,67 +3,97 @@ package ast
import (
"strings"

"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/experiments"
"github.com/go-task/task/v3/internal/omap"
)

// Vars is a string[string] variables map.
type Vars struct {
omap.OrderedMap[string, Var]
om *orderedmap.OrderedMap[string, Var]
}

type VarElement orderedmap.Element[string, Var]

func NewVars(els ...*VarElement) *Vars {
vs := &Vars{
om: orderedmap.NewOrderedMap[string, Var](),
}
for _, el := range els {
vs.Set(el.Key, el.Value)
}
return vs
}

func (vs *Vars) Len() int {
if vs == nil || vs.om == nil {
return 0
}
return vs.om.Len()
}

func (vs *Vars) Get(key string) (Var, bool) {
if vs == nil || vs.om == nil {
return Var{}, false
}
return vs.om.Get(key)
}

func (vs *Vars) Set(key string, value Var) bool {
if vs == nil {
vs = NewVars()
}
if vs.om == nil {
vs.om = orderedmap.NewOrderedMap[string, Var]()
}
return vs.om.Set(key, value)
}

func (vs *Vars) Range(f func(k string, v Var) error) error {
if vs == nil || vs.om == nil {
return nil
}
for pair := vs.om.Front(); pair != nil; pair = pair.Next() {
if err := f(pair.Key, pair.Value); err != nil {
return err
}
}
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())
_ = vs.Range(func(k string, v Var) error {
if v.Sh != "" {
for pair := vs.om.Front(); pair != nil; pair = pair.Next() {
if pair.Value.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
return nil
}

if v.Live != nil {
m[k] = v.Live
if pair.Value.Live != nil {
m[pair.Key] = pair.Value.Live
} else {
m[k] = v.Value
m[pair.Key] = pair.Value.Value
}
return nil
})
return
}

// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
func (vs *Vars) Range(f func(k string, v Var) error) error {
if vs == nil {
return nil
}
return vs.OrderedMap.Range(f)
return
}

// Wrapper around OrderedMap.Merge to ensure we don't get nil pointer errors
func (vs *Vars) Merge(other *Vars, include *Include) {
if vs == nil || other == nil {
if vs == nil || vs.om == nil || other == nil {
return
}
_ = other.Range(func(key string, value Var) error {
for pair := other.om.Front(); pair != nil; pair = pair.Next() {
if include != nil && include.AdvancedImport {
value.Dir = include.Dir
pair.Value.Dir = include.Dir
}
vs.Set(key, value)
return nil
})
}

// Wrapper around OrderedMap.Len to ensure we don't get nil pointer errors
func (vs *Vars) Len() int {
if vs == nil {
return 0
vs.om.Set(pair.Key, pair.Value)
}
return vs.OrderedMap.Len()
}

// DeepCopy creates a new instance of Vars and copies
@@ -73,8 +103,34 @@ func (vs *Vars) DeepCopy() *Vars {
return nil
}
return &Vars{
OrderedMap: vs.OrderedMap.DeepCopy(),
om: deepcopy.OrderedMap(vs.om),
}
}

func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
vs.om = orderedmap.NewOrderedMap[string, Var]()
switch node.Kind {
case yaml.MappingNode:
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
// the map manually. We increment over 2 values at a time and assign
// them as a key-value pair.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]

// Decode the value node into a Task struct
var v Var
if err := valueNode.Decode(&v); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}

// Add the task to the ordered map
vs.Set(keyNode.Value, v)
}
return nil
}

return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("vars")
}

// Var represents either a static or dynamic variable.
4 changes: 2 additions & 2 deletions taskfile/dotenv.go
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
return nil, err
}

env := &ast.Vars{}
env := ast.NewVars()
cache := &templater.Cache{Vars: vars}

for _, dotEnvPath := range tf.Dotenv {
@@ -40,7 +40,7 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
return nil, err
}
for key, value := range envs {
if ok := env.Exists(key); !ok {
if _, ok := env.Get(key); !ok {
env.Set(key, ast.Var{Value: value})
}
}
13 changes: 6 additions & 7 deletions variables.go
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ import (
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fingerprint"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -86,7 +85,7 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
new.Prefix = new.Task
}

dotenvEnvs := &ast.Vars{}
dotenvEnvs := ast.NewVars()
if len(new.Dotenv) > 0 {
for _, dotEnvPath := range new.Dotenv {
dotEnvPath = filepathext.SmartJoin(new.Dir, dotEnvPath)
@@ -98,14 +97,14 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
return nil, err
}
for key, value := range envs {
if ok := dotenvEnvs.Exists(key); !ok {
if _, ok := dotenvEnvs.Get(key); !ok {
dotenvEnvs.Set(key, ast.Var{Value: value})
}
}
}
}

new.Env = &ast.Vars{}
new.Env = ast.NewVars()
new.Env.Merge(templater.ReplaceVars(e.Taskfile.Env, cache), nil)
new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache), nil)
new.Env.Merge(templater.ReplaceVars(origTask.Env, cache), nil)
@@ -297,11 +296,11 @@ func itemsFromFor(
// Get the list from a variable and split it up
if f.Var != "" {
if vars != nil {
v := vars.Get(f.Var)
v, ok := vars.Get(f.Var)
// 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 v.Value != nil && v.Sh == "" {
if ok && v.Sh == "" {
switch value := v.Value.(type) {
case string:
if f.Split != "" {
@@ -329,7 +328,7 @@ func itemsFromFor(
}

// product generates the cartesian product of the input map of slices.
func product(inputMap omap.OrderedMap[string, []any]) []map[string]any {
func product(inputMap *ast.Matrix) []map[string]any {
if inputMap.Len() == 0 {
return nil
}

0 comments on commit 3396992

Please sign in to comment.