Skip to content

Commit 5a61312

Browse files
committed
feature: move to top level functions that do the type assertion
1 parent a43de13 commit 5a61312

File tree

3 files changed

+478
-239
lines changed

3 files changed

+478
-239
lines changed

cli.go

Lines changed: 95 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -148,44 +148,88 @@ type ParsedFrom struct {
148148
RawDefault bool
149149
}
150150

151-
func (c *Command) Opt(id string) any {
152-
if v, ok := c.LookupOpt(id); ok {
153-
return v
154-
}
155-
panic("no parsed option value found for id '" + id + "'")
156-
}
157-
158-
func (c *Command) LookupOpt(id string) (any, bool) {
151+
// LookupOpt looks for an option value with the given id in the given Command and converts
152+
// the value to the given type T through an untested type assertion (so this will panic if
153+
// the value is found and can't be converted to type T). So if the option is present, the
154+
// typed value will be returned and the boolean will be true. Otherwise, the zero value of
155+
// type T will be returned and the boolean will be false.
156+
func LookupOpt[T any](c *Command, id string) (T, bool) {
159157
for i := len(c.Opts) - 1; i >= 0; i-- {
160158
if c.Opts[i].ID == id {
161-
return c.Opts[i].Value, true
159+
return c.Opts[i].Value.(T), true
162160
}
163161
}
164-
return nil, false
162+
var zero T
163+
return zero, false
164+
}
165+
166+
// GetOpt gets the option value with the given id in the given Command and converts the
167+
// value to the given type T through an untested type assertion. This function will panic
168+
// if the value isn't found or if the value can't be converted to type T. To check
169+
// whether the value is found instead of panicking, see [LookupOpt].
170+
func GetOpt[T any](c *Command, id string) T {
171+
if v, ok := LookupOpt[T](c, id); ok {
172+
return v
173+
}
174+
panic("no parsed option value found for id '" + id + "'")
165175
}
166176

167-
func (c *Command) Arg(id string) any {
168-
if v, ok := c.LookupArg(id); ok {
177+
// GetOptOr looks for an option value with the given id in the given Command and converts
178+
// the value to the given type T through an untested type assertion (so this will panic if
179+
// the value is found and can't be converted to type T). If the value isn't found, the
180+
// given fallback value will be returned. To check whether the value is found instead of
181+
// using a fallback value, see [LookupOpt].
182+
func GetOptOr[T any](c *Command, id string, fallback T) T {
183+
if v, ok := LookupOpt[T](c, id); ok {
169184
return v
170185
}
171-
panic("no parsed argument value found for id '" + id + "'")
186+
return fallback
172187
}
173188

174-
func (c *Command) LookupArg(id string) (any, bool) {
189+
// LookupArg looks for a positional argument value with the given id in the given Command
190+
// and converts the value to the given type T through an untested type assertion (so this
191+
// will panic if the value is found and can't be converted to type T). So if the positional
192+
// argument is present, the typed value will be returned and the boolean will be true.
193+
// Otherwise, the zero value of type T will be returned and the boolean will be false.
194+
func LookupArg[T any](c *Command, id string) (T, bool) {
175195
for i := len(c.Args) - 1; i >= 0; i-- {
176196
if c.Args[i].ID == id {
177-
return c.Args[i].Value, true
197+
return c.Args[i].Value.(T), true
178198
}
179199
}
180-
return nil, false
200+
var zero T
201+
return zero, false
202+
}
203+
204+
// GetArg gets the positional argument value with the given id in the given Command and
205+
// converts the value to the given type T through an untested type assertion. This
206+
// function will panic if the value isn't found or if the value can't be converted to type
207+
// T. To check whether the value is found instead of panicking, see [LookupArg].
208+
func GetArg[T any](c *Command, id string) T {
209+
if v, ok := LookupArg[T](c, id); ok {
210+
return v
211+
}
212+
panic("no parsed value found for positional argument id '" + id + "'")
213+
}
214+
215+
// GetArgOr looks for a positional argument value with the given id in the given Command
216+
// and converts the value to the given type T through an untested type assertion (so this
217+
// will panic if the value is found and can't be converted to type T). If the value isn't
218+
// found, the given fallback value will be returned. To check whether the value is found
219+
// instead of using a fallback value, see [LookupArg].
220+
func GetArgOr[T any](c *Command, id string, fallback T) T {
221+
if v, ok := LookupArg[T](c, id); ok {
222+
return v
223+
}
224+
return fallback
181225
}
182226

183227
// ParseOrExit will parse input based on this CommandInfo. If help was requested, it
184228
// will print the help message and exit the program successfully (status code 0). If
185229
// there is any other error, it will print the error and exit the program with failure
186230
// (status code 1). The input parameter semantics are the same as [CommandInfo.Parse].
187-
func (c CommandInfo) ParseOrExit(args ...string) Command {
188-
p, err := c.Parse(args...)
231+
func (in CommandInfo) ParseOrExit(args ...string) *Command {
232+
c, err := in.Parse(args...)
189233
if err != nil {
190234
if e, ok := err.(HelpRequestError); ok {
191235
fmt.Print(e.HelpMsg)
@@ -195,47 +239,61 @@ func (c CommandInfo) ParseOrExit(args ...string) Command {
195239
os.Exit(1)
196240
}
197241
}
198-
return p
242+
return c
199243
}
200244

201245
// ParseOrExit will parse input based on this CommandInfo. If no function arguments
202246
// are provided, the [os.Args] will be used.
203-
func (c *CommandInfo) Parse(args ...string) (Command, error) {
204-
if !c.isPrepped {
205-
c.prepareAndValidate()
206-
c.isPrepped = true
247+
func (in *CommandInfo) Parse(args ...string) (*Command, error) {
248+
if !in.isPrepped {
249+
in.prepareAndValidate()
250+
in.isPrepped = true
207251
}
208252
if args == nil {
209253
args = os.Args[1:]
210254
}
211-
p := Command{
255+
c := &Command{
212256
Opts: make([]Input, 0, len(args)),
213257
}
214-
err := parse(c, &p, args)
215-
return p, err
258+
err := parse(in, c, args)
259+
return c, err
216260
}
217261

218262
type HelpRequestError struct {
219-
RootCause error
220-
HelpMsg string
263+
HelpMsg string
221264
}
222265

223266
func (h HelpRequestError) Error() string {
224-
if h.RootCause != nil {
225-
return h.RootCause.Error()
226-
}
227267
return h.HelpMsg
228268
}
229269

230-
func lookupOptionByShortName(c *CommandInfo, shortName string) *InputInfo {
231-
for i := range c.opts {
232-
if c.opts[i].nameShort == shortName {
233-
return &c.opts[i]
270+
func lookupOptionByShortName(in *CommandInfo, shortName string) *InputInfo {
271+
for i := range in.opts {
272+
if in.opts[i].nameShort == shortName {
273+
return &in.opts[i]
234274
}
235275
}
236276
return nil
237277
}
238278

279+
func hasOpt(c *Command, id string) bool {
280+
for i := range c.Opts {
281+
if c.Opts[i].ID == id {
282+
return true
283+
}
284+
}
285+
return false
286+
}
287+
288+
func hasArg(c *Command, id string) bool {
289+
for i := range c.Args {
290+
if c.Args[i].ID == id {
291+
return true
292+
}
293+
}
294+
return false
295+
}
296+
239297
func parse(c *CommandInfo, p *Command, args []string) error {
240298
// set any defaults
241299
for i := range c.opts {
@@ -419,8 +477,7 @@ func parse(c *CommandInfo, p *Command, args []string) error {
419477
var missing []string
420478
for i := range c.opts {
421479
if c.opts[i].isRequired {
422-
_, ok := p.LookupOpt(c.opts[i].id)
423-
if !ok {
480+
if !hasOpt(p, c.opts[i].id) {
424481
var name string
425482
if c.opts[i].nameLong != "" {
426483
name = "--" + c.opts[i].nameLong
@@ -458,8 +515,7 @@ func parse(c *CommandInfo, p *Command, args []string) error {
458515
if !c.args[i].isRequired {
459516
break
460517
}
461-
_, ok := p.LookupArg(c.args[i].id)
462-
if !ok {
518+
if !hasArg(p, c.args[i].id) {
463519
missing = append(missing, c.args[i].valueName)
464520
}
465521
}

0 commit comments

Comments
 (0)