From 13d7486555659207fff1676f996704bafb17653f Mon Sep 17 00:00:00 2001 From: Timothy Rule <34501912+trulede@users.noreply.github.com> Date: Tue, 5 May 2026 16:10:45 +0200 Subject: [PATCH] Add templating function 'joinList' --- internal/templater/funcs.go | 5 ++ internal/templater/templater_test.go | 68 ++++++++++++++++++++++++ website/src/docs/reference/templating.md | 3 +- 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 internal/templater/templater_test.go diff --git a/internal/templater/funcs.go b/internal/templater/funcs.go index 03a43fc9f5..bb934e3bbd 100644 --- a/internal/templater/funcs.go +++ b/internal/templater/funcs.go @@ -33,6 +33,7 @@ func init() { "splitArgs": splitArgs, "IsSH": IsSH, // Deprecated "joinPath": filepath.Join, + "joinPathList": joinPathList, "relPath": filepath.Rel, "absPath": filepath.Abs, "merge": merge, @@ -131,3 +132,7 @@ func mustToYaml(v any) (string, error) { } return string(output), nil } + +func joinPathList(paths ...string) string { + return strings.Join(paths, string(filepath.ListSeparator)) +} diff --git a/internal/templater/templater_test.go b/internal/templater/templater_test.go new file mode 100644 index 0000000000..e0888aacf8 --- /dev/null +++ b/internal/templater/templater_test.go @@ -0,0 +1,68 @@ +package templater + +import ( + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/go-task/task/v3/taskfile/ast" +) + +func TestTemplateFuncs(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + template string + expected string + }{ + { + name: "joinPath", + template: `{{ joinPath .BaseDir "dir" "file.txt" }}`, + expected: func() string { + switch os := runtime.GOOS; os { + case "windows": + return "base\\dir\\file.txt" + default: + return "base/dir/file.txt" + } + }(), + }, + { + name: "joinPath with single argument", + template: `{{ joinPath "dir1" }}`, + expected: "dir1", + }, + { + name: "joinPathList", + template: `{{ joinPathList .BaseDir "subdir" "file.txt" }}`, + expected: func() string { + switch os := runtime.GOOS; os { + case "windows": + return "base;subdir;file.txt" + default: + return "base:subdir:file.txt" + } + }(), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + vars := ast.NewVars( + &ast.VarElement{ + Key: "BaseDir", + Value: ast.Var{Value: "base"}, + }, + ) + cache := &Cache{Vars: vars} + cache.ResetCache() + result := Replace(tc.template, cache) + require.NoError(t, cache.Err()) + assert.Equal(t, tc.expected, result) + }) + } +} diff --git a/website/src/docs/reference/templating.md b/website/src/docs/reference/templating.md index d73c2aa5ab..9ddcf7eb3d 100644 --- a/website/src/docs/reference/templating.md +++ b/website/src/docs/reference/templating.md @@ -616,7 +616,8 @@ tasks: - echo "{{.WIN_PATH | toSlash}}" # Convert to forward slashes - echo "{{.WIN_PATH | fromSlash}}" # Convert to OS-specific slashes - echo "{{joinPath .OUTPUT_DIR .BINARY_NAME}}" # Join path elements - - echo "Relative {{relPath .ROOT_DIR .TASKFILE_DIR}}" # Get relative path + - echo "{{joinPathList .PATH_1 .PATH_2 .PATH_3}}" # Join a list of path elements using the OS List Separator + - echo "Relative {{relPath .ROOT_DIR .TASKFILE_DIR}}" # Get relative path - echo '{{absPath "../sibling"}}' # Resolve to an absolute path ```