@@ -7,14 +7,21 @@ import (
7
7
"strings"
8
8
)
9
9
10
+ type positionalArg struct {
11
+ name string
12
+ description string
13
+ value interface {}
14
+ }
15
+
10
16
type argParser struct {
11
17
// State
12
18
isTopLevel bool
13
19
parsed bool
14
20
argOffset int
15
21
16
22
// Pre-parsing
17
- subcommands map [string ]Subcommand
23
+ subcommands map [string ]Subcommand
24
+ positionalArgs []* positionalArg
18
25
19
26
// Post-parsing
20
27
selectedSubcommand Subcommand
@@ -37,6 +44,15 @@ func NewArgParser(usageString string) *argParser {
37
44
out := a .FlagSet .Output ()
38
45
fmt .Fprintf (out , "usage: %s\n \n " , usageString )
39
46
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
+
40
56
// Print subcommands (if any)
41
57
if len (a .subcommands ) > 0 {
42
58
if a .isTopLevel {
@@ -70,12 +86,54 @@ func (a *argParser) Subcommand(subcommand Subcommand) {
70
86
a .subcommands [subcommand .Name ()] = subcommand
71
87
}
72
88
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
+
73
117
func (a * argParser ) Parse (args []string ) {
74
118
if a .parsed {
75
119
// Do nothing if we've already parsed args
76
120
return
77
121
}
78
122
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
+
79
137
err := a .FlagSet .Parse (args )
80
138
if err != nil {
81
139
panic ("argParser FlagSet error handling should be 'ExitOnError', but error encountered" )
@@ -95,6 +153,27 @@ func (a *argParser) Parse(args []string) {
95
153
a .argOffset ++
96
154
}
97
155
} 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
+
98
177
if a .NArg () != 0 {
99
178
// If not using subcommands, all args should be accounted for
100
179
// Exit with usage if not
0 commit comments