Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

organising commands by types #238

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
20 changes: 20 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"unsafe"
)

const DefaultCommandType = "command"

// Command represents an application command. Commands can be added to the
// parser (which itself is a command) and are selected/executed when its name
// is specified on the command line. The Command type embeds a Group and
Expand All @@ -19,6 +21,9 @@ type Command struct {
// The name by which the command can be invoked
Name string

// Commands can be classified by types
Type string

// The active sub command (set by parsing) or nil
Active *Command

Expand Down Expand Up @@ -150,6 +155,7 @@ func newCommand(name string, shortDescription string, longDescription string, da
return &Command{
Group: newGroup(shortDescription, longDescription, data),
Name: name,
Type: DefaultCommandType,
}
}

Expand Down Expand Up @@ -395,6 +401,20 @@ func (c commandList) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}

// selectedVisibleCommands returns a list of commands that are of type 't' and visible.
// if t is empty, it returns all visible commands
func (c *Command) selectedVisibleCommands(t string) []*Command {
cmdList := commandList(c.sortedVisibleCommands())
var ret commandList
for _, cmd := range cmdList {
if t != "" && cmd.Type != t {
continue
}
ret = append(ret, cmd)
}
return []*Command(ret)
}

func (c *Command) sortedVisibleCommands() []*Command {
ret := commandList(c.visibleCommands())
sort.Sort(ret)
Expand Down
2 changes: 1 addition & 1 deletion completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ func (c *completion) completeValue(value reflect.Value, prefix string, match str
i := value.Interface()

var ret []Completion

if cmp, ok := i.(Completer); ok {

ret = cmp.Complete(match)
} else if value.CanAddr() {
if cmp, ok = value.Addr().Interface().(Completer); ok {
Expand Down
10 changes: 10 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ func Example() {
// Example of a filename (useful for completion)
Filename Filename `long:"filename" description:"A filename"`

//// Example of an interface
//Interface interface{}

// Example of positional arguments
Args struct {
ID string
Expand All @@ -56,6 +59,11 @@ func Example() {
cmd.Process.Release()
}

//// Set the value of interface
//opts.Interface = struct {
// Sub string `long:"sub-field" description:"An embedded interface field"`
//}{}

// Make some fake arguments to parse.
args := []string{
"-vv",
Expand All @@ -69,6 +77,7 @@ func Example() {
"--intmap", "a:1",
"--intmap", "b:5",
"--filename", "hello.go",
//"--sub-field", "hello",
"id",
"10",
"remaining1",
Expand All @@ -92,6 +101,7 @@ func Example() {
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
fmt.Printf("Filename: %v\n", opts.Filename)
//fmt.Printf("Interface: %+v\n", opts.Interface)
fmt.Printf("Args.ID: %s\n", opts.Args.ID)
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
Expand Down
15 changes: 15 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,21 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h
if err := g.scanStruct(fld, &field, handler); err != nil {
return err
}
} else if kind == reflect.Interface {
if fld.IsNil() {
continue
}
v := reflect.ValueOf(fld.Interface())
switch v.Kind() {
case reflect.Struct:
if err := g.scanStruct(v, &field, handler); err != nil {
return err
}
case reflect.Ptr:
if err := g.scanStruct(reflect.Indirect(v), &field, handler); err != nil {
return err
}
}
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
flagCountBefore := len(g.options) + len(g.groups)

Expand Down
38 changes: 23 additions & 15 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"io"
"runtime"
"sort"
"strings"
"unicode/utf8"
)
Expand Down Expand Up @@ -274,7 +275,7 @@ func maxCommandLength(s []*Command) int {
// option provides a convenient way to add a -h/--help option group to the
// command line parser which will automatically show the help messages using
// this method.
func (p *Parser) WriteHelp(writer io.Writer) {
func (p *Parser) WriteHelp(writer io.Writer, plural func(s string) string) {
if writer == nil {
return
}
Expand Down Expand Up @@ -461,29 +462,36 @@ func (p *Parser) WriteHelp(writer io.Writer) {

c = c.Active
}
allTypes := make([]string, 0, len(p.AllTypes))
for t, _ := range p.AllTypes {
allTypes = append(allTypes, t)
}
sort.Strings(allTypes)
for _, t := range allTypes {

scommands := cmd.sortedVisibleCommands()
scommands := cmd.selectedVisibleCommands(t)

if len(scommands) > 0 {
maxnamelen := maxCommandLength(scommands)
if len(scommands) > 0 {
maxnamelen := maxCommandLength(scommands)

fmt.Fprintln(wr)
fmt.Fprintln(wr, "Available commands:")
fmt.Fprintln(wr)
fmt.Fprintf(wr, "Available %s:\n", plural(t))

for _, c := range scommands {
fmt.Fprintf(wr, " %s", c.Name)

for _, c := range scommands {
fmt.Fprintf(wr, " %s", c.Name)
if len(c.ShortDescription) > 0 {
pad := strings.Repeat(" ", maxnamelen-len(c.Name))
fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription)

if len(c.ShortDescription) > 0 {
pad := strings.Repeat(" ", maxnamelen-len(c.Name))
fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription)
if len(c.Aliases) > 0 {
fmt.Fprintf(wr, " (aliases: %s)", strings.Join(c.Aliases, ", "))
}

if len(c.Aliases) > 0 {
fmt.Fprintf(wr, " (aliases: %s)", strings.Join(c.Aliases, ", "))
}

fmt.Fprintln(wr)
}

fmt.Fprintln(wr)
}
}

Expand Down
27 changes: 26 additions & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ type Parser struct {
// Embedded, see Command for more information
*Command

// Histogram of valid Command types
AllTypes map[string]int

// A usage string to be displayed in the help message.
Usage string

Expand Down Expand Up @@ -52,6 +55,10 @@ type Parser struct {
// command to be executed when parsing has finished.
CommandHandler func(command Commander, args []string) error

// PluralHandler is a function gets called to handle the formation of
// plurals in the help screen. If not specified, it just appends an 's'.
PluralHandler func(s string) string

internalError error
}

Expand Down Expand Up @@ -172,20 +179,31 @@ func NewNamedParser(appname string, options Options) *Parser {
p := &Parser{
Command: newCommand(appname, "", "", nil),
Options: options,
AllTypes: make(map[string]int),
NamespaceDelimiter: ".",
}
p.AddType(DefaultCommandType)

p.Command.parent = p

return p
}

func (p *Parser) AddType(s string) {
p.AllTypes[s]++
}

// Parse parses the command line arguments from os.Args using Parser.ParseArgs.
// For more detailed information see ParseArgs.
func (p *Parser) Parse() ([]string, error) {
return p.ParseArgs(os.Args[1:])
}

func (p *Parser) Complete(match []string) []Completion {
comp := &completion{parser: p}
return comp.complete(match)
}

// ParseArgs parses the command line arguments according to the option groups that
// were added to the parser. On successful parsing of the arguments, the
// remaining, non-option, arguments (if any) are returned. The returned error
Expand Down Expand Up @@ -671,7 +689,14 @@ func (p *Parser) parseNonOption(s *parseState) error {
func (p *Parser) showBuiltinHelp() error {
var b bytes.Buffer

p.WriteHelp(&b)
plural := func(s string) string {
return fmt.Sprintf("%ss", s)
}
if p.PluralHandler != nil {
plural = p.PluralHandler
}

p.WriteHelp(&b, plural)
return newError(ErrHelp, b.String())
}

Expand Down