Github: github.com/integrii/flaggy

Today, I am happy to announce flaggy as stable for production use. Flaggy was originally built out of frustrations with all other flag packages available today. There are a lot of flag packages out there, but missing features, lack of subcommand support, and strange hard-to-remember patterns are all too common. Flaggy is new, but has been polished and tested extensively. I think you’re going to like it.

Most people use Cobra when they want subcommands and robust flag support, but that imposes an entire package structure on your project. Your main ends up as a tiny program that calls a variety of specially crafted packages. I brought this up as an issue and despite getting a few thumbs up, never got a response (or even label applied).

Some projects try to directly use Cobra’s pflag package, which parses arguments, but that package makes it hard to setup subcommands. You must understand and use a FlagSet.

The Go flag package isn’t too shabby and you can even implement a FlagSet there too, but it requires a lot of vebose declarations and if statements to wire it all together.

The next most popular, as shown by imports on Godoc.org is gnuflag. Again, this flags package requires wiring up their own version of FlagSets.

Finally, there is go-flags, which takes a approach of using tags on struct properties to create flags. This feels weird.

There needed to be something simpler and more idiomatic.

Flaggy is here

Flaggy’s description on Github is:

Idiomatic Go input parsing with subcommands, positional values, and flags at any position. No required project or package layout and no external dependencies.

That’s exactly what it is. It’s an easy to use flags package that makes the right assumptions without restricting your workflow. You can get very advanced if you want to, but by default flaggy does an excellent job of doing everything it possibly can for you without asking.

A simple example

Lets say you are making a tiny program to do something simple. You just want a single flag. In that case, you would end up with a program like this:

package main

import "github.com/integrii/flaggy"

var stringFlag = "defaultValue"

func init() {
	flaggy.String(&stringFlag, "f", "flag", "A test string flag")
	flaggy.Parse()
}

func main() {
	print(stringFlag)
}

The user can now specify flags with either one dash (-), or two (--) for both short and long flag names. The user can also use an equals operator to assign values if they don’t feel comfortable with spaces. (-flag=value)

Notice that, by default, the stringFlag has the value it is assigned in your code. If the user does not use the flag, the value stays to whatever you declared it as. This makes graceful use of go’s default values behavior while making it simple to set values to their defaults through whatever mechanism makes sense to your program (os.GetEnv() or possibly a configuration file). Flags specified by the user override the value, but only if they use the flag.

Also notice that flaggy.String() was used, not flaggy.StringVar(). Flaggy has no *Var functions. Everything is a *Var variable and assigned via a pointer, which is the pattern used in nearly every go program you can find. Under the hood, this made flaggy’s code very manageable. Depending on the type of the pointer supplied, a different parser is called on the associated argument!

An example subcommand

Subcommands are the star of the show with flaggy. Check out this program that implements a subcommand that has it’s own specific flag:

package main

import "github.com/integrii/flaggy"

var stringFlag = "defaultValue"

func init() {
	subcommand := flaggy.NewSubcommand("subcommandExample")
	subcommand.String(&stringFlag, "f", "flag", "A test string flag")
	flaggy.AttachSubcommand(subcommand, 1)
	flaggy.Parse()
}

func main() {
	print(stringFlag)
}

Notice that all we had to do differently was to create a subcommand with a name, then assign the flag onto the subcommand instead of directly on the flaggy package.

The subcommand then would be used this way:

$ ./command subcommandExample -f test
test

Alternatively, we could have attached the subcommand onto a different subcommand to nest the subcommands easily. Simple!

You may be wondering how you can tell if the subcommand its self was invoked. That is easy, too. We just check the bool on the subcommand anytime after calling flaggy.Parse().

if subcommand.Used {
  print("subcommand was used")
}

Positional values

Take this kubectl command for example. You use Kubernetes, right?

$ kubectl logs podName

In this case, logs is our subcommand, and podName is a positional value. Positional values are as simple as flags. You just need to specify which numerical position they are at in relativity to the root of your program or any subcommand, and if they must be supplied by the user.

package main

import "github.com/integrii/flaggy"

var positionalValue = "defaultValue"

func init() {
	flaggy.AddPositionalValue(&positionalValue, "test", 1, true, "a test positional value")
	flaggy.Parse()
}

func main() {
	print(positionalValue)
}

You then can run this program like this to see the positional get parsed.

$ ./commandName testPositional
testPositional

If we tried to execute the program without supplying the positional value (we specified this positional must be passed):

$ ./commandName
commandName
  Usage:
    positionalValue [test]

  Positional Variables:
    test - (Required) a test positional value (default: defaultValue)

Required positional variable test not found at position 1

By the way, you can intersperse flags and positional values together in any order you like. Flaggy will sort them all out! It does not matter if the user puts flags in front of your positional value:

$ ./commandName -f flagHere --other-flag=OtherFlag testPositional
testPositional

Help and versioning

Every flag can have both a short and long name along with a description for help output. By default, flaggy provides a version flag (--version) and a help flag (-h, --help). Flaggy’s help gives you lots of details as you add more features to your app.

testCommand - Description goes here.  Get more information at http://flaggy.flag.
This is a prepend for help

  Usage:
    testCommand [subcommandA|subcommandB|subcommandC] [testPositionalA] [testPositionalB]

  Positional Variables:
    testPositionalA - (Required) Test positional A does some things with a positional value. (default: defaultPosA)
    testPositionalB - Test positional B does some less than serious things with a positional value.

  Subcommands:
    subcommandA (a) - Subcommand A is a command that does stuff
    subcommandB (b) - Subcommand B is a command that does other stuff
    subcommandC (c) - Subcommand C is a command that does SERIOUS stuff

  Flags:
    -s --stringFlag  This is a test string flag that does some stringy string stuff.
    -i --intFlg  This is a test int flag that does some interesting int stuff. (default: 0)
    -b --boolFlag  This is a test bool flag that does some booly bool stuff.
    -d --durationFlag  This is a test duration flag that does some untimely stuff. (default: 0s)

This is an append for help
This is a help addon message

If you asked for the version of your program with --version, you would get something like this:

$ ./commandName --version
Version: 0.0.0

To set the version, simply use flaggy.SetVersion("myVersion").

You can customize help templates with flaggy too - but that is a topic for the future.

Thats enough for now

I hope you’ve seen enough to encourage you to give flaggy a try. The examples here and more are available on github and on the godoc to help get you started.

« Back to Article List

Comments

comments powered by Disqus