enumflag

package module
v0.10.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2020 License: Apache-2.0 Imports: 4 Imported by: 36

README

CLI Enumeration Flags

GoDoc GitHub build and test Go Report Card Coverage

enumflag is a Golang package which supplements the Golang CLI flag packages spf13/cobra and spf13/pflag with enumeration flags, including support for enumeration slices.

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), liberated from parsing strings and validating enumeration flags.

Alternatives

In case you are just interested in string-based one-of-a-set flags, then the following packages offer you a minimalist approach:

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.

How To Use

Start With Your Own Enum Types

Without further ado, here's how to define and use enum flags in your own applications...

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/thediveo/enumflag"
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag, but
// it doesn't need to be as long as it is compatible with enumflag.Flag, so
// either an int or uint.
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.
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 ③.

import (
    "fmt"
    "os"

    log "github.com/sirupsen/logrus"
    "github.com/spf13/cobra"
    "github.com/thediveo/enumflag"
)

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 specifies the CLI flag, but not 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 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 from my lxkns Linux namespaces discovery project:

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"
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(...).

import (
    "fmt"

    "github.com/spf13/cobra"
    "github.com/thediveo/enumflag"
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag, but
// it doesn't need to be as long as it is compatible with enumflag.Flag, so
// either an int or uint.
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"},
}

// User-defined enum flag types should be derived from "enumflag.Flag"; however
// this is not strictly necessary as long as they can be converted into the
// "enumflag.Flag" type. Actually, "enumflag.Flag" is just a fancy name for an
// "uint". In order to use such user-defined enum flags as flag slices, simply
// wrap them using enumflag.NewSlice.
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()
}

lxkns is Copyright 2020 Harald Albrecht, and licensed under the Apache License, Version 2.0.

Documentation

Overview

Package enumflag supplements the Golang CLI flag handling packages spf13/cobra and spf13/pflag with enumeration flags.

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.

Application programmers then simply deal with enumeration values in form of uints (or ints), liberated from parsing strings and validating enumeration flags.

Example

User-defined enum flag types should be derived from "enumflag.Flag"; however this is not strictly necessary as long as they can be converted into the "enumflag.Flag" type. Actually, "enumflag.Flag" is just a fancy name for an "uint". In order to use such user-defined enum flags, simply wrap them using enumflag.New.

package main

import (
	"fmt"

	"github.com/spf13/cobra"
	"github.com/thediveo/enumflag"
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag, but
// it doesn't need to be as long as it is compatible with enumflag.Flag, so
// either an int or uint.
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"},
}

// User-defined enum flag types should be derived from "enumflag.Flag"; however
// this is not strictly necessary as long as they can be converted into the
// "enumflag.Flag" type. Actually, "enumflag.Flag" is just a fancy name for an
// "uint". In order to use such user-defined enum flags, simply wrap them using
// enumflag.New.
func main() {
	// ④ Define your enum flag value.
	var foomode FooMode
	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()
}
Output:

mode is: 1="bar"
Example (External)
package main

import (
	"fmt"
	"os"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/thediveo/enumflag"
)

func init() {
	log.SetOutput(os.Stdout)
}

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'")

	_ = rootCmd.Execute()
	rootCmd.SetArgs([]string{"--log", "debug"})
	_ = rootCmd.Execute()
}
Output:

logging level is: 3="warning"
logging level is: 5="debug"
Example (Slice)

User-defined enum flag types should be derived from "enumflag.Flag"; however this is not strictly necessary as long as they can be converted into the "enumflag.Flag" type. Actually, "enumflag.Flag" is just a fancy name for an "uint". In order to use such user-defined enum flags as flag slices, simply wrap them using enumflag.NewSlice.

package main

import (
	"fmt"

	"github.com/spf13/cobra"
	"github.com/thediveo/enumflag"
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag, but
// it doesn't need to be as long as it is compatible with enumflag.Flag, so
// either an int or uint.
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"},
}

// User-defined enum flag types should be derived from "enumflag.Flag"; however
// this is not strictly necessary as long as they can be converted into the
// "enumflag.Flag" type. Actually, "enumflag.Flag" is just a fancy name for an
// "uint". In order to use such user-defined enum flags as flag slices, simply
// wrap them using enumflag.NewSlice.
func main() {
	// ④ 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 enum 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()
}
Output:

mode is: [111 222]="[moo,møø]"

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EnumCaseSensitivity

type EnumCaseSensitivity bool

EnumCaseSensitivity specifies whether the textual representations of enum values are considered to be case sensitive, or not.

const (
	EnumCaseInsensitive EnumCaseSensitivity = false
	EnumCaseSensitive   EnumCaseSensitivity = true
)

Controls whether the textual representations for enum values are case sensitive, or not.

type EnumSliceValue added in v0.10.0

type EnumSliceValue struct {
	*EnumValue
	// contains filtered or unexported fields
}

EnumSliceValue wraps a slice of enum values for a user-defined enum type.

func NewSlice added in v0.10.0

func NewSlice(flag interface{}, typename string, mapping interface{}, sensitivity EnumCaseSensitivity) *EnumSliceValue

NewSlice warps a given enum slice variable so that it can ve used as a flag Value with pflag.Var and pflag.VarP. It takes the same parameters as New, with the exception of expecting a slice instead of a single enum var.

func (*EnumSliceValue) Set added in v0.10.0

func (e *EnumSliceValue) Set(val string) error

Set either sets or merges the enum slice flag: the first call will set the flag value to the specified set of enum values. Later calls then merge enum values instead of replacing the current set. This mimics the behavior of pflag's slice flags.

func (*EnumSliceValue) String added in v0.10.0

func (e *EnumSliceValue) String() string

String returns the textual representation of an enumeration (flag) slice, which can contain multiple enumeration values from the same enumeration simultaneously. In case multiple textual representations (=identifiers) exist for the same enumeration value, then only the first textual representation is returned, which is considered to be the canonical one.

type EnumValue added in v0.10.0

type EnumValue struct {
	// contains filtered or unexported fields
}

EnumValue wraps a user-defined enum type value and implements the pflag.Value interface, so the user's enum type value can directly be used with the fine pflag drop-in package for Golang CLI flags.

func New added in v0.10.0

func New(flag interface{}, typename string, mapping interface{}, sensitivity EnumCaseSensitivity) *EnumValue

New wraps a given enum variable so that it can be used as a flag Value with pflag.Var and pflag.VarP. The specified flag must be a pointer to a user-defined enum value, as otherwise the flag value cannot be managed (changed) later on, when a CLI user tries to set it via its corresponding CLI flag.

func (*EnumValue) Get added in v0.10.0

func (e *EnumValue) Get() interface{}

Get returns the managed enum value as a convenience.

func (*EnumValue) Set added in v0.10.0

func (e *EnumValue) Set(val string) error

Set sets the enum flag to the specified enum value. If the specified value isn't a valid enum value, then the enum flag will be unchanged and an error returned instead.

func (*EnumValue) String added in v0.10.0

func (e *EnumValue) String() string

String returns the textual representation of an enumeration (flag) value. In case multiple textual representations (=identifiers) exist for the same enumeration value, then only the first textual representation is returned, which is considered to be the canonical one.

func (*EnumValue) Type added in v0.10.0

func (e *EnumValue) Type() string

Type returns the name of the flag value type. The type name is used in error message.

type Flag

type Flag uint

Flag represents a CLI (enumeration) flag which can take on only a single enumeration value out of a fixed set of enumeration values. Applications using the enumflag package might want to derive their enumeration flags from Flag, such as "type MyFoo enumflag.Flag", but they don't need to. The only requirement for user-defined enumeration flags is that they must be compatible with the Flag type.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL