1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
|
# CLI Enumeration Flags
[](https://pkg.go.dev/github.com/thediveo/enumflag/v2)
[](https://img.shields.io/github/license/thediveo/enumflag)

[](https://goreportcard.com/report/github.com/thediveo/enumflag/v2)

`enumflag/v2` is a Golang package which supplements the Golang CLI flag packages
[spf13/cobra](https://github.com/spf13/cobra) and
[spf13/pflag](https://github.com/spf13/pflag) with enumeration flags, including
support for enumeration slices. Thanks to Go generics, `enumflag/v2` now
provides type-safe enumeration flags (and thus requires Go 1.18 or later).
> The v2 API is source-compatible with v0 unless you've used the `Get()` method
> in the past. However, since the use of Go generics might be a breaking change
> to downstream projects the semantic major version of `enumflag` thus went from
> v0 straight to v2.
For instance, users can specify enum flags as `--mode=foo` or `--mode=bar`,
where `foo` and `bar` are valid enumeration values. Other values which are not
part of the set of allowed enumeration values cannot be set and raise CLI flag
errors. In case of an enumeration _slice_ flag users can specify multiple
enumeration values either with a single flag `--mode=foo,bar` or multiple flag
calls, such as `--mode=foo --mode=bar`.
Application programmers then simply deal with enumeration values in form of
uints (or ints, _erm_, anything that satisfies `comparable`s),
liberated from parsing strings and validating enumeration flags.
For devcontainer instructions, please see the [section "DevContainer"
below](#devcontainer).
## Alternatives
In case you are just interested in string-based one-of-a-set flags, then the
following packages offer you a minimalist approach:
- [hashicorp/packer/helper/enumflag](https://godoc.org/github.com/hashicorp/packer/helper/enumflag)
really is a reduced-to-the-max version without any whistles and bells.
- [creachadair/goflags/enumflag](https://godoc.org/github.com/creachadair/goflags/enumflag)
has a similar, but slightly more elaborate API with additional "indices" for
enumeration values.
But if you instead want to handle one-of-a-set flags as properly typed
enumerations instead of strings, or if you need (multiple-of-a-set) slice
support, then please read on.
## Installation
To add `enumflag/v2` as a dependency, in your Go module issue:
```bash
go get github.com/thediveo/enumflag/v2
```
## How To Use
- [start with your own enum types](#start-with-your-own-enum-types),
- optional: [shell completion](#shell-completion),
- optional: [use existing enum types and non-zero defaults](#use-existing-enum-types),
- optional: [CLI flag with default](#cli-flag-with-default),
- optional: [CLI flag without a default value](#cli-flag-without-default),
- optional: [slice of enums](#slice-of-enums).
### Start With Your Own Enum Types
Without further ado, here's how to define and use enum flags in your own
applications...
```go
import (
"fmt"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies comparable.
type FooMode enumflag.Flag
// ② Define the enumeration values for FooMode.
const (
Foo FooMode = iota
Bar
)
// ③ Map enumeration values to their textual representations (value
// identifiers).
var FooModeIds = map[FooMode][]string{
Foo: {"foo"},
Bar: {"bar"},
}
// ④ Now use the FooMode enum flag. If you want a non-zero default, then
// simply set it here, such as in "foomode = Bar".
var foomode FooMode
func main() {
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("mode is: %d=%q\n",
foomode,
cmd.PersistentFlags().Lookup("mode").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().VarP(
enumflag.New(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
rootCmd.SetArgs([]string{"--mode", "bAr"})
_ = rootCmd.Execute()
}
```
The boilerplate pattern is always the same:
1. Define your own new enumeration type, such as `type FooMode enumflag.Flag`.
2. Define the constants in your enumeration.
3. Define the mapping of the constants onto enum values (textual
representations).
4. Somewhere, declare a flag variable of your enum flag type.
- If you want to use a non-zero default enum value, just go ahead and set
it: `var foomode = Bar`. It will be used correctly.
5. Wire up your flag variable to its flag long and short names, et cetera.
### Shell Completion
Dynamic flag completion can be enabled by calling the `RegisterCompletion(...)`
receiver of an enum flag (more precise: flag value) created using
`enumflag.New(...)`. `enumflag` supports dynamic flag completion for both scalar
and slice enum flags. Unfortunately, due to the cobra API design it isn't
possible for `enumflag` to offer a fluent API. Instead, creation, adding, and
registering have to be carried out as separate instructions.
```go
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
ef := enumflag.New(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive)
rootCmd.PersistentFlags().VarP(
ef,
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
// ⑥ register completion
ef.RegisterCompletion(rootCmd, "mode", enumflag.Help[FooMode]{
Foo: "foos the output",
Bar: "bars the output",
})
```
Please note for shell completion to work, your root command needs to have at
least one (explicit) sub command. Otherwise, `cobra` won't automatically add an
additional `completion` sub command. For more details, please refer to cobra's
documentation on [Generating shellcompletions](https://github.com/spf13/cobra/blob/main/site/content/completions/_index.md).
### Use Existing Enum Types
A typical example might be your application using a 3rd party logging package
and you want to offer a `-v` log level CLI flag. Here, we use the existing 3rd
party enum values and set a non-zero default for our logging CLI flag.
Considering the boiler plate shown above, we can now leave out steps ① and ②,
because these definitions come from a 3rd party package. We only need to
supply the textual enum names as ③.
```go
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
func main() {
// ①+② skip "define your own enum flag type" and enumeration values, as we
// already have a 3rd party one.
// ③ Map 3rd party enumeration values to their textual representations
var LoglevelIds = map[log.Level][]string{
log.TraceLevel: {"trace"},
log.DebugLevel: {"debug"},
log.InfoLevel: {"info"},
log.WarnLevel: {"warning", "warn"},
log.ErrorLevel: {"error"},
log.FatalLevel: {"fatal"},
log.PanicLevel: {"panic"},
}
// ④ Define your enum flag value and set the your logging default value.
var loglevel log.Level = log.WarnLevel
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("logging level is: %d=%q\n",
loglevel,
cmd.PersistentFlags().Lookup("log").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().Var(
enumflag.New(&loglevel, "log", LoglevelIds, enumflag.EnumCaseInsensitive),
"log",
"sets logging level; can be 'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'panic'")
// Defaults to what we set above: warn level.
_ = rootCmd.Execute()
// User specifies a specific level, such as log level.
rootCmd.SetArgs([]string{"--log", "debug"})
_ = rootCmd.Execute()
}
```
### CLI Flag With Default
Sometimes you might want a CLI enum flag to have a default value when the user
just specifies the CLI flag **without its value**. A good example is the
`--color` flag of the `ls` command:
- if just specified as `--color` without a value, it
will default to the value of `auto`;
- otherwise, as specific value can be given, such as
- `--color=always`,
- `--color=never`,
- or even `--color=auto`.
In such situations, use spf13/pflags's
[`NoOptDefVal`](https://godoc.org/github.com/spf13/pflag#Flag) to set the
flag's default value *as text*, if the flag is on the command line without any
options.
The gist here is as follows, please see also
[colormode.go](https://github.com/TheDiveO/lxkns/blob/master/cmd/internal/pkg/style/colormode.go)
from my [lxkns](https://github.com/TheDiveO/lxkns) Linux namespaces discovery
project:
```go
rootCmd.PersistentFlags().VarP(
enumflag.New(&colorize, "color", colorModeIds, enumflag.EnumCaseSensitive),
"color", "c",
"colorize the output; can be 'always' (default if omitted), 'auto',\n"+
"or 'never'")
rootCmd.PersistentFlags().Lookup("color").NoOptDefVal = "always"
```
### CLI Flag Without Default
In other situations you might _not_ want to have a default value set, because a
particular CLI flag is mandatory (using cobra's
[MarkFlagRequired](https://pkg.go.dev/github.com/spf13/cobra#MarkFlagRequired)).
Here, cobra's help should not show a (useless) default enum flag setting but
only the available enum values.
**Don't assign the zero value** of your enum type to any value, except the
"non-existing" default.
```go
// ② Define the enumeration values for FooMode; do not assign the zero value to
// any enum value except for the "no default" default.
const (
NoDefault FooMode = iota // optional; must be the zero value.
Foo // make sure to not use the zero value.
Bar
)
```
Also, **don't map the zero value** of your enum type.
```go
// ③ Map enumeration values to their textual representations (value
// identifiers).
var FooModeIds = map[FooMode][]string{
// ...do NOT include/map the "no default" zero value!
Foo: {"foo"},
Bar: {"bar"},
}
```
Finally, simply use `enumflag.NewWithoutDefault` instead of `enumflag.New` –
that's all.
```go
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().VarP(
enumflag.NewWithoutDefault(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
```
### Slice of Enums
For a slice of enumerations, simply declare your variable to be a slice of your
enumeration type and then use `enumflag.NewSlice(...)` instead of
`enumflag.New(...)`.
```go
import (
"fmt"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies comparable.
type MooMode enumflag.Flag
// ② Define the enumeration values for FooMode.
const (
Moo MooMode = (iota + 1) * 111
Møø
Mimimi
)
// ③ Map enumeration values to their textual representations (value
// identifiers).
var MooModeIds = map[MooMode][]string{
Moo: {"moo"},
Møø: {"møø"},
Mimimi: {"mimimi"},
}
func Example_slice() {
// ④ Define your enum slice flag value.
var moomode []MooMode
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("mode is: %d=%q\n",
moomode,
cmd.PersistentFlags().Lookup("mode").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enumm slice flag.
rootCmd.PersistentFlags().VarP(
enumflag.NewSlice(&moomode, "mode", MooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"can be any combination of 'moo', 'møø', 'mimimi'")
rootCmd.SetArgs([]string{"--mode", "Moo,møø"})
_ = rootCmd.Execute()
}
```
## DevContainer
> [!CAUTION]
>
> Do **not** use VSCode's "~~Dev Containers: Clone Repository in Container
> Volume~~" command, as it is utterly broken by design, ignoring
> `.devcontainer/devcontainer.json`.
1. `git clone https://github.com/thediveo/enumflag`
2. in VSCode: Ctrl+Shift+P, "Dev Containers: Open Workspace in Container..."
3. select `enumflag.code-workspace` and off you go...
## Contributing
Please see [CONTRIBUTING.md](CONTRIBUTING.md).
## Copyright and License
`lxkns` is Copyright 2020, 2025 Harald Albrecht, and licensed under the Apache
License, Version 2.0.
|