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

Expose command spec #178

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 46 additions & 8 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func (p path) Child(f reflect.StructField) path {
}
}

// spec represents a command line option
type spec struct {
// Spec represents a command line option
type Spec struct {
dest path
field reflect.StructField // the struct field from which this option was created
long string // the --long form for this option, or empty if none
Expand All @@ -57,12 +57,44 @@ type spec struct {
placeholder string // name of the data in help
}

func (s Spec) LongName() string {
return s.long
}

func (s Spec) ShortName() string {
return s.short
}

func (s Spec) IsRequired() bool {
return s.required
}

func (s Spec) IsPositional() bool {
return s.positional
}

func (s Spec) IsSeparate() bool {
return s.separate
}

func (s Spec) Help() string {
return s.help
}

func (s Spec) DefaultVal() string {
return s.defaultVal
}

func (s Spec) Placeholder() string {
return s.placeholder
}

// command represents a named subcommand, or the top-level command
type command struct {
name string
help string
dest path
specs []*spec
specs []*Spec
subcommands []*command
parent *command
}
Expand Down Expand Up @@ -237,6 +269,12 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
return &p, nil
}

func (p *Parser) GetArgumentSpecs() []*Spec {
argSpecs := make([]*Spec, 0, len(p.cmd.specs))
argSpecs = append(argSpecs, p.cmd.specs...)
return argSpecs
}

func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
// commands can only be created from pointers to structs
if t.Kind() != reflect.Ptr {
Expand Down Expand Up @@ -277,7 +315,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {

// duplicate the entire path to avoid slice overwrites
subdest := dest.Child(field)
spec := spec{
spec := Spec{
dest: subdest,
field: field,
long: strings.ToLower(field.Name),
Expand Down Expand Up @@ -434,7 +472,7 @@ func (p *Parser) Parse(args []string) error {
}

// process environment vars for the given arguments
func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error {
func (p *Parser) captureEnvVars(specs []*Spec, wasPresent map[*Spec]bool) error {
for _, spec := range specs {
if spec.env == "" {
continue
Expand Down Expand Up @@ -482,14 +520,14 @@ func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error
// the underlying struct field
func (p *Parser) process(args []string) error {
// track the options we have seen
wasPresent := make(map[*spec]bool)
wasPresent := make(map[*Spec]bool)

// union of specs for the chain of subcommands encountered so far
curCmd := p.cmd
p.lastCmd = curCmd

// make a copy of the specs because we will add to this list each time we expand a subcommand
specs := make([]*spec, len(curCmd.specs))
specs := make([]*Spec, len(curCmd.specs))
copy(specs, curCmd.specs)

// deal with environment vars
Expand Down Expand Up @@ -706,7 +744,7 @@ func (p *Parser) val(dest path) reflect.Value {
}

// findOption finds an option from its name, or returns null if no spec is found
func findOption(specs []*spec, name string) *spec {
func findOption(specs []*Spec, name string) *Spec {
for _, spec := range specs {
if spec.positional {
continue
Expand Down
53 changes: 53 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1456,3 +1456,56 @@ func TestMustParsePrintsVersion(t *testing.T) {
assert.Equal(t, 0, *exitCode)
assert.Equal(t, "example 3.2.1\n", b.String())
}

func TestSpecValues(t *testing.T) {
var args struct {
A string `arg:"positional,required" placeholder:"X"`
B bool `arg:"positional" default:"true"`
C int `arg:"-c,--c-arg"`
D string `arg:"-D,separate" help:"d-separate-args"`
}
parser, err := NewParser(Config{}, &args)
assert.NoError(t, err)
argSpecs := parser.GetArgumentSpecs()
assert.Len(t, argSpecs, 4)

argSpec := argSpecs[0]
assert.Equal(t, "a", argSpec.LongName())
assert.Equal(t, "", argSpec.ShortName())
assert.Equal(t, true, argSpec.IsRequired())
assert.Equal(t, true, argSpec.IsPositional())
assert.Equal(t, false, argSpec.IsSeparate())
assert.Equal(t, "", argSpec.Help())
assert.Equal(t, "", argSpec.DefaultVal())
assert.Equal(t, "X", argSpec.Placeholder())

argSpec = argSpecs[1]
assert.Equal(t, "b", argSpec.LongName())
assert.Equal(t, "", argSpec.ShortName())
assert.Equal(t, false, argSpec.IsRequired())
assert.Equal(t, true, argSpec.IsPositional())
assert.Equal(t, false, argSpec.IsSeparate())
assert.Equal(t, "", argSpec.Help())
assert.Equal(t, "true", argSpec.DefaultVal())
assert.Equal(t, "B", argSpec.Placeholder())

argSpec = argSpecs[2]
assert.Equal(t, "c-arg", argSpec.LongName())
assert.Equal(t, "c", argSpec.ShortName())
assert.Equal(t, false, argSpec.IsRequired())
assert.Equal(t, false, argSpec.IsPositional())
assert.Equal(t, false, argSpec.IsSeparate())
assert.Equal(t, "", argSpec.Help())
assert.Equal(t, "", argSpec.DefaultVal())
assert.Equal(t, "C-ARG", argSpec.Placeholder())

argSpec = argSpecs[3]
assert.Equal(t, "d", argSpec.LongName())
assert.Equal(t, "D", argSpec.ShortName())
assert.Equal(t, false, argSpec.IsRequired())
assert.Equal(t, false, argSpec.IsPositional())
assert.Equal(t, true, argSpec.IsSeparate())
assert.Equal(t, "d-separate-args", argSpec.Help())
assert.Equal(t, "", argSpec.DefaultVal())
assert.Equal(t, "D", argSpec.Placeholder())
}
14 changes: 7 additions & 7 deletions usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro

// writeUsageForSubcommand writes usage information for the given subcommand
func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
var positionals, longOptions, shortOptions []*spec
var positionals, longOptions, shortOptions []*Spec
for _, spec := range cmd.specs {
switch {
case spec.positional:
Expand Down Expand Up @@ -216,7 +216,7 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error

// writeHelp writes the usage string for the given subcommand
func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
var positionals, longOptions, shortOptions []*spec
var positionals, longOptions, shortOptions []*Spec
for _, spec := range cmd.specs {
switch {
case spec.positional:
Expand Down Expand Up @@ -253,7 +253,7 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
}

// obtain a flattened list of options from all ancestors
var globals []*spec
var globals []*Spec
ancestor := cmd.parent
for ancestor != nil {
globals = append(globals, ancestor.specs...)
Expand All @@ -269,14 +269,14 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
}

// write the list of built in options
p.printOption(w, &spec{
p.printOption(w, &Spec{
cardinality: zero,
long: "help",
short: "h",
help: "display this help and exit",
})
if p.version != "" {
p.printOption(w, &spec{
p.printOption(w, &Spec{
cardinality: zero,
long: "version",
help: "display version and exit",
Expand All @@ -292,7 +292,7 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
}
}

func (p *Parser) printOption(w io.Writer, spec *spec) {
func (p *Parser) printOption(w io.Writer, spec *Spec) {
ways := make([]string, 0, 2)
if spec.long != "" {
ways = append(ways, synopsis(spec, "--"+spec.long))
Expand Down Expand Up @@ -327,7 +327,7 @@ func (p *Parser) lookupCommand(path ...string) (*command, error) {
return cmd, nil
}

func synopsis(spec *spec, form string) string {
func synopsis(spec *Spec, form string) string {
if spec.cardinality == zero {
return form
}
Expand Down