~samwhited/cli

8da9bdbba125beb76c6f3d3e9be1850cbf634b69 — Sam Whited 2 years ago 5252292 v0.0.7
cli: remove auto help printing

Let the user print the help file if no such command is specified, or
there is nothing to run.
6 files changed, 67 insertions(+), 39 deletions(-)

M cmd.go
M cmd_test.go
M example_article_test.go
M example_help_test.go
M example_subcommands_test.go
M example_test.go
M cmd.go => cmd.go +19 -12
@@ 16,12 16,18 @@
package cli // import "mellium.im/cli"

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"strings"
)

var (
	ErrInvalidCmd = errors.New("cli: no such command")
	ErrNoRun      = errors.New("cli: no run function was specified for the command")
)

// Command represents a new subcommand.
type Command struct {
	// Usage always starts with the name of the command, followed by a description


@@ 99,14 105,13 @@ func (c *Command) ShortDesc() string {
}

// Exec attempts to run the command that matches the first argument passed in
// (or the current command if no command name is provided and a Run function has
// been specified).
// (or the current command if the command has no name but does have a Run
// function).
// It parses unparsed flags for each subcommand it encounters.
// If no command matches help information is written to stderr.
// If a command matches, there are remaining arguments after flag parsing
// completes, and no Run function is provided, help information is written to
// stdout.
func (c *Command) Exec(stdout, stderr io.Writer, args ...string) error {
// If no command matches, ErrInvalidCmd is returned.
// If a command matches and there are flags, but no run function has been
// provided, ErrNoRun is returned.
func (c *Command) Exec(args ...string) error {
	if c == nil {
		return nil
	}


@@ 123,21 128,23 @@ func (c *Command) Exec(stdout, stderr io.Writer, args ...string) error {
		if c.Run != nil {
			return c.Run(c)
		}
		c.Help(stdout)
		return nil
		return ErrNoRun
	}
	wantCmd := args[0]
	for _, cmd := range c.Commands {
		if cmd.Name() != args[0] {
			continue
		}

		return cmd.Exec(stdout, stderr, args[1:]...)
		return cmd.Exec(args[1:]...)
	}
	if c.Run != nil {
		return c.Run(c, args...)
	}
	c.Help(stderr)
	return nil
	if wantCmd == c.Name() {
		return ErrNoRun
	}
	return ErrInvalidCmd
}

func printCmds(w io.Writer, commands ...*Command) {

M cmd_test.go => cmd_test.go +38 -13
@@ 8,9 8,6 @@ import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"testing"

	"mellium.im/cli"


@@ 68,7 65,7 @@ func TestCommand(t *testing.T) {

var csTestCase = [...]struct {
	cs  *cli.Command
	run string
	run []string
	err error
}{
	0: {},


@@ 80,21 77,49 @@ var csTestCase = [...]struct {
				{Usage: "three [opts]"},
			},
		},
		run: "ran",
		err: nil,
		run: []string{"one"},
		err: cli.ErrNoRun,
	},
	2: {
		cs: &cli.Command{
			Commands: []*cli.Command{
				{Usage: "one [opts]", Flags: func() *flag.FlagSet {
					f := flag.NewFlagSet("one", flag.ExitOnError)
					f.Bool("v", false, "verbose")
					return f
				}()},
				{Usage: "two [opts]"},
				{Usage: "three [opts]"},
			},
		},
		run: []string{"one", "-v"},
		err: cli.ErrNoRun,
	},
	3: {
		cs: &cli.Command{
			Commands: []*cli.Command{
				{Usage: "one [opts]"},
				{Usage: "two [opts]"},
				{Usage: "three [opts]"},
			},
		},
		run: []string{"one", "-v"},
		err: cli.ErrInvalidCmd,
	},
	4: {
		cs: &cli.Command{
			Commands: []*cli.Command{},
		},
		run: []string{"ran"},
		err: cli.ErrInvalidCmd,
	},
}

func TestCommandSet(t *testing.T) {
	for i, tc := range csTestCase {
		t.Run(fmt.Sprintf("Run/%d", i), func(t *testing.T) {
			r, w, _ := os.Pipe()
			go io.Copy(ioutil.Discard, r)
			if err := tc.cs.Exec(ioutil.Discard, w); err != nil {
				t.Errorf("Expected nil error when running with zero args, got=%v", err)
			}
			if err := tc.cs.Exec(ioutil.Discard, w, tc.run+" "+"arg1 "+"arg2"); err != tc.err {
				t.Errorf("Wrong err when running with args, want='%v', got='%v'", tc.err, err)
			if err := tc.cs.Exec(tc.run...); err != tc.err {
				t.Errorf("Wrong err: want='%v', got='%v'", tc.err, err)
			}
		})
		if tc.cs != nil {

M example_article_test.go => example_article_test.go +2 -3
@@ 6,7 6,6 @@ package cli_test

import (
	"fmt"
	"os"

	"mellium.im/cli"
)


@@ 35,10 34,10 @@ func Example_articles() {
	}
	cmds.Commands = append(cmds.Commands, cli.Help(cmds))
	fmt.Println("$ git help")
	cmds.Exec(os.Stdout, os.Stdout, "help")
	cmds.Exec("help")

	fmt.Print("$ git help article\n\n")
	cmds.Exec(os.Stdout, os.Stdout, "help", "article")
	cmds.Exec("help", "article")

	// Output:
	// $ git help

M example_help_test.go => example_help_test.go +1 -3
@@ 5,8 5,6 @@
package cli_test

import (
	"os"

	"mellium.im/cli"
)



@@ 18,7 16,7 @@ func ExampleHelp() {
		commitCmd(nil),
		cli.Help(cmds),
	}
	cmds.Exec(os.Stdout, os.Stdout, "help")
	cmds.Exec("help")

	// Output:
	// Usage: git [options] command

M example_subcommands_test.go => example_subcommands_test.go +6 -7
@@ 6,7 6,6 @@ package cli_test

import (
	"fmt"
	"os"

	"mellium.im/cli"
)


@@ 41,22 40,22 @@ Tidy makes sure go.mod matches the source code in the module…`,
	}
	cmds.Commands = append(cmds.Commands, cli.Help(cmds))
	fmt.Println("$ go help")
	cmds.Exec(os.Stdout, os.Stdout, "help")
	cmds.Exec("help")

	fmt.Print("$ go help mod\n\n")
	cmds.Exec(os.Stdout, os.Stdout, "help", "mod")
	cmds.Exec("help", "mod")

	fmt.Print("$ go help mod tidy\n\n")
	cmds.Exec(os.Stdout, os.Stdout, "help", "mod", "tidy")
	cmds.Exec("help", "mod", "tidy")

	fmt.Print("$ go\n\n")
	cmds.Exec(os.Stdout, os.Stdout)
	cmds.Exec()

	fmt.Print("$ go mod\n\n")
	cmds.Exec(os.Stdout, os.Stdout, "mod")
	cmds.Exec("mod")

	fmt.Print("$ go mod tidy\n\n")
	cmds.Exec(os.Stdout, os.Stdout, "mod", "tidy")
	cmds.Exec("mod", "tidy")

	// Output:
	// $ go help

M example_test.go => example_test.go +1 -1
@@ 53,7 53,7 @@ func Example() {
	}

	// In a real main function, this would probably be os.Args[1:]
	cmds.Exec(os.Stdout, os.Stdout, "-config", "mygit.config", "commit", "-interactive", "-h")
	cmds.Exec("-config", "mygit.config", "commit", "-interactive", "-h")

	// Output:
	// Using config file: mygit.config