Skip to content

Commit 378a78b

Browse files
authored
Use Exec and ExecFunc (#34)
1 parent a48909f commit 378a78b

File tree

3 files changed

+140
-71
lines changed

3 files changed

+140
-71
lines changed

acmd.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,41 @@ type Command struct {
3737
Description string
3838

3939
// Do will be invoked.
40+
// Deprecated: use ExecFunc or Exec.
4041
Do func(ctx context.Context, args []string) error
4142

43+
// ExecFunc represents the command function.
44+
// Use Exec if you have struct implementing this function.
45+
ExecFunc func(ctx context.Context, args []string) error
46+
47+
// Exec represents the command function.
48+
// Will be used only if ExecFunc is nil.
49+
Exec Exec
50+
4251
// Subcommands of the command.
4352
Subcommands []Command
4453

4554
// IsHidden reports whether command should not be show in help. Default false.
4655
IsHidden bool
4756
}
4857

58+
// simple way to get exec function
59+
func (cmd *Command) getExec() func(ctx context.Context, args []string) error {
60+
switch {
61+
case cmd.ExecFunc != nil:
62+
return cmd.ExecFunc
63+
case cmd.Exec != nil:
64+
return cmd.Exec.ExecCommand
65+
default:
66+
return nil
67+
}
68+
}
69+
70+
// Exec represents a command to run.
71+
type Exec interface {
72+
ExecCommand(ctx context.Context, args []string) error
73+
}
74+
4975
// Config for the runner.
5076
type Config struct {
5177
// AppName is an optional name for the app, if empty os.Args[0] will be used.
@@ -158,15 +184,15 @@ func (r *Runner) init() error {
158184
Command{
159185
Name: "help",
160186
Description: "shows help message",
161-
Do: func(ctx context.Context, args []string) error {
187+
ExecFunc: func(ctx context.Context, args []string) error {
162188
r.cfg.Usage(r.cfg, r.cmds)
163189
return nil
164190
},
165191
},
166192
Command{
167193
Name: "version",
168194
Description: "shows version of the application",
169-
Do: func(ctx context.Context, args []string) error {
195+
ExecFunc: func(ctx context.Context, args []string) error {
170196
fmt.Fprintf(r.cfg.Output, "%s version: %s\n\n", r.cfg.AppName, r.cfg.Version)
171197
return nil
172198
},
@@ -183,11 +209,11 @@ func validateCommand(cmd Command) error {
183209
cmds := cmd.Subcommands
184210

185211
switch {
186-
case cmd.Do == nil && len(cmds) == 0:
187-
return fmt.Errorf("command %q function cannot be nil or must have subcommands", cmd.Name)
212+
case cmd.getExec() == nil && len(cmds) == 0:
213+
return fmt.Errorf("command %q exec function cannot be nil OR must have subcommands", cmd.Name)
188214

189-
case cmd.Do != nil && len(cmds) != 0:
190-
return fmt.Errorf("command %q function cannot be set and have subcommands", cmd.Name)
215+
case cmd.getExec() != nil && len(cmds) != 0:
216+
return fmt.Errorf("command %q exec function cannot be set AND have subcommands", cmd.Name)
191217

192218
case cmd.Name == "help" || cmd.Name == "version":
193219
return fmt.Errorf("command %q is reserved", cmd.Name)
@@ -271,15 +297,15 @@ func findCmd(cfg Config, cmds []Command, args []string) (func(ctx context.Contex
271297
}
272298

273299
// go deeper into subcommands
274-
if c.Do == nil {
300+
if c.getExec() == nil {
275301
if len(params) == 0 {
276302
return nil, nil, errors.New("no args for command provided")
277303
}
278304
cmds, args = c.Subcommands, params
279305
found = true
280306
break
281307
}
282-
return c.Do, params, nil
308+
return c.getExec(), params, nil
283309
}
284310

285311
if !found {

acmd_test.go

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestRunner(t *testing.T) {
3131
Name: "foo",
3232
Subcommands: []Command{
3333
{
34-
Name: "for", Do: func(ctx context.Context, args []string) error {
34+
Name: "for", ExecFunc: func(ctx context.Context, args []string) error {
3535
fmt.Fprint(buf, "for")
3636
return nil
3737
},
@@ -40,7 +40,7 @@ func TestRunner(t *testing.T) {
4040
},
4141
{
4242
Name: "bar",
43-
Do: func(ctx context.Context, args []string) error {
43+
ExecFunc: func(ctx context.Context, args []string) error {
4444
fmt.Fprint(buf, "bar")
4545
return nil
4646
},
@@ -50,7 +50,7 @@ func TestRunner(t *testing.T) {
5050
{
5151
Name: "status",
5252
Description: "status command gives status of the state",
53-
Do: func(ctx context.Context, args []string) error {
53+
ExecFunc: func(ctx context.Context, args []string) error {
5454
return nil
5555
},
5656
},
@@ -70,7 +70,7 @@ func TestRunner(t *testing.T) {
7070
func TestRunnerMustSetDefaults(t *testing.T) {
7171
app := "./someapp"
7272
args := append([]string{app, "runner"}, os.Args[1:]...)
73-
cmds := []Command{{Name: "foo", Do: nopFunc}}
73+
cmds := []Command{{Name: "foo", ExecFunc: nopFunc}}
7474
r := RunnerOf(cmds, Config{
7575
Args: args,
7676
Output: io.Discard,
@@ -105,7 +105,7 @@ func TestRunnerMustSetDefaults(t *testing.T) {
105105
}
106106

107107
func TestRunnerWithoutArgs(t *testing.T) {
108-
cmds := []Command{{Name: "foo", Do: nopFunc}}
108+
cmds := []Command{{Name: "foo", ExecFunc: nopFunc}}
109109
r := RunnerOf(cmds, Config{
110110
Args: []string{"./app"},
111111
Output: io.Discard,
@@ -119,15 +119,15 @@ func TestRunnerWithoutArgs(t *testing.T) {
119119

120120
func TestRunnerMustSortCommands(t *testing.T) {
121121
cmds := []Command{
122-
{Name: "foo", Do: nopFunc},
122+
{Name: "foo", ExecFunc: nopFunc},
123123
{Name: "xyz"},
124-
{Name: "cake", Do: nopFunc},
125-
{Name: "foo2", Do: nopFunc},
124+
{Name: "cake", ExecFunc: nopFunc},
125+
{Name: "foo2", ExecFunc: nopFunc},
126126
}
127127
cmds[1].Subcommands = []Command{
128-
{Name: "a", Do: nopFunc},
129-
{Name: "c", Do: nopFunc},
130-
{Name: "b", Do: nopFunc},
128+
{Name: "a", ExecFunc: nopFunc},
129+
{Name: "c", ExecFunc: nopFunc},
130+
{Name: "b", ExecFunc: nopFunc},
131131
}
132132

133133
r := RunnerOf(cmds, Config{
@@ -164,7 +164,7 @@ func TestRunnerJustExit(t *testing.T) {
164164
doExitOld, doExit = doExit, doExitOld
165165

166166
buf := &bytes.Buffer{}
167-
r := RunnerOf([]Command{{Name: "foo", Do: nopFunc}}, Config{
167+
r := RunnerOf([]Command{{Name: "foo", ExecFunc: nopFunc}}, Config{
168168
AppName: "exit-test",
169169
Output: buf,
170170
})
@@ -189,84 +189,85 @@ func TestRunnerInit(t *testing.T) {
189189
wantErrStr string
190190
}{
191191
{
192-
cmds: []Command{{Name: "app:cre.ate", Do: nopFunc}},
192+
cmds: []Command{{Name: "app:cre.ate", ExecFunc: nopFunc}},
193193
wantErrStr: ``,
194194
},
195195
{
196-
cmds: []Command{{Name: "", Do: nopFunc}},
196+
cmds: []Command{{Name: "", ExecFunc: nopFunc}},
197197
wantErrStr: `command "" must contains only letters, digits, - and _`,
198198
},
199199
{
200-
cmds: []Command{{Name: "foo%", Do: nopFunc}},
200+
cmds: []Command{{Name: "foo%", ExecFunc: nopFunc}},
201201
wantErrStr: `command "foo%" must contains only letters, digits, - and _`,
202202
},
203203
{
204-
cmds: []Command{{Name: "foo", Alias: "%", Do: nopFunc}},
204+
cmds: []Command{{Name: "foo", Alias: "%", ExecFunc: nopFunc}},
205205
wantErrStr: `command alias "%" must contains only letters, digits, - and _`,
206206
},
207207
{
208-
cmds: []Command{{Name: "foo%", Do: nil}},
209-
wantErrStr: `command "foo%" function cannot be nil`,
208+
cmds: []Command{{Name: "foo%", ExecFunc: nil}},
209+
wantErrStr: `command "foo%" exec function cannot be nil OR must have subcommands`,
210210
},
211211
{
212-
cmds: []Command{{Name: "foo", Do: nil}},
213-
wantErrStr: `command "foo" function cannot be nil or must have subcommands`,
212+
cmds: []Command{{Name: "foo", ExecFunc: nil}},
213+
wantErrStr: `command "foo" exec function cannot be nil OR must have subcommands`,
214214
},
215215
{
216216
cmds: []Command{{
217217
Name: "foobar",
218-
Do: nopFunc,
218+
ExecFunc: nopFunc,
219219
Subcommands: []Command{{Name: "nested"}},
220220
}},
221-
wantErrStr: `command "foobar" function cannot be set and have subcommands`,
221+
wantErrStr: `command "foobar" exec function cannot be set AND have subcommands`,
222222
},
223223
{
224-
cmds: []Command{{Name: "foo", Do: nopFunc}},
224+
cmds: []Command{{Name: "foo", ExecFunc: nopFunc}},
225225
cfg: Config{
226226
Args: []string{},
227227
},
228228
wantErrStr: `no args provided`,
229229
},
230230
{
231-
cmds: []Command{{Name: "help", Do: nopFunc}},
231+
cmds: []Command{{Name: "help", ExecFunc: nopFunc}},
232232
wantErrStr: `command "help" is reserved`,
233233
},
234234
{
235-
cmds: []Command{{Name: "version", Do: nopFunc}},
235+
cmds: []Command{{Name: "version", ExecFunc: nopFunc}},
236236
wantErrStr: `command "version" is reserved`,
237237
},
238238
{
239-
cmds: []Command{{Name: "foo", Alias: "help", Do: nopFunc}},
239+
cmds: []Command{{Name: "foo", Alias: "help", ExecFunc: nopFunc}},
240240
wantErrStr: `command alias "help" is reserved`,
241241
},
242242
{
243-
cmds: []Command{{Name: "foo", Alias: "version", Do: nopFunc}},
243+
cmds: []Command{{Name: "foo", Alias: "version", ExecFunc: nopFunc}},
244244
wantErrStr: `command alias "version" is reserved`,
245245
},
246246
{
247-
cmds: []Command{{Name: "a", Do: nopFunc}, {Name: "a", Do: nopFunc}},
247+
cmds: []Command{{Name: "a", ExecFunc: nopFunc}, {Name: "a", ExecFunc: nopFunc}},
248248
wantErrStr: `duplicate command "a"`,
249249
},
250250
{
251-
cmds: []Command{{Name: "aaa", Do: nopFunc}, {Name: "b", Alias: "aaa", Do: nopFunc}},
251+
cmds: []Command{{Name: "aaa", ExecFunc: nopFunc}, {Name: "b", Alias: "aaa", ExecFunc: nopFunc}},
252252
wantErrStr: `duplicate command alias "aaa"`,
253253
},
254254
{
255-
cmds: []Command{{Name: "aaa", Alias: "a", Do: nopFunc}, {Name: "bbb", Alias: "a", Do: nopFunc}},
255+
cmds: []Command{{Name: "aaa", Alias: "a", ExecFunc: nopFunc}, {Name: "bbb", Alias: "a", ExecFunc: nopFunc}},
256256
wantErrStr: `duplicate command alias "a"`,
257257
},
258258
{
259-
cmds: []Command{{Name: "a", Do: nopFunc}, {Name: "b", Alias: "a", Do: nopFunc}},
259+
cmds: []Command{{Name: "a", ExecFunc: nopFunc}, {Name: "b", Alias: "a", ExecFunc: nopFunc}},
260260
wantErrStr: `duplicate command alias "a"`,
261261
},
262262
}
263263

264264
for _, tc := range testCases {
265265
tc.cfg.Output = io.Discard
266266
err := RunnerOf(tc.cmds, tc.cfg).Run()
267+
failIfOk(t, err)
267268

268269
if got := err.Error(); tc.wantErrStr != "" && !strings.Contains(got, tc.wantErrStr) {
269-
t.Fatalf("want %q got %q", tc.wantErrStr, got)
270+
t.Fatalf("\nhave: %+v\nwant: %+v\n", tc.wantErrStr, got)
270271
}
271272
}
272273
}
@@ -279,25 +280,25 @@ func TestRunner_suggestCommand(t *testing.T) {
279280
}{
280281
{
281282
cmds: []Command{
282-
{Name: "for", Do: nopFunc},
283-
{Name: "foo", Do: nopFunc},
284-
{Name: "bar", Do: nopFunc},
283+
{Name: "for", ExecFunc: nopFunc},
284+
{Name: "foo", ExecFunc: nopFunc},
285+
{Name: "bar", ExecFunc: nopFunc},
285286
},
286287
args: []string{"./someapp", "fooo"},
287288
want: `"fooo" unknown command, did you mean "foo"?` + "\n" + `Run "myapp help" for usage.` + "\n\n",
288289
},
289290
{
290-
cmds: []Command{{Name: "for", Do: nopFunc}},
291+
cmds: []Command{{Name: "for", ExecFunc: nopFunc}},
291292
args: []string{"./someapp", "hell"},
292293
want: `"hell" unknown command, did you mean "help"?` + "\n" + `Run "myapp help" for usage.` + "\n\n",
293294
},
294295
{
295-
cmds: []Command{{Name: "for", Do: nopFunc}},
296+
cmds: []Command{{Name: "for", ExecFunc: nopFunc}},
296297
args: []string{"./someapp", "verZION"},
297298
want: `"verZION" unknown command` + "\n" + `Run "myapp help" for usage.` + "\n\n",
298299
},
299300
{
300-
cmds: []Command{{Name: "for", Do: nopFunc}},
301+
cmds: []Command{{Name: "for", ExecFunc: nopFunc}},
301302
args: []string{"./someapp", "verZion"},
302303
want: `"verZion" unknown command, did you mean "version"?` + "\n" + `Run "myapp help" for usage.` + "\n\n",
303304
},
@@ -337,9 +338,9 @@ func TestHasHelpFlag(t *testing.T) {
337338
func TestCommand_IsHidden(t *testing.T) {
338339
buf := &bytes.Buffer{}
339340
cmds := []Command{
340-
{Name: "for", Do: nopFunc},
341-
{Name: "foo", Do: nopFunc, IsHidden: true},
342-
{Name: "bar", Do: nopFunc},
341+
{Name: "for", ExecFunc: nopFunc},
342+
{Name: "foo", ExecFunc: nopFunc, IsHidden: true},
343+
{Name: "bar", ExecFunc: nopFunc},
343344
}
344345
r := RunnerOf(cmds, Config{
345346
Args: []string{"./someapp", "help"},
@@ -360,7 +361,7 @@ func TestExit(t *testing.T) {
360361
cmds := []Command{
361362
{
362363
Name: "for",
363-
Do: func(ctx context.Context, args []string) error {
364+
ExecFunc: func(ctx context.Context, args []string) error {
364365
return ErrCode(wantStatus)
365366
},
366367
},
@@ -404,9 +405,9 @@ func failIfErr(t testing.TB, err error) {
404405
}
405406
}
406407

407-
func mustEqual(t testing.TB, got, want interface{}) {
408+
func mustEqual(t testing.TB, have, want interface{}) {
408409
t.Helper()
409-
if !reflect.DeepEqual(got, want) {
410-
t.Fatalf("\nhave %+v\nwant %+v", got, want)
410+
if !reflect.DeepEqual(have, want) {
411+
t.Fatalf("\nhave: %+v\nwant: %+v\n", have, want)
411412
}
412413
}

0 commit comments

Comments
 (0)