Skip to content

Commit 56eedde

Browse files
Breaking: Add cli.FlagOption for configuring flags
1 parent 17a4e7a commit 56eedde

11 files changed

Lines changed: 218 additions & 168 deletions

File tree

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func run() error {
7777
cli.Stdout(os.Stdout),
7878
cli.Example("Do a thing", "quickstart something"),
7979
cli.Example("Count the things", "quickstart something --count 3"),
80-
cli.Flag(&count, "count", 'c', 0, "Count the things"),
80+
cli.Flag(&count, "count", 'c', "Count the things"),
8181
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
8282
fmt.Fprintf(cmd.Stdout(), "Hello from quickstart!, my args were: %v, count was %d\n", cmd.Args(), count)
8383
return nil
@@ -168,10 +168,10 @@ func buildCmd() (*cli.Command, error) {
168168
return cli.New(
169169
// ...
170170
// Signature is cli.Flag(*T, name, shorthand, default, description)
171-
cli.Flag(&options.name, "name", 'n', "", "The name of something"),
172-
cli.Flag(&options.force, "force", cli.NoShortHand, false, "Force delete without confirmation"),
173-
cli.Flag(&options.size, "size", 's', 0, "Size of something"),
174-
cli.Flag(&options.items, "items", 'i', nil, "Items to include"),
171+
cli.Flag(&options.name, "name", 'n', "The name of something"),
172+
cli.Flag(&options.force, "force", cli.NoShortHand, "Force delete without confirmation"),
173+
cli.Flag(&options.size, "size", 's', "Size of something"),
174+
cli.Flag(&options.items, "items", 'i', "Items to include"),
175175
// ...
176176
)
177177
}
@@ -374,7 +374,7 @@ Consider the following example of a bad shorthand value:
374374
var delete bool
375375

376376
// Note: "de" is a bad shorthand, it's two letters
377-
cli.New("demo", cli.Flag(&delete, "delete", "de", false, "Delete something"))
377+
cli.New("demo", cli.Flag(&delete, "delete", "de", "Delete something"))
378378
```
379379

380380
In `cli` this is impossible as we use `rune` as the type for a flag shorthand, so the above example would not compile. Instead you must specify a valid rune:
@@ -383,14 +383,14 @@ In `cli` this is impossible as we use `rune` as the type for a flag shorthand, s
383383
var delete bool
384384

385385
// Ahhh, that's better
386-
cli.New("demo", cli.Flag(&delete, "delete", 'd', false, "Delete something"))
386+
cli.New("demo", cli.Flag(&delete, "delete", 'd', "Delete something"))
387387
```
388388

389389
And if you don't want a shorthand? i.e. just `--delete` with no `-d` option:
390390

391391
```go
392392
var delete bool
393-
cli.New("demo", cli.Flag(&delete, "delete", cli.NoShortHand, false, "Delete something"))
393+
cli.New("demo", cli.Flag(&delete, "delete", cli.NoShortHand, "Delete something"))
394394
```
395395

396396
## In the Wild

command.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ func New(name string, options ...Option) (*Command, error) {
6565
}
6666

6767
// Ensure we always have at least help and version flags
68-
err := Flag(&cfg.helpCalled, "help", 'h', false, "Show help for "+name).apply(&cfg)
68+
err := Flag(&cfg.helpCalled, "help", 'h', "Show help for "+name).apply(&cfg)
6969
errs = errors.Join(errs, err) // nil errors are discarded in join
7070

71-
err = Flag(&cfg.versionCalled, "version", 'V', false, "Show version info for "+name).apply(&cfg)
71+
err = Flag(&cfg.versionCalled, "version", 'V', "Show version info for "+name).apply(&cfg)
7272

7373
errs = errors.Join(errs, err)
7474
if errs != nil {

command_test.go

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func TestExecute(t *testing.T) {
105105

106106
return nil
107107
}),
108-
cli.Flag(&force, "force", 'f', false, "Force something"),
108+
cli.Flag(&force, "force", 'f', "Force something"),
109109
}
110110

111111
cmd, err := cli.New("test", slices.Concat(options, tt.options)...)
@@ -270,8 +270,8 @@ func TestSubCommandExecute(t *testing.T) {
270270

271271
sub1 := func() (*cli.Command, error) {
272272
defaultOpts := []cli.Option{
273-
cli.Flag(&force, "force", 'f', false, "Force for sub1"),
274-
cli.Flag(&something, "something", 's', "", "Something for sub1"),
273+
cli.Flag(&force, "force", 'f', "Force for sub1"),
274+
cli.Flag(&something, "something", 's', "Something for sub1"),
275275
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
276276
if something == "" {
277277
something = "<empty>"
@@ -315,8 +315,8 @@ func TestSubCommandExecute(t *testing.T) {
315315

316316
return nil
317317
}),
318-
cli.Flag(&deleteMe, "delete", 'd', false, "Delete for sub2"),
319-
cli.Flag(&number, "number", 'n', -1, "Number for sub2"),
318+
cli.Flag(&deleteMe, "delete", 'd', "Delete for sub2"),
319+
cli.Flag(&number, "number", 'n', "Number for sub2", cli.FlagDefault(-1)),
320320
}
321321

322322
opts := slices.Concat(defaultOpts, tt.sub2Options)
@@ -433,7 +433,7 @@ func TestHelp(t *testing.T) {
433433
cli.OverrideArgs([]string{"--help"}),
434434
cli.Arg(new(string), "src", "The file to copy"), // This one is required
435435
cli.Arg(new(string), "dest", "Destination to copy to", cli.ArgDefault("destination.txt")), // This one is optional
436-
cli.Flag(new(flag.Count), "verbosity", 'v', 0, "Increase the verbosity level"),
436+
cli.Flag(new(flag.Count), "verbosity", 'v', "Increase the verbosity level"),
437437
cli.Run(func(ctx context.Context, cmd *cli.Command) error { return nil }),
438438
},
439439
wantErr: false,
@@ -493,10 +493,16 @@ func TestHelp(t *testing.T) {
493493
cli.Short("A cool CLI to do things"),
494494
cli.Long("A longer, probably multiline description"),
495495
cli.SubCommands(sub1, sub2),
496-
cli.Flag(new(bool), "delete", 'd', false, "Delete something"),
497-
cli.Flag(new(int), "count", flag.NoShortHand, -1, "Count something"),
498-
cli.Flag(new([]string), "things", flag.NoShortHand, nil, "Names of things"),
499-
cli.Flag(new([]string), "more", flag.NoShortHand, []string{"one", "two"}, "Names of things with a default"),
496+
cli.Flag(new(bool), "delete", 'd', "Delete something"),
497+
cli.Flag(new(int), "count", flag.NoShortHand, "Count something", cli.FlagDefault(-1)),
498+
cli.Flag(new([]string), "things", flag.NoShortHand, "Names of things"),
499+
cli.Flag(
500+
new([]string),
501+
"more",
502+
flag.NoShortHand,
503+
"Names of things with a default",
504+
cli.FlagDefault([]string{"one", "two"}),
505+
),
500506
},
501507
wantErr: false,
502508
},
@@ -735,16 +741,16 @@ func TestOptionValidation(t *testing.T) {
735741
{
736742
name: "flag already exists",
737743
options: []cli.Option{
738-
cli.Flag(new(int), "count", 'c', 0, "Count something"),
739-
cli.Flag(new(int), "count", 'c', 0, "Count something (again)"),
744+
cli.Flag(new(int), "count", 'c', "Count something"),
745+
cli.Flag(new(int), "count", 'c', "Count something (again)"),
740746
},
741747
errMsg: `flag "count" already defined`,
742748
},
743749
{
744750
name: "flag short already exists",
745751
options: []cli.Option{
746-
cli.Flag(new(int), "count", 'c', 0, "Count something"),
747-
cli.Flag(new(string), "config", 'c', "", "Path to config file"),
752+
cli.Flag(new(int), "count", 'c', "Count something"),
753+
cli.Flag(new(string), "config", 'c', "Path to config file"),
748754
},
749755
errMsg: `could not add flag "config" to command "test": shorthand "c" already in use for flag "count"`,
750756
},
@@ -884,8 +890,8 @@ func TestCommandOptionOrder(t *testing.T) {
884890
cli.Arg(new(string), "third", "Third arg"),
885891
cli.Version("v1.2.3"),
886892
cli.SubCommands(sub),
887-
cli.Flag(&f, "flag", 'f', false, "Set a bool flag"),
888-
cli.Flag(&count, "count", 'c', 0, "Count a thing"),
893+
cli.Flag(&f, "flag", 'f', "Set a bool flag"),
894+
cli.Flag(&count, "count", 'c', "Count a thing"),
889895
}
890896

891897
baseLineOptions := slices.Concat(
@@ -1001,9 +1007,9 @@ func BenchmarkNew(b *testing.B) {
10011007
cli.Version("dev"),
10021008
cli.Commit("dfdddaf"),
10031009
cli.Example("An example", "bench --help"),
1004-
cli.Flag(new(bool), "force", 'f', false, "Force something"),
1005-
cli.Flag(new(string), "name", 'n', "", "The name of something"),
1006-
cli.Flag(new(int), "count", 'c', 1, "Count something"),
1010+
cli.Flag(new(bool), "force", 'f', "Force something"),
1011+
cli.Flag(new(string), "name", 'n', "The name of something"),
1012+
cli.Flag(new(int), "count", 'c', "Count something", cli.FlagDefault(1)),
10071013
cli.Run(func(ctx context.Context, cmd *cli.Command) error { return nil }),
10081014
)
10091015
if err != nil {

examples/cover/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func main() {
2020
cli.Version("v1.2.3"),
2121
cli.Stdout(os.Stdout),
2222
cli.Example("Do a thing", "demo thing --count"),
23-
cli.Flag(&count, "count", 'c', 0, "Count the thing"),
23+
cli.Flag(&count, "count", 'c', "Count the thing"),
2424
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
2525
fmt.Fprintln(cmd.Stdout(), "Hello from demo, my arguments were: ", cmd.Args())
2626
return nil

examples/quickstart/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func run() error {
2929
cli.Stdout(os.Stdout),
3030
cli.Example("Do a thing", "quickstart something"),
3131
cli.Example("Count the things", "quickstart something --count 3"),
32-
cli.Flag(&count, "count", 'c', 0, "Count the things"),
32+
cli.Flag(&count, "count", 'c', "Count the things"),
3333
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
3434
fmt.Fprintf(cmd.Stdout(), "Hello from quickstart!, my args were: %v, count was %d\n", cmd.Args(), count)
3535
return nil

examples/subcommands/cli.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ func buildSayCommand() (*cli.Command, error) {
3939
cli.Short("Print a message"),
4040
cli.Example("Say a well known phrase", "demo say hello world"),
4141
cli.Example("Now louder", "demo say hello world --shout"),
42-
cli.Flag(&options.shout, "shout", 's', false, "Say the message louder"),
43-
cli.Flag(&options.count, "count", 'c', 0, "Count the things"),
44-
cli.Flag(&options.thing, "thing", 't', "", "Name of the thing"),
45-
cli.Flag(&options.items, "item", 'i', nil, "Items to add to a list"),
42+
cli.Flag(&options.shout, "shout", 's', "Say the message louder"),
43+
cli.Flag(&options.count, "count", 'c', "Count the things"),
44+
cli.Flag(&options.thing, "thing", 't', "Name of the thing"),
45+
cli.Flag(&options.items, "item", 'i', "Items to add to a list"),
4646
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
4747
if options.shout {
4848
for _, arg := range cmd.Args() {
@@ -88,10 +88,10 @@ func buildDoCommand() (*cli.Command, error) {
8888
cli.Example("Do it for a specific duration", "demo do something --duration 1m30s"),
8989
cli.Version("do version"),
9090
cli.Arg(&thing, "thing", "Thing to do"),
91-
cli.Flag(&options.count, "count", 'c', 1, "Number of times to do the thing"),
92-
cli.Flag(&options.fast, "fast", 'f', false, "Do the thing quickly"),
93-
cli.Flag(&options.verbosity, "verbosity", 'v', 0, "Increase the verbosity level"),
94-
cli.Flag(&options.duration, "duration", 'd', 1*time.Second, "Do the thing for a specific duration"),
91+
cli.Flag(&options.count, "count", 'c', "Number of times to do the thing", cli.FlagDefault(1)),
92+
cli.Flag(&options.fast, "fast", 'f', "Do the thing quickly"),
93+
cli.Flag(&options.verbosity, "verbosity", 'v', "Increase the verbosity level"),
94+
cli.Flag(&options.duration, "duration", 'd', "Do the thing for a specific duration", cli.FlagDefault(1*time.Second)),
9595
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
9696
if options.fast {
9797
fmt.Fprintf(

internal/flag/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package flag
2+
3+
import "go.followtheprocess.codes/cli/flag"
4+
5+
// Config represents the internal configuration of a [Flag].
6+
type Config[T flag.Flaggable] struct {
7+
// DefaultValue holds the intended default value of the flag.
8+
DefaultValue T
9+
}

internal/flag/flag.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,7 @@ type Flag[T flag.Flaggable] struct {
3333
//
3434
// The name should be as it appears on the command line, e.g. "force" for a --force flag. An optional
3535
// shorthand can be created by setting short to a single letter value, e.g. "f" to also create a -f version of "force".
36-
//
37-
// If you want the flag to be longhand only, pass "" for short.
38-
//
39-
// var force bool
40-
// flag.New(&force, "force", 'f', false, "Force deletion without confirmation")
41-
func New[T flag.Flaggable](p *T, name string, short rune, value T, usage string) (Flag[T], error) {
36+
func New[T flag.Flaggable](p *T, name string, short rune, usage string, config Config[T]) (Flag[T], error) {
4237
if err := validateFlagName(name); err != nil {
4338
return Flag[T]{}, fmt.Errorf("invalid flag name %q: %w", name, err)
4439
}
@@ -51,14 +46,14 @@ func New[T flag.Flaggable](p *T, name string, short rune, value T, usage string)
5146
p = new(T)
5247
}
5348

54-
*p = value
49+
*p = config.DefaultValue
5550

5651
// If the default value is not the zero value for the type, it is treated as
5752
// significant and shown to the user
58-
if !isZeroIsh(value) {
53+
if !isZeroIsh(*p) {
5954
// \t so that defaults get aligned by tabwriter when the command
6055
// dumps the flags
61-
usage += fmt.Sprintf("\t[default: %v]", value)
56+
usage += fmt.Sprintf("\t[default: %v]", *p)
6257
}
6358

6459
flag := Flag[T]{

0 commit comments

Comments
 (0)