diff --git a/cli.go b/cli.go index 6c86876..e97e18e 100644 --- a/cli.go +++ b/cli.go @@ -1,7 +1,6 @@ package cli import "os" -import "flag" // The name of the program. Defaults to os.Args[0] var Name = os.Args[0] @@ -20,13 +19,18 @@ var Flags []Flag // The action to execute when no subcommands are specified var Action = ShowHelp -func Run(args []string) { - context := Context{} - if len(args) > 1 { - name := args[1] +func Run(arguments []string) { + + set := flagSet(Flags) + set.Parse(arguments[1:]) + + context := NewContext(set, set) + args := context.Args() + if len(args) > 0 { + name := args[0] for _, c := range append(Commands, HelpCommand) { - if c.Name == name || c.ShortName == name { - c.Action(context) + if c.HasName(name) { + c.Run(context) return } } @@ -36,16 +40,4 @@ func Run(args []string) { Action(context) } -type Command struct { - Name string - ShortName string - Usage string - Description string - Action Handler - Flags flag.FlagSet -} - -type Context struct { -} - -type Handler func(context Context) +type Handler func(context *Context) diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..1c083bb --- /dev/null +++ b/cli_test.go @@ -0,0 +1,69 @@ +package cli + +import ( + "reflect" + "testing" +) + +func Test_SettingFlags(t *testing.T) { + Flags = []Flag{ + StringFlag{"foo", "default", "a string flag"}, + IntFlag{"bar", 42, "an int flag"}, + BoolFlag{"bat", "a bool flag"}, + } + Action = func(c *Context) { + expect(t, c.String("foo"), "hello world") + expect(t, c.Int("bar"), 245) + expect(t, c.Bool("bat"), true) + } + Run([]string{"command", "--foo", "hello world", "--bar", "245", "--bat"}) +} + +func Test_FlagDefaults(t *testing.T) { + Flags = []Flag{ + StringFlag{"foo", "default", "a string flag"}, + IntFlag{"bar", 42, "an int flag"}, + BoolFlag{"bat", "a bool flag"}, + } + Action = func(c *Context) { + expect(t, c.String("foo"), "default") + expect(t, c.Int("bar"), 42) + expect(t, c.Bool("bat"), false) + } + Run([]string{"command"}) +} + +func TestCommands(t *testing.T) { + Flags = []Flag{ + StringFlag{"name", "jeremy", "a name to print"}, + } + Commands = []Command{ + { + Name: "print", + Flags: []Flag{ + IntFlag{"age", 50, "the age of the person"}, + }, + Action: func(c *Context) { + expect(t, c.GlobalString("name"), "jordie") + expect(t, c.Int("age"), 21) + }, + }, + } + Action = func(c *Context) { + t.Error("default action should not be called") + } + Run([]string{"command", "--name", "jordie", "print", "--age", "21"}) +} + +/* Test Helpers */ +func expect(t *testing.T, a interface{}, b interface{}) { + if a != b { + t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if a == b { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} diff --git a/command.go b/command.go new file mode 100644 index 0000000..0a9a7ec --- /dev/null +++ b/command.go @@ -0,0 +1,20 @@ +package cli + +type Command struct { + Name string + ShortName string + Usage string + Description string + Action Handler + Flags []Flag +} + +func (command Command) Run(c *Context) { + set := flagSet(command.Flags) + set.Parse(c.Args()[1:]) + command.Action(NewContext(set, c.globalSet)) +} + +func (command Command) HasName(name string) bool { + return command.Name == name || command.ShortName == name +} diff --git a/command_test.go b/command_test.go new file mode 100644 index 0000000..7f1e458 --- /dev/null +++ b/command_test.go @@ -0,0 +1 @@ +package cli diff --git a/context.go b/context.go new file mode 100644 index 0000000..ae50463 --- /dev/null +++ b/context.go @@ -0,0 +1,88 @@ +package cli + +import ( + "flag" + "strconv" +) + +// Context is a type that is passed through to +// each Handler action in a cli application. Context +// can be used to retrieve context-specific Args and +// parsed command-line options. +type Context struct { + flagSet *flag.FlagSet + globalSet *flag.FlagSet +} + +func NewContext(set *flag.FlagSet, globalSet *flag.FlagSet) *Context { + return &Context{set, globalSet} +} + +// Looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Int(name string) int { + return c.lookupInt(name, c.flagSet) +} + +// Looks up the value of a local bool flag, returns false if no bool flag exists +func (c *Context) Bool(name string) bool { + return c.lookupBool(name, c.flagSet) +} + +// Looks up the value of a local string flag, returns "" if no string flag exists +func (c *Context) String(name string) string { + return c.lookupString(name, c.flagSet) +} + +// Looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalInt(name string) int { + return c.lookupInt(name, c.globalSet) +} + +// Looks up the value of a global bool flag, returns false if no bool flag exists +func (c *Context) GlobalBool(name string) bool { + return c.lookupBool(name, c.globalSet) +} + +// Looks up the value of a global string flag, returns "" if no string flag exists +func (c *Context) GlobalString(name string) string { + return c.lookupString(name, c.globalSet) +} + +func (c *Context) Args() []string { + return c.flagSet.Args() +} + +func (c *Context) lookupInt(name string, set *flag.FlagSet) int { + f := set.Lookup(name) + if f != nil { + val, err := strconv.Atoi(f.Value.String()) + if err != nil { + return 0 + } + return val + } else { + return 0 + } +} + +func (c *Context) lookupString(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + return f.Value.String() + } else { + return "" + } +} + +func (c *Context) lookupBool(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return val + } else { + return false + } +} diff --git a/context_test.go b/context_test.go new file mode 100644 index 0000000..d859179 --- /dev/null +++ b/context_test.go @@ -0,0 +1,46 @@ +package cli + +import ( + "flag" + "testing" +) + +func Test_New(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Int("myflag", 42, "doc") + c := NewContext(set, globalSet) + expect(t, c.Int("myflag"), 12) + expect(t, c.GlobalInt("myflag"), 42) +} + +func Test_Int(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + c := NewContext(set, set) + expect(t, c.Int("myflag"), 12) +} + +func Test_String(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.String("myflag", "hello world", "doc") + c := NewContext(set, set) + expect(t, c.String("myflag"), "hello world") +} + +func Test_Bool(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(set, set) + expect(t, c.Bool("myflag"), false) +} + +func Test_Args(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(set, set) + set.Parse([]string{"--myflag", "bat", "baz"}) + expect(t, len(c.Args()), 2) + expect(t, c.Bool("myflag"), true) +} diff --git a/flag.go b/flag.go index c619453..0e28ea0 100644 --- a/flag.go +++ b/flag.go @@ -1,27 +1,58 @@ package cli import "fmt" +import "flag" type Flag interface { - fmt.Stringer + fmt.Stringer + Apply(*flag.FlagSet) +} + +func flagSet(flags []Flag) *flag.FlagSet { + set := flag.NewFlagSet(Name, flag.ExitOnError) + for _, f := range flags { + f.Apply(set) + } + return set } type BoolFlag struct { - Name string - Value bool - Usage string -} - -type StringFlag struct { - Name string - Value string - Usage string -} - -func (f StringFlag) String() string { - return fmt.Sprintf("--%v 'string'\t%v", f.Name, f.Usage) + Name string + Usage string } func (f BoolFlag) String() string { - return fmt.Sprintf("--%v\t%v", f.Name, f.Usage) + return fmt.Sprintf("--%v\t%v", f.Name, f.Usage) +} + +func (f BoolFlag) Apply(set *flag.FlagSet) { + set.Bool(f.Name, false, f.Usage) +} + +type StringFlag struct { + Name string + Value string + Usage string +} + +func (f StringFlag) String() string { + return fmt.Sprintf("--%v '%v'\t%v", f.Name, f.Value, f.Usage) +} + +func (f StringFlag) Apply(set *flag.FlagSet) { + set.String(f.Name, f.Value, f.Usage) +} + +type IntFlag struct { + Name string + Value int + Usage string +} + +func (f IntFlag) String() string { + return fmt.Sprintf("--%v '%v'\t%v", f.Name, f.Value, f.Usage) +} + +func (f IntFlag) Apply(set *flag.FlagSet) { + set.Int(f.Name, f.Value, f.Usage) } diff --git a/help.go b/help.go index 72a4303..b5cb95e 100644 --- a/help.go +++ b/help.go @@ -22,7 +22,7 @@ func init() { HelpCommand.Action = ShowHelp } -func ShowHelp(c Context) { +func ShowHelp(c *Context) { helpTemplate := `NAME: {{.Name}} - {{.Usage}}