Skip to content

Commit c5afffb

Browse files
authored
feat: recursive config search (#2166)
* refactor: experiments flags * refactor: args.Parse * feat: recursive search for taskrc files * feat: consolidate some code into new fsext package * feat: add tests for search and default dir * fix: linting issues
1 parent 1ae3bf0 commit c5afffb

File tree

20 files changed

+616
-233
lines changed

20 files changed

+616
-233
lines changed

.taskrc.yml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
experiments:
2+
GENTLE_FORCE: 0
3+
REMOTE_TASKFILES: 0
4+
ENV_PRECEDENCE: 0

args/args.go

+26
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,36 @@ package args
33
import (
44
"strings"
55

6+
"github.com/spf13/pflag"
7+
"mvdan.cc/sh/v3/syntax"
8+
69
"github.com/go-task/task/v3"
710
"github.com/go-task/task/v3/taskfile/ast"
811
)
912

13+
// Get fetches the remaining arguments after CLI parsing and splits them into
14+
// two groups: the arguments before the double dash (--) and the arguments after
15+
// the double dash.
16+
func Get() ([]string, []string, error) {
17+
args := pflag.Args()
18+
doubleDashPos := pflag.CommandLine.ArgsLenAtDash()
19+
20+
if doubleDashPos == -1 {
21+
return args, nil, nil
22+
}
23+
24+
var quotedCliArgs []string
25+
for _, arg := range args[doubleDashPos:] {
26+
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
27+
if err != nil {
28+
return nil, nil, err
29+
}
30+
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
31+
}
32+
33+
return args[:doubleDashPos], quotedCliArgs, nil
34+
}
35+
1036
// Parse parses command line argument: tasks and global variables
1137
func Parse(args ...string) ([]*task.Call, *ast.Vars) {
1238
calls := []*task.Call{}

cmd/task/task.go

+4-32
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8-
"strings"
98

109
"github.com/spf13/pflag"
11-
"mvdan.cc/sh/v3/syntax"
1210

1311
"github.com/go-task/task/v3"
1412
"github.com/go-task/task/v3/args"
@@ -78,7 +76,7 @@ func run() error {
7876
if err != nil {
7977
return err
8078
}
81-
args, _, err := getArgs()
79+
_, args, err := args.Get()
8280
if err != nil {
8381
return err
8482
}
@@ -145,17 +143,12 @@ func run() error {
145143
return nil
146144
}
147145

148-
var (
149-
calls []*task.Call
150-
globals *ast.Vars
151-
)
152-
153-
tasksAndVars, cliArgs, err := getArgs()
146+
// Parse the remaining arguments
147+
argv, cliArgs, err := args.Get()
154148
if err != nil {
155149
return err
156150
}
157-
158-
calls, globals = args.Parse(tasksAndVars...)
151+
calls, globals := args.Parse(argv...)
159152

160153
// If there are no calls, run the default task instead
161154
if len(calls) == 0 {
@@ -181,24 +174,3 @@ func run() error {
181174

182175
return e.Run(ctx, calls...)
183176
}
184-
185-
func getArgs() ([]string, string, error) {
186-
var (
187-
args = pflag.Args()
188-
doubleDashPos = pflag.CommandLine.ArgsLenAtDash()
189-
)
190-
191-
if doubleDashPos == -1 {
192-
return args, "", nil
193-
}
194-
195-
var quotedCliArgs []string
196-
for _, arg := range args[doubleDashPos:] {
197-
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
198-
if err != nil {
199-
return nil, "", err
200-
}
201-
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
202-
}
203-
return args[:doubleDashPos], strings.Join(quotedCliArgs, " "), nil
204-
}

errors/errors.go

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ const (
88
CodeUnknown // Used when no other exit code is appropriate
99
)
1010

11+
// TaskRC related exit codes
12+
const (
13+
CodeTaskRCNotFoundError int = iota + 50
14+
)
15+
1116
// Taskfile related exit codes
1217
const (
1318
CodeTaskfileNotFound int = iota + 100

errors/errors_taskrc.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package errors
2+
3+
import "fmt"
4+
5+
type TaskRCNotFoundError struct {
6+
URI string
7+
Walk bool
8+
}
9+
10+
func (err TaskRCNotFoundError) Error() string {
11+
var walkText string
12+
if err.Walk {
13+
walkText = " (or any of the parent directories)"
14+
}
15+
return fmt.Sprintf(`task: No Task config file found at %q%s`, err.URI, walkText)
16+
}
17+
18+
func (err TaskRCNotFoundError) Code() int {
19+
return CodeTaskRCNotFoundError
20+
}

internal/experiments/experiment.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"slices"
66
"strconv"
7+
8+
"github.com/go-task/task/v3/taskrc/ast"
79
)
810

911
type Experiment struct {
@@ -14,8 +16,11 @@ type Experiment struct {
1416

1517
// New creates a new experiment with the given name and sets the values that can
1618
// enable it.
17-
func New(xName string, allowedValues ...int) Experiment {
18-
value := experimentConfig.Experiments[xName]
19+
func New(xName string, config *ast.TaskRC, allowedValues ...int) Experiment {
20+
var value int
21+
if config != nil {
22+
value = config.Experiments[xName]
23+
}
1924

2025
if value == 0 {
2126
value, _ = strconv.Atoi(getEnv(xName))

internal/experiments/experiment_test.go

+76-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/stretchr/testify/assert"
88

99
"github.com/go-task/task/v3/internal/experiments"
10+
"github.com/go-task/task/v3/taskrc/ast"
1011
)
1112

1213
func TestNew(t *testing.T) {
@@ -16,60 +17,124 @@ func TestNew(t *testing.T) {
1617
)
1718
tests := []struct {
1819
name string
20+
config *ast.TaskRC
1921
allowedValues []int
20-
value int
22+
env int
2123
wantEnabled bool
2224
wantActive bool
2325
wantValid error
26+
wantValue int
2427
}{
2528
{
26-
name: `[] allowed, value=""`,
29+
name: `[] allowed, env=""`,
2730
wantEnabled: false,
2831
wantActive: false,
2932
},
3033
{
31-
name: `[] allowed, value="1"`,
32-
value: 1,
34+
name: `[] allowed, env="1"`,
35+
env: 1,
3336
wantEnabled: false,
3437
wantActive: false,
3538
wantValid: &experiments.InactiveError{
3639
Name: exampleExperiment,
3740
},
41+
wantValue: 1,
3842
},
3943
{
40-
name: `[1] allowed, value=""`,
44+
name: `[1] allowed, env=""`,
4145
allowedValues: []int{1},
4246
wantEnabled: false,
4347
wantActive: true,
4448
},
4549
{
46-
name: `[1] allowed, value="1"`,
50+
name: `[1] allowed, env="1"`,
4751
allowedValues: []int{1},
48-
value: 1,
52+
env: 1,
4953
wantEnabled: true,
5054
wantActive: true,
55+
wantValue: 1,
5156
},
5257
{
53-
name: `[1] allowed, value="2"`,
58+
name: `[1] allowed, env="2"`,
5459
allowedValues: []int{1},
55-
value: 2,
60+
env: 2,
5661
wantEnabled: false,
5762
wantActive: true,
5863
wantValid: &experiments.InvalidValueError{
5964
Name: exampleExperiment,
6065
AllowedValues: []int{1},
6166
Value: 2,
6267
},
68+
wantValue: 2,
69+
},
70+
{
71+
name: `[1, 2] allowed, env="1"`,
72+
allowedValues: []int{1, 2},
73+
env: 1,
74+
wantEnabled: true,
75+
wantActive: true,
76+
wantValue: 1,
77+
},
78+
{
79+
name: `[1, 2] allowed, env="1"`,
80+
allowedValues: []int{1, 2},
81+
env: 2,
82+
wantEnabled: true,
83+
wantActive: true,
84+
wantValue: 2,
85+
},
86+
{
87+
name: `[1] allowed, config="1"`,
88+
config: &ast.TaskRC{
89+
Experiments: map[string]int{
90+
exampleExperiment: 1,
91+
},
92+
},
93+
allowedValues: []int{1},
94+
wantEnabled: true,
95+
wantActive: true,
96+
wantValue: 1,
97+
},
98+
{
99+
name: `[1] allowed, config="2"`,
100+
config: &ast.TaskRC{
101+
Experiments: map[string]int{
102+
exampleExperiment: 2,
103+
},
104+
},
105+
allowedValues: []int{1},
106+
wantEnabled: false,
107+
wantActive: true,
108+
wantValid: &experiments.InvalidValueError{
109+
Name: exampleExperiment,
110+
AllowedValues: []int{1},
111+
Value: 2,
112+
},
113+
wantValue: 2,
114+
},
115+
{
116+
name: `[1, 2] allowed, env="1", config="2"`,
117+
config: &ast.TaskRC{
118+
Experiments: map[string]int{
119+
exampleExperiment: 2,
120+
},
121+
},
122+
allowedValues: []int{1, 2},
123+
env: 1,
124+
wantEnabled: true,
125+
wantActive: true,
126+
wantValue: 2,
63127
},
64128
}
65129
for _, tt := range tests {
66130
t.Run(tt.name, func(t *testing.T) {
67-
t.Setenv(exampleExperimentEnv, strconv.Itoa(tt.value))
68-
x := experiments.New(exampleExperiment, tt.allowedValues...)
131+
t.Setenv(exampleExperimentEnv, strconv.Itoa(tt.env))
132+
x := experiments.New(exampleExperiment, tt.config, tt.allowedValues...)
69133
assert.Equal(t, exampleExperiment, x.Name)
70134
assert.Equal(t, tt.wantEnabled, x.Enabled())
71135
assert.Equal(t, tt.wantActive, x.Active())
72136
assert.Equal(t, tt.wantValid, x.Valid())
137+
assert.Equal(t, tt.wantValue, x.Value)
73138
})
74139
}
75140
}

0 commit comments

Comments
 (0)