mirror of
https://github.com/go-task/task.git
synced 2025-01-08 04:04:08 +02:00
feat: use external package for ordered maps (#1797)
This commit is contained in:
parent
dbe6e41ac8
commit
2965841eb7
@ -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, "=") {
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
@ -34,46 +33,56 @@ 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",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAR",
|
||||
Value: ast.Var{
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAZ",
|
||||
Value: ast.Var{
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
[]string{"FOO", "BAR", "BAZ"},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
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,
|
||||
ExpectedCalls: []*ast.Call{},
|
||||
@ -85,16 +94,21 @@ 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",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAR",
|
||||
Value: ast.Var{
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
[]string{"FOO", "BAR"},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
@ -104,8 +118,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
go.mod
1
go.mod
@ -9,6 +9,7 @@ require (
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/elliotchance/orderedmap/v2 v2.6.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/go-git/go-billy/v5 v5.6.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
|
2
go.sum
2
go.sum
@ -38,6 +38,8 @@ github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucV
|
||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elliotchance/orderedmap/v2 v2.6.0 h1:Zzo4k/u6hTRSt4NbYVphwOn5fBKlLpcbaV00INfJ1WI=
|
||||
github.com/elliotchance/orderedmap/v2 v2.6.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -1,164 +0,0 @@
|
||||
package omap
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/internal/exp"
|
||||
)
|
||||
|
||||
// An OrderedMap is a wrapper around a regular map that maintains an ordered
|
||||
// list of the map's keys. This allows you to run deterministic and ordered
|
||||
// operations on the map such as printing/serializing/iterating.
|
||||
type OrderedMap[K cmp.Ordered, V any] struct {
|
||||
s []K
|
||||
m map[K]V
|
||||
}
|
||||
|
||||
// New will create a new OrderedMap of the given type and return it.
|
||||
func New[K cmp.Ordered, V any]() OrderedMap[K, V] {
|
||||
return OrderedMap[K, V]{
|
||||
s: make([]K, 0),
|
||||
m: make(map[K]V),
|
||||
}
|
||||
}
|
||||
|
||||
// FromMap will create a new OrderedMap from the given map. Since Golang maps
|
||||
// are unordered, the order of the created OrderedMap will be random.
|
||||
func FromMap[K cmp.Ordered, V any](m map[K]V) OrderedMap[K, V] {
|
||||
om := New[K, V]()
|
||||
om.m = m
|
||||
om.s = exp.Keys(m)
|
||||
return om
|
||||
}
|
||||
|
||||
func FromMapWithOrder[K cmp.Ordered, V any](m map[K]V, order []K) OrderedMap[K, V] {
|
||||
om := New[K, V]()
|
||||
if len(m) != len(order) {
|
||||
panic("length of map and order must be equal")
|
||||
}
|
||||
om.m = m
|
||||
om.s = order
|
||||
for key := range om.m {
|
||||
if !slices.Contains(om.s, key) {
|
||||
panic("order keys must match map keys")
|
||||
}
|
||||
}
|
||||
return om
|
||||
}
|
||||
|
||||
// Len will return the number of items in the map.
|
||||
func (om *OrderedMap[K, V]) Len() int {
|
||||
return len(om.s)
|
||||
}
|
||||
|
||||
// Set will set the value for a given key.
|
||||
func (om *OrderedMap[K, V]) Set(key K, value V) {
|
||||
if om.m == nil {
|
||||
om.m = make(map[K]V)
|
||||
}
|
||||
if _, ok := om.m[key]; !ok {
|
||||
om.s = append(om.s, key)
|
||||
}
|
||||
om.m[key] = value
|
||||
}
|
||||
|
||||
// Get will return the value for a given key.
|
||||
// If the key does not exist, it will return the zero value of the value type.
|
||||
func (om *OrderedMap[K, V]) Get(key K) V {
|
||||
value, ok := om.m[key]
|
||||
if !ok {
|
||||
var zero V
|
||||
return zero
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Exists will return whether or not the given key exists.
|
||||
func (om *OrderedMap[K, V]) Exists(key K) bool {
|
||||
_, ok := om.m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Sort will sort the map.
|
||||
func (om *OrderedMap[K, V]) Sort() {
|
||||
slices.Sort(om.s)
|
||||
}
|
||||
|
||||
// SortFunc will sort the map using the given function.
|
||||
func (om *OrderedMap[K, V]) SortFunc(less func(i, j K) int) {
|
||||
slices.SortFunc(om.s, less)
|
||||
}
|
||||
|
||||
// Keys will return a slice of the map's keys in order.
|
||||
func (om *OrderedMap[K, V]) Keys() []K {
|
||||
return om.s
|
||||
}
|
||||
|
||||
// Values will return a slice of the map's values in order.
|
||||
func (om *OrderedMap[K, V]) Values() []V {
|
||||
var values []V
|
||||
for _, key := range om.s {
|
||||
values = append(values, om.m[key])
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Range will iterate over the map and call the given function for each key/value.
|
||||
func (om *OrderedMap[K, V]) Range(fn func(key K, value V) error) error {
|
||||
for _, key := range om.s {
|
||||
if err := fn(key, om.m[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges the given Vars into the caller one
|
||||
func (om *OrderedMap[K, V]) Merge(other OrderedMap[K, V]) {
|
||||
// nolint: errcheck
|
||||
other.Range(func(key K, value V) error {
|
||||
om.Set(key, value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) DeepCopy() OrderedMap[K, V] {
|
||||
return OrderedMap[K, V]{
|
||||
s: deepcopy.Slice(om.s),
|
||||
m: deepcopy.Map(om.m),
|
||||
}
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
// Even numbers contain the keys
|
||||
// Odd numbers contain the values
|
||||
case yaml.MappingNode:
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
// Decode the key
|
||||
keyNode := node.Content[i]
|
||||
var k K
|
||||
if err := keyNode.Decode(&k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the value
|
||||
valueNode := node.Content[i+1]
|
||||
var v V
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the key and value
|
||||
om.Set(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variables", node.Line, node.ShortTag())
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package omap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestFromMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := map[int]string{3: "three", 1: "one", 2: "two"}
|
||||
om := FromMap(m)
|
||||
assert.Len(t, om.m, 3)
|
||||
assert.Len(t, om.s, 3)
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, om.s)
|
||||
for key, value := range m {
|
||||
assert.Equal(t, om.Get(key), value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetGetExists(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
om := New[int, string]()
|
||||
assert.False(t, om.Exists(1))
|
||||
assert.Equal(t, "", om.Get(1))
|
||||
om.Set(1, "one")
|
||||
assert.True(t, om.Exists(1))
|
||||
assert.Equal(t, "one", om.Get(1))
|
||||
}
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
om.Sort()
|
||||
assert.Equal(t, []int{1, 2, 3}, om.s)
|
||||
}
|
||||
|
||||
func TestSortFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
om.SortFunc(func(a, b int) int {
|
||||
return b - a
|
||||
})
|
||||
assert.Equal(t, []int{3, 2, 1}, om.s)
|
||||
}
|
||||
|
||||
func TestKeysValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
assert.Equal(t, []int{3, 1, 2}, om.Keys())
|
||||
assert.Equal(t, []string{"three", "one", "two"}, om.Values())
|
||||
}
|
||||
|
||||
func Range(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
|
||||
expectedKeys := []int{3, 1, 2}
|
||||
expectedValues := []string{"three", "one", "two"}
|
||||
|
||||
keys := make([]int, 0, len(expectedKeys))
|
||||
values := make([]string, 0, len(expectedValues))
|
||||
|
||||
err := om.Range(func(key int, value string) error {
|
||||
keys = append(keys, key)
|
||||
values = append(values, value)
|
||||
return nil
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expectedKeys, keys)
|
||||
assert.ElementsMatch(t, expectedValues, values)
|
||||
}
|
||||
|
||||
func TestOrderedMapMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
om1 := New[string, int]()
|
||||
om1.Set("a", 1)
|
||||
om1.Set("b", 2)
|
||||
|
||||
om2 := New[string, int]()
|
||||
om2.Set("b", 3)
|
||||
om2.Set("c", 4)
|
||||
|
||||
om1.Merge(om2)
|
||||
|
||||
expectedKeys := []string{"a", "b", "c"}
|
||||
expectedValues := []int{1, 3, 4}
|
||||
|
||||
assert.Equal(t, len(expectedKeys), len(om1.s))
|
||||
assert.Equal(t, len(expectedKeys), len(om1.m))
|
||||
|
||||
for i, key := range expectedKeys {
|
||||
assert.True(t, om1.Exists(key))
|
||||
assert.Equal(t, expectedValues[i], om1.Get(key))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYAML(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
yamlString := `
|
||||
3: three
|
||||
1: one
|
||||
2: two
|
||||
`
|
||||
var om OrderedMap[int, string]
|
||||
err := yaml.Unmarshal([]byte(yamlString), &om)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedKeys := []int{3, 1, 2}
|
||||
expectedValues := []string{"three", "one", "two"}
|
||||
|
||||
assert.Equal(t, expectedKeys, om.Keys())
|
||||
assert.Equal(t, expectedValues, om.Values())
|
||||
}
|
@ -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"
|
||||
@ -55,11 +54,12 @@ func TestGroupWithBeginEnd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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{
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,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)
|
||||
|
@ -20,10 +20,11 @@ func (e *Executor) areTaskRequiredVarsSet(t *ast.Task, call *ast.Call) error {
|
||||
var missingVars []string
|
||||
var notAllowedValuesVars []errors.NotAllowedVar
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
value, isString := vars.Get(requiredVar.Name).Value.(string)
|
||||
if !vars.Exists(requiredVar.Name) {
|
||||
value, ok := vars.Get(requiredVar.Name)
|
||||
if !ok {
|
||||
missingVars = append(missingVars, requiredVar.Name)
|
||||
} else {
|
||||
value, isString := value.Value.(string)
|
||||
if isString && requiredVar.Enum != nil && !slices.Contains(requiredVar.Enum, value) {
|
||||
notAllowedValuesVars = append(notAllowedValuesVars, errors.NotAllowedVar{
|
||||
Value: value,
|
||||
|
2
setup.go
2
setup.go
@ -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
task.go
2
task.go
@ -451,7 +451,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
|
||||
|
@ -2755,7 +2755,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,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
|
||||
|
@ -1,11 +1,11 @@
|
||||
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"
|
||||
omap "github.com/go-task/task/v3/internal/omap"
|
||||
)
|
||||
|
||||
// Include represents information about included taskfiles
|
||||
@ -22,27 +22,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
|
||||
@ -51,22 +104,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
taskfile/ast/matrix.go
Normal file
95
taskfile/ast/matrix.go
Normal file
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
@ -40,17 +39,23 @@ 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",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "PARAM2",
|
||||
Value: ast.Var{
|
||||
Value: "VALUE2",
|
||||
},
|
||||
},
|
||||
[]string{"PARAM1", "PARAM2"},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
yamlDeferredCmd,
|
||||
&ast.Cmd{},
|
||||
@ -60,14 +65,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,
|
||||
},
|
||||
},
|
||||
@ -80,17 +86,23 @@ 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"},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||
|
@ -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.
|
||||
@ -100,13 +169,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,
|
||||
@ -118,52 +187,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 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
tasks.Range(func(name string, task *Task) error {
|
||||
// Set the task's name
|
||||
if task == nil {
|
||||
task = &Task{
|
||||
Task: name,
|
||||
// Set the task name and location
|
||||
v.Task = keyNode.Value
|
||||
v.Location = &Location{
|
||||
Line: keyNode.Line,
|
||||
Column: keyNode.Column,
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.Set(name, task)
|
||||
return nil
|
||||
})
|
||||
|
||||
*t = Tasks{
|
||||
OrderedMap: tasks,
|
||||
// Add the task to the ordered map
|
||||
t.Set(keyNode.Value, &v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -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 != nil && *v.Sh != "" {
|
||||
for pair := vs.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if pair.Value.Sh != nil && *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)
|
||||
}
|
||||
|
||||
// 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,10 +103,36 @@ 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.
|
||||
type Var struct {
|
||||
Value any
|
||||
|
@ -22,7 +22,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 {
|
||||
@ -41,7 +41,7 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
|
||||
return nil, fmt.Errorf("error reading env file %s: %w", dotEnvPath, 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
variables.go
13
variables.go
@ -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 == nil {
|
||||
if ok && v.Sh == nil {
|
||||
switch value := v.Value.(type) {
|
||||
case string:
|
||||
if f.Split != "" {
|
||||
@ -333,7 +332,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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user