Skip to content

Commit 7ad48e6

Browse files
authored
refactor(internal/cli): rewrite LookupCommand iteratively (#2137)
Rewrite LookupCommand iteratively by replace recursive calls with a for loop. Additionally, update tests to expect nil for empty remaining arguments, and merge the lookup function logic directly into LookupCommand.
1 parent 6c6b171 commit 7ad48e6

File tree

2 files changed

+33
-79
lines changed

2 files changed

+33
-79
lines changed

internal/cli/cli.go

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -111,37 +111,29 @@ func hasFlags(fs *flag.FlagSet) bool {
111111
return visited
112112
}
113113

114-
// LookupCommand recursively looks up the command specified by the given arguments.
114+
// LookupCommand looks up the command specified by the given arguments.
115115
// It returns the command, the remaining arguments, and an error if the command
116116
// is not found.
117117
func LookupCommand(cmd *Command, args []string) (*Command, []string, error) {
118-
if len(args) == 0 {
119-
return cmd, nil, nil
120-
}
121-
subcommand, err := lookup(cmd, args[0])
122-
if err != nil {
123-
cmd.Flags.Usage()
124-
return nil, nil, err
125-
}
126-
// If the next argument matches a potential flag (first char is `-`), parse the
127-
// remaining arguments as flags. Check if argument is a flag before calling
128-
// `lookupCommand` again to avoid flags from being treated as subcommands.
129-
if len(args) > 1 && args[1][0] == '-' {
130-
return subcommand, args[1:], nil
131-
}
132-
if len(subcommand.Commands) > 0 {
133-
return LookupCommand(subcommand, args[1:])
134-
}
135-
return subcommand, args[1:], nil
136-
}
137-
138-
// lookup finds a command by its name, and returns an error if the command is
139-
// not found.
140-
func lookup(c *Command, name string) (*Command, error) {
141-
for _, sub := range c.Commands {
142-
if sub.Name() == name {
143-
return sub, nil
118+
// If the first character of the argument is '-' assume it is a flag.
119+
for len(args) > 0 && args[0][0] != '-' {
120+
name := args[0]
121+
var found *Command
122+
for _, sub := range cmd.Commands {
123+
if sub.Name() == name {
124+
found = sub
125+
break
126+
}
127+
}
128+
if found == nil {
129+
if len(cmd.Commands) == 0 {
130+
break
131+
}
132+
cmd.Flags.Usage()
133+
return nil, nil, fmt.Errorf("invalid command: %q", name)
144134
}
135+
cmd = found
136+
args = args[1:]
145137
}
146-
return nil, fmt.Errorf("invalid command: %q", name)
138+
return cmd, args, nil
147139
}

internal/cli/cli_test.go

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"testing"
2323

2424
"github.com/google/go-cmp/cmp"
25+
"github.com/google/go-cmp/cmp/cmpopts"
2526
"github.com/googleapis/librarian/internal/config"
2627
)
2728

@@ -53,41 +54,6 @@ func TestParseAndSetFlags(t *testing.T) {
5354
}
5455
}
5556

56-
func TestLookup(t *testing.T) {
57-
commands := []*Command{
58-
{Short: "foo runs the foo command"},
59-
{Short: "bar runs the bar command"},
60-
}
61-
62-
for _, test := range []struct {
63-
name string
64-
wantErr bool
65-
}{
66-
{"foo", false},
67-
{"bar", false},
68-
{"baz", true}, // not found case
69-
} {
70-
t.Run(test.name, func(t *testing.T) {
71-
cmd := &Command{}
72-
cmd.Commands = commands
73-
sub, err := lookup(cmd, test.name)
74-
if test.wantErr {
75-
if err == nil {
76-
t.Fatal(err)
77-
}
78-
return
79-
}
80-
81-
if err != nil {
82-
t.Fatal(err)
83-
}
84-
if sub.Name() != test.name {
85-
t.Errorf("got = %q, want = %q", sub.Name(), test.name)
86-
}
87-
})
88-
}
89-
}
90-
9157
func TestRun(t *testing.T) {
9258
executed := false
9359
cmd := &Command{
@@ -250,7 +216,6 @@ func TestLookupCommand(t *testing.T) {
250216
{
251217
name: "no args",
252218
cmd: root,
253-
args: []string{},
254219
wantCmd: root,
255220
},
256221
{
@@ -260,18 +225,16 @@ func TestLookupCommand(t *testing.T) {
260225
wantCmd: sub1,
261226
},
262227
{
263-
name: "find sub2",
264-
cmd: root,
265-
args: []string{"sub2"},
266-
wantCmd: sub2,
267-
wantArgs: []string{},
228+
name: "find sub2",
229+
cmd: root,
230+
args: []string{"sub2"},
231+
wantCmd: sub2,
268232
},
269233
{
270-
name: "find sub1sub1",
271-
cmd: root,
272-
args: []string{"sub1", "sub1sub1"},
273-
wantCmd: sub1sub1,
274-
wantArgs: []string{},
234+
name: "find sub1sub1",
235+
cmd: root,
236+
args: []string{"sub1", "sub1sub1"},
237+
wantCmd: sub1sub1,
275238
},
276239
{
277240
name: "find sub1sub1 with args",
@@ -317,8 +280,7 @@ func TestLookupCommand(t *testing.T) {
317280
t.Run(test.name, func(t *testing.T) {
318281
gotCmd, gotArgs, err := LookupCommand(test.cmd, test.args)
319282
if (err != nil) != test.wantErr {
320-
t.Errorf("lookupCommand() error = %v, wantErr %v", err, test.wantErr)
321-
return
283+
t.Fatalf("error = %v, wantErr %v", err, test.wantErr)
322284
}
323285
if gotCmd != test.wantCmd {
324286
var gotName, wantName string
@@ -328,10 +290,10 @@ func TestLookupCommand(t *testing.T) {
328290
if test.wantCmd != nil {
329291
wantName = test.wantCmd.Name()
330292
}
331-
t.Errorf("lookupCommand() gotCmd.Name() = %q, want %q", gotName, wantName)
293+
t.Errorf("gotCmd.Name() = %q, want %q", gotName, wantName)
332294
}
333-
if diff := cmp.Diff(test.wantArgs, gotArgs); diff != "" {
334-
t.Errorf("lookupCommand() args mismatch (-want +got):\n%s", diff)
295+
if diff := cmp.Diff(test.wantArgs, gotArgs, cmpopts.EquateEmpty()); diff != "" {
296+
t.Errorf("mismatch (-want +got):\n%s", diff)
335297
}
336298
})
337299
}

0 commit comments

Comments
 (0)