Skip to content

cardinalby/go-struct-flags

Repository files navigation

test list Go Reference

Parse command line flags into a struct with tags

The library is a wrapper around the standard library's flag package providing a way to parse command line flags into a struct fields

The main features are:

  • Ignore unknown flags
  • Distinguish not passed flags from default values that is difficult with the standard flag package
  • More convenient way to define flags using struct tags
  • Using nested structs for similar flag groups

Example

go get github.com/cardinalby/go-struct-flags

Define your flags struct with tags:

import (
    flago "github.com/cardinalby/go-struct-flags"
)

type MyFlags struct {
    // Verbose can be filled both by "-v" and "-verbose" flags
    Verbose   int      `flags:"verbose,v" flagUsage:"verbose mode: 1,2,3"`
	
    // Login is optional, it will be set only if passed
    Login     *string  `flag:"login" flagUsage:"user login"`
	
    // FileNames will contain flag.Args() after Parse()
    FileNames []string `flagArgs:"true"`
}

// This is the way to set defaults:
myFlags := MyFlags{}

Create new FlagSet and register the struct:

flagSet := flago.NewFlagSet("myApp", flag.ExitOnError)
if err := flagSet.StructVar(&myFlags); err != nil {
    // error can happen if struct tags are invalid
    panic(err)
}

Parse command line flags:

// Use short form of "-v"
// Normally you would use os.Args[1:] instead of hardcoded values
if err := flagSet.Parse([]string{"-v", "2", "--login", "user1", "file1", "file2"}); err != nil {
    // Parse has default behavior
    panic(err)
}

// myFlags.Verbose == 2
// *myFlags.Login == "user1"
// myFlags.FileNames == []string{"file1", "file2"}

Check if flag is passed

Using pointer fields enables you to distinguish not passed flags from default values:

// Use long form of "--verbose" and don't pass "--login"
if err := flagSet.Parse([]string{"--verbose", "2"}); err != nil {
    // Parse has default behavior
    panic(err)
}

// myFlags.Verbose == 2
// myFlags.Login == nil
// len(myFlags.FileNames) == 0

Set defaults

Unlike std flag package the library doesn't provide a way to explicitly set "default" values.

You can do it in native way just assigning them to struct fields before parsing (both for pointer and non-pointer fields)

defaultLogin := "admin"
myFlags := MyFlags{
    Verbose: 1,
    Login: &defaultLogin,
}

// No flags are passed
if err := flagSet.Parse("file1", "file2"); err != nil {
    // Parse has default behavior
    panic(err)
}

// myFlags.Verbose == 1       <--- hasn't been changed
// *myFlags.Login == "admin"  <--- hasn't been changed
// myFlags.FileNames == []string{"file1", "file2"}

Using nested structs

You can create logically similar flag groups assigning them a prefix using nested structs:

type PersonFlags struct {
    Name  string `flag:"name" flagUsage:"person name"`
    Email string `flag:"email" flagUsage:"person email"`
}
type MyParentFlags struct {
    Sender   PersonFlags `flagPrefix:"sender-" flagUsagePrefix:"sender "`
    Receiver PersonFlags `flagPrefix:"receiver-" flagUsagePrefix:"receiver "`
}

flagSet := NewFlagSet("myApp", flag.ExitOnError)
myFlags := MyParentFlags{}
if err := flagSet.StructVar(&myFlags); err != nil {
    panic(err)
}

if err := flagSet.Parse([]string{
    "--sender-name", "John",
    "--sender-email", "[email protected]",
    "--receiver-name", "Dave",
    "--receiver-email", "[email protected]",
}); err != nil {
    panic(err)
}

// myFlags.Sender.Name == "John"
// myFlags.Sender.Email == "[email protected]"
// myFlags.Receiver.Name == "Dave"
// myFlags.Receiver.Email == "[email protected]"

See tests for more examples.

Constructing

The library provides two constructors for flago.FlagSet:

  • Wrap(*flag.FlagSet) that wraps the existing std flag.FlagSet instance that can have some flags already registered or be used to register new flags using its methods.
  • NewFlagSet() creates new flag.FlagSet instance and sets its Usage to flago.DefaultUsage
  • Functions with the names matching flago.FlagSet method names for a default flago.CommandLine FlagSet instance are available in the same manner as in the standard flag package.

Configure Parse() behavior

πŸ”Ή Ignore unknown flags

SetIgnoreUnknown(true) method call will make Parse() ignore unknown flags instead of returning an error.

To retrieve unknown flags that have been ignored, call GetIgnoredArgs() after Parse().

With unknown flags it's not always clear how to treat them: as bool flags or as flags with the following values. See docs for SetIgnoreUnknownAmbiguousAsBoolFlags(...) for details.

πŸ”Ή Allow parsing multiple aliases

SetAllowParsingMultipleAliases(true) method call will make Parse() not return an error if multiple aliases of the same field are passed. The last passed value will be used.

Default behavior is to return an error containing flago.ErrMultipleAliases.

Supported struct tags

To parse flags and args to struct fields you should use StructVar() or StructVarWithPrefix() methods.

The methods accept optional arguments of pointers to ignored fields. These fields will not be registered as flags.

The struct fields that don't have any "flag" tags will be ignored.

Define named flag(s) for a field

πŸ”» flag="name"

Defines flag name for a field. Unlike json package, if the name is not set, the field will be ignored.

  • Field should have type supported by flag package or be pointer to such type.
  • If the field is a pointer, it will be set only if the flag is passed to Parse().
  • If it's not a pointer and is the correspondent flag is not passed, its default value will remain.

πŸ”» flags="name1,name2"

Same as flag but defines multiple comma-separated flag names (aliases) for a field.

  • You should use either flag or flags tag for a field, not both.
  • By default, Parse() will return an error containing flago.ErrMultipleAliases if multiple aliases of the same field are passed. You can change this behavior using SetAllowParsingMultipleAliases(true)

πŸ”Έ flagUsage="message"

Defines flag usage message for a field. Should be used only for fields with flag or flags tag.

πŸ”Έ flagRequired="true"

Defines that the field is required and Parse() will return an error containing flago.ErrIsRequired if the flag is not passed and doesn't have a default value (field is not initialized by non-zero value at the moment of registration)

Assign remaining args

πŸ”» flagArgs="true"

Field will be filled with flag.Args() (remaining command line args after named flags) after Parse().

  • Other "flag" tags should not be used for such fields.

Describe nested structs

The library parses fields in nested structs if explicitly instructed with flagPrefix tag on a field containing another struct.

πŸ”» flagPrefix="pref"

Instructs the library to parse fields in nested struct.

  • All resulting flag names (specified by flag and flags tags) in the nested struct will have the specified prefix.
  • With flagPrefix="" nested struct will still be parsed but without using prefix for its fields.
  • The prefix does not affect fields tagged with flagArgs.

πŸ”Έ flagUsagePrefix="usage_pref"

This tag is used only for nested struct fields with flagPrefix tag.

Instructs the library to use the specified prefix for flag usage messages of fields in nested struct.

Usage help message

If you use flago.NewFlagSet() constructor, resulting FlagSet will assign own default implementation of Usage that prints help message in the same manner as the standard flag package but grouping aliases assigned to a field using flags tag in one line.

If you use flago.Wrap() constructor, it doesn't override default Usage of the standard FlagSet. You can do it manually: flagSet.Usage = flago.DefaultUsage

Field types support

  • StructVar() method parses fields and their tags and calls the correspondent FlagSet.***Var() methods depending on the field type.
  • So fields should have types supported by flag package or be pointers to such types.
  • Fields implementing flag.Value and func(string) error fields are also supported (but can't be pointers).

Special case

If a field has encoding.TextUnmarshaler interface, it also should implement encoding.TextMarshaler.

The library will call FlagSet.TextVar() on such fields that requires a default "marshaler" value.

cmdargs sub-package

Provides helper tools for manipulating command line arguments:

  • Iterate over arguments as tokens with known roles
  • Iterate over flags with values, unnamed args, ...
  • Lookup for flags by name
  • Delete flag by name
  • Upsert flag by name
  • Mutate flags (make it inline, change value, name, ...)
  • Strip unknown flags

About

Go library for parsing flags into struct fields

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages