Skip to content

Commit

Permalink
Remove ParseAndRun and update examples
Browse files Browse the repository at this point in the history
  • Loading branch information
mfridman committed Jan 7, 2025
1 parent cf5befe commit 864da5e
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 50 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,18 @@ root := &cli.Command{
return nil
},
}
if err := cli.ParseAndRun(context.Background(), root, os.Args[1:], nil); err != nil {
if err := cli.Parse(root, os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
fmt.Fprintf(os.Stdout, "%s\n", cli.DefaultUsage(root))
return
}
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if err := cli.Run(context.Background(), root, nil); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
```

This code defines a simple `echo` command that echoes back the input. It supports a `-c` flag to
Expand Down
6 changes: 5 additions & 1 deletion examples/cmd/echo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ func main() {
return nil
},
}
if err := cli.ParseAndRun(context.Background(), root, os.Args[1:], nil); err != nil {
if err := cli.Parse(root, os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
fmt.Fprintf(os.Stdout, "%s\n", cli.DefaultUsage(root))
return
}
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if err := cli.Run(context.Background(), root, nil); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
16 changes: 6 additions & 10 deletions examples/cmd/task/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,16 @@ func main() {
},
}

if err := cli.ParseAndRun(
context.Background(),
root,
os.Args[1:],
nil,
); err != nil {
if err := cli.Parse(root, os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
fmt.Fprintf(os.Stdout, "%s\n", cli.DefaultUsage(root))
return
}
fmt.Fprintf(os.Stderr, "%s\n\nerror: %v\n",
cli.DefaultUsage(root),
err,
)
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if err := cli.Run(context.Background(), root, nil); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
Expand Down
41 changes: 21 additions & 20 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,6 @@ import (
"os"
)

// ParseAndRun parses the command hierarchy and runs the command. A convenience function that
// combines [Parse] and [Run] into a single call. See [Parse] and [Run] for more details.
func ParseAndRun(ctx context.Context, root *Command, args []string, options *RunOptions) (retErr error) {
if err := Parse(root, args); err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
switch err := r.(type) {
case error:
retErr = fmt.Errorf("internal: %v", err)
default:
retErr = fmt.Errorf("recovered: %v", r)
}
}
}()
return Run(ctx, root, options)
}

// RunOptions specifies options for running a command.
type RunOptions struct {
// Stdin, Stdout, and Stderr are the standard input, output, and error streams for the command.
Expand All @@ -48,10 +29,30 @@ func Run(ctx context.Context, root *Command, options *RunOptions) error {
if root.state == nil || len(root.state.path) == 0 {
return errors.New("command has not been parsed")
}
cmd := root.terminal()
if cmd == nil {
// This should never happen, but if it does, it's likely a bug in the Parse function.
return errors.New("no terminal command found")
}

options = checkAndSetRunOptions(options)
updateState(root.state, options)

return root.terminal().Exec(ctx, root.state)
return run(ctx, cmd, root.state)
}

func run(ctx context.Context, cmd *Command, state *State) (retErr error) {
defer func() {
if r := recover(); r != nil {
switch err := r.(type) {
case error:
retErr = fmt.Errorf("internal: %v", err)
default:
retErr = fmt.Errorf("recover: %v", r)
}
}
}()
return cmd.Exec(ctx, state)
}

func updateState(s *State, opt *RunOptions) {
Expand Down
50 changes: 32 additions & 18 deletions run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ import (
func TestRun(t *testing.T) {
t.Parallel()

t.Run("parse and run", func(t *testing.T) {
t.Run("print version", func(t *testing.T) {
t.Parallel()
var count int

root := &Command{
Name: "count",
Usage: "count [flags] [command]",
Flags: FlagsFunc(func(fset *flag.FlagSet) {
fset.Bool("dry-run", false, "dry run")
}),
Name: "printer",
Usage: "printer [flags] [command]",
SubCommands: []*Command{
{
Name: "version",
Expand All @@ -32,35 +28,53 @@ func TestRun(t *testing.T) {
},
},
},
Exec: func(ctx context.Context, s *State) error { return nil },
}
err := Parse(root, []string{"version"})
require.NoError(t, err)

output := bytes.NewBuffer(nil)
require.NoError(t, err)
err = Run(context.Background(), root, &RunOptions{Stdout: output})
require.NoError(t, err)
require.Equal(t, "1.0.0\n", output.String())
})

t.Run("parse and run", func(t *testing.T) {
t.Parallel()
var count int

root := &Command{
Name: "count",
Usage: "count [flags] [command]",
Flags: FlagsFunc(func(f *flag.FlagSet) {
f.Bool("dry-run", false, "dry run")
}),
Exec: func(ctx context.Context, s *State) error {
if !GetFlag[bool](s, "dry-run") {
count++
}
return nil
},
}

output := bytes.NewBuffer(nil)
err := ParseAndRun(context.Background(), root, []string{"version"}, &RunOptions{
Stdout: output,
})
err := Parse(root, nil)
require.NoError(t, err)
require.Equal(t, "1.0.0\n", output.String())
output.Reset()

// Run the command 3 times
for i := 0; i < 3; i++ {
err := ParseAndRun(context.Background(), root, nil, nil)
err := Run(context.Background(), root, nil)
require.NoError(t, err)
}
require.Equal(t, 3, count)
// Run with dry-run flag
err = ParseAndRun(context.Background(), root, []string{"--dry-run"}, nil)
err = Parse(root, []string{"--dry-run"})
require.NoError(t, err)
err = Run(context.Background(), root, nil)
require.NoError(t, err)
require.Equal(t, 3, count)
})
t.Run("typo suggestion", func(t *testing.T) {
t.Parallel()

root := &Command{
Name: "count",
Usage: "count [flags] [command]",
Expand All @@ -77,7 +91,7 @@ func TestRun(t *testing.T) {
Exec: func(ctx context.Context, s *State) error { return nil },
}

err := ParseAndRun(context.Background(), root, []string{"verzion"}, nil)
err := Parse(root, []string{"verzion"})
require.Error(t, err)
require.Contains(t, err.Error(), `unknown command "verzion". Did you mean one of these?`)
require.Contains(t, err.Error(), ` version`)
Expand Down

0 comments on commit 864da5e

Please sign in to comment.