Using flag package, but cleanly handling non-flag argument errors

I’m writing a command line tool using Go, and am using the standard flag package. My tool will take a series of flag (-X=Y) arguments, and then some string value arguments, e.g.:

myTool [options] requiredArg1 requiredArg2

Easy: you use the flag package to specify and parse the options; once it’s done, flag.Args() will have the two required arguments.

But! how do I handle errors, especially those flag doesn’t see? I want an error to produce something like the following:

<Error message describing specific problem>
Usage: myTool [options] requiredArg1 requiredArg2
    <Option listing>

I can produce the <Option listing> with flag.PrintDefaults(), and if there’s a problem with an unknown flag then flag will print an error message and usage line. But, what if there’s a problem with the required argument count? How do I output my own error message, and then get flag to print usage and the option listing? What if a string flag value doesn’t match my own predicate; how do I complain? If I do it explicitly in my own, and the user has both an unknown flag and the wrong number of arguments, how do I avoid printing it all out twice? Or, if I decide to do all the error output myself, how do I keep flag from outputting its own messages?

I’ve RTFM’d, but the doc is pretty poor, jumping straight from “just flags” to “customizing your own flag types”; it never talks about the rest of argument parsing that most command line tools will need to do.

Thanks,
Dan

I usually look at how other people handle it. For example, I put this together based on the Go stringer command’s usage of the flag package:

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
)

var (
	progname = filepath.Base(os.Args[0])
	allowedArg1s = map[string]struct{}{
		"foo": {},
		"bar": {},
		"quux": {},
	}
)

func main() {
	optOne := flag.Int("one", 1, "option one controls Thing one")
	optTwo := flag.String("two", "2", "option two controls Thing two")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `Do something with Thing one and Thing two
Usage of %s:
	%s [flags] [positional arg1] [positional arg2]

Positional arg1 must acquiesce the adjunct.  Positional arg2, if provided,
must reticulate the splines.
Flags:
`, progname, progname)
		flag.PrintDefaults()
	}
	flag.Parse()
	args := flag.Args()
	var arg1, arg2 string
	switch len(args) {
	case 0:
		log.Fatal("positional arg1 is required")
	case 1:
		arg1 = args[0]
		arg2 = "default"
	case 2:
		arg1 = args[0]
		arg2 = args[1]
	default:
		log.Fatal("only two positional arguments are allowed")
	}
	if _, ok := allowedArg1s[arg1]; !ok {
		sb := strings.Builder{}
		for k := range allowedArg1s {
			if sb.Len() > 0 {
				sb.WriteString(", ")
			}
			sb.WriteString(k)
		}
		log.Fatal("Invalid positional arg1.  Allowed options are: ", sb.String())
	}
	_, _, _, _ = arg1, arg2, optOne, optTwo
}

And, flag.Usage = func() {...} was the key. Boy, would it be nice if that were mentioned in the documentation at flag package - flag - pkg.go.dev .

Thanks,
Dan

See Usage documented among flag’s variables: flag package - flag - pkg.go.dev

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.