Skip to content

Commit 95148b6

Browse files
feat(core): support positional args as dynamic args as well (#4621)
Signed-off-by: Olivier Cano <[email protected]>
1 parent 8866224 commit 95148b6

File tree

3 files changed

+104
-53
lines changed

3 files changed

+104
-53
lines changed

core/arg_specs.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@ var AllLocalities = "all"
1414

1515
type ArgSpecs []*ArgSpec
1616

17+
// GetPositionalArg returns the last positional argument from the arg specs.
1718
func (s ArgSpecs) GetPositionalArg() *ArgSpec {
1819
var positionalArg *ArgSpec
1920
for _, argSpec := range s {
2021
if argSpec.Positional {
21-
if positionalArg != nil {
22-
panic(fmt.Errorf("more than one positional parameter detected: %s and %s are flagged as positional arg", positionalArg.Name, argSpec.Name))
23-
}
2422
positionalArg = argSpec
2523
}
2624
}

core/cobra_utils.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
4141

4242
positionalArgSpec := cmd.ArgSpecs.GetPositionalArg()
4343

44-
// If this command has no positional argument we execute the run
45-
if positionalArgSpec == nil {
44+
// If this command has no positional argument or the positional arg is already passed, we execute the run
45+
if positionalArgSpec == nil || rawArgs.Has(positionalArgSpec.Name) {
46+
if positionalArgSpec != nil && rawArgs.Has(positionalArgSpec.Name) {
47+
rawArgs = rawArgs.RemoveAllPositional()
48+
}
4649
data, err := run(ctx, cobraCmd, cmd, rawArgs)
4750
if err != nil {
4851
return err
@@ -55,22 +58,11 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
5558

5659
positionalArgs := rawArgs.GetPositionalArgs()
5760

58-
// Return an error if a positional argument was provide using `key=value` syntax.
59-
value, exist := rawArgs.Get(positionalArgSpec.Name)
60-
if exist {
61-
otherArgs := rawArgs.Remove(positionalArgSpec.Name)
62-
63-
return &CliError{
64-
Err: errors.New("a positional argument is required for this command"),
65-
Hint: positionalArgHint(meta.BinaryName, cmd, value, otherArgs, len(positionalArgs) > 0),
66-
}
67-
}
68-
6961
// If no positional arguments were provided, return an error
7062
if len(positionalArgs) == 0 {
7163
return &CliError{
7264
Err: errors.New("a positional argument is required for this command"),
73-
Hint: positionalArgHint(meta.BinaryName, cmd, "<"+positionalArgSpec.Name+">", rawArgs, false),
65+
Hint: positionalArgHint(meta.BinaryName, cmd, "<"+positionalArgSpec.Name+">", rawArgs.Remove(positionalArgSpec.Name), false),
7466
}
7567
}
7668

core/cobra_utils_test.go

Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ type testType struct {
1717
Tag string
1818
}
1919

20+
type testTypeManyTags struct {
21+
NameID string
22+
Tags []string
23+
}
24+
2025
type testDate struct {
2126
Date *time.Time
2227
}
@@ -78,6 +83,45 @@ func testGetCommands() *core.Commands {
7883
return argsI, nil
7984
},
8085
},
86+
&core.Command{
87+
Namespace: "test",
88+
Resource: "many-positional",
89+
ArgSpecs: core.ArgSpecs{
90+
{
91+
Name: "name-id",
92+
Positional: true,
93+
},
94+
{
95+
Name: "tag",
96+
Positional: true,
97+
},
98+
},
99+
AllowAnonymousClient: true,
100+
ArgsType: reflect.TypeOf(testType{}),
101+
Run: func(_ context.Context, argsI interface{}) (i interface{}, e error) {
102+
return argsI, nil
103+
},
104+
},
105+
&core.Command{
106+
Namespace: "test",
107+
Resource: "many-multi-positional",
108+
ArgSpecs: core.ArgSpecs{
109+
{
110+
Name: "name-id",
111+
Positional: true,
112+
},
113+
{
114+
Name: "tags",
115+
Positional: true,
116+
},
117+
},
118+
AcceptMultiplePositionalArgs: true,
119+
AllowAnonymousClient: true,
120+
ArgsType: reflect.TypeOf(testTypeManyTags{}),
121+
Run: func(_ context.Context, argsI interface{}) (i interface{}, e error) {
122+
return argsI, nil
123+
},
124+
},
81125
&core.Command{
82126
Namespace: "test",
83127
Resource: "raw-args",
@@ -196,42 +240,6 @@ func Test_PositionalArg(t *testing.T) {
196240
}),
197241
),
198242
}))
199-
200-
t.Run("Invalid1", core.Test(&core.TestConfig{
201-
Commands: testGetCommands(),
202-
Cmd: "scw test positional name-id=plop tag=world",
203-
Check: core.TestCheckCombine(
204-
core.TestCheckExitCode(1),
205-
core.TestCheckError(&core.CliError{
206-
Err: errors.New("a positional argument is required for this command"),
207-
Hint: "Try running: scw test positional plop tag=world",
208-
}),
209-
),
210-
}))
211-
212-
t.Run("Invalid2", core.Test(&core.TestConfig{
213-
Commands: testGetCommands(),
214-
Cmd: "scw test positional tag=world name-id=plop",
215-
Check: core.TestCheckCombine(
216-
core.TestCheckExitCode(1),
217-
core.TestCheckError(&core.CliError{
218-
Err: errors.New("a positional argument is required for this command"),
219-
Hint: "Try running: scw test positional plop tag=world",
220-
}),
221-
),
222-
}))
223-
224-
t.Run("Invalid3", core.Test(&core.TestConfig{
225-
Commands: testGetCommands(),
226-
Cmd: "scw test positional plop name-id=plop",
227-
Check: core.TestCheckCombine(
228-
core.TestCheckExitCode(1),
229-
core.TestCheckError(&core.CliError{
230-
Err: errors.New("a positional argument is required for this command"),
231-
Hint: "Try running: scw test positional plop",
232-
}),
233-
),
234-
}))
235243
})
236244

237245
t.Run("simple", core.Test(&core.TestConfig{
@@ -240,6 +248,24 @@ func Test_PositionalArg(t *testing.T) {
240248
Check: core.TestCheckExitCode(0),
241249
}))
242250

251+
t.Run("simple2", core.Test(&core.TestConfig{
252+
Commands: testGetCommands(),
253+
Cmd: "scw test positional name-id=plop tag=world",
254+
Check: core.TestCheckExitCode(0),
255+
}))
256+
257+
t.Run("simple3", core.Test(&core.TestConfig{
258+
Commands: testGetCommands(),
259+
Cmd: "scw test positional tag=world name-id=plop",
260+
Check: core.TestCheckExitCode(0),
261+
}))
262+
263+
t.Run("simple4", core.Test(&core.TestConfig{
264+
Commands: testGetCommands(),
265+
Cmd: "scw test positional plop name-id=plop",
266+
Check: core.TestCheckExitCode(0),
267+
}))
268+
243269
t.Run("full command", core.Test(&core.TestConfig{
244270
Commands: testGetCommands(),
245271
Cmd: "scw test positional plop tag=world",
@@ -272,6 +298,41 @@ func Test_PositionalArg(t *testing.T) {
272298
core.TestCheckGolden(),
273299
),
274300
}))
301+
302+
t.Run("many positional", core.Test(&core.TestConfig{
303+
Commands: testGetCommands(),
304+
Cmd: "scw test many-positional tag1 name-id=plop",
305+
Check: core.TestCheckExitCode(0),
306+
}))
307+
308+
t.Run("many positional", core.Test(&core.TestConfig{
309+
Commands: testGetCommands(),
310+
Cmd: "scw test many-positional tag1 name-id=plop",
311+
Check: core.TestCheckCombine(
312+
core.TestCheckExitCode(0),
313+
func(t *testing.T, ctx *core.CheckFuncCtx) {
314+
t.Helper()
315+
res := ctx.Result.(*testType)
316+
assert.Equal(t, "plop", res.NameID)
317+
assert.Equal(t, "tag1", res.Tag)
318+
},
319+
),
320+
}))
321+
322+
t.Run("many multi-positional", core.Test(&core.TestConfig{
323+
Commands: testGetCommands(),
324+
Cmd: "scw test many-multi-positional pos1 pos2 name-id=plop",
325+
Check: core.TestCheckCombine(
326+
core.TestCheckExitCode(0),
327+
func(t *testing.T, ctx *core.CheckFuncCtx) {
328+
t.Helper()
329+
res := ctx.Result.(*testTypeManyTags)
330+
assert.Equal(t, "plop", res.NameID)
331+
assert.Equal(t, "pos1", res.Tags[0])
332+
assert.Equal(t, "pos2", res.Tags[1])
333+
},
334+
),
335+
}))
275336
}
276337

277338
func Test_MultiPositionalArg(t *testing.T) {

0 commit comments

Comments
 (0)