-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first take: sub command namespacing with usage update
- Loading branch information
Greg Osuri
committed
Nov 8, 2015
1 parent
8d79579
commit 2de2b70
Showing
7 changed files
with
328 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,40 @@ | ||
# nscommand | ||
# cmdns | ||
|
||
nscommand is a go library for [Cobra](https://github.com/spf13/cobra) for adding namespace to subcommands. A namedspaced command is alternative way to structure sub commands, similar to `rake db:migrate` and `heroku apps:create`. | ||
cmdns is a go library for [Cobra](https://github.com/spf13/cobra) for adding namespaces to subcommands. Command namespacing is an alternative way to structure sub commands, similar to `rake db:migrate` and `ovrclk apps:create`. | ||
|
||
Cobra a popular library for creating powerful modern CLI applications used by [OvrClk](http://ovrclk.com), [Docker](https://github.com/docker/distribution), [Parse (CLI)](https://parse.com/), [Kubernetes](http://kubernetes.io/), and many more widely used Go projects. | ||
Cobra a popular library for creating powerful modern CLI applications used by [Kubernetes](http://kubernetes.io/), [Docker](https://github.com/docker/distribution), [Parse](https://github.com/ParsePlatform/parse-cli), and many more widely used Go projects. | ||
|
||
## Example | ||
|
||
```go | ||
ovrclk := &cobra.Command{Use: "ovrclk"} | ||
apps := &cobra.Command{Use: "apps"} | ||
apps.AddCommand(&cobra.Command{Use: "info", Run: runFunc}) | ||
ovrclk.AddCommand(apps) | ||
|
||
// Enable namespacing | ||
cmdns.Enable(ovrclk) | ||
|
||
ovrclk.Execute() | ||
``` | ||
|
||
The above example will namespace `info` with `apps`. It lets you run: | ||
|
||
```sh | ||
$ ovrclk apps:info | ||
``` | ||
|
||
And, updates the usage function for `ovrclk:apps -h`: | ||
|
||
```sh | ||
Available Commands: | ||
apps:info | ||
|
||
Use "ovrclk [command] --help" for more information about a command. | ||
``` | ||
|
||
To disable overiding usage function: | ||
|
||
```go | ||
cmdns.SetOverrideUsageFunc(false) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package cmdns | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// DefaultCmdNS is the default CmdNS for the package | ||
var DefaultCmdNS = New() | ||
|
||
// SetOverrideUsageFunc when set to true will overide the command's usage function with the package's usage function that displays namespaces using the DefaultCmdNS | ||
func SetOverrideUsageFunc(v bool) *CmdNS { | ||
return DefaultCmdNS.SetOverrideUsageFunc(v) | ||
} | ||
|
||
// Enable enables namespacing for the command using the DefaultCmdNS | ||
func Enable(cmd *cobra.Command) error { | ||
return DefaultCmdNS.Enable(cmd) | ||
} | ||
|
||
// CmdNS is the struct represting the component that namespaces sucommands | ||
type CmdNS struct { | ||
// Namespaces | ||
Namespaces []*Namespace | ||
OverrideUsageFunc bool | ||
} | ||
|
||
// New returns a new instance of the CmdNS | ||
func New() *CmdNS { | ||
return &CmdNS{ | ||
Namespaces: make([]*Namespace, 0), | ||
OverrideUsageFunc: true, | ||
} | ||
} | ||
|
||
// SetOverrideUsageFunc when set to true will overide the command's usage | ||
// function with the package's usage function that displays namespaces | ||
func (c *CmdNS) SetOverrideUsageFunc(v bool) *CmdNS { | ||
c.OverrideUsageFunc = v | ||
return c | ||
} | ||
|
||
// Enable enables namespacing for the command's subcommands | ||
func (c *CmdNS) Enable(cmd *cobra.Command) error { | ||
if cmd == nil { | ||
return errors.New("cmdns: cmd cannot be nil") | ||
} | ||
for _, child := range cmd.Commands() { | ||
n := NewNamespace() | ||
n.OverrideUsageFunc = c.OverrideUsageFunc | ||
c.Namespaces = append(c.Namespaces, n) | ||
if err := n.Enable(child); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Namespace represents a namespace for a command | ||
type Namespace struct { | ||
OverrideUsageFunc bool | ||
|
||
cmd *cobra.Command | ||
commands []*cobra.Command | ||
} | ||
|
||
// NewNamespace returns a new Namespace | ||
func NewNamespace() *Namespace { | ||
return &Namespace{ | ||
OverrideUsageFunc: true, | ||
commands: make([]*cobra.Command, 0), | ||
} | ||
} | ||
|
||
// AvailableCommands returns the namespaced commands that are available | ||
func (n *Namespace) AvailableCommands() []*cobra.Command { | ||
return n.commands | ||
} | ||
|
||
// Command returns the command for the namespace | ||
func (n *Namespace) Command() *cobra.Command { | ||
return n.cmd | ||
} | ||
|
||
// Enable enables namespacing for a sub-commmand and its immediated children. | ||
// It returns an error if the command does not have a parent | ||
func (n *Namespace) Enable(cmd *cobra.Command) error { | ||
if !cmd.HasParent() { | ||
return errors.New("cmdns: command is required a parent") | ||
} | ||
|
||
// Do not bind if there are not available sub commands | ||
if !cmd.HasAvailableSubCommands() { | ||
return nil | ||
} | ||
|
||
if n.OverrideUsageFunc { | ||
cmd.SetUsageFunc(n.UsageFunc()) | ||
} | ||
|
||
for _, c := range cmd.Commands() { | ||
if !c.IsAvailableCommand() { | ||
continue | ||
} | ||
// copy the command add it to the root command with a prefix of its parent. | ||
nc := *c | ||
nc.Use = cmd.Name() + ":" + c.Use | ||
c.Parent().AddCommand(&nc) | ||
|
||
// hide the command so it does not show in available commands list | ||
nc.Hidden = true | ||
n.commands = append(n.commands, &nc) | ||
} | ||
n.cmd = cmd | ||
return nil | ||
} | ||
|
||
// UsageFunc returns the usage function for the command that renders namespaces | ||
func (n *Namespace) UsageFunc() (f func(*cobra.Command) error) { | ||
return func(*cobra.Command) error { | ||
err := tmpl(n.Command().Out(), usageTemplate, n) | ||
if err != nil { | ||
fmt.Print(err) | ||
} | ||
return err | ||
} | ||
} | ||
|
||
var usageTemplate = `{{$ns := .}}{{with .Command}}Usage:{{if .Runnable}} | ||
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if gt .Aliases 0}} | ||
Aliases: | ||
{{.NameAndAliases}} | ||
{{end}}{{if .HasExample}} | ||
Examples: | ||
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}} | ||
Available Commands:{{range $ns.AvailableCommands}} | ||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{ if .HasLocalFlags}} | ||
Flags: | ||
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}} | ||
Global Flags: | ||
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} | ||
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} | ||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }} | ||
Use "{{.Parent.CommandPath}} [command] --help" for more information about a command.{{end}}{{end}} | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package cmdns_test | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/gosuri/cmdns" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var runFunc = func(cmd *cobra.Command, args []string) { fmt.Println("run", cmd.Name()) } | ||
|
||
func Example() { | ||
ovrclk := &cobra.Command{Use: "ovrclk"} | ||
apps := &cobra.Command{Use: "apps"} | ||
apps.AddCommand(&cobra.Command{Use: "info", Run: runFunc}) | ||
ovrclk.AddCommand(apps) | ||
|
||
// Enable namespacing | ||
cmdns.Enable(ovrclk) | ||
|
||
ovrclk.Execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Package cmdns provides namespacing to Cobra command's subcommands | ||
// Command namespacing is an alternative way to structure sub commands, similar to `rake db:migrate` and `ovrclk apps:create`. | ||
package cmdns |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/gosuri/cmdns" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var helpFunc = func(cmd *cobra.Command, args []string) { cmd.Help() } | ||
var runFunc = func(cmd *cobra.Command, args []string) { fmt.Println("run", cmd.Name()) } | ||
|
||
func main() { | ||
hugo := &cobra.Command{ | ||
Use: "hugo", | ||
Short: "Hugo is a very fast static site generator", | ||
Long: "A Fast and Flexible Static Site Generator built with love by spf13 and friends in Go", | ||
} | ||
|
||
newCmd := &cobra.Command{ | ||
Use: "new", | ||
Short: "Create new content for your site", | ||
Long: "Create a new content file and automatically set the date and title. It will guess which kind of file to create based on the path provided.", | ||
Run: runFunc, | ||
} | ||
hugo.AddCommand(newCmd) | ||
|
||
newSiteCmd := &cobra.Command{ | ||
Use: "site", | ||
Short: "Create a new site in the provided directory", | ||
Long: "The new site will have the correct structure, but no content or theme yet", | ||
Run: runFunc, | ||
} | ||
newCmd.AddCommand(newSiteCmd) | ||
|
||
newThemeCmd := &cobra.Command{ | ||
Use: "theme", | ||
Short: "Create a new theme", | ||
Long: "Create a new theme (skeleton) called [name] in the current directory. New theme is a skeleton.", | ||
Run: runFunc, | ||
} | ||
newCmd.AddCommand(newThemeCmd) | ||
|
||
// listCmd := &cobra.Command{Use: "list", Run: runFunc} | ||
// listCmd.AddCommand(&cobra.Command{Use: "list", Run: runFunc}) | ||
// listCmd.AddCommand(&cobra.Command{Use: "add", Run: runFunc}) | ||
//hugo.AddCommand(listCmd) | ||
|
||
cmdns.Enable(hugo) | ||
if err := hugo.Execute(); err != nil { | ||
panic(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/gosuri/cmdns" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var helpFunc = func(cmd *cobra.Command, args []string) { cmd.Help() } | ||
var runFunc = func(cmd *cobra.Command, args []string) { fmt.Println("run", cmd.Name()) } | ||
|
||
func main() { | ||
ovrclk := &cobra.Command{Use: "ovrclk"} | ||
apps := &cobra.Command{Use: "apps"} | ||
apps.AddCommand(&cobra.Command{Use: "info", Run: runFunc}) | ||
ovrclk.AddCommand(apps) | ||
|
||
// Enable namespacing | ||
cmdns.Enable(ovrclk) | ||
|
||
ovrclk.Execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package cmdns | ||
|
||
import ( | ||
"fmt" | ||
"github.com/spf13/cobra" | ||
"io" | ||
"strings" | ||
"text/template" | ||
"unicode" | ||
) | ||
|
||
var templateFuncs template.FuncMap = template.FuncMap{ | ||
"trim": strings.TrimSpace, | ||
"trimRightSpace": trimRightSpace, | ||
"rpad": rpad, | ||
"gt": cobra.Gt, | ||
"eq": cobra.Eq, | ||
} | ||
|
||
func trimRightSpace(s string) string { | ||
return strings.TrimRightFunc(s, unicode.IsSpace) | ||
} | ||
|
||
//rpad adds padding to the right of a string | ||
func rpad(s string, padding int) string { | ||
template := fmt.Sprintf("%%-%ds", padding) | ||
return fmt.Sprintf(template, s) | ||
} | ||
|
||
// tmpl executes the given template text on data, writing the result to w. | ||
func tmpl(w io.Writer, text string, data interface{}) error { | ||
t := template.New("top") | ||
t.Funcs(templateFuncs) | ||
template.Must(t.Parse(text)) | ||
return t.Execute(w, data) | ||
} |