// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package getopt parses command lines using getopt(3) syntax. // It is a replacement for flag.Parse but still expects flags themselves // to be defined in package flag. // // Flags defined with one-letter names are available as short flags // (invoked using one dash, as in -x) and all flags are available as // long flags (invoked using two dashes, as in --x or --xylophone). // // To use, define flags as usual with package flag. Then introduce // any aliases by calling getopt.Alias: // // getopt.Alias("n", "dry-run") // getopt.Alias("v", "verbose") // // Or call getopt.Aliases to define a list of aliases: // // getopt.Aliases( // "n", "dry-run", // "v", "verbose", // ) // // One name in each pair must already be defined in package flag // (so either "n" or "dry-run", and also either "v" or "verbose"). // // Then parse the command-line: // // getopt.Parse() // // If it encounters an error, Parse calls flag.Usage and then exits the program. // // When writing a custom flag.Usage function, call getopt.PrintDefaults // instead of flag.PrintDefaults to get a usage message that includes the // names of aliases in flag descriptions. // // At initialization time, this package installs a new flag.Usage that is the // same as the default flag.Usage except that it calls getopt.PrintDefaults // instead of flag.PrintDefaults. // // This package also defines a FlagSet wrapping the standard flag.FlagSet. // // Caveat // // In general Go flag parsing is preferred for new programs, because // it is not as pedantic about the number of dashes used to invoke // a flag (you can write -verbose or --verbose and the program // does not care). This package is meant to be used in situations // where, for legacy reasons, it is important to use exactly getopt(3) // syntax, such as when rewriting in Go an existing tool that already // uses getopt(3). package main // import "rsc.io/getopt" import ( "flag" "fmt" "io" "os" "reflect" "strings" "unicode/utf8" ) func init() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) PrintDefaults() // ours not package flag's } CommandLine.FlagSet = flag.CommandLine CommandLine.name = os.Args[0] CommandLine.errorHandling = flag.ExitOnError CommandLine.outw = os.Stderr CommandLine.Usage = func() { flag.Usage() } } var CommandLine FlagSet // A FlagSet is a set of defined flags. // It wraps and provides the same interface as flag.FlagSet // but parses command line arguments using getopt syntax. // // Note that "go doc" shows only the methods customized // by package getopt; FlagSet also provides all the methods // of the embedded flag.FlagSet, like Bool, Int, NArg, and so on. type FlagSet struct { *flag.FlagSet alias map[string]string unalias map[string]string name string errorHandling flag.ErrorHandling outw io.Writer } func (f *FlagSet) out() io.Writer { if f.outw == nil { return os.Stderr } return f.outw } // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. func (f *FlagSet) SetOutput(output io.Writer) { f.FlagSet.SetOutput(output) f.outw = output } // NewFlagSet returns a new, empty flag set with the specified name and error // handling property. func NewFlagSet(name string, errorHandling flag.ErrorHandling) *FlagSet { f := new(FlagSet) f.Init(name, errorHandling) return f } // Init sets the name and error handling proprety for a flag set. func (f *FlagSet) Init(name string, errorHandling flag.ErrorHandling) { if f.FlagSet == nil { f.FlagSet = new(flag.FlagSet) } f.FlagSet.Init(name, errorHandling) f.name = name f.errorHandling = errorHandling f.FlagSet.Usage = f.defaultUsage } func (f *FlagSet) init() { if f.alias == nil { f.alias = make(map[string]string) f.unalias = make(map[string]string) } } // Lookup returns the Flag structure of the named flag, // returning nil if none exists. // If name is a defined alias for a defined flag, // Lookup returns the original flag; in this case // the Name field in the result will differ from the // name passed to Lookup. func (f *FlagSet) Lookup(name string) *flag.Flag { if x, ok := f.alias[name]; ok { name = x } return f.FlagSet.Lookup(name) } // Alias introduces an alias for an existing flag name. // The short name must be a single letter, and the long name must be multiple letters. // Exactly one name must be defined as a flag already: the undefined name is introduced // as an alias for the defined name. // Alias panics if both names are already defined or if both are undefined. // // For example, if a flag named "v" is already defined using package flag, // then it is available as -v (or --v). Calling Alias("v", "verbose") makes the same // flag also available as --verbose. func Alias(short, long string) { CommandLine.Alias(short, long) } // Alias introduces an alias for an existing flag name. // The short name must be a single letter, and the long name must be multiple letters. // Exactly one name must be defined as a flag already: the undefined name is introduced // as an alias for the defined name. // Alias panics if both names are already defined or if both are undefined. // // For example, if a flag named "v" is already defined using package flag, // then it is available as -v (or --v). Calling Alias("v", "verbose") makes the same // flag also available as --verbose. func (f *FlagSet) Alias(short, long string) { f.init() if short == "" || long == "" { panic("Alias: invalid empty flag name") } if utf8.RuneCountInString(short) != 1 { panic("Alias: invalid short flag name -" + short) } if utf8.RuneCountInString(long) == 1 { panic("Alias: invalid long flag name --" + long) } f1 := f.Lookup(short) f2 := f.Lookup(long) if f1 == nil && f2 == nil { panic("Alias: neither -" + short + " nor -" + long + " is a defined flag") } if f1 != nil && f2 != nil { panic("Alias: both -" + short + " and -" + long + " are defined flags") } if f1 != nil { f.alias[long] = short f.unalias[short] = long } else { f.alias[short] = long f.unalias[long] = short } } // Aliases introduces zero or more aliases. The argument list must consist of an // even number of strings making up a sequence of short, long pairs to be passed // to Alias. func Aliases(list ...string) { CommandLine.Aliases(list...) } // Aliases introduces zero or more aliases. The argument list must consist of an // even number of strings making up a sequence of short, long pairs to be passed // to Alias. func (f *FlagSet) Aliases(list ...string) { if len(list)%2 != 0 { panic("getopt: Aliases not invoked with pairs") } for i := 0; i < len(list); i += 2 { f.Alias(list[i], list[i+1]) } } type boolFlag interface { IsBoolFlag() bool } func (f *FlagSet) failf(format string, args ...interface{}) error { err := fmt.Errorf(format, args...) fmt.Fprintln(f.out(), err) f.Usage() return err } // defaultUsage is the default function to print a usage message. func (f *FlagSet) defaultUsage() { if f.name == "" { fmt.Fprintf(f.out(), "Usage:\n") } else { fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) } f.PrintDefaults() } // Parse parses the command-line flags from os.Args[1:]. func Parse() { CommandLine.Parse(os.Args[1:]) } // Parse parses flag definitions from the argument list, // which should not include the command name. // Parse must be called after all flags and aliases in the FlagSet are defined // and before flags are accessed by the program. // The return value will be flag.ErrHelp if -h or --help were used but not defined. func (f *FlagSet) Parse(args []string) error { for len(args) > 0 { arg := args[0] if len(arg) < 2 || arg[0] != '-' { break } args = args[1:] if arg[:2] == "--" { // Process single long option. if arg == "--" { break } name := arg[2:] value := "" haveValue := false if i := strings.Index(name, "="); i >= 0 { name, value = name[:i], name[i+1:] haveValue = true } fg := f.Lookup(name) if fg == nil { if name == "h" || name == "help" { // TODO ErrHelp } return f.failf("flag provided but not defined: --%s", name) } if b, ok := fg.Value.(boolFlag); ok && b.IsBoolFlag() { if haveValue { if err := fg.Value.Set(value); err != nil { return f.failf("invalid boolean value %q for --%s: %v", value, name, err) } } else { if err := fg.Value.Set("true"); err != nil { return f.failf("invalid boolean flag %s: %v", name, err) } } continue } if !haveValue { if len(args) == 0 { return f.failf("missing argument for --%s", name) } value, args = args[0], args[1:] } if err := fg.Value.Set(value); err != nil { return f.failf("invalid value %q for flag --%s: %v", value, name, err) } continue } // Process one or more short options. for arg = arg[1:]; arg != ""; { r, size := utf8.DecodeRuneInString(arg) if r == utf8.RuneError && size == 1 { return f.failf("invalid UTF8 in command-line flags") } name := arg[:size] arg = arg[size:] fg := f.Lookup(name) if fg == nil { if name == "h" { // TODO ErrHelp } return f.failf("flag provided but not defined: -%s", name) } if b, ok := fg.Value.(boolFlag); ok && b.IsBoolFlag() { if err := fg.Value.Set("true"); err != nil { return f.failf("invalid boolean flag %s: %v", name, err) } continue } if arg == "" { if len(args) == 0 { return f.failf("missing argument for -%s", name) } arg, args = args[0], args[1:] } if err := fg.Value.Set(arg); err != nil { return f.failf("invalid value %q for flag -%s: %v", arg, name, err) } break // consumed arg } } // Arrange for flag.NArg, flag.Args, etc to work properly. f.FlagSet.Parse(append([]string{"--"}, args...)) return nil } // PrintDefaults is like flag.PrintDefaults but includes information // about short/long alias pairs and prints the correct syntax for // long flags. func PrintDefaults() { CommandLine.PrintDefaults() } // PrintDefaults is like flag.PrintDefaults but includes information // about short/long alias pairs and prints the correct syntax for // long flags. func (f *FlagSet) PrintDefaults() { f.FlagSet.VisitAll(func(fg *flag.Flag) { name := fg.Name short, long := "", "" other := f.unalias[name] if utf8.RuneCountInString(name) > 1 { long, short = name, other } else { short, long = name, other } var s string if short != "" { s = fmt.Sprintf(" -%s", short) // Two spaces before -; see next two comments. if long != "" { s += ", --" + long } } else { s = fmt.Sprintf(" --%s", long) // Two spaces before -; see next two comments. } name, usage := flag.UnquoteUsage(fg) if len(name) > 0 { s += " " + name } // Boolean flags of one ASCII letter are so common we // treat them specially, putting their usage on the same line. if len(s) <= 4 { // space, space, '-', 'x'. s += "\t" } else { // Four spaces before the tab triggers good alignment // for both 4- and 8-space tab stops. s += "\n \t" } s += usage if !isZeroValue(fg, fg.DefValue) { if strings.HasSuffix(reflect.TypeOf(fg.Value).String(), "stringValue") { // put quotes on the value s += fmt.Sprintf(" (default %q)", fg.DefValue) } else { s += fmt.Sprintf(" (default %v)", fg.DefValue) } } fmt.Fprint(f.out(), s, "\n") }) } // isZeroValue guesses whether the string represents the zero // value for a flag. It is not accurate but in practice works OK. func isZeroValue(f *flag.Flag, value string) bool { // Build a zero value of the flag's Value type, and see if the // result of calling its String method equals the value passed in. // This works unless the Value type is itself an interface type. typ := reflect.TypeOf(f.Value) var z reflect.Value if typ.Kind() == reflect.Ptr { z = reflect.New(typ.Elem()) } else { z = reflect.Zero(typ) } if value == z.Interface().(flag.Value).String() { return true } switch value { case "false": return true case "": return true case "0": return true } return false }