@@ -15,6 +15,7 @@ const usageExitCode int = 2
1515type positionalArg struct {
1616 name string
1717 description string
18+ required bool
1819 value interface {}
1920}
2021
@@ -60,7 +61,7 @@ func NewArgParser(logger log.TraceLogger, usageString string) *argParser {
6061 fmt .Fprint (out , "\n " )
6162 }
6263
63- // Print subcommands (if any)
64+ // Print subcommands or positional args (if any)
6465 if len (a .subcommands ) > 0 {
6566 if a .isTopLevel {
6667 fmt .Fprintln (out , "Commands:" )
@@ -69,6 +70,10 @@ func NewArgParser(logger log.TraceLogger, usageString string) *argParser {
6970 }
7071 a .printSubcommands ()
7172 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 " )
7277 }
7378 }
7479
@@ -93,31 +98,48 @@ func (a *argParser) Subcommand(subcommand Subcommand) {
9398 a .subcommands [subcommand .Name ()] = subcommand
9499}
95100
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 ) {
97117 a .positionalArgs = append (a .positionalArgs , & positionalArg {
98118 name : name ,
99119 description : description ,
120+ required : required ,
100121 value : arg ,
101122 })
102123}
103124
104- func (a * argParser ) PositionalString (name string , description string ) * string {
125+ func (a * argParser ) PositionalString (name string , description string , required bool ) * string {
105126 arg := new (string )
106- a .PositionalStringVar (name , description , arg )
127+ a .PositionalStringVar (name , description , arg , required )
107128 return arg
108129}
109130
110- func (a * argParser ) PositionalListVar (name string , description string , arg * []string ) {
131+ func (a * argParser ) PositionalListVar (name string , description string , arg * []string , required bool ) {
111132 a .positionalArgs = append (a .positionalArgs , & positionalArg {
112133 name : name ,
113134 description : description ,
135+ required : required ,
114136 value : arg ,
115137 })
116138}
117139
118- func (a * argParser ) PositionalList (name string , description string ) * []string {
140+ func (a * argParser ) PositionalList (name string , description string , required bool ) * []string {
119141 arg := & []string {}
120- a .PositionalListVar (name , description , arg )
142+ a .PositionalListVar (name , description , arg , required )
121143 return arg
122144}
123145
@@ -131,7 +153,14 @@ func (a *argParser) Parse(ctx context.Context, args []string) {
131153 if len (a .subcommands ) > 0 && len (a .positionalArgs ) > 0 {
132154 panic ("cannot mix subcommands and positional args" )
133155 }
156+
157+ mustBeOptional := false
134158 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+
135164 if i < len (a .positionalArgs )- 1 {
136165 // Only the last positional arg can be a list
137166 _ , isList := positionalArg .value .(* []string )
@@ -165,6 +194,14 @@ func (a *argParser) Parse(ctx context.Context, args []string) {
165194 } else {
166195 // Handle positional args
167196 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+
168205 // First, try single string case
169206 sPtr , isStr := arg .value .(* string )
170207 if isStr {
0 commit comments