Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ const (
ExitCodeMisconfiguration ExitCode = 2
)

// Execute the correct command in the given command hierarchy (starting at "root"), configured from the given CLI args
// and environment variables. The command will be executed with the given context after all pre-RunFunc hooks have been
// successfully executed in the command hierarchy.
func Execute(ctx context.Context, w io.Writer, root *Command, args []string, envVars map[string]string) (exitCode ExitCode) {
// ExecuteWithContext the correct command in the given command hierarchy (starting at "root"), configured from the given
// CLI args and environment variables. The command will be executed with the given context after all pre-RunFunc hooks
// have been successfully executed in the command hierarchy.
func ExecuteWithContext(ctx context.Context, w io.Writer, root *Command, args []string, envVars map[string]string) (exitCode ExitCode) {
exitCode = ExitCodeSuccess

// We insist on getting the root command - so that we can infer correctly which command the user wanted to invoke
Expand Down Expand Up @@ -103,3 +103,16 @@ func Execute(ctx context.Context, w io.Writer, root *Command, args []string, env
}
return
}

// Execute the correct command in the given command hierarchy (starting at "root"), configured from the given
// CLI args and environment variables. The command will be executed with a context that gets canceled when an OS signal
// for termination is received, after all pre-RunFunc hooks have been successfully executed in the command hierarchy.
//
//goland:noinspection GoUnusedExportedFunction
func Execute(w io.Writer, root *Command, args []string, envVars map[string]string) ExitCode {
// Prepare a context that gets canceled if OS termination signals are sent
ctx, cancel := context.WithCancel(SetupSignalHandler())
defer cancel()

return ExecuteWithContext(ctx, w, root, args, envVars)
}
20 changes: 10 additions & 10 deletions execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,22 @@ func TestExecute(t *testing.T) {
child := MustNew("child", "desc", "long desc", nil, nil)
_ = MustNew("root", "desc", "long desc", nil, nil, child)
b := &bytes.Buffer{}
With(t).Verify(Execute(ctx, b, child, nil, nil)).Will(EqualTo(ExitCodeError)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, b, child, nil, nil)).Will(EqualTo(ExitCodeError)).OrFail()
With(t).Verify(b).Will(Say(`^unsupported operation: command must be the root command$`)).OrFail()
})

t.Run("applies configuration", func(t *testing.T) {
ctx := context.Background()
cmd := MustNew("cmd", "desc", "long desc", &ActionWithConfig{}, nil)
With(t).Verify(Execute(ctx, os.Stderr, cmd, []string{"--my-flag=V1"}, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, os.Stderr, cmd, []string{"--my-flag=V1"}, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(cmd.action.(*ActionWithConfig).MyFlag).Will(EqualTo("V1")).OrFail()
})

t.Run("prints usage on CLI parse errors", func(t *testing.T) {
ctx := context.Background()
cmd := MustNew("cmd", "desc", "long desc", &ActionWithConfig{}, nil)
b := &bytes.Buffer{}
With(t).Verify(Execute(ctx, b, cmd, []string{"--bad-flag=V1"}, nil)).Will(EqualTo(ExitCodeMisconfiguration)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, b, cmd, []string{"--bad-flag=V1"}, nil)).Will(EqualTo(ExitCodeMisconfiguration)).OrFail()
With(t).Verify(cmd.action.(*ActionWithConfig).MyFlag).Will(BeEmpty()).OrFail()
With(t).Verify(b.String()).Will(EqualTo("unknown flag: --bad-flag\nUsage: cmd [--help] [--my-flag=VALUE]\n")).OrFail()
})
Expand All @@ -96,7 +96,7 @@ func TestExecute(t *testing.T) {
ctx := context.Background()
cmd := MustNew("cmd", "desc", "long desc", &ActionWithConfig{}, nil)
b := &bytes.Buffer{}
With(t).Verify(Execute(ctx, b, cmd, []string{"--help"}, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, b, cmd, []string{"--help"}, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(b.String()).Will(EqualTo(`
cmd: desc

Expand All @@ -118,7 +118,7 @@ Flags:
sub2 := MustNew("sub2", "desc", "long desc", &ActionWithConfig{}, []any{&PreRunHookWithConfig{}})
sub1 := MustNew("sub1", "desc", "long desc", nil, []any{&PreRunHookWithConfig{}}, sub2)
root := MustNew("cmd", "desc", "long desc", nil, []any{&PreRunHookWithConfig{}}, sub1)
With(t).Verify(Execute(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()

rootPreRunHook := root.preRunHooks[0].(*PreRunHookWithConfig)
sub1PreRunHook := sub1.preRunHooks[0].(*PreRunHookWithConfig)
Expand Down Expand Up @@ -149,7 +149,7 @@ Flags:
sub2PreRunHook := sub2.preRunHooks[0].(*PreRunHookWithConfig)
sub2Action := sub2.action.(*ActionWithConfig)

With(t).Verify(Execute(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)).Will(EqualTo(ExitCodeError)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)).Will(EqualTo(ExitCodeError)).OrFail()
With(t).Verify(rootPreRunHook.callTime).Will(Not(BeNil())).OrFail()
With(t).Verify(rootPreRunHook.callTime.Before(*sub1PreRunHook.callTime)).Will(EqualTo(true)).OrFail()
With(t).Verify(sub1PreRunHook.callTime).Will(Not(BeNil())).OrFail()
Expand All @@ -163,7 +163,7 @@ Flags:
sub1 := MustNew("sub1", "desc", "long desc", nil, []any{&PostRunHookWithConfig{}}, sub2)
root := MustNew("cmd", "desc", "long desc", nil, []any{&PostRunHookWithConfig{}}, sub1)

exitCode := Execute(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)
exitCode := ExecuteWithContext(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)
With(t).Verify(exitCode).Will(EqualTo(ExitCodeSuccess)).OrFail()

rootPostRunHook := root.postRunHooks[0].(*PostRunHookWithConfig)
Expand Down Expand Up @@ -198,7 +198,7 @@ Flags:
sub1 := MustNew("sub1", "desc", "long desc", nil, []any{passThroughPostHook()}, sub2)
root := MustNew("cmd", "desc", "long desc", nil, []any{passThroughPostHook()}, sub1)

exitCode := Execute(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)
exitCode := ExecuteWithContext(ctx, os.Stderr, root, []string{"sub1", "sub2"}, nil)
With(t).Verify(exitCode).Will(EqualTo(ExitCodeError)).OrFail()

rootPostRunHook := root.postRunHooks[0].(*PostRunHookWithConfig)
Expand Down Expand Up @@ -231,7 +231,7 @@ Flags:
root := MustNew("cmd", "desc", "long desc", action, nil)

b := &bytes.Buffer{}
With(t).Verify(Execute(ctx, b, root, nil, nil)).Will(EqualTo(ExitCodeMisconfiguration)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, b, root, nil, nil)).Will(EqualTo(ExitCodeMisconfiguration)).OrFail()
With(t).Verify(action.TrackingAction.callTime).Will(BeNil()).OrFail()
With(t).Verify(b.String()).Will(EqualTo("required flag is missing: --my-flag\nUsage: cmd [--help] --my-flag=VALUE\n")).OrFail()
})
Expand All @@ -248,7 +248,7 @@ Flags:
root := MustNew("cmd", "desc", "long desc", action, nil)

b := &bytes.Buffer{}
With(t).Verify(Execute(ctx, b, root, nil, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(ExecuteWithContext(ctx, b, root, nil, nil)).Will(EqualTo(ExitCodeSuccess)).OrFail()
With(t).Verify(action.TrackingAction.callTime).Will(Not(BeNil())).OrFail()
With(t).Verify(b.String()).Will(BeEmpty()).OrFail()
})
Expand Down