@@ -15,6 +15,7 @@ const usageExitCode int = 2
15
15
type positionalArg struct {
16
16
name string
17
17
description string
18
+ required bool
18
19
value interface {}
19
20
}
20
21
@@ -60,7 +61,7 @@ func NewArgParser(logger log.TraceLogger, usageString string) *argParser {
60
61
fmt .Fprint (out , "\n " )
61
62
}
62
63
63
- // Print subcommands (if any)
64
+ // Print subcommands or positional args (if any)
64
65
if len (a .subcommands ) > 0 {
65
66
if a .isTopLevel {
66
67
fmt .Fprintln (out , "Commands:" )
@@ -69,6 +70,10 @@ func NewArgParser(logger log.TraceLogger, usageString string) *argParser {
69
70
}
70
71
a .printSubcommands ()
71
72
fmt .Fprint (out , "\n " )
73
+ } else if len (a .positionalArgs ) > 0 {
74
+ fmt .Fprintln (out , "Positional arguments:" )
75
+ a .printPositionalArgs ()
76
+ fmt .Fprint (out , "\n " )
72
77
}
73
78
}
74
79
@@ -93,31 +98,48 @@ func (a *argParser) Subcommand(subcommand Subcommand) {
93
98
a .subcommands [subcommand .Name ()] = subcommand
94
99
}
95
100
96
- func (a * argParser ) PositionalStringVar (name string , description string , arg * string ) {
101
+ func (a * argParser ) printPositionalArgs () {
102
+ out := a .FlagSet .Output ()
103
+ for _ , arg := range a .positionalArgs {
104
+ optionalStr := ""
105
+ if ! arg .required {
106
+ optionalStr = "(optional) "
107
+ }
108
+ fmt .Fprintf (out , " %s\n \t %s%s\n " ,
109
+ arg .name ,
110
+ optionalStr ,
111
+ strings .ReplaceAll (strings .TrimSpace (arg .description ), "\n " , "\n \t " ),
112
+ )
113
+ }
114
+ }
115
+
116
+ func (a * argParser ) PositionalStringVar (name string , description string , arg * string , required bool ) {
97
117
a .positionalArgs = append (a .positionalArgs , & positionalArg {
98
118
name : name ,
99
119
description : description ,
120
+ required : required ,
100
121
value : arg ,
101
122
})
102
123
}
103
124
104
- func (a * argParser ) PositionalString (name string , description string ) * string {
125
+ func (a * argParser ) PositionalString (name string , description string , required bool ) * string {
105
126
arg := new (string )
106
- a .PositionalStringVar (name , description , arg )
127
+ a .PositionalStringVar (name , description , arg , required )
107
128
return arg
108
129
}
109
130
110
- func (a * argParser ) PositionalListVar (name string , description string , arg * []string ) {
131
+ func (a * argParser ) PositionalListVar (name string , description string , arg * []string , required bool ) {
111
132
a .positionalArgs = append (a .positionalArgs , & positionalArg {
112
133
name : name ,
113
134
description : description ,
135
+ required : required ,
114
136
value : arg ,
115
137
})
116
138
}
117
139
118
- func (a * argParser ) PositionalList (name string , description string ) * []string {
140
+ func (a * argParser ) PositionalList (name string , description string , required bool ) * []string {
119
141
arg := & []string {}
120
- a .PositionalListVar (name , description , arg )
142
+ a .PositionalListVar (name , description , arg , required )
121
143
return arg
122
144
}
123
145
@@ -131,7 +153,14 @@ func (a *argParser) Parse(ctx context.Context, args []string) {
131
153
if len (a .subcommands ) > 0 && len (a .positionalArgs ) > 0 {
132
154
panic ("cannot mix subcommands and positional args" )
133
155
}
156
+
157
+ mustBeOptional := false
134
158
for i , positionalArg := range a .positionalArgs {
159
+ mustBeOptional = mustBeOptional || ! positionalArg .required
160
+ if mustBeOptional && positionalArg .required {
161
+ panic ("cannot have required args after optional args" )
162
+ }
163
+
135
164
if i < len (a .positionalArgs )- 1 {
136
165
// Only the last positional arg can be a list
137
166
_ , isList := positionalArg .value .(* []string )
@@ -165,6 +194,14 @@ func (a *argParser) Parse(ctx context.Context, args []string) {
165
194
} else {
166
195
// Handle positional args
167
196
for _ , arg := range a .positionalArgs {
197
+ if a .NArg () == 0 {
198
+ if arg .required {
199
+ a .Usage (ctx , "No value specified for required argument '%s'" , arg .name )
200
+ } else {
201
+ break
202
+ }
203
+ }
204
+
168
205
// First, try single string case
169
206
sPtr , isStr := arg .value .(* string )
170
207
if isStr {
0 commit comments