Skip to content

Commit 6769943

Browse files
Breaking: Completely rework how positional arguments are handled (#178)
* Implement the arg.Value approach * Hook arg up (mostly) * Fix command tests expecting args in a different way * Add tests for the arg parsing mechanisms * Rename some stuff * Hook up the help * Update the docs * Update some tests
1 parent 14524a0 commit 6769943

25 files changed

Lines changed: 1655 additions & 1094 deletions

File tree

README.md

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Tiny, simple, but powerful CLI framework for modern Go 🚀
2424
- [Commands](#commands)
2525
- [Sub Commands](#sub-commands)
2626
- [Flags](#flags)
27+
- [Arguments](#arguments)
2728
- [Core Principles](#core-principles)
2829
- [😱 Well behaved libraries don't panic](#-well-behaved-libraries-dont-panic)
2930
- [🧘🏻 Keep it Simple](#-keep-it-simple)
@@ -64,33 +65,29 @@ func main() {
6465

6566
func run() error {
6667
var count int
68+
6769
cmd, err := cli.New(
6870
"quickstart",
6971
cli.Short("Short description of your command"),
7072
cli.Long("Much longer text..."),
7173
cli.Version("v1.2.3"),
7274
cli.Commit("7bcac896d5ab67edc5b58632c821ec67251da3b8"),
7375
cli.BuildDate("2024-08-17T10:37:30Z"),
74-
cli.Allow(cli.MinArgs(1)), // Must have at least one argument
7576
cli.Stdout(os.Stdout),
7677
cli.Example("Do a thing", "quickstart something"),
7778
cli.Example("Count the things", "quickstart something --count 3"),
7879
cli.Flag(&count, "count", 'c', 0, "Count the things"),
79-
cli.Run(runQuickstart(&count)),
80+
cli.Run(func(cmd *cli.Command) error {
81+
fmt.Fprintf(cmd.Stdout(), "Hello from quickstart!, my args were: %v, count was %d\n", cmd.Args(), count)
82+
return nil
83+
}),
8084
)
8185
if err != nil {
8286
return err
8387
}
8488

8589
return cmd.Execute()
8690
}
87-
88-
func runQuickstart(count *int) func(cmd *cli.Command, args []string) error {
89-
return func(cmd *cli.Command, args []string) error {
90-
fmt.Fprintf(cmd.Stdout(), "Hello from quickstart!, my args were: %v, count was %d\n", args, *count)
91-
return nil
92-
}
93-
}
9491
```
9592

9693
Will get you the following:
@@ -215,6 +212,80 @@ The types you can use for flags currently are:
215212
> [!NOTE]
216213
> You basically can't get this wrong, if you try and use an unsupported type, the Go compiler will yell at you
217214
215+
### Arguments
216+
217+
There are two approaches to positional arguments in `cli`, you can either just get the raw arguments yourself with `cmd.Args()` and do whatever you want with them:
218+
219+
```go
220+
cli.New(
221+
"my-command",
222+
// ...
223+
cli.Run(func(cmd *cli.Command) error {
224+
fmt.Fprintf(cmd.Stdout(), "Hello! My arguments were: %v\n", cmd.Args())
225+
return nil
226+
})
227+
)
228+
```
229+
230+
This will return a `[]string` containing all the positional arguments to your command (not flags, they've already been parsed elsewhere!)
231+
232+
Or, if you want to get smarter 🧠 `cli` allows you to define *type safe* representations of your arguments, with or without default values! This follows a similar
233+
idea to [Flags](#flags)
234+
235+
That works like this:
236+
237+
```go
238+
// Define a struct to hold your arguments
239+
type myArgs struct {
240+
name string
241+
age int
242+
employed bool
243+
}
244+
245+
// Instantiate it
246+
var args myArgs
247+
248+
// Tell cli about your arguments with the Arg option
249+
cli.New(
250+
"my-command",
251+
// ... other options here
252+
cli.Arg(&args.name, "name", "The name of a person"),
253+
cli.Arg(&args.age, "age", "How old the person is"),
254+
cli.Arg(&args.employed, "employed", "Whether they are employed", cli.ArgDefault(true))
255+
)
256+
```
257+
258+
And just like [Flags](#flags), your argument types are all inferred and parsed automatically ✨, and you get nicer `--help` output too! Have a look at the [`./examples`](https://github.com/FollowTheProcess/cli/tree/main/examples)
259+
to see more!
260+
261+
> [!NOTE]
262+
> Just like flags, you can't really get this wrong. The types you can use for arguments are part of a generic constraint so using the wrong type results in a compiler error
263+
264+
The types you can currently use for positional args are:
265+
266+
- `int`
267+
- `int8`
268+
- `int16`
269+
- `int32`
270+
- `int64`
271+
- `uint`
272+
- `uint8`
273+
- `uint16`
274+
- `uint32`
275+
- `uint64`
276+
- `uintptr`
277+
- `float32`
278+
- `float64`
279+
- `string`
280+
- `bool`
281+
- `[]byte` (interpreted as a hex string)
282+
- `time.Time`
283+
- `time.Duration`
284+
- `net.IP`
285+
286+
> [!WARNING]
287+
> Slice types are not supported (yet), for those you need to use the `cmd.Args()` method to get the arguments manually. I'm working on this!
288+
218289
## Core Principles
219290

220291
When designing and implementing `cli`, I had some core goals and guiding principles for implementation.
@@ -269,7 +340,6 @@ cmd, err := cli.New(
269340
cli.Short("Short description of your command"),
270341
cli.Long("Much longer text..."),
271342
cli.Version("v1.2.3"),
272-
cli.Allow(cli.MinArgs(1)),
273343
cli.Stdout(os.Stdout),
274344
cli.Example("Do a thing", "test run thing --now"),
275345
cli.Flag(&count, "count", 'c', 0, "Count the things"),
@@ -319,8 +389,8 @@ I built `cli` for my own uses really, so I've quickly adopted it across a number
319389

320390
- <https://github.com/FollowTheProcess/txtract>
321391
- <https://github.com/FollowTheProcess/tag>
322-
- <https://github.com/FollowTheProcess/spok>
323392
- <https://github.com/FollowTheProcess/gowc>
393+
- <https://github.com/FollowTheProcess/zap>
324394

325395
[spf13/cobra]: https://github.com/spf13/cobra
326396
[spf13/pflag]: https://github.com/spf13/pflag

arg/arg.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Package arg provides mechanisms for defining and configuring command line arguments.
2+
package arg
3+
4+
import (
5+
"net"
6+
"time"
7+
)
8+
9+
// TODO(@FollowTheProcess): Slices of stuff
10+
11+
// Argable is a type constraint that defines any type capable of being parsed as a command line arg.
12+
type Argable interface {
13+
int |
14+
int8 |
15+
int16 |
16+
int32 |
17+
int64 |
18+
uint |
19+
uint8 |
20+
uint16 |
21+
uint32 |
22+
uint64 |
23+
uintptr |
24+
float32 |
25+
float64 |
26+
string |
27+
bool |
28+
[]byte |
29+
time.Time |
30+
time.Duration |
31+
net.IP
32+
}

args.go

Lines changed: 0 additions & 160 deletions
This file was deleted.

0 commit comments

Comments
 (0)