diff --git a/executor_test.go b/executor_test.go index d963fa6d3f..cd4a7d0290 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1108,6 +1108,20 @@ func TestIncludeSilent(t *testing.T) { ) } +func TestGeneratesArray(t *testing.T) { + t.Parallel() + + NewExecutorTest(t, + WithName("generates-array"), + WithExecutorOptions( + task.WithDir("testdata/generates-array"), + task.WithSilent(true), + task.WithForce(true), + ), + WithTask("default"), + ) +} + func TestFailfast(t *testing.T) { t.Parallel() diff --git a/internal/templater/templater.go b/internal/templater/templater.go index 896cba23c1..a04a8ad6b3 100644 --- a/internal/templater/templater.go +++ b/internal/templater/templater.go @@ -2,8 +2,10 @@ package templater import ( "bytes" + "encoding/json" "fmt" "maps" + "regexp" "strings" "github.com/go-task/template" @@ -58,6 +60,38 @@ func ResolveRef(ref string, cache *Cache) any { return val } +func resolveDirectLookup(text string, cache *Cache) any { + // If there is already an error, do nothing + if cache.err != nil { + return nil + } + + // Initialize the cache map if it's not already initialized + if cache.cacheMap == nil { + cache.cacheMap = cache.Vars.ToCacheMap() + } + + // Lookup should be in the form '{{.LOOKUP}}'. + re := regexp.MustCompile(`^\{\{(\..+)\}\}$`) + match := re.FindStringSubmatch(text) + if len(match) != 2 { + return text + } + + // Resolve the lookup. + t, err := template.New("resolver").Funcs(templateFuncs).Parse(text) + if err != nil { + cache.err = err + return nil + } + val, err := t.Resolve(cache.cacheMap) + if err != nil { + cache.err = err + return nil + } + return val +} + func Replace[T any](v T, cache *Cache) T { return ReplaceWithExtra(v, cache, nil) } @@ -105,11 +139,48 @@ func ReplaceGlobs(globs []*ast.Glob, cache *Cache) []*ast.Glob { return nil } - new := make([]*ast.Glob, len(globs)) - for i, g := range globs { - new[i] = &ast.Glob{ - Glob: Replace(g.Glob, cache), - Negate: g.Negate, + new := []*ast.Glob{} + for _, g := range globs { + _glob := resolveDirectLookup(g.Glob, cache) + switch glob := _glob.(type) { + case []any: + for _, v := range glob { + new = append(new, &ast.Glob{ + Glob: Replace(v.(string), cache), + Negate: g.Negate, + }) + } + case string: + glob = Replace(glob, cache) + var jv any + if err := json.Unmarshal([]byte(glob), &jv); err == nil { + // JSON data (highly probable). + switch val := jv.(type) { + case []any: + for _, v := range val { + new = append(new, &ast.Glob{ + Glob: v.(string), + Negate: g.Negate, + }) + } + case string: + new = append(new, &ast.Glob{ + Glob: val, + Negate: g.Negate, + }) + } + } else { + // Otherwise take the glob as provided. + new = append(new, &ast.Glob{ + Glob: glob, + Negate: g.Negate, + }) + } + default: + new = append(new, &ast.Glob{ + Glob: Replace(glob.(string), cache), + Negate: g.Negate, + }) } } return new diff --git a/testdata/generates-array/.gitignore b/testdata/generates-array/.gitignore new file mode 100644 index 0000000000..6bfe71a515 --- /dev/null +++ b/testdata/generates-array/.gitignore @@ -0,0 +1,2 @@ +foo.* +bar.* \ No newline at end of file diff --git a/testdata/generates-array/Taskfile.yml b/testdata/generates-array/Taskfile.yml new file mode 100644 index 0000000000..2d266e8fb9 --- /dev/null +++ b/testdata/generates-array/Taskfile.yml @@ -0,0 +1,61 @@ +version: '3' + +method: none + +vars: + GLOB_SINGLE: 'foo.*' + GLOB_ARRAY: ['*.txt', '*.yml'] + GLOB_STRING: '*.txt:*.yml' + GLOB_SPLIT: '{{splitList ":" .GLOB_STRING | toJson}}' + +tasks: + default: + cmds: + - cmd: touch foo.txt + - cmd: touch bar.txt + - cmd: touch foo.yml + - cmd: touch bar.yml + - task: direct + - task: single + - task: array + - task: string + - task: split + + direct: + sources: + - '*.txt' + cmds: + - echo "== {{.TASK}} ==" + - for: sources + cmd: echo {{.ITEM}} + single: + sources: + - '{{.GLOB_SINGLE}}' + cmds: + - echo "== {{.TASK}} ==" + - for: sources + cmd: echo {{.ITEM}} + array: + sources: + - '{{.GLOB_ARRAY}}' + - exclude: Taskfile.yml + cmds: + - echo "== {{.TASK}} ==" + - for: sources + cmd: echo {{.ITEM}} + string: + sources: + - '{{splitList ":" .GLOB_STRING | toJson}}' + - exclude: Taskfile.yml + cmds: + - echo "== {{.TASK}} ==" + - for: sources + cmd: echo {{.ITEM}} + split: + sources: + - '{{.GLOB_SPLIT}}' + - exclude: Taskfile.yml + cmds: + - echo "== {{.TASK}} ==" + - for: sources + cmd: echo {{.ITEM}} diff --git a/testdata/generates-array/testdata/TestGeneratesArray-generates-array.golden b/testdata/generates-array/testdata/TestGeneratesArray-generates-array.golden new file mode 100644 index 0000000000..ba644d5609 --- /dev/null +++ b/testdata/generates-array/testdata/TestGeneratesArray-generates-array.golden @@ -0,0 +1,21 @@ +== direct == +bar.txt +foo.txt +== single == +foo.txt +foo.yml +== array == +bar.txt +bar.yml +foo.txt +foo.yml +== string == +bar.txt +bar.yml +foo.txt +foo.yml +== split == +bar.txt +bar.yml +foo.txt +foo.yml