Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func run() error {
cli.Stdout(os.Stdout),
cli.Example("Do a thing", "quickstart something"),
cli.Example("Count the things", "quickstart something --count 3"),
cli.Flag(&count, "count", 'c', 0, "Count the things"),
cli.Flag(&count, "count", 'c', "Count the things"),
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
fmt.Fprintf(cmd.Stdout(), "Hello from quickstart!, my args were: %v, count was %d\n", cmd.Args(), count)
return nil
Expand Down Expand Up @@ -168,10 +168,10 @@ func buildCmd() (*cli.Command, error) {
return cli.New(
// ...
// Signature is cli.Flag(*T, name, shorthand, default, description)
cli.Flag(&options.name, "name", 'n', "", "The name of something"),
cli.Flag(&options.force, "force", cli.NoShortHand, false, "Force delete without confirmation"),
cli.Flag(&options.size, "size", 's', 0, "Size of something"),
cli.Flag(&options.items, "items", 'i', nil, "Items to include"),
cli.Flag(&options.name, "name", 'n', "The name of something"),
cli.Flag(&options.force, "force", cli.NoShortHand, "Force delete without confirmation"),
cli.Flag(&options.size, "size", 's', "Size of something"),
cli.Flag(&options.items, "items", 'i', "Items to include"),
// ...
)
}
Expand Down Expand Up @@ -374,7 +374,7 @@ Consider the following example of a bad shorthand value:
var delete bool

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

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:
Expand All @@ -383,14 +383,14 @@ In `cli` this is impossible as we use `rune` as the type for a flag shorthand, s
var delete bool

// Ahhh, that's better
cli.New("demo", cli.Flag(&delete, "delete", 'd', false, "Delete something"))
cli.New("demo", cli.Flag(&delete, "delete", 'd', "Delete something"))
```

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

```go
var delete bool
cli.New("demo", cli.Flag(&delete, "delete", cli.NoShortHand, false, "Delete something"))
cli.New("demo", cli.Flag(&delete, "delete", cli.NoShortHand, "Delete something"))
```

## In the Wild
Expand Down
4 changes: 2 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ func New(name string, options ...Option) (*Command, error) {
}

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

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

errs = errors.Join(errs, err)
if errs != nil {
Expand Down
44 changes: 25 additions & 19 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestExecute(t *testing.T) {

return nil
}),
cli.Flag(&force, "force", 'f', false, "Force something"),
cli.Flag(&force, "force", 'f', "Force something"),
}

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

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

return nil
}),
cli.Flag(&deleteMe, "delete", 'd', false, "Delete for sub2"),
cli.Flag(&number, "number", 'n', -1, "Number for sub2"),
cli.Flag(&deleteMe, "delete", 'd', "Delete for sub2"),
cli.Flag(&number, "number", 'n', "Number for sub2", cli.FlagDefault(-1)),
}

opts := slices.Concat(defaultOpts, tt.sub2Options)
Expand Down Expand Up @@ -433,7 +433,7 @@ func TestHelp(t *testing.T) {
cli.OverrideArgs([]string{"--help"}),
cli.Arg(new(string), "src", "The file to copy"), // This one is required
cli.Arg(new(string), "dest", "Destination to copy to", cli.ArgDefault("destination.txt")), // This one is optional
cli.Flag(new(flag.Count), "verbosity", 'v', 0, "Increase the verbosity level"),
cli.Flag(new(flag.Count), "verbosity", 'v', "Increase the verbosity level"),
cli.Run(func(ctx context.Context, cmd *cli.Command) error { return nil }),
},
wantErr: false,
Expand Down Expand Up @@ -493,10 +493,16 @@ func TestHelp(t *testing.T) {
cli.Short("A cool CLI to do things"),
cli.Long("A longer, probably multiline description"),
cli.SubCommands(sub1, sub2),
cli.Flag(new(bool), "delete", 'd', false, "Delete something"),
cli.Flag(new(int), "count", flag.NoShortHand, -1, "Count something"),
cli.Flag(new([]string), "things", flag.NoShortHand, nil, "Names of things"),
cli.Flag(new([]string), "more", flag.NoShortHand, []string{"one", "two"}, "Names of things with a default"),
cli.Flag(new(bool), "delete", 'd', "Delete something"),
cli.Flag(new(int), "count", flag.NoShortHand, "Count something", cli.FlagDefault(-1)),
cli.Flag(new([]string), "things", flag.NoShortHand, "Names of things"),
cli.Flag(
new([]string),
"more",
flag.NoShortHand,
"Names of things with a default",
cli.FlagDefault([]string{"one", "two"}),
),
},
wantErr: false,
},
Expand Down Expand Up @@ -735,16 +741,16 @@ func TestOptionValidation(t *testing.T) {
{
name: "flag already exists",
options: []cli.Option{
cli.Flag(new(int), "count", 'c', 0, "Count something"),
cli.Flag(new(int), "count", 'c', 0, "Count something (again)"),
cli.Flag(new(int), "count", 'c', "Count something"),
cli.Flag(new(int), "count", 'c', "Count something (again)"),
},
errMsg: `flag "count" already defined`,
},
{
name: "flag short already exists",
options: []cli.Option{
cli.Flag(new(int), "count", 'c', 0, "Count something"),
cli.Flag(new(string), "config", 'c', "", "Path to config file"),
cli.Flag(new(int), "count", 'c', "Count something"),
cli.Flag(new(string), "config", 'c', "Path to config file"),
},
errMsg: `could not add flag "config" to command "test": shorthand "c" already in use for flag "count"`,
},
Expand Down Expand Up @@ -884,8 +890,8 @@ func TestCommandOptionOrder(t *testing.T) {
cli.Arg(new(string), "third", "Third arg"),
cli.Version("v1.2.3"),
cli.SubCommands(sub),
cli.Flag(&f, "flag", 'f', false, "Set a bool flag"),
cli.Flag(&count, "count", 'c', 0, "Count a thing"),
cli.Flag(&f, "flag", 'f', "Set a bool flag"),
cli.Flag(&count, "count", 'c', "Count a thing"),
}

baseLineOptions := slices.Concat(
Expand Down Expand Up @@ -1001,9 +1007,9 @@ func BenchmarkNew(b *testing.B) {
cli.Version("dev"),
cli.Commit("dfdddaf"),
cli.Example("An example", "bench --help"),
cli.Flag(new(bool), "force", 'f', false, "Force something"),
cli.Flag(new(string), "name", 'n', "", "The name of something"),
cli.Flag(new(int), "count", 'c', 1, "Count something"),
cli.Flag(new(bool), "force", 'f', "Force something"),
cli.Flag(new(string), "name", 'n', "The name of something"),
cli.Flag(new(int), "count", 'c', "Count something", cli.FlagDefault(1)),
cli.Run(func(ctx context.Context, cmd *cli.Command) error { return nil }),
)
if err != nil {
Expand Down
Binary file modified docs/img/cancel.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/namedargs.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/quickstart.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/subcommands.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/cover/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func main() {
cli.Version("v1.2.3"),
cli.Stdout(os.Stdout),
cli.Example("Do a thing", "demo thing --count"),
cli.Flag(&count, "count", 'c', 0, "Count the thing"),
cli.Flag(&count, "count", 'c', "Count the thing"),
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
fmt.Fprintln(cmd.Stdout(), "Hello from demo, my arguments were: ", cmd.Args())
return nil
Expand Down
2 changes: 1 addition & 1 deletion examples/quickstart/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func run() error {
cli.Stdout(os.Stdout),
cli.Example("Do a thing", "quickstart something"),
cli.Example("Count the things", "quickstart something --count 3"),
cli.Flag(&count, "count", 'c', 0, "Count the things"),
cli.Flag(&count, "count", 'c', "Count the things"),
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
fmt.Fprintf(cmd.Stdout(), "Hello from quickstart!, my args were: %v, count was %d\n", cmd.Args(), count)
return nil
Expand Down
16 changes: 8 additions & 8 deletions examples/subcommands/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ func buildSayCommand() (*cli.Command, error) {
cli.Short("Print a message"),
cli.Example("Say a well known phrase", "demo say hello world"),
cli.Example("Now louder", "demo say hello world --shout"),
cli.Flag(&options.shout, "shout", 's', false, "Say the message louder"),
cli.Flag(&options.count, "count", 'c', 0, "Count the things"),
cli.Flag(&options.thing, "thing", 't', "", "Name of the thing"),
cli.Flag(&options.items, "item", 'i', nil, "Items to add to a list"),
cli.Flag(&options.shout, "shout", 's', "Say the message louder"),
cli.Flag(&options.count, "count", 'c', "Count the things"),
cli.Flag(&options.thing, "thing", 't', "Name of the thing"),
cli.Flag(&options.items, "item", 'i', "Items to add to a list"),
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
if options.shout {
for _, arg := range cmd.Args() {
Expand Down Expand Up @@ -88,10 +88,10 @@ func buildDoCommand() (*cli.Command, error) {
cli.Example("Do it for a specific duration", "demo do something --duration 1m30s"),
cli.Version("do version"),
cli.Arg(&thing, "thing", "Thing to do"),
cli.Flag(&options.count, "count", 'c', 1, "Number of times to do the thing"),
cli.Flag(&options.fast, "fast", 'f', false, "Do the thing quickly"),
cli.Flag(&options.verbosity, "verbosity", 'v', 0, "Increase the verbosity level"),
cli.Flag(&options.duration, "duration", 'd', 1*time.Second, "Do the thing for a specific duration"),
cli.Flag(&options.count, "count", 'c', "Number of times to do the thing", cli.FlagDefault(1)),
cli.Flag(&options.fast, "fast", 'f', "Do the thing quickly"),
cli.Flag(&options.verbosity, "verbosity", 'v', "Increase the verbosity level"),
cli.Flag(&options.duration, "duration", 'd', "Do the thing for a specific duration", cli.FlagDefault(1*time.Second)),
cli.Run(func(ctx context.Context, cmd *cli.Command) error {
if options.fast {
fmt.Fprintf(
Expand Down
9 changes: 9 additions & 0 deletions internal/flag/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package flag

import "go.followtheprocess.codes/cli/flag"

// Config represents the internal configuration of a [Flag].
type Config[T flag.Flaggable] struct {
// DefaultValue holds the intended default value of the flag.
DefaultValue T
}
13 changes: 4 additions & 9 deletions internal/flag/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ type Flag[T flag.Flaggable] struct {
//
// The name should be as it appears on the command line, e.g. "force" for a --force flag. An optional
// shorthand can be created by setting short to a single letter value, e.g. "f" to also create a -f version of "force".
//
// If you want the flag to be longhand only, pass "" for short.
//
// var force bool
// flag.New(&force, "force", 'f', false, "Force deletion without confirmation")
func New[T flag.Flaggable](p *T, name string, short rune, value T, usage string) (Flag[T], error) {
func New[T flag.Flaggable](p *T, name string, short rune, usage string, config Config[T]) (Flag[T], error) {
if err := validateFlagName(name); err != nil {
return Flag[T]{}, fmt.Errorf("invalid flag name %q: %w", name, err)
}
Expand All @@ -51,14 +46,14 @@ func New[T flag.Flaggable](p *T, name string, short rune, value T, usage string)
p = new(T)
}

*p = value
*p = config.DefaultValue

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

flag := Flag[T]{
Expand Down
Loading
Loading