Skip to content

Commit 9b77324

Browse files
committed
argparse: add handling for positional arguments
Add the ability to specify single- or multi-item positional arguments, represented as 'string's and '[]string's, respectively. These arguments are processes in order of specification, and multi-item positional args are only allowed as the *last* positional argument specified. Signed-off-by: Victoria Dye <[email protected]>
1 parent 40965a9 commit 9b77324

File tree

1 file changed

+80
-1
lines changed

1 file changed

+80
-1
lines changed

internal/argparse/argparse.go

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import (
77
"strings"
88
)
99

10+
type positionalArg struct {
11+
name string
12+
description string
13+
value interface{}
14+
}
15+
1016
type argParser struct {
1117
// State
1218
isTopLevel bool
1319
parsed bool
1420
argOffset int
1521

1622
// Pre-parsing
17-
subcommands map[string]Subcommand
23+
subcommands map[string]Subcommand
24+
positionalArgs []*positionalArg
1825

1926
// Post-parsing
2027
selectedSubcommand Subcommand
@@ -37,6 +44,15 @@ func NewArgParser(usageString string) *argParser {
3744
out := a.FlagSet.Output()
3845
fmt.Fprintf(out, "usage: %s\n\n", usageString)
3946

47+
// Print flags (if any)
48+
flagCount := 0
49+
a.FlagSet.VisitAll(func(f *flag.Flag) { flagCount++ })
50+
if flagCount > 0 {
51+
fmt.Fprintln(out, "Flags:")
52+
a.FlagSet.PrintDefaults()
53+
fmt.Fprint(out, "\n")
54+
}
55+
4056
// Print subcommands (if any)
4157
if len(a.subcommands) > 0 {
4258
if a.isTopLevel {
@@ -70,12 +86,54 @@ func (a *argParser) Subcommand(subcommand Subcommand) {
7086
a.subcommands[subcommand.Name()] = subcommand
7187
}
7288

89+
func (a *argParser) PositionalStringVar(name string, description string, arg *string) {
90+
a.positionalArgs = append(a.positionalArgs, &positionalArg{
91+
name: name,
92+
description: description,
93+
value: arg,
94+
})
95+
}
96+
97+
func (a *argParser) PositionalString(name string, description string) *string {
98+
arg := new(string)
99+
a.PositionalStringVar(name, description, arg)
100+
return arg
101+
}
102+
103+
func (a *argParser) PositionalListVar(name string, description string, arg *[]string) {
104+
a.positionalArgs = append(a.positionalArgs, &positionalArg{
105+
name: name,
106+
description: description,
107+
value: arg,
108+
})
109+
}
110+
111+
func (a *argParser) PositionalList(name string, description string) *[]string {
112+
arg := &[]string{}
113+
a.PositionalListVar(name, description, arg)
114+
return arg
115+
}
116+
73117
func (a *argParser) Parse(args []string) {
74118
if a.parsed {
75119
// Do nothing if we've already parsed args
76120
return
77121
}
78122

123+
// Validate
124+
if len(a.subcommands) > 0 && len(a.positionalArgs) > 0 {
125+
panic("cannot mix subcommands and positional args")
126+
}
127+
for i, positionalArg := range a.positionalArgs {
128+
if i < len(a.positionalArgs)-1 {
129+
// Only the last positional arg can be a list
130+
_, isList := positionalArg.value.(*[]string)
131+
if isList {
132+
panic("only the last positional arg can be a list type")
133+
}
134+
}
135+
}
136+
79137
err := a.FlagSet.Parse(args)
80138
if err != nil {
81139
panic("argParser FlagSet error handling should be 'ExitOnError', but error encountered")
@@ -95,6 +153,27 @@ func (a *argParser) Parse(args []string) {
95153
a.argOffset++
96154
}
97155
} else {
156+
// Handle positional args
157+
for _, arg := range a.positionalArgs {
158+
// First, try single string case
159+
sPtr, isStr := arg.value.(*string)
160+
if isStr {
161+
*sPtr = a.Arg(0)
162+
a.argOffset++
163+
continue
164+
}
165+
166+
// Next, try list case
167+
lPtr, isList := arg.value.(*[]string)
168+
if isList {
169+
*lPtr = a.Args()
170+
a.argOffset += a.NArg()
171+
break
172+
}
173+
174+
panic("Positional arg has invalid type")
175+
}
176+
98177
if a.NArg() != 0 {
99178
// If not using subcommands, all args should be accounted for
100179
// Exit with usage if not

0 commit comments

Comments
 (0)