Skip to content

Commit cb40f71

Browse files
committed
refactor: remove explicit build step, add default help option in prep stage
1 parent 8356d2a commit cb40f71

File tree

6 files changed

+115
-96
lines changed

6 files changed

+115
-96
lines changed

builder.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,20 @@ var (
1616
errReqArgAfterOptional = "required positional arguments cannot come after optional ones"
1717
)
1818

19-
func (c CommandInfo) Build() *RootCommandInfo {
20-
c.validate()
21-
return &RootCommandInfo{c: c}
22-
}
19+
func (c *CommandInfo) prepareAndValidate() {
20+
// Add the default help option here as long as this
21+
// command doesn't already have a help option.
22+
var hasHelpOpt bool
23+
for i := range c.opts {
24+
if c.opts[i].helpGen != nil {
25+
hasHelpOpt = true
26+
break
27+
}
28+
}
29+
if !hasHelpOpt {
30+
*c = (*c).Opt(DefaultHelpInput)
31+
}
2332

24-
func (c *CommandInfo) validate() {
2533
// option assertions
2634
for i := 0; i < len(c.opts)-1; i++ {
2735
for z := i + 1; z < len(c.opts); z++ {
@@ -64,7 +72,7 @@ func (c *CommandInfo) validate() {
6472
}
6573

6674
for i := range c.subcmds {
67-
c.subcmds[i].validate()
75+
c.subcmds[i].prepareAndValidate()
6876
}
6977
}
7078

@@ -80,12 +88,11 @@ func NewCmd(name string) CommandInfo {
8088
}
8189
}
8290

83-
c := CommandInfo{
91+
return CommandInfo{
8492
name: name,
8593
path: []string{name},
8694
opts: make([]InputInfo, 0, 5),
8795
}
88-
return c.Opt(DefaultHelpInput)
8996
}
9097

9198
func (c CommandInfo) Help(blurb string) CommandInfo {

builder_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestBuilder(t *testing.T) {
1515
name: "control test clean ones",
1616
builds: []func(){
1717
func() { NewCmd("a") },
18-
func() { NewCmd("a").Build() },
18+
func() { c := NewCmd("a"); c.Parse() },
1919
},
2020
expPanicVals: []any{nil, nil},
2121
},
@@ -92,15 +92,15 @@ func TestBuilder(t *testing.T) {
9292
Opt(NewOpt("o3")).
9393
Opt(NewOpt("o1")).
9494
Opt(NewOpt("o5"))).
95-
Build()
95+
ParseOrExit()
9696
},
9797
func() {
9898
NewCmd("root").
9999
Arg(NewArg("a1")).
100100
Arg(NewArg("a1")).
101101
Arg(NewArg("a2")).
102102
Arg(NewArg("a3")).
103-
Build()
103+
ParseOrExit()
104104
},
105105
},
106106
expPanicVals: []any{
@@ -115,19 +115,19 @@ func TestBuilder(t *testing.T) {
115115
NewCmd("root").
116116
Opt(NewOpt("aa").Short('a')).
117117
Opt(NewOpt("bb").Short('b')).
118-
Build()
118+
ParseOrExit([]string{}...)
119119
},
120120
func() {
121121
NewCmd("root").
122122
Opt(NewOpt("aa").Short('a')).
123123
Opt(NewOpt("bb").Short('a')).
124-
Build()
124+
ParseOrExit()
125125
},
126126
func() {
127127
NewCmd("root").
128128
Opt(NewOpt("aa").ShortOnly('b')).
129129
Opt(NewOpt("bb").Short('b')).
130-
Build()
130+
ParseOrExit()
131131
},
132132
},
133133
expPanicVals: []any{
@@ -144,7 +144,7 @@ func TestBuilder(t *testing.T) {
144144
Opt(NewOpt("aa").Long("aaa")).
145145
Opt(NewOpt("bb").Long("bbb")).
146146
Opt(NewOpt("cc").Long("aaa")).
147-
Build()
147+
ParseOrExit()
148148
},
149149
},
150150
expPanicVals: []any{
@@ -189,7 +189,7 @@ func TestBuilder(t *testing.T) {
189189
NewCmd("root").
190190
Subcmd(NewCmd("bb")).
191191
Subcmd(NewCmd("bb")).
192-
Build()
192+
ParseOrExit()
193193
},
194194
func() {
195195
NewCmd("root").
@@ -198,7 +198,7 @@ func TestBuilder(t *testing.T) {
198198
Subcmd(NewCmd("bb")).
199199
Subcmd(NewCmd("aa")).
200200
Subcmd(NewCmd("cc"))).
201-
Build()
201+
ParseOrExit()
202202
},
203203
},
204204
expPanicVals: []any{

cli.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ type CommandInfo struct {
102102
opts []InputInfo
103103
args []InputInfo
104104
subcmds []CommandInfo
105+
106+
isPrepped bool
105107
}
106108

107109
type InputInfo struct {
@@ -178,13 +180,11 @@ func (c *Command) LookupArg(id string) (any, bool) {
178180
return nil, false
179181
}
180182

181-
type RootCommandInfo struct{ c CommandInfo }
182-
183-
// ParseOrExit will parse input based on this RootCommandInfo. If help was requested, it
183+
// ParseOrExit will parse input based on this CommandInfo. If help was requested, it
184184
// will print the help message and exit the program successfully (status code 0). If
185185
// there is any other error, it will print the error and exit the program with failure
186-
// (status code 1). The input parameter semantics are the same as [RootCommandInfo.Parse].
187-
func (c *RootCommandInfo) ParseOrExit(args ...string) Command {
186+
// (status code 1). The input parameter semantics are the same as [CommandInfo.Parse].
187+
func (c CommandInfo) ParseOrExit(args ...string) Command {
188188
p, err := c.Parse(args...)
189189
if err != nil {
190190
if e, ok := err.(HelpRequestError); ok {
@@ -198,16 +198,20 @@ func (c *RootCommandInfo) ParseOrExit(args ...string) Command {
198198
return p
199199
}
200200

201-
// ParseOrExit will parse input based on this RootCommandInfo. If no function arguments
201+
// ParseOrExit will parse input based on this CommandInfo. If no function arguments
202202
// are provided, the [os.Args] will be used.
203-
func (c *RootCommandInfo) Parse(args ...string) (Command, error) {
203+
func (c *CommandInfo) Parse(args ...string) (Command, error) {
204+
if !c.isPrepped {
205+
c.prepareAndValidate()
206+
c.isPrepped = true
207+
}
204208
if args == nil {
205209
args = os.Args[1:]
206210
}
207211
p := Command{
208212
Opts: make([]Input, 0, len(args)),
209213
}
210-
err := parse(&c.c, &p, args)
214+
err := parse(c, &p, args)
211215
return p, err
212216
}
213217

cli_test.go

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestParsing(t *testing.T) {
2222
}
2323
type testCase struct {
2424
name string
25-
cmd *RootCommandInfo
25+
cmd CommandInfo
2626
variations []testInputOutput
2727
}
2828

@@ -36,8 +36,7 @@ func TestParsing(t *testing.T) {
3636
Opt(NewOpt("bb").ShortOnly('b')).
3737
Opt(NewOpt("cc").Required()).
3838
Opt(NewOpt("dd").Default("v4")).
39-
Opt(NewOpt("ee")).
40-
Build(),
39+
Opt(NewOpt("ee")),
4140
variations: []testInputOutput{
4241
{
4342
ttInfo: ttCase(),
@@ -91,8 +90,7 @@ func TestParsing(t *testing.T) {
9190
Opt(NewIntOpt("int").Env("INT").Default("123")).
9291
Opt(NewUintOpt("uint").Env("UINT").Default("456")).
9392
Opt(NewFloat32Opt("f32").Env("F32").Default("1.23")).
94-
Opt(NewFloat64Opt("f64").Env("F64").Default("4.56")).
95-
Build(),
93+
Opt(NewFloat64Opt("f64").Env("F64").Default("4.56")),
9694
variations: []testInputOutput{
9795
// no input, just relying on the default values
9896
{
@@ -167,8 +165,7 @@ func TestParsing(t *testing.T) {
167165
Arg(NewArg("arg1").Required()).
168166
Arg(NewArg("arg2").Required().Env("ARG2")).
169167
Arg(NewArg("arg3")).
170-
Arg(NewArg("arg4").Default("Z").Env("ARG4")).
171-
Build(),
168+
Arg(NewArg("arg4").Default("Z").Env("ARG4")),
172169
variations: []testInputOutput{
173170
{
174171
ttInfo: ttCase(),
@@ -239,8 +236,7 @@ func TestParsing(t *testing.T) {
239236
name: "dashdash_and_eq",
240237
cmd: NewCmd("ddeq").
241238
Opt(NewOpt("opt1").Short('o')).
242-
Arg(NewArg("arg1")).
243-
Build(),
239+
Arg(NewArg("arg1")),
244240
variations: []testInputOutput{
245241
{
246242
ttInfo: ttCase(),
@@ -302,8 +298,7 @@ func TestParsing(t *testing.T) {
302298
Opt(NewOpt("cc"))).
303299
Subcmd(NewCmd("two").
304300
Opt(NewOpt("dd")).
305-
Opt(NewOpt("ee"))).
306-
Build(),
301+
Opt(NewOpt("ee"))),
307302
variations: []testInputOutput{
308303
{
309304
ttInfo: ttCase(),
@@ -345,8 +340,7 @@ func TestParsing(t *testing.T) {
345340
cmd: NewCmd("cmd").
346341
Opt(NewOpt("aa").Required()).
347342
Subcmd(NewCmd("one").
348-
Opt(NewOpt("cc").Required())).
349-
Build(),
343+
Opt(NewOpt("cc").Required())),
350344
variations: []testInputOutput{
351345
{
352346
ttInfo: ttCase(),
@@ -372,8 +366,7 @@ func TestParsing(t *testing.T) {
372366
x, _ := strconv.Atoi(s[:comma])
373367
y, _ := strconv.Atoi(s[comma+1:])
374368
return image.Point{X: x, Y: y}, nil
375-
})).
376-
Build(),
369+
})),
377370
variations: []testInputOutput{
378371
{
379372
ttInfo: ttCase(),
@@ -392,8 +385,7 @@ func TestParsing(t *testing.T) {
392385
cmd: NewCmd("shst").
393386
Opt(NewBoolOpt("bb").Short('b')).
394387
Opt(NewOpt("aa").Short('a')).
395-
Opt(NewBoolOpt("cc").Short('c')).
396-
Build(),
388+
Opt(NewBoolOpt("cc").Short('c')),
397389
variations: []testInputOutput{
398390
{
399391
ttInfo: ttCase(),
@@ -626,7 +618,7 @@ func TestLookups(t *testing.T) {
626618
}
627619
type testCase struct {
628620
name string
629-
cmd *RootCommandInfo
621+
cmd CommandInfo
630622
variations []testInputOutput
631623
}
632624

@@ -638,8 +630,7 @@ func TestLookups(t *testing.T) {
638630
Opt(NewOpt("bb").ShortOnly('b')).
639631
Opt(NewOpt("cc").Required()).
640632
Opt(NewOpt("dd").Default("v4")).
641-
Opt(NewOpt("ee")).
642-
Build(),
633+
Opt(NewOpt("ee")),
643634
variations: []testInputOutput{
644635
{
645636
args: []string{"-b", "v2", "--aa", "--cc=v3"},

0 commit comments

Comments
 (0)