From d9e0e04725607e437041bd3b15916fc4fb6e9be6 Mon Sep 17 00:00:00 2001 From: Jerry Wiltse Date: Sat, 9 May 2026 10:19:33 -0400 Subject: [PATCH] feat: add `joinEnv` and `joinUrl` string functions and 2 new system vars (#2408) --- compiler.go | 12 ++-- internal/summary/summary.go | 4 +- internal/templater/funcs.go | 20 ++++-- website/src/docs/reference/templating.md | 83 +++++++++++++++++++++--- 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/compiler.go b/compiler.go index 733d5f3b..68aa3cd3 100644 --- a/compiler.go +++ b/compiler.go @@ -202,11 +202,13 @@ func (c *Compiler) getSpecialVars(t *ast.Task, call *Call) (map[string]string, e // across platforms. This prevents issues with backslashes being interpreted // as escape sequences when paths are used in shell commands on Windows. allVars := map[string]string{ - "TASK_EXE": filepath.ToSlash(os.Args[0]), - "ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)), - "ROOT_DIR": filepath.ToSlash(c.Dir), - "USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir), - "TASK_VERSION": version.GetVersion(), + "TASK_EXE": filepath.ToSlash(os.Args[0]), + "ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)), + "ROOT_DIR": filepath.ToSlash(c.Dir), + "USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir), + "TASK_VERSION": version.GetVersion(), + "PATH_LIST_SEPARATOR": string(os.PathListSeparator), + "FILE_PATH_SEPARATOR": string(os.PathSeparator), } if t != nil { allVars["TASK"] = t.Task diff --git a/internal/summary/summary.go b/internal/summary/summary.go index 3a63b41c..9edd9511 100644 --- a/internal/summary/summary.go +++ b/internal/summary/summary.go @@ -285,7 +285,9 @@ func isEnvVar(key string, envVars map[string]bool) bool { key == "TASKFILE_DIR" || key == "USER_WORKING_DIR" || key == "ALIAS" || - key == "MATCH" { + key == "MATCH" || + key == "PATH_LIST_SEPARATOR" || + key == "FILE_PATH_SEPARATOR" { return true } return envVars[key] diff --git a/internal/templater/funcs.go b/internal/templater/funcs.go index 03a43fc9..2fe85b96 100644 --- a/internal/templater/funcs.go +++ b/internal/templater/funcs.go @@ -3,6 +3,8 @@ package templater import ( "maps" "math/rand/v2" + "os" + "path" "path/filepath" "runtime" "strings" @@ -21,8 +23,8 @@ var templateFuncs template.FuncMap func init() { taskFuncs := template.FuncMap{ - "OS": os, - "ARCH": arch, + "OS": goos, + "ARCH": goarch, "numCPU": runtime.NumCPU, "catLines": catLines, "splitLines": splitLines, @@ -33,6 +35,8 @@ func init() { "splitArgs": splitArgs, "IsSH": IsSH, // Deprecated "joinPath": filepath.Join, + "joinEnv": joinEnv, + "joinUrl": joinUrl, "relPath": filepath.Rel, "absPath": filepath.Abs, "merge": merge, @@ -57,11 +61,11 @@ func init() { maps.Copy(templateFuncs, taskFuncs) } -func os() string { +func goos() string { return runtime.GOOS } -func arch() string { +func goarch() string { return runtime.GOARCH } @@ -95,6 +99,14 @@ func IsSH() bool { return true } +func joinEnv(elem ...string) string { + return strings.Join(elem, string(os.PathListSeparator)) +} + +func joinUrl(elem ...string) string { + return path.Join(elem...) +} + func merge(base map[string]any, v ...map[string]any) map[string]any { cap := len(v) for _, m := range v { diff --git a/website/src/docs/reference/templating.md b/website/src/docs/reference/templating.md index d73c2aa5..b100abc4 100644 --- a/website/src/docs/reference/templating.md +++ b/website/src/docs/reference/templating.md @@ -249,6 +249,32 @@ tasks: - echo "Working {{.USER_WORKING_DIR}}" ``` +#### `FILE_PATH_SEPARATOR` + +- **Type**: `string` +- **Description**: OS-specific path separator: Windows = `\`, others = `/` + +::: info + +> See `joinPath` in [Path Functions](#path-functions) for joining filesystem paths for use with +> file system operations. + +::: + +### Environment Variables + +#### `PATH_LIST_SEPARATOR` + +- **Type**: `string` +- **Description**: OS-specific path separator for environment variables: Windows = `;`, others = `:` + +::: info + +> See `joinEnv` in [Environment Variable Functions](#environment-variable-functions) for joining +> paths for use in environment variables. + +::: + ### Status #### `CHECKSUM` @@ -597,9 +623,9 @@ tasks: tasks: platform: cmds: - - echo "OS {{OS}}" # linux, darwin, windows, etc. - - echo "Architecture {{ARCH}}" # amd64, arm64, etc. - - echo "CPU cores {{numCPU}}" # Number of CPU cores + - echo "OS {{OS}}" # linux, darwin, windows, etc. + - echo "Architecture {{ARCH}}" # amd64, arm64, etc. + - echo "CPU cores {{numCPU}}" # Number of CPU cores - echo "Building for {{OS}}/{{ARCH}}" ``` @@ -613,11 +639,52 @@ tasks: OUTPUT_DIR: 'dist' BINARY_NAME: 'myapp' cmds: - - 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 '{{absPath "../sibling"}}' # Resolve to an absolute path + - 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 '{{absPath "../sibling"}}' # Resolve to an absolute path +``` + +#### Environment Variable Functions + +```yaml +tasks: + paths: + vars: + WIN_PATH1: 'C:\Users\Person\bin' + WIN_PATH2: 'C:\Shared\bin' + cmds: + # Join paths for Windows ENV vars: + # C:\Users\Person\bin;C:\Shared\bin + - echo "{{joinEnv .WIN_PATH1 .WIN_PATH2}}" +``` + +```yaml +tasks: + paths: + vars: + POSIX_PATH1: '/users/person/.local/bin' + POSIX_PATH2: '/usr/bin' + cmds: + # Join paths for POSIX ENV vars: + # /users/person/.local/bin:/usr/bin + - echo "{{joinEnv .POSIX_PATH1 .POSIX_PATH2}}" +``` + +#### URLs + +```yaml +tasks: + paths: + vars: + SERVER: 'http://localhost' + PATH1: 'path1' + PATH2: 'path2' + cmds: + # Join paths for URL: + # http://localhost/path1/path2 + - echo "{{joinUrl .SERVER .PATH1 .PATH2}}" ``` ### Data Structure Functions