diff --git a/Goopfile b/Goopfile index 5e44e6b..2b5efd0 100644 --- a/Goopfile +++ b/Goopfile @@ -1,3 +1,4 @@ github.com/onsi/ginkgo github.com/onsi/gomega github.com/mattn/goveralls +code.google.com/p/go.tools/cmd/cover diff --git a/Goopfile.lock b/Goopfile.lock index 1e3af96..cf144b7 100644 --- a/Goopfile.lock +++ b/Goopfile.lock @@ -1,4 +1,8 @@ code.google.com/p/go-uuid #7dda39b2e7d5e265014674c5af696ba4186679e9 +code.google.com/p/go.net #9cbdc6102a92af1201a1dbe68587693f2684e8a1 +code.google.com/p/go.tools/cmd/cover #a9cbf58422bba01fd4a6c6db5c95530003e31c3a +code.google.com/p/goauth2 #afe77d958c701557ec5dc56f6936fcc194d15520 +code.google.com/p/google-api-go-client #11626ef0c2fd66fd8c9a128f6ae38b77cd101f30 github.com/mattn/goveralls #89ffc28b5e4c741a73c4ce4dd63dcf5695ca627d github.com/onsi/ginkgo #6bd31378f9b5c099a60ab96b89f8fa9b48678d44 github.com/onsi/gomega #a0ee4df1f2d58a75dd9d74e755615706d68e9ce9 diff --git a/Makefile b/Makefile index 8549d08..a696666 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -test: install-deps +test: @clear goop exec sh -c "cd cli && go test" diff --git a/README.md b/README.md index 632aaa7..fd050d1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,20 @@ A go-lang library to help build self documenting command line applications. get the package with: ``` -go get github.com/jwaldrip/odin/cli +go get gopkg.in/jwaldrip/odin.v1/cli +``` + +import it with: + +```go +import "gopkg.in/jwaldrip/odin.v1/cli" +``` + +or using a a package manager like [Goop](https://github.com/nitrous-io/goop): + +``` +# Goopfile +github.com/jwaldrip/odin #v1.4.0 ``` ## Usage @@ -87,37 +100,37 @@ package main import ( "fmt" - "github.com/jwaldrip/odin/cli" + "gopkg.in/jwaldrip/odin.v1/cli" ) // CLI is the odin CLI -var CLI = cli.New("0.0.1", "my cli", func(c cli.Command){ +var app = cli.New("0.0.1", "my cli", func(c cli.Command){ if c.Flag("gopher").Get() == true { - fmt.Println("IT IS JUST GOPHERTASTIC!!!") + fmt.Println(IS JUST GOPHERTASTIC!!!") } else { - fmt.Println("It is just fine") + fmt.Println("Is pretty dandy") } }) func init(){ - CLI.DefineBoolFlag("gopher", false, "is it gophertastic?") - CLI.FlagAlias('g', "gopher") + app.DefineBoolFlag("gopher", false, "is it gophertastic?") + app.FlagAlias('g', "gopher") } func main(){ - CLI.Start() + app.Start() } ``` ``` $ mycli -It is just fine +Is pretty dandy $ mycli --gopher -IT IS JUST GOPHERTASTIC!!! +IS JUST GOPHERTASTIC!!! $ mycli -g -IT IS JUST GOPHERTASTIC!!! +IS JUST GOPHERTASTIC!!! ``` ### Required Parameters @@ -135,7 +148,7 @@ cli.New(version string, description string, fn func(Command), params ...string) *or at a later time...* ```go -cli.DefineParams(params ...string) +app.DefineParams(params ...string) ``` #### Accessing @@ -196,6 +209,43 @@ cmd.Parent().Param("name") cmd.Parent().Flag("name") ``` +#### Flag Inheritence + +In addition to accesing the parent params via the `Parent()` you can instruct a sub command to inherit a flag from its parent. + +**example:** + +```go +package main + +import ( + "fmt" + "github.com/jwaldrip/odin" +) + +var app = cli.New("0.0.1", "sample command", func(Command){}) + +func init(){ + app.DefineStringFlag("user", "", "the user") + subcmd := app.DefineSubCommand("action", "perform an action", func(c cli.Command){ + fmt.Println("the user is:", c.Flag("user")) + fmt.Println("the action is:", c.Param("actionName")) + }) + subcmd.DefineParams("actionName") + subcmd.InheritFlag("user") +} + +func main(){ + app.Start() +} +``` + +``` +$ mycmd --user=jason action hello +the user is: jason +the action is: hello +``` + ### Self Documentation #### Usage @@ -241,6 +291,7 @@ greet-with 1.0.0 * Bash Completion * Zsh Completion * CLI Bootstrapping +* Param Inheritence ## Contributing diff --git a/cli/CLI.go b/cli/CLI.go index 2239dc4..8f6d14e 100644 --- a/cli/CLI.go +++ b/cli/CLI.go @@ -14,23 +14,25 @@ import "github.com/jwaldrip/odin/cli/values" type CLI struct { ErrorHandling ErrorHandling - aliases map[rune]*Flag - description string - errOutput io.Writer - flags flagMap - flagsTerminated bool - flagValues map[*Flag]values.Value - fn func(Command) - name string - params paramsList - paramValues map[*Param]values.Value - paramsParsed bool - parent Command - stdOutput io.Writer - subCommands map[string]*SubCommand - unparsedArgs values.List - usage func() - version string + aliases map[rune]*Flag + description string + errOutput io.Writer + flags flagMap + flagsTerminated bool + flagValues map[*Flag]values.Value + fn func(Command) + inheritedFlags flagMap + name string + params paramsList + paramValues map[*Param]values.Value + paramsParsed bool + parent Command + propogatingFlags flagMap + stdOutput io.Writer + subCommands map[string]*SubCommand + unparsedArgs values.List + usage func() + version string } func (cmd *CLI) init(name, desc string, fn func(Command), paramNames ...string) { diff --git a/cli/FlagMap.go b/cli/FlagMap.go index 0996f95..13b37a8 100644 --- a/cli/FlagMap.go +++ b/cli/FlagMap.go @@ -5,6 +5,29 @@ import "sort" // flagMap is a map of flags with the name as a string key type flagMap map[string]*Flag +func (fm flagMap) Merge(fm2 flagMap) flagMap { + mergedMap := make(flagMap) + if fm != nil { + for k, v := range fm { + mergedMap[k] = v + } + } + if fm2 != nil { + for k, v := range fm2 { + mergedMap[k] = v + } + } + return mergedMap +} + +func (fm flagMap) Names() []string { + var keys []string + for k := range fm { + keys = append(keys, k) + } + return keys +} + // Sort returns a sorted list of flags func (fm flagMap) Sort() []*Flag { list := make(sort.StringSlice, len(fm)) @@ -20,3 +43,19 @@ func (fm flagMap) Sort() []*Flag { } return result } + +func (fm flagMap) Without(fm2 flagMap) flagMap { + diffedMap := make(flagMap) + if fm == nil { + return diffedMap + } + if fm2 == nil { + return fm + } + for k, v := range fm { + if _, exist := fm2[k]; !exist { + diffedMap[k] = v + } + } + return diffedMap +} diff --git a/cli/ParamsList.go b/cli/ParamsList.go index 59b6d57..c97618a 100644 --- a/cli/ParamsList.go +++ b/cli/ParamsList.go @@ -26,8 +26,8 @@ func (l paramsList) Compare(Y paramsList) paramsList { // Names returns the list of parameters names as a slice of strings func (l paramsList) Names() []string { var names []string - for i := 0; i < len(l); i++ { - names = append(names, l[i].Name) + for _, item := range l { + names = append(names, item.Name) } return names } diff --git a/cli/flag_access.go b/cli/flag_access.go index 898b90a..2137307 100644 --- a/cli/flag_access.go +++ b/cli/flag_access.go @@ -9,10 +9,7 @@ import ( // Flag returns the Value interface to the value of the named flag, // returning nil if none exists. func (cmd *CLI) Flag(name string) values.Value { - flag, ok := cmd.flags[name] - if !ok { - panic(fmt.Sprintf("flag not defined %v", name)) - } + flag := cmd.getFlag(name) value := cmd.flagValues[flag] return value } @@ -25,3 +22,13 @@ func (cmd *CLI) Flags() values.Map { } return flags } + +func (cmd *CLI) getFlag(name string) *Flag { + var ok bool + var flag *Flag + flag, ok = cmd.inheritedFlags.Merge(cmd.flags)[name] + if !ok { + panic(fmt.Sprintf("flag not defined %v", name)) + } + return flag +} diff --git a/cli/flag_inheritance.go b/cli/flag_inheritance.go new file mode 100644 index 0000000..3a8e31d --- /dev/null +++ b/cli/flag_inheritance.go @@ -0,0 +1,53 @@ +package cli + +// InheritFlags allow flag values inherit from the commands parent +func (cmd *CLI) InheritFlags(names ...string) { + for _, name := range names { + cmd.InheritFlag(name) + } +} + +// InheritFlag allows a flags value to inherit from the commands parent +func (cmd *CLI) InheritFlag(name string) { + if cmd.parent == nil { + panic("command does not have a parent") + } + flag := cmd.parent.(*CLI).getFlag(name) + if cmd.inheritedFlags == nil { + cmd.inheritedFlags = make(flagMap) + } + cmd.inheritedFlags[name] = flag +} + +func (cmd *CLI) setFlagValuesFromParent() { + for name, flag := range cmd.inheritedFlags { + if _, exist := cmd.flags[name]; !exist { + cmd.flagValues[flag] = cmd.parent.Flag(name) + } + } +} + +// SubCommandsInheritFlags tells all subcommands to inherit flags +func (cmd *CLI) SubCommandsInheritFlags(names ...string) { + for _, name := range names { + cmd.SubCommandsInheritFlag(name) + } +} + +// SubCommandsInheritFlag tells all subcommands to inherit a flag +func (cmd *CLI) SubCommandsInheritFlag(name string) { + flag := cmd.getFlag(name) + if cmd.propogatingFlags == nil { + cmd.propogatingFlags = make(flagMap) + } + cmd.propogatingFlags[name] = flag +} + +func (cmd *CLI) copyPropogatingFlags() { + if cmd.parent == nil { + return + } + parentPropogatingFlags := cmd.parent.(*CLI).propogatingFlags + cmd.propogatingFlags = parentPropogatingFlags.Without(cmd.flags).Merge(cmd.propogatingFlags) + cmd.InheritFlags(parentPropogatingFlags.Names()...) +} diff --git a/cli/flag_inheritance_test.go b/cli/flag_inheritance_test.go new file mode 100644 index 0000000..2f4c442 --- /dev/null +++ b/cli/flag_inheritance_test.go @@ -0,0 +1,123 @@ +package cli_test + +import ( + . "github.com/jwaldrip/odin/cli" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("CLI Start", func() { + + var cli *CLI + var sub *SubCommand + var cmd Command + var subcmd Command + var didRun bool + var didRunSub bool + + BeforeEach(func() { + didRun = false + runFn := func(c Command) { + cmd = c + didRun = true + } + cli = New("v1.0.0", "sample description", runFn) + cli.ErrorHandling = PanicOnError + cli.Mute() + didRunSub = false + cli.DefineBoolFlag("foo", false, "a foo flag") + cli.DefineStringFlag("bar", "", "a foo flag") + sub = cli.DefineSubCommand("razzle", "razzle dazzle me", func(c Command) { + subcmd = c + didRunSub = true + }) + }) + + Describe("InheritFlag", func() { + It("should properly inherit a flag value from its parent", func() { + sub.InheritFlag("foo") + cli.Start("cmd", "--foo", "razzle") + Expect(subcmd.Flag("foo").Get()).To(Equal(true)) + }) + + Context("when there is not parent", func() { + It("should raise an error", func() { + Expect(cli.Parent()).To(BeNil()) + Expect(func() { cli.InheritFlag("") }).Should(Panic()) + }) + }) + }) + + Describe("InheritFlags", func() { + It("should properly inherit flag values from its parent", func() { + sub.InheritFlags("foo", "bar") + cli.Start("cmd", "--foo", "--bar=dive", "razzle") + Expect(subcmd.Flag("foo").Get()).To(Equal(true)) + Expect(subcmd.Flag("bar").Get()).To(Equal("dive")) + }) + }) + + Describe("SubCommandsInheritFlag", func() { + It("should propogate its flag to the sub commands", func() { + cli.SubCommandsInheritFlag("foo") + cli.Start("cmd", "--foo", "razzle") + Expect(subcmd.Flag("foo").Get()).To(Equal(true)) + }) + + It("should propogate deeply", func() { + var subsubcmd Command + sub.DefineSubCommand("baz", "a deeper command", func(c Command) { subsubcmd = c }) + cli.SubCommandsInheritFlag("foo") + cli.Start("cmd", "--foo", "razzle", "baz") + Expect(subsubcmd.Flag("foo").Get()).To(Equal(true)) + }) + + It("should not propogate to parents", func() { + var subsubcmd Command + cli.SubCommandsInheritFlag("foo") + sub.DefineStringFlag("raz", "", "a foo flag") + sub.SubCommandsInheritFlag("raz") + sub.DefineSubCommand("baz", "a deeper command", func(c Command) { subsubcmd = c }) + cli.Start("cmd", "razzle", "--raz=taz", "baz") + Expect(func() { cmd.Flag("raz") }).To(Panic()) + Expect(subsubcmd.Flag("raz").Get()).To(Equal("taz")) + }) + + It("should allow overridding", func() { + var subsubcmd Command + cli.SubCommandsInheritFlag("foo") + sub.DefineStringFlag("foo", "", "a foo flag") + sub.DefineSubCommand("baz", "a deeper command", func(c Command) { subsubcmd = c }) + cli.Start("cmd", "--foo", "razzle", "--foo=bizare") + Expect(subcmd.Flag("foo").Get()).To(Equal("bizare")) + }) + + It("overriding should stop propogation", func() { + var subsubcmd Command + cli.SubCommandsInheritFlag("foo") + sub.DefineStringFlag("foo", "", "a foo flag") + sub.DefineSubCommand("baz", "a deeper command", func(c Command) { subsubcmd = c }) + cli.Start("cmd", "--foo", "razzle", "--foo=bizare", "baz") + Expect(func() { subsubcmd.Flag("foo") }).To(Panic()) + }) + }) + + Describe("SubCommandsInheritFlags", func() { + It("should propogate its flags to the sub commands", func() { + cli.SubCommandsInheritFlags("foo", "bar") + cli.Start("cmd", "--foo", "--bar=dive", "razzle") + Expect(subcmd.Flag("foo").Get()).To(Equal(true)) + Expect(subcmd.Flag("bar").Get()).To(Equal("dive")) + }) + + It("should propogate deeply", func() { + var subsubcmd Command + sub.DefineSubCommand("baz", "a deeper command", func(c Command) { subsubcmd = c }) + cli.SubCommandsInheritFlags("foo", "bar") + cli.Start("cmd", "--foo", "--bar=dive", "razzle", "baz") + Expect(subsubcmd.Flag("foo").Get()).To(Equal(true)) + Expect(subsubcmd.Flag("bar").Get()).To(Equal("dive")) + }) + }) +}) diff --git a/cli/flag_parsing.go b/cli/flag_parsing.go index 4b7b169..83d019e 100644 --- a/cli/flag_parsing.go +++ b/cli/flag_parsing.go @@ -86,6 +86,12 @@ func (cmd *CLI) parseFlags(args []string) []string { // Set all the flags to defaults before setting cmd.setFlagDefaults() + // copy propogating flags + cmd.copyPropogatingFlags() + + // Set inherited values + cmd.setFlagValuesFromParent() + // Set each flag by its set value for { // Break if no arguments remain @@ -116,9 +122,8 @@ func (cmd *CLI) parseFlags(args []string) []string { // setAliasValues sets the values of flags from thier aliases func (cmd *CLI) setAliasValues(flags []*Flag, args []string) []string { - for i := 0; i < len(flags); i++ { + for i, flag := range flags { isLastFlag := i == len(flags)-1 - flag := flags[i] if isLastFlag { args = cmd.setFlagValue(flag, args) } else { diff --git a/cli/freeform_args_parsing.go b/cli/freeform_args_parsing.go index c112e3d..9ab106c 100644 --- a/cli/freeform_args_parsing.go +++ b/cli/freeform_args_parsing.go @@ -3,8 +3,8 @@ package cli import "github.com/jwaldrip/odin/cli/values" func (cmd *CLI) assignUnparsedArgs(args []string) { - for i := 0; i < len(args); i++ { + for _, arg := range args { str := "" - cmd.unparsedArgs = append(cmd.unparsedArgs, values.NewString(args[i], &str)) + cmd.unparsedArgs = append(cmd.unparsedArgs, values.NewString(arg, &str)) } } diff --git a/cli/param_definitions.go b/cli/param_definitions.go index b7b6f3d..f073576 100644 --- a/cli/param_definitions.go +++ b/cli/param_definitions.go @@ -3,8 +3,7 @@ package cli // DefineParams sets params names from strings func (cmd *CLI) DefineParams(names ...string) { var params []*Param - for i := 0; i < len(names); i++ { - name := names[i] + for _, name := range names { param := &Param{Name: name} params = append(params, param) } diff --git a/main.go b/main.go index 04c7151..584e989 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import odin "github.com/jwaldrip/odin/cli" // VERSION is the odin version -var VERSION = "1.3.1" +var VERSION = "1.4.0" var cli = odin.New(VERSION, "a command line DSL for go-lang", func(cmd odin.Command) { cmd.Usage() })