Skip to content

Commit 2d90de8

Browse files
committed
feat(summary): add vars, env, and requires display
1 parent a927ffb commit 2d90de8

File tree

8 files changed

+311
-2
lines changed

8 files changed

+311
-2
lines changed

compiler.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
6161
newVar := templater.ReplaceVar(v, cache)
6262
// If the variable should not be evaluated, but is nil, set it to an empty string
6363
// This stops empty interface errors when using the templater to replace values later
64+
// Preserve the Sh field so it can be displayed in summary
6465
if !evaluateShVars && newVar.Value == nil {
65-
result.Set(k, ast.Var{Value: ""})
66+
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
6667
return nil
6768
}
6869
// If the variable should not be evaluated and it is set, we can set it and return
6970
if !evaluateShVars {
70-
result.Set(k, ast.Var{Value: newVar.Value})
71+
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
7172
return nil
7273
}
7374
// Now we can check for errors since we've handled all the cases when we don't want to evaluate

executor_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,30 @@ func TestAlias(t *testing.T) {
621621
)
622622
}
623623

624+
func TestSummaryWithVarsAndRequires(t *testing.T) {
625+
t.Parallel()
626+
627+
// Test basic case from prompt.md - vars and requires
628+
NewExecutorTest(t,
629+
WithName("vars-and-requires"),
630+
WithExecutorOptions(
631+
task.WithDir("testdata/summary-vars-requires"),
632+
task.WithSummary(true),
633+
),
634+
WithTask("mytask"),
635+
)
636+
637+
// Test with shell variables
638+
NewExecutorTest(t,
639+
WithName("shell-vars"),
640+
WithExecutorOptions(
641+
task.WithDir("testdata/summary-vars-requires"),
642+
task.WithSummary(true),
643+
),
644+
WithTask("with-sh-var"),
645+
)
646+
}
647+
624648
func TestLabel(t *testing.T) {
625649
t.Parallel()
626650

internal/summary/summary.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package summary
22

33
import (
4+
"fmt"
5+
"os"
46
"strings"
57

68
"github.com/go-task/task/v3/internal/logger"
@@ -29,6 +31,9 @@ func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
2931
func PrintTask(l *logger.Logger, t *ast.Task) {
3032
printTaskName(l, t)
3133
printTaskDescribingText(t, l)
34+
printTaskVars(l, t)
35+
printTaskEnv(l, t)
36+
printTaskRequires(l, t)
3237
printTaskDependencies(l, t)
3338
printTaskAliases(l, t)
3439
printTaskCommands(l, t)
@@ -118,3 +123,186 @@ func printTaskCommands(l *logger.Logger, t *ast.Task) {
118123
}
119124
}
120125
}
126+
127+
// printTaskVars prints the variables defined in a task in YAML format.
128+
// It displays the vars section with proper indentation and formatting.
129+
// Filters out OS environment variables, auto-generated Task variables, and Taskfile env vars.
130+
// Returns early if the task has no variables defined.
131+
func printTaskVars(l *logger.Logger, t *ast.Task) {
132+
if t.Vars == nil || t.Vars.Len() == 0 {
133+
return
134+
}
135+
136+
// Create a set of OS environment variable names to filter them out
137+
osEnvVars := getEnvVarNames()
138+
139+
// Create a set of Taskfile env variable names to avoid duplication
140+
taskfileEnvVars := make(map[string]bool)
141+
if t.Env != nil {
142+
for key := range t.Env.All() {
143+
taskfileEnvVars[key] = true
144+
}
145+
}
146+
147+
// Check if there are any non-environment variables to display
148+
hasNonEnvVars := false
149+
for key := range t.Vars.All() {
150+
if !isEnvVar(key, osEnvVars) && !taskfileEnvVars[key] {
151+
hasNonEnvVars = true
152+
break
153+
}
154+
}
155+
156+
if !hasNonEnvVars {
157+
return
158+
}
159+
160+
l.Outf(logger.Default, "\n")
161+
l.Outf(logger.Default, "vars:\n")
162+
163+
for key, value := range t.Vars.All() {
164+
// Only display variables that are not from OS environment or Taskfile env
165+
if !isEnvVar(key, osEnvVars) && !taskfileEnvVars[key] {
166+
formattedValue := formatVarValue(value)
167+
l.Outf(logger.Yellow, " %s: %s\n", key, formattedValue)
168+
}
169+
}
170+
}
171+
172+
// printTaskEnv prints the environment variables defined in a task in YAML format.
173+
// It displays the env section with proper indentation and formatting.
174+
// Filters out OS environment variables and auto-generated Task variables.
175+
// Returns early if the task has no environment variables defined.
176+
func printTaskEnv(l *logger.Logger, t *ast.Task) {
177+
if t.Env == nil || t.Env.Len() == 0 {
178+
return
179+
}
180+
181+
// Create a set of OS environment variable names to filter them out
182+
envVars := getEnvVarNames()
183+
184+
// Check if there are any non-environment variables to display
185+
hasNonEnvVars := false
186+
for key := range t.Env.All() {
187+
if !isEnvVar(key, envVars) {
188+
hasNonEnvVars = true
189+
break
190+
}
191+
}
192+
193+
if !hasNonEnvVars {
194+
return
195+
}
196+
197+
l.Outf(logger.Default, "\n")
198+
l.Outf(logger.Default, "env:\n")
199+
200+
for key, value := range t.Env.All() {
201+
// Only display variables that are not from OS environment
202+
if !isEnvVar(key, envVars) {
203+
formattedValue := formatVarValue(value)
204+
l.Outf(logger.Yellow, " %s: %s\n", key, formattedValue)
205+
}
206+
}
207+
}
208+
209+
// formatVarValue formats a variable value based on its type.
210+
// Handles static values, shell commands (sh:), references (ref:), and maps.
211+
func formatVarValue(v ast.Var) string {
212+
// Shell command - check this first before Value
213+
// because dynamic vars may have both Sh and an empty Value
214+
if v.Sh != nil {
215+
return fmt.Sprintf("sh: %s", *v.Sh)
216+
}
217+
218+
// Reference
219+
if v.Ref != "" {
220+
return fmt.Sprintf("ref: %s", v.Ref)
221+
}
222+
223+
// Static value
224+
if v.Value != nil {
225+
// Check if it's a map or complex type
226+
if m, ok := v.Value.(map[string]any); ok {
227+
return formatMap(m, 4)
228+
}
229+
// Simple string value
230+
return fmt.Sprintf(`"%v"`, v.Value)
231+
}
232+
233+
return `""`
234+
}
235+
236+
// formatMap formats a map value with proper indentation for YAML.
237+
func formatMap(m map[string]any, indent int) string {
238+
if len(m) == 0 {
239+
return "{}"
240+
}
241+
242+
var result strings.Builder
243+
result.WriteString("\n")
244+
spaces := strings.Repeat(" ", indent)
245+
246+
for k, v := range m {
247+
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v))
248+
}
249+
250+
return result.String()
251+
}
252+
253+
// printTaskRequires prints the required variables for a task in YAML format.
254+
// It displays the requires section with proper indentation and formatting.
255+
// Returns early if the task has no required variables.
256+
func printTaskRequires(l *logger.Logger, t *ast.Task) {
257+
if t.Requires == nil || len(t.Requires.Vars) == 0 {
258+
return
259+
}
260+
261+
l.Outf(logger.Default, "\n")
262+
l.Outf(logger.Default, "requires:\n")
263+
l.Outf(logger.Default, " vars:\n")
264+
265+
for _, v := range t.Requires.Vars {
266+
// If the variable has enum constraints, format accordingly
267+
if len(v.Enum) > 0 {
268+
l.Outf(logger.Yellow, " - %s:\n", v.Name)
269+
l.Outf(logger.Yellow, " enum:\n")
270+
for _, enumValue := range v.Enum {
271+
l.Outf(logger.Yellow, " - %s\n", enumValue)
272+
}
273+
} else {
274+
// Simple required variable
275+
l.Outf(logger.Yellow, " - %s\n", v.Name)
276+
}
277+
}
278+
}
279+
280+
// getEnvVarNames returns a set of all OS environment variable names.
281+
func getEnvVarNames() map[string]bool {
282+
envMap := make(map[string]bool)
283+
for _, e := range os.Environ() {
284+
parts := strings.SplitN(e, "=", 2)
285+
if len(parts) > 0 {
286+
envMap[parts[0]] = true
287+
}
288+
}
289+
return envMap
290+
}
291+
292+
// isEnvVar checks if a variable is from OS environment or auto-generated by Task.
293+
func isEnvVar(key string, envVars map[string]bool) bool {
294+
// Filter out auto-generated Task variables
295+
if strings.HasPrefix(key, "TASK_") ||
296+
strings.HasPrefix(key, "CLI_") ||
297+
strings.HasPrefix(key, "ROOT_") ||
298+
key == "TASK" ||
299+
key == "TASKFILE" ||
300+
key == "TASKFILE_DIR" ||
301+
key == "USER_WORKING_DIR" ||
302+
key == "ALIAS" ||
303+
key == "MATCH" {
304+
return true
305+
}
306+
// Filter out OS environment variables
307+
return envVars[key]
308+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
version: 3
2+
3+
vars:
4+
GLOBAL_VAR: "I am a global var"
5+
6+
env:
7+
GLOBAL_ENV: "I am a global env"
8+
9+
tasks:
10+
test-env:
11+
desc: Task with vars and env
12+
vars:
13+
LOCAL_VAR: "I am a local var"
14+
env:
15+
LOCAL_ENV: "I am a local env"
16+
DATABASE_URL: "postgres://localhost/mydb"
17+
requires:
18+
vars:
19+
- API_KEY
20+
cmds:
21+
- echo "Testing env vars"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: 3
2+
3+
vars:
4+
GLOBAL_VAR: "I am global"
5+
ANOTHER_GLOBAL: "Also global"
6+
7+
tasks:
8+
test-globals:
9+
desc: Task with global and local vars
10+
vars:
11+
LOCAL_VAR: "I am local"
12+
requires:
13+
vars:
14+
- REQUIRED_VAR
15+
cmds:
16+
- echo {{ .GLOBAL_VAR }} {{ .LOCAL_VAR }}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
version: 3
2+
3+
tasks:
4+
mytask:
5+
desc: It does things
6+
summary: |
7+
It does things and has optional and required variables.
8+
vars:
9+
OPTIONAL_VAR: "hello"
10+
requires:
11+
vars:
12+
- REQUIRED_VAR
13+
cmds:
14+
- cmd: echo {{ .OPTIONAL_VAR }} {{ .REQUIRED_VAR }}
15+
16+
with-sh-var:
17+
desc: Task with shell variable
18+
vars:
19+
DYNAMIC_VAR:
20+
sh: echo "world"
21+
STATIC_VAR: "hello"
22+
cmds:
23+
- echo {{ .DYNAMIC_VAR }}
24+
25+
no-vars:
26+
desc: Task without variables
27+
cmds:
28+
- echo "no vars here"
29+
30+
only-requires:
31+
desc: Task with only requires
32+
requires:
33+
vars:
34+
- NEEDED_VAR
35+
cmds:
36+
- echo {{ .NEEDED_VAR }}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
task: with-sh-var
2+
3+
Task with shell variable
4+
5+
vars:
6+
DYNAMIC_VAR: sh: echo "world"
7+
STATIC_VAR: "hello"
8+
9+
commands:
10+
- echo
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
task: mytask
2+
3+
It does things and has optional and required variables.
4+
5+
vars:
6+
OPTIONAL_VAR: "hello"
7+
8+
requires:
9+
vars:
10+
- REQUIRED_VAR
11+
12+
commands:
13+
- echo hello

0 commit comments

Comments
 (0)