« Back to Index

[Golang CLI Flags and Subcommands]

View original Gist on GitHub

Tags: #tags: golang, go, cli, flags, subcommands, logs, logging

0. README.md

Three approaches…

  1. Flags are known up front. 👌🏻
  2. Flags are defined by user (ugly data structure). ❌
  3. Flags are defined by user (dynamic struct creation BUT doesn’t work fully) ❌
  4. Flags are defined by user (dynamic population of a struct provided by the user) ✅

flag.FlagSet ?

Note: the trick to understanding flag.NewFlagSet is that you have to pass it a string of command line args that you need it to parse. flag.Parse is able to do this automatically for you, but a flag set is expected to work with a subset of the arguments provided to the program when it’s being run. The way I typically do this is as follows…

A simple FlagSet example:

package main

import (
	"fmt"
	"os"

	"github.com/integralist/go-gitbranch/internal/pkg/create"
)

func main() {
	args := os.Args[1:]

	if len(args) == 0 {
		fmt.Println("no subcommand provided")
		os.Exit(1)
	}

	switch args[0] {
	case "create":
		flags := create.ParseFlags(args[1:])
		fmt.Println("name:", flags.Name)
	case "rename":
		//
	case "checkout":
		//
	case "delete":
		//
	}
}

////////////////////////////////////////////////////////////////////////

package create

import (
	"flag"
)

type Flags struct {
	Name string
}

// ParseFlags defines and parses flags for the create subcommand.
func ParseFlags(args []string) Flags {
	fs := flag.NewFlagSet("create", flag.ExitOnError)
	name := fs.String("name", "", "name of the branch to create")
	fs.Parse(args)

	return Flags{
		Name: *name,
	}
}

More context for identifying subcommands dynamically…

// Example: app -debug foo -x 1 -y 2

// define flag(s), in this case a -debug flag, then parse it...
flag.Parse()

// slice args from after the program name "app" (so `args` = `-debug foo -x 1 -y 2`)
args := os.Args[1:]

// identify the command ("foo" in this case)
cmd := identifyCommand(args)

// identify the command's flags ("-x 1 -y 2" in this case)
cmdFlags := commandFlags(cmd, flag.Args())

// identifyCommand parses the arguments provided looking for a 'command'
//
// this implementation presumes that the format of the arguments will be...
//
// <program> <flag(s)> <command> <flag(s) for command>
//
func identifyCommand(args []string) string {
  	// list of supported/known commands...
 	commands := []string{"foo", "bar", "baz"}
  
	commandIndex := 0
	commandSeen := false

	for _, arg := range args {
		if commandSeen {
			break
		}

		if strings.HasPrefix(arg, "-") == true {
			commandIndex++
			continue
		}

		for _, cmd := range commands {
			if arg == cmd.Name {
				commandSeen = true
				break
			}
		}

		if !commandSeen {
			commandIndex++
		}
	}

	if !commandSeen {
		return ""
	}

	return args[commandIndex]
}

// commandFlags parses out the arguments that are meant for the new flag set.
//
// args: flag.Args()
// cmd: the point in the list of arguments where you want to slice from.
//
// Example: app -debug foo -x 1 -y 2
//
// the `cmd` would be foo, and so we want to return a slice starting from the flag -x
// for the FlagSet to parse.
//
func commandFlags(cmd string, args []string) []string {
	for i, v := range args {
		if v == cmd {
			return args[i+1:]
		}
	}

	return []string{}
}

// I've not provided an implementation for commandFlagSet but it basically
// defines a bunch of flags off a cfs variable and returns a *flag.FlagSet
//
// Example: 
// cfs := flag.NewFlagSet("foo", flag.ExitOnError)
// cfs.String(...)
//
cfs := commandFlagSet(cmd, cmdFlags)

err := cfs.Parse(cmdFlags)
if err != nil {
	// ...
}

// NOTE: flag.Parse type will also have Visit/VisitAll methods.
//
cfs.VisitAll(func(f *flag.Flag) {
	// check value of every flag
})

1. app.go

/* 
go run fastly.go diff -foo

Usage of diff:
  -foo string
        default (default "desc")

go run fastly.go -debug diff  // enables debug logs 
*/

package main

import (
	"flag"
	"flags"
	"fmt"
	"os"

	"github.com/sirupsen/logrus"
)

// AppVersion is the application version
const AppVersion = "0.0.1"

var logger *logrus.Entry

func init() {
	logrus.SetFormatter(&logrus.JSONFormatter{})
	logrus.SetLevel(logrus.InfoLevel)
	logger = logrus.WithFields(logrus.Fields{
		"package": "main",
	})
}

func main() {
	f := flags.New()

	if *f.Top.Help == true {
		flag.PrintDefaults()
		os.Exit(1)
	}

	if *f.Top.Version == true {
		fmt.Println(AppVersion)
		os.Exit(1)
	}

	if *f.Top.Debug == true {
		logrus.SetLevel(logrus.DebugLevel)
	}

	logger.Debug("application starting")

	args := os.Args[1:] // strip first arg `fastly`
	arg, counter := flags.Check(args)

	switch arg {
	case "diff":
		f.Top.Diff.Parse(args[counter:])
	case "upload":
		f.Top.Upload.Parse(args[counter:])
	default:
		fmt.Printf("%v is not valid command.\n", arg)
		os.Exit(1)
	}
}

1. flags.go

// flags/flags.go

package flags

import (
	"flag"
	"os"
	"strings"

	"github.com/sirupsen/logrus"
)

var logger *logrus.Entry

func init() {
	logger = logrus.WithFields(logrus.Fields{
		"package": "flags",
	})
}

// TopLevelFlags defines the common settings across all commands
type TopLevelFlags struct {
	Help, Debug, Version                   *bool
	Token, Service, Directory, Match, Skip *string
	Diff, Upload                           *flag.FlagSet
}

// SubCommandFlags defines the settings for the subcommands
type SubCommandFlags struct {
	Foo, Bar *string
}

// Flags defines type of structure returned to user
type Flags struct {
	Top TopLevelFlags
	Sub SubCommandFlags
}

// New returns defined flags
func New() Flags {
	topLevelFlags := TopLevelFlags{
		Help:      flag.Bool("help", false, "show available flags"),
		Debug:     flag.Bool("debug", false, "show any error/diff output + debug logs"),
		Version:   flag.Bool("version", false, "show application version"),
		Token:     flag.String("token", os.Getenv("FASTLY_API_TOKEN"), "your fastly api token (fallback: FASTLY_API_TOKEN)"),
		Service:   flag.String("service", os.Getenv("FASTLY_SERVICE_ID"), "your service id (fallback: FASTLY_SERVICE_ID)"),
		Directory: flag.String("dir", os.Getenv("VCL_DIRECTORY"), "vcl directory to compare files against"),
		Match:     flag.String("match", "", "regex for matching vcl directories (will also try: VCL_MATCH_DIRECTORY)"),
		Skip:      flag.String("skip", "^____", "regex for skipping vcl directories (will also try: VCL_SKIP_DIRECTORY)"),
		Diff:      flag.NewFlagSet("diff", flag.ExitOnError),
		Upload:    flag.NewFlagSet("upload", flag.ExitOnError),
	}
	flag.Parse()

	subCommandFlags := SubCommandFlags{
		Foo: topLevelFlags.Diff.String("foo", "foo is upload only", "foo default"),
		Bar: topLevelFlags.Upload.String("bar", "bar is upload only", "bar default"),
	}

	return Flags{
		Top: topLevelFlags,
		Sub: subCommandFlags,
	}
}

// Check determines if a flag was specified before the subcommand
// Then returns the subcommand argument value based on the correct index
// Followed by the index of where the subcommand's flags start in the args list
func Check(args []string) (string, int) {
	counter := 0
	subcommandSeen := false

	for _, arg := range args {
		if subcommandSeen {
			break
		}

		if strings.HasPrefix(arg, "-") == true {
			counter++
			continue
		}

		if arg == "diff" || arg == "upload" {
			subcommandSeen = true
		} else {
			counter++
		}
	}

	subcommandFlagsIndex := counter + 1

	logger.WithFields(logrus.Fields{
		"args":       args,
		"counter":    counter,
		"subcommand": args[counter],
		"index":      subcommandFlagsIndex,
	}).Debug("subcommand selected")

	return args[counter], subcommandFlagsIndex
}

2. flags defined by user.go

package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"
)

var (
	ErrNoArgs       = errors.New("no flags or commands provided")
	ErrCmdFlagParse = errors.New("failed to parse the command flags")
)

type Flag struct {
	NameLong  string
	NameShort string
	Default   interface{}
	Usage     string
	Value     interface{}
}

type Flags []*Flag

type Command struct {
	Name  string
	Flags Flags
}

type Commands []Command

type Schema struct {
	Commands Commands
	Flags    Flags
}

func (s *Schema) Parse() error {
	args := os.Args[1:]
	if len(args) == 0 {
		return ErrNoArgs
	}

	for _, f := range s.Flags {
		switch def := f.Default.(type) {
		case bool:
			v, _ := f.Value.(bool)
			flag.BoolVar(&v, f.NameLong, def, f.Usage)
			flag.BoolVar(&v, f.NameShort, def, f.Usage+" (shorthand)")
		case string:
			v, _ := f.Value.(string)
			flag.StringVar(&v, f.NameLong, def, f.Usage)
			flag.StringVar(&v, f.NameShort, def, f.Usage+" (shorthand)")
		case int:
			v, _ := f.Value.(int)
			flag.IntVar(&v, f.NameLong, def, f.Usage)
			flag.IntVar(&v, f.NameShort, def, f.Usage+" (shorthand)")
		case float64:
			v, _ := f.Value.(float64)
			flag.Float64Var(&v, f.NameLong, def, f.Usage)
			flag.Float64Var(&v, f.NameShort, def, f.Usage+" (shorthand)")
		}
	}

	flag.Parse()

	flag.VisitAll(func(f *flag.Flag) {
		for _, sf := range s.Flags {
			if f.Name == sf.NameLong || f.Name == sf.NameShort {
				sf.Value = f.Value
			}
		}
	})

	cmd := s.identifyCommand(args)
	cmdFlags := s.commandFlags(cmd, flag.Args())

	for _, c := range s.Commands {
		if c.Name == cmd {
			cfs := flag.NewFlagSet(c.Name, flag.ExitOnError)

			for _, f := range c.Flags {
				switch def := f.Default.(type) {
				case bool:
					v, _ := f.Value.(bool)
					cfs.BoolVar(&v, f.NameLong, def, f.Usage)
					cfs.BoolVar(&v, f.NameShort, def, f.Usage+" (shorthand)")
				case string:
					v, _ := f.Value.(string)
					cfs.StringVar(&v, f.NameLong, def, f.Usage)
					cfs.StringVar(&v, f.NameShort, def, f.Usage+" (shorthand)")
				case int:
					v, _ := f.Value.(int)
					cfs.IntVar(&v, f.NameLong, def, f.Usage)
					cfs.IntVar(&v, f.NameShort, def, f.Usage+" (shorthand)")
				case float64:
					v, _ := f.Value.(float64)
					cfs.Float64Var(&v, f.NameLong, def, f.Usage)
					cfs.Float64Var(&v, f.NameShort, def, f.Usage+" (shorthand)")
				}
			}

			err := cfs.Parse(cmdFlags)
			if err != nil {
				return fmt.Errorf("%s %w", ErrCmdFlagParse, err)
			}

			cfs.VisitAll(func(f *flag.Flag) {
				for _, cf := range c.Flags {
					if cf.NameLong == f.Name || cf.NameShort == f.Name {
						cf.Value = f.Value
					}
				}
			})

			break
		}
	}

	return nil
}

func (s *Schema) identifyCommand(args []string) string {
	commandIndex := 0
	commandSeen := false

	for _, arg := range args {
		if commandSeen {
			break
		}

		if strings.HasPrefix(arg, "-") == true {
			commandIndex++
			continue
		}

		for _, cmd := range s.Commands {
			if arg == cmd.Name {
				commandSeen = true
				break
			}
		}

		if !commandSeen {
			commandIndex++
		}
	}

	if !commandSeen {
		return ""
	}

	return args[commandIndex]
}

func (s *Schema) commandFlags(cmd string, args []string) []string {
	for i, v := range args {
		if v == cmd {
			return args[i+1:]
		}
	}

	return []string{}
}

func main() {
	// NOTE: when using verbose syntax and explicitly naming the Flag struct
	// type, then the field `Value` can be omitted. But with the short syntax
	// used below the compiler complains and so we have to pass `nil`.
	//
	flags := Flags{
		{"debug", "d", false, "description", nil},
		{"number", "n", 0, "description", nil},
	}

	fooFlags := Flags{
		{"AAA", "a", "", "description", nil},
		{"BBB", "b", 0, "description", nil},
	}

	barFlags := Flags{
		{"CCC", "c", false, "description", nil},
	}

	commands := Commands{
		{"foo", fooFlags},
		{"bar", barFlags},
		{"baz", nil},
	}

	schema := &Schema{
		Flags:    flags,
		Commands: commands,
	}

	err := schema.Parse()
	if err != nil {
		fmt.Printf("error parsing schema: %v\n", err)
		os.Exit(1)
	}

	// NOTE: after running the following command we can access the values:
	//
	// go run test_flags.go -debug -n 123 foo -a foobar -b 666
	//
	fmt.Printf("schema: %+v\n", schema)
	fmt.Printf("schema.Flags[0].Value: %v\n", schema.Flags[0].Value)
	fmt.Printf("schema.Flags[1].Value: %v\n", schema.Flags[1].Value)
	fmt.Printf("schema.Commands[0].Flags[0].Value: %v\n", schema.Commands[0].Flags[0].Value)
	fmt.Printf("schema.Commands[0].Flags[1].Value: %v\n", schema.Commands[0].Flags[1].Value)

	// TODO: figure out better data strucuture for sake of user experience,
	// because having to dip into an array is nasty/fugly
	//
}

3. flags via dynamic struct - app.go

// this is the consumer of the code

package main

import (
	"fmt"
	"os"

	flag "github.com/integralist/go-flags/flags"
)

func main() {
	// standalone flags (i.e. no command necessary)
	//
	// examples: app -debug
	//           app -d
	//
	flags := flag.Flags{
		{"debug", "d", "bool", "description"},
	}

	// flags for the foo command
	//
	fooFlags := flag.Flags{
		{"AAA", "a", "string", "description"},
		{"BBB", "b", "int", "description"},
	}

	// flags for the bar command
	//
	barFlags := flag.Flags{
		{"CCC", "c", "bool", "description"},
	}

	// commands
	//
	// examples: app foo
	//           app foo -a test -b 123
	//           app bar -c
	//           app baz
	//
	commands := flag.Commands{
		{"foo", fooFlags},
		{"bar", barFlags},
		{"baz", nil},
	}

	schema := &flag.Schema{
		Flags:    flags,
		Commands: commands,
	}

	// produces the following data structure...
	//
	// &{
	// 	Commands:[
	// 		{
	// 			Name:foo
	// 			Flags:[
	// 				{Verbose:AAA Short:a Type:string Usage:description}
	// 				{Verbose:BBB Short:b Type:int Usage:description}
	// 			]
	// 		}
	// 		{
	// 			Name:bar
	// 			Flags:[
	// 				{Verbose:CCC Short:c Type:bool Usage:description}
	// 			]
	// 		}
	// 		{
	// 			Name:baz
	// 			Flags:[]
	// 		}
	// 	]
	// 	Flags:[
	// 		{Verbose:debug Short:d Type:bool Usage:description}
	// 	]
	// }

	// TODO: should 'no args' really be an error?
	//
	fd, err := schema.Parse()
	if err != nil && err != flag.ErrNoArgs {
		fmt.Printf("something unexpected happened: %w", err)
		os.Exit(1)
	}

	if fd.Cmd == "" {
		fmt.Println("no recognized flag or command was provided")
	} else {
		fmt.Println("command executed:", fd.Cmd)
	}

  	// TODO: figure out how to get at the other fields?
  	//
	// fmt.Println(fd.Debug, fd.D, fd.AAA, fd.A, fd.BBB, fd.B, fd.CCC, fd.C)
  	//
  	// ...because we can't type assert the returned data structure.
}

3. flags via dynamic struct - builder.go

// builder.go uses reflection to dynamically generate a struct.
// the struct will contain the parsed flag values.
//
// the struct generation code was extracted from the great work done by:
// https://github.com/Ompluscator/dynamic-struct
// so all credit for that code goes to him.
//
package flags

import (
	"reflect"
)

// DynamicStruct contains defined dynamic struct.
// This definition can't be changed anymore, once is built.
// It provides a method for creating new instances of same defintion.
type DynamicStruct interface {
	// New provides new instance of defined dynamic struct.
	//
	// value := dStruct.New()
	//
	New() interface{}
}

// Builder holds all fields' definitions for desired structs.
type Builder interface {
	// AddField creates new struct's field.
	// It expects field's name, type and string.
	// Type is provided as an instance of some golang type.
	// Tag is provided as classical golang field tag.
	//
	// builder.AddField("SomeFloatField", 0.0, `json:"boolean" validate:"gte=10"`)
	//
	AddField(name string, typ interface{}, tag string) Builder

	// Build returns definition for dynamic struct.
	// Definition can be used to create new instances.
	//
	// dStruct := builder.Build()
	//
	Build() DynamicStruct
}

type dynamicStructImpl struct {
	definition reflect.Type
}

type fieldConfigImpl struct {
	typ interface{}
	tag string
}

type builderImpl struct {
	fields map[string]*fieldConfigImpl
}

func (b *builderImpl) AddField(name string, typ interface{}, tag string) Builder {
	b.fields[name] = &fieldConfigImpl{
		typ: typ,
		tag: tag,
	}

	return b
}

func (b *builderImpl) Build() DynamicStruct {
	var structFields []reflect.StructField

	for name, field := range b.fields {
		structFields = append(structFields, reflect.StructField{
			Name: name,
			Type: reflect.TypeOf(field.typ),
			Tag:  reflect.StructTag(field.tag),
		})
	}

	return &dynamicStructImpl{
		definition: reflect.StructOf(structFields),
	}
}

func (ds *dynamicStructImpl) New() interface{} {
	return reflect.New(ds.definition).Interface()
}

// NewStruct returns new clean instance of Builder interface
// for defining fresh dynamic struct.
//
// builder := NewStruct()
//
func NewStruct() Builder {
	return &builderImpl{
		fields: map[string]*fieldConfigImpl{},
	}
}

3. flags via dynamic struct - flags.go

package flags

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"
)

var (
	ErrNoArgs               = errors.New("no flags or commands provided")
	ErrCmdFlagParse         = errors.New("failed to parse the command flags")
	ErrInterpolationMarshal = errors.New("unable to parse interpolation map into json")
	ErrStructBuild          = errors.New("failed to dynamically generate struct")
)

type Data struct {
	Cmd string
}

type Flag struct {
	Verbose string
	Short   string
	Type    string
	Usage   string
}

type Flags []Flag

type Command struct {
	Name  string
	Flags Flags
}

type CommandParsed struct {
	Command
	FlagSet *flag.FlagSet
}

type Commands []Command

type Schema struct {
	Commands Commands
	Flags    Flags
}

func (s *Schema) Parse() (*Data, error) {
	args := os.Args[1:]

	if len(args) == 0 {
		return &Data{}, ErrNoArgs
	}

	for _, f := range s.Flags {
		switch f.Type {
		case "bool":
			var boolType bool
			flagValue := false
			flagUsage := f.Usage

			flag.BoolVar(&boolType, f.Verbose, flagValue, flagUsage)
			flag.BoolVar(&boolType, f.Short, flagValue, flagUsage+" (shorthand)")
		case "string":
			var stringType string
			flagValue := ""
			flagUsage := f.Usage

			flag.StringVar(&stringType, f.Verbose, flagValue, flagUsage)
			flag.StringVar(&stringType, f.Short, flagValue, flagUsage+" (shorthand)")
		case "int":
			var intType int
			flagValue := 0
			flagUsage := f.Usage

			flag.IntVar(&intType, f.Verbose, flagValue, flagUsage)
			flag.IntVar(&intType, f.Short, flagValue, flagUsage+" (shorthand)")
		case "float":
			var floatType float64
			flagValue := 0.0
			flagUsage := f.Usage

			flag.Float64Var(&floatType, f.Verbose, flagValue, flagUsage)
			flag.Float64Var(&floatType, f.Short, flagValue, flagUsage+" (shorthand)")
		}
	}

	flag.Parse()

	cmd := s.identifyCommand(args)
	cmdFlags := s.commandFlags(cmd, flag.Args())

	instance := NewStruct()

	data := make(map[string]interface{})
	data["cmd"] = cmd

	flag.VisitAll(func(f *flag.Flag) {
		// create zero value struct dynamically
		// later on we'll be able to populate it dynamically too
		//
		for _, sf := range s.Flags {
			if sf.Verbose == f.Name || sf.Short == f.Name {
				title := strings.Title(f.Name)
				tag := fmt.Sprintf(`json:"%s"`, f.Name) // f.Name must be present in data bytes
				data[f.Name] = f.Value

				switch sf.Type {
				case "bool":
					instance.AddField(title, false, "")
				case "string":
					instance.AddField(title, "", tag)
				case "int":
					instance.AddField(title, 0, tag)
				case "float":
					instance.AddField(title, 0.0, tag)
				}
			}
		}
	})

	cfs := s.commandFlagSet(cmd, cmdFlags)

	err := cfs.Parse(cmdFlags)
	if err != nil {
		return &Data{}, fmt.Errorf("%s %w", ErrCmdFlagParse, err)
	}

	cfs.VisitAll(func(f *flag.Flag) {
		// create zero value struct dynamically
		// later on we'll be able to populate it dynamically too
		//
		for _, c := range s.Commands {
			if c.Name == cmd {
				for _, cf := range c.Flags {
					if cf.Verbose == f.Name || cf.Short == f.Name {
						title := strings.Title(f.Name)
						tag := fmt.Sprintf(`json:"%s"`, f.Name) // f.Name must be present in data bytes

						// TODO:
						//
						// figure out how to protect against a top-level flag and a command
						// flag both having the same field name.
						//
						// e.g. this should be a valid command:
						// app -debug foocmd -debug
						//
						// our current implementation would mean the command's -debug flag
						// would override the value in the top-level -debug. ok it's a
						// contrived example so it's unlikely to happen but it's not
						// impossible to want the same flag at both the top-level and at a
						// command specific level.
						//
						data[f.Name] = f.Value

						switch cf.Type {
						case "bool":
							instance.AddField(title, false, "")
						case "string":
							instance.AddField(title, "", tag)
						case "int":
							instance.AddField(title, 0, tag)
						case "float":
							instance.AddField(title, 0.0, tag)
						}
					}
				}
			}
		}
	})

	instance.AddField("Cmd", "", `json:"cmd"`)
	ds := instance.Build().New()

	j, err := json.Marshal(data)
	if err != nil {
		return &Data{}, fmt.Errorf("%s %w", ErrInterpolationMarshal, err)
	}

	fmt.Println("interpolated data:", string(j))

	err = json.Unmarshal(j, &ds)
	if err != nil {
		return &Data{}, fmt.Errorf("%s %w", ErrStructBuild, err)
	}

	// TODO:
	//
	// figure out how to type assert interface{} to concrete type?
	// otherwise dynamically generating a struct is pointless :grimace:

	fmt.Printf("\ndynamic struct data:\n%+v\n\ntype:\n%T\n\n", ds, ds)

	// v := reflect.ValueOf(ds).Type()

	// var ei interface{} = ds

	// d, ok := ei.(*struct {
	// 	AAA   string "json:\"AAA\""
	// 	BBB   int    "json:\"BBB\""
	// 	A     string "json:\"a\""
	// 	B     int    "json:\"b\""
	// 	Cmd   string "json:\"cmd\""
	// 	D     bool
	// 	Debug bool
	// })
	// if !ok {
	// 	fmt.Println("uh-oh:", d, ok)
	// }
	// fmt.Println(d.Cmd, d.Debug)

	return &Data{
		Cmd: cmd,
	}, nil
}

func (s *Schema) commandFlagSet(cmd string, cmdFlags []string) *flag.FlagSet {
	for _, c := range s.Commands {
		if c.Name == cmd {
			cfs := flag.NewFlagSet(c.Name, flag.ExitOnError)

			for _, f := range c.Flags {
				switch f.Type {
				case "bool":
					var boolType bool
					flagValue := false
					flagUsage := f.Usage

					cfs.BoolVar(&boolType, f.Verbose, flagValue, flagUsage)
					cfs.BoolVar(&boolType, f.Short, flagValue, flagUsage+" (shorthand)")
				case "string":
					var stringType string
					flagValue := ""
					flagUsage := f.Usage

					cfs.StringVar(&stringType, f.Verbose, flagValue, flagUsage)
					cfs.StringVar(&stringType, f.Short, flagValue, flagUsage+" (shorthand)")
				case "int":
					var intType int
					flagValue := 0
					flagUsage := f.Usage

					cfs.IntVar(&intType, f.Verbose, flagValue, flagUsage)
					cfs.IntVar(&intType, f.Short, flagValue, flagUsage+" (shorthand)")
				case "float":
					var floatType float64
					flagValue := 0.0
					flagUsage := f.Usage

					cfs.Float64Var(&floatType, f.Verbose, flagValue, flagUsage)
					cfs.Float64Var(&floatType, f.Short, flagValue, flagUsage+" (shorthand)")
				}
			}

			return cfs
		}
	}

	return &flag.FlagSet{}
}

func (s *Schema) commandFlags(cmd string, args []string) []string {
	for i, v := range args {
		if v == cmd {
			return args[i+1:]
		}
	}

	return []string{}
}

func (s *Schema) identifyCommand(args []string) string {
	commandIndex := 0
	commandSeen := false

	for _, arg := range args {
		if commandSeen {
			break
		}

		if strings.HasPrefix(arg, "-") == true {
			commandIndex++
			continue
		}

		for _, cmd := range s.Commands {
			if arg == cmd.Name {
				commandSeen = true
				break
			}
		}

		if !commandSeen {
			commandIndex++
		}
	}

	if !commandSeen {
		return ""
	}

	return args[commandIndex]
}
package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"reflect"
	"strings"
)

var (
	ErrNoArgs = errors.New("no flags or commands provided")
)

type Schema struct {
	Debug   bool   `short:"d" usage:"enable debug level logs"`
	Number  int    `short:"n" usage:"a number field"`
	Message string `short:"m" usage:"a message field"`
	Foo     struct {
		AAA string `short:"a" usage:"does A"`
		BBB string `short:"b" usage:"does B"`
	}
	Bar struct {
		CCC bool `short:"c" usage:"does C"`
	}
}

func Parse(s interface{}) error {
	args := os.Args[1:]
	if len(args) == 0 {
		return ErrNoArgs
	}

	st := reflect.TypeOf(s).Elem()
	v := reflect.ValueOf(s)
	v = reflect.Indirect(v)

	IterFields(st, v, func(field reflect.Value, sf reflect.StructField) {
		switch field.Kind() {
		case reflect.Bool:
			var v bool
			flag.BoolVar(&v, strings.ToLower(sf.Name), false, sf.Tag.Get("usage"))
			flag.BoolVar(&v, sf.Tag.Get("short"), false, sf.Tag.Get("usage")+" (shorthand)")
		case reflect.Int:
			var v int
			flag.IntVar(&v, strings.ToLower(sf.Name), 0, sf.Tag.Get("usage"))
			flag.IntVar(&v, sf.Tag.Get("short"), 0, sf.Tag.Get("usage")+" (shorthand)")
		case reflect.String:
			var v string
			flag.StringVar(&v, strings.ToLower(sf.Name), "", sf.Tag.Get("usage"))
			flag.StringVar(&v, sf.Tag.Get("short"), "", sf.Tag.Get("usage")+" (shorthand)")
		}
	})

	flag.Parse()

	IterFields(st, v, func(field reflect.Value, sf reflect.StructField) {
		flag.Visit(func(f *flag.Flag) {
			getter, ok := f.Value.(flag.Getter)
			if ok {
				if f.Name == strings.ToLower(sf.Name) || f.Name == sf.Tag.Get("short") {
					switch field.Kind() {
					case reflect.Bool:
						if b, ok := getter.Get().(bool); ok {
							field.Set(reflect.ValueOf(b))
						}
					case reflect.Int:
						if i, ok := getter.Get().(int); ok {
							field.Set(reflect.ValueOf(i))
						}
					case reflect.String:
						if s, ok := getter.Get().(string); ok {
							field.Set(reflect.ValueOf(s))
						}
					}
				}
			}
		})
	})

	cmds := []string{"foo", "bar", "baz"}
	cmd := IdentifyCommand(cmds, args)
	cmdFlags := CommandFlags(cmd, flag.Args())

	cfs := CommandFlagSet(cmd, cmdFlags, st, v)
	err := cfs.Parse(cmdFlags)
	if err != nil {
		return err
	}

	IterFields(st, v, func(field reflect.Value, sf reflect.StructField) {
		cfs.Visit(func(f *flag.Flag) {
			getter, ok := f.Value.(flag.Getter)
			if ok {
				if f.Name == strings.ToLower(sf.Name) || f.Name == sf.Tag.Get("short") {
					switch field.Kind() {
					case reflect.Bool:
						if b, ok := getter.Get().(bool); ok {
							field.Set(reflect.ValueOf(b))
						}
					case reflect.Int:
						if i, ok := getter.Get().(int); ok {
							field.Set(reflect.ValueOf(i))
						}
					case reflect.String:
						if s, ok := getter.Get().(string); ok {
							field.Set(reflect.ValueOf(s))
						}
					}
				}
			}
		})
	})

	return nil
}

// IterFields iterates over all fields of a struct, including nested structs,
// and processes their individual fields by passing them into a callback.
//
func IterFields(st reflect.Type, v reflect.Value, fn func(f reflect.Value, sf reflect.StructField)) {
	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		sf := st.Field(i)

		if field.Kind() == reflect.Struct {
			st := reflect.TypeOf(field.Interface())

			for i := 0; i < st.NumField(); i++ {
				field := field.Field(i)
				st := st.Field(i)

				if field.CanSet() {
					fn(field, st)
				}
			}
		} else if field.CanSet() {
			fn(field, sf)
		}
	}
}

// IdentifyCommand parses the arguments provided looking for a 'command'.
//
// this implementation presumes that the format of the arguments will be...
//
// <program> <flag(s)> <command> <flag(s) for command>
//
func IdentifyCommand(cmds, args []string) string {
	commandIndex := 0
	commandSeen := false

	for _, arg := range args {
		if commandSeen {
			break
		}

		if strings.HasPrefix(arg, "-") == true {
			commandIndex++
			continue
		}

		for _, cmd := range cmds {
			if arg == cmd {
				commandSeen = true
				break
			}
		}

		if !commandSeen {
			commandIndex++
		}
	}

	if !commandSeen {
		return ""
	}

	return args[commandIndex]
}

// CommandFlags parses the flags that are provided after the 'command'.
//
func CommandFlags(cmd string, args []string) []string {
	for i, v := range args {
		if v == cmd {
			return args[i+1:]
		}
	}

	return []string{}
}

// CommandFlagSet defines flags for the command as a FlagSet.
//
func CommandFlagSet(cmd string, cmdFlags []string, st reflect.Type, v reflect.Value) *flag.FlagSet {
	cfs := flag.NewFlagSet(cmd, flag.ExitOnError)

	IterFields(st, v, func(field reflect.Value, sf reflect.StructField) {
		switch field.Kind() {
		case reflect.Bool:
			var v bool
			cfs.BoolVar(&v, strings.ToLower(sf.Name), false, sf.Tag.Get("usage"))
			cfs.BoolVar(&v, sf.Tag.Get("short"), false, sf.Tag.Get("usage")+" (shorthand)")
		case reflect.Int:
			var v int
			cfs.IntVar(&v, strings.ToLower(sf.Name), 0, sf.Tag.Get("usage"))
			cfs.IntVar(&v, sf.Tag.Get("short"), 0, sf.Tag.Get("usage")+" (shorthand)")
		case reflect.String:
			var v string
			cfs.StringVar(&v, strings.ToLower(sf.Name), "", sf.Tag.Get("usage"))
			cfs.StringVar(&v, sf.Tag.Get("short"), "", sf.Tag.Get("usage")+" (shorthand)")
		}
	})

	return cfs
}

// EXAMPLE:
//
// go run test_flags.go -debug -n 123 -m "something here" foo -a foobar -b 666
//
func main() {
	var s Schema

	err := Parse(&s)
	if err != nil {
		fmt.Printf("error parsing schema: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("\nfinal struct: %+v\n", s)
	/*
		Output:

		{
			Debug:true
			Number:123
			Message:something here
			Foo:{
				AAA:foobar
				BBB:666
			}
			Bar:{
				CCC:false
			}
		}
	*/

	fmt.Printf("\nDebug: %+v\n", s.Debug)   // true
	fmt.Printf("Number: %+v\n", s.Number)   // 123
	fmt.Printf("Message: %+v\n", s.Message) // something here
	fmt.Printf("AAA: %+v\n", s.Foo.AAA)     // foobar
	fmt.Printf("BBB: %+v\n", s.Foo.BBB)     // 666
	fmt.Printf("CCC: %+v\n", s.Bar.CCC)     // false
}