flagtag - Defining command line flags by tagging struct fields

919 views
Skip to first unread message

Danny

unread,
Jun 9, 2014, 1:03:43 PM6/9/14
to golan...@googlegroups.com
Hi all,

I'd like to announce a small package that provides support for tagging struct fields as a way to declare command line arguments.

The package works by calling functions of the flag package for each of the tagged fields. With a single function call, you can set up a number of tags with default values and usage descriptions. The flags are created using flag.*Var functions, such as flag.IntVar(...), so once the flags are parsed, the tagged struct fields will contain either the user-provided flag value or the default value specified in the tag.

The package has support for nested structs, all primitive types that are supported by 'flag', time.Duration and it tests whether a field variable implements the http://golang.org/pkg/flag/#Value interface. 'flagtag' simply uses the facilities provided by the 'flag' package. It is possible to partially use the flagtag package for simple flags and use the 'flag' package directly for complex requirements.

I believe that the package will be most useful when creating small tools that do not require any special flag behavior. In its simplest form you can create flags by simply tagging the appropriate struct fields, then calling MustConfigureAndParse(&s) where s is the variable of the (tagged) struct type. With just a single function to call, setting up the flags is easy. By having tags for the appropriate struct fields, it is very easy to keep an overview of the flags that are available and by which flag names. (Please keep in mind that the Must* functions will panic if invalid tag data is provided. Use ConfigureAndParse(&s) if you want an error returned.)

Let me know what you think. I appreciate any feedback, also because this is the first package I am actually announcing. There is probably a lot that can be improved in the code, the documentation or anything else.

Kind regards,
Danny


A simple example:

package main

import (
        "fmt"
        "github.com/cobratbq/flagtag"
)

type Configuration struct {
        Greeting string `flag:"greet,Hello,The greeting."`
        Name     string `flag:"name,User,The user's name."`
        Times    int    `flag:"times,1,Number of repeats."`
}

func main() {
        var config Configuration
        flagtag.MustConfigureAndParse(&config)

        for i := 0; i < config.Times; i++ {
                fmt.Printf("%s %s!\n", config.Greeting, config.Name)
        }
}

Arne Hormann

unread,
Jun 10, 2014, 6:38:45 AM6/10/14
to golan...@googlegroups.com
I love that idea and built something somewhat similar as a prototype - but I still have to make it a real package and add some tests.

Still, there are some notable differences between your approach and mine.

I built mine as a wrapper around the flag package and use its Value interface and command line parsing.
Keeping the client code compatible with the standard lib and independent of my own implementation is a huge benefit IMO.

I also don't add the default values to the tag. Just create an instance, set all default values and register it.
No need for a "must" and it reads a lot easier.
You also gain the ability to use different default values for different applications - or multiple instances of the same configuration (e.g. different email settings for a newsletter and error reports).

I also tried to enable usage in 12factor apps - every configuration setting is also associated with an environment variable.

And all available settings can be explored to dynamically generate documentation. The level of detail may be controlled with an additional tag named "tag".
Aliasing of commands ("--file" or "-f") is also possible.

I started to write this after I got a little angry when watching the Docker video from Gophercon where they say flag is not good enough and one shouldn't use it.

Arne Hormann

unread,
Jun 10, 2014, 6:55:52 AM6/10/14
to golan...@googlegroups.com
Oh my, I'm blind. Yours is built around flag, too. Strike that part, the rest still applies :)

Danny

unread,
Jun 10, 2014, 6:12:29 PM6/10/14
to golan...@googlegroups.com
Hi Arne,

Indeed it seems many people have had similar ideas. I noticed, after my post, that a few posts down someone else also had a similar idea. I believe the package is called 'flarg'. Something about scratching an itch ...

I did a quick scan through your code. I'm not sure how much your approach differs from mine. It might not be that big a change. I noticed one thing in particular ... I have been struggling with some issues w.r.t. reflection and you have a much nicer and cleaner approach to it. Well actually, one thin in particular, which is accessing the value of a struct field. I use unsafe.Pointer to get to that location. ... It works, but it isn't as nice. I can certainly improve in that area. (But... I already knew that.)

There has been quite some discussion on whether or not the 'flag' package is sufficient. I believe that in most cases it is and that was a reason for me to see how I could improve on it. I noticed that for a rather simple application I was still writing a lot of configuration/options parsing code, so I wanted to get rid of that.
I've watched that talk too on a recording of Gophercon. Indeed, I still think that flag should be sufficient for a lot of cases.

I actually really like the default values, so I will certainly support that, although default values are somewhat limited now, since you cannot use a comma (separator symbol). The way I approach the flag.Value vs. primitive types choice is that I first test for applicability of the flag.Value interface. If that interfaces matches, I use that. Otherwise I fall back to primitive types. Also, I decided to match by Kind() as you can then also apply the flags to derived types (as in "type foo int"). To bo honest, that was actually a side effect of my "juggling" with reflection, but I think I'll keep it. It could prove useful.

Additionally, I decided to "unwrap" pointers and interfaces to see if there's something useful "behind the curtain". I wanted the package to be fairly widely applicable such that you are not forced back into using the original 'flag' package.

Thanks for your response,
Danny
Reply all
Reply to author
Forward
0 new messages