taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

getopt.go (12337B)


      1 // Copyright 2017 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package getopt parses command lines using getopt(3) syntax.
      6 // It is a replacement for flag.Parse but still expects flags themselves
      7 // to be defined in package flag.
      8 //
      9 // Flags defined with one-letter names are available as short flags
     10 // (invoked using one dash, as in -x) and all flags are available as
     11 // long flags (invoked using two dashes, as in --x or --xylophone).
     12 //
     13 // To use, define flags as usual with package flag. Then introduce
     14 // any aliases by calling getopt.Alias:
     15 //
     16 //	getopt.Alias("n", "dry-run")
     17 //	getopt.Alias("v", "verbose")
     18 //
     19 // Or call getopt.Aliases to define a list of aliases:
     20 //
     21 //	getopt.Aliases(
     22 //		"n", "dry-run",
     23 //		"v", "verbose",
     24 //	)
     25 //
     26 // One name in each pair must already be defined in package flag
     27 // (so either "n" or "dry-run", and also either "v" or "verbose").
     28 //
     29 // Then parse the command-line:
     30 //
     31 //	getopt.Parse()
     32 //
     33 // If it encounters an error, Parse calls flag.Usage and then exits the program.
     34 //
     35 // When writing a custom flag.Usage function, call getopt.PrintDefaults
     36 // instead of flag.PrintDefaults to get a usage message that includes the
     37 // names of aliases in flag descriptions.
     38 //
     39 // At initialization time, this package installs a new flag.Usage that is the
     40 // same as the default flag.Usage except that it calls getopt.PrintDefaults
     41 // instead of flag.PrintDefaults.
     42 //
     43 // This package also defines a FlagSet wrapping the standard flag.FlagSet.
     44 //
     45 // Caveat
     46 //
     47 // In general Go flag parsing is preferred for new programs, because
     48 // it is not as pedantic about the number of dashes used to invoke
     49 // a flag (you can write -verbose or --verbose and the program
     50 // does not care). This package is meant to be used in situations
     51 // where, for legacy reasons, it is important to use exactly getopt(3)
     52 // syntax, such as when rewriting in Go an existing tool that already
     53 // uses getopt(3).
     54 package getopt // import "rsc.io/getopt"
     55 
     56 import (
     57 	"flag"
     58 	"fmt"
     59 	"io"
     60 	"os"
     61 	"reflect"
     62 	"strings"
     63 	"unicode/utf8"
     64 )
     65 
     66 func init() {
     67 	flag.Usage = func() {
     68 		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
     69 		PrintDefaults() // ours not package flag's
     70 	}
     71 
     72 	CommandLine.FlagSet = flag.CommandLine
     73 	CommandLine.name = os.Args[0]
     74 	CommandLine.errorHandling = flag.ExitOnError
     75 	CommandLine.outw = os.Stderr
     76 	CommandLine.Usage = func() { flag.Usage() }
     77 }
     78 
     79 var CommandLine FlagSet
     80 
     81 // A FlagSet is a set of defined flags.
     82 // It wraps and provides the same interface as flag.FlagSet
     83 // but parses command line arguments using getopt syntax.
     84 //
     85 // Note that "go doc" shows only the methods customized
     86 // by package getopt; FlagSet also provides all the methods
     87 // of the embedded flag.FlagSet, like Bool, Int, NArg, and so on.
     88 type FlagSet struct {
     89 	*flag.FlagSet
     90 
     91 	alias         map[string]string
     92 	unalias       map[string]string
     93 	name          string
     94 	errorHandling flag.ErrorHandling
     95 	outw          io.Writer
     96 }
     97 
     98 func (f *FlagSet) out() io.Writer {
     99 	if f.outw == nil {
    100 		return os.Stderr
    101 	}
    102 	return f.outw
    103 }
    104 
    105 // SetOutput sets the destination for usage and error messages.
    106 // If output is nil, os.Stderr is used.
    107 func (f *FlagSet) SetOutput(output io.Writer) {
    108 	f.FlagSet.SetOutput(output)
    109 	f.outw = output
    110 }
    111 
    112 // NewFlagSet returns a new, empty flag set with the specified name and error
    113 // handling property.
    114 func NewFlagSet(name string, errorHandling flag.ErrorHandling) *FlagSet {
    115 	f := new(FlagSet)
    116 	f.Init(name, errorHandling)
    117 	return f
    118 }
    119 
    120 // Init sets the name and error handling proprety for a flag set.
    121 func (f *FlagSet) Init(name string, errorHandling flag.ErrorHandling) {
    122 	if f.FlagSet == nil {
    123 		f.FlagSet = new(flag.FlagSet)
    124 	}
    125 	f.FlagSet.Init(name, errorHandling)
    126 	f.name = name
    127 	f.errorHandling = errorHandling
    128 	f.FlagSet.Usage = f.defaultUsage
    129 }
    130 
    131 func (f *FlagSet) init() {
    132 	if f.alias == nil {
    133 		f.alias = make(map[string]string)
    134 		f.unalias = make(map[string]string)
    135 	}
    136 }
    137 
    138 // Lookup returns the Flag structure of the named flag,
    139 // returning nil if none exists.
    140 // If name is a defined alias for a defined flag,
    141 // Lookup returns the original flag; in this case
    142 // the Name field in the result will differ from the
    143 // name passed to Lookup.
    144 func (f *FlagSet) Lookup(name string) *flag.Flag {
    145 	if x, ok := f.alias[name]; ok {
    146 		name = x
    147 	}
    148 	return f.FlagSet.Lookup(name)
    149 }
    150 
    151 // Alias introduces an alias for an existing flag name.
    152 // The short name must be a single letter, and the long name must be multiple letters.
    153 // Exactly one name must be defined as a flag already: the undefined name is introduced
    154 // as an alias for the defined name.
    155 // Alias panics if both names are already defined or if both are undefined.
    156 //
    157 // For example, if a flag named "v" is already defined using package flag,
    158 // then it is available as -v (or --v). Calling Alias("v", "verbose") makes the same
    159 // flag also available as --verbose.
    160 func Alias(short, long string) {
    161 	CommandLine.Alias(short, long)
    162 }
    163 
    164 // Alias introduces an alias for an existing flag name.
    165 // The short name must be a single letter, and the long name must be multiple letters.
    166 // Exactly one name must be defined as a flag already: the undefined name is introduced
    167 // as an alias for the defined name.
    168 // Alias panics if both names are already defined or if both are undefined.
    169 //
    170 // For example, if a flag named "v" is already defined using package flag,
    171 // then it is available as -v (or --v). Calling Alias("v", "verbose") makes the same
    172 // flag also available as --verbose.
    173 func (f *FlagSet) Alias(short, long string) {
    174 	f.init()
    175 	if short == "" || long == "" {
    176 		panic("Alias: invalid empty flag name")
    177 	}
    178 	if utf8.RuneCountInString(short) != 1 {
    179 		panic("Alias: invalid short flag name -" + short)
    180 	}
    181 	if utf8.RuneCountInString(long) == 1 {
    182 		panic("Alias: invalid long flag name --" + long)
    183 	}
    184 
    185 	f1 := f.Lookup(short)
    186 	f2 := f.Lookup(long)
    187 	if f1 == nil && f2 == nil {
    188 		panic("Alias: neither -" + short + " nor -" + long + " is a defined flag")
    189 	}
    190 	if f1 != nil && f2 != nil {
    191 		panic("Alias: both -" + short + " and -" + long + " are defined flags")
    192 	}
    193 
    194 	if f1 != nil {
    195 		f.alias[long] = short
    196 		f.unalias[short] = long
    197 	} else {
    198 		f.alias[short] = long
    199 		f.unalias[long] = short
    200 	}
    201 }
    202 
    203 // Aliases introduces zero or more aliases. The argument list must consist of an
    204 // even number of strings making up a sequence of short, long pairs to be passed
    205 // to Alias.
    206 func Aliases(list ...string) {
    207 	CommandLine.Aliases(list...)
    208 }
    209 
    210 // Aliases introduces zero or more aliases. The argument list must consist of an
    211 // even number of strings making up a sequence of short, long pairs to be passed
    212 // to Alias.
    213 func (f *FlagSet) Aliases(list ...string) {
    214 	if len(list)%2 != 0 {
    215 		panic("getopt: Aliases not invoked with pairs")
    216 	}
    217 	for i := 0; i < len(list); i += 2 {
    218 		f.Alias(list[i], list[i+1])
    219 	}
    220 }
    221 
    222 type boolFlag interface {
    223 	IsBoolFlag() bool
    224 }
    225 
    226 func (f *FlagSet) failf(format string, args ...interface{}) error {
    227 	err := fmt.Errorf(format, args...)
    228 	fmt.Fprintln(f.out(), err)
    229 	f.Usage()
    230 	return err
    231 }
    232 
    233 // defaultUsage is the default function to print a usage message.
    234 func (f *FlagSet) defaultUsage() {
    235 	if f.name == "" {
    236 		fmt.Fprintf(f.out(), "Usage:\n")
    237 	} else {
    238 		fmt.Fprintf(f.out(), "Usage of %s:\n", f.name)
    239 	}
    240 	f.PrintDefaults()
    241 }
    242 
    243 // Parse parses the command-line flags from os.Args[1:].
    244 func Parse() {
    245 	CommandLine.Parse(os.Args[1:])
    246 }
    247 
    248 // Parse parses flag definitions from the argument list,
    249 // which should not include the command name.
    250 // Parse must be called after all flags and aliases in the FlagSet are defined
    251 // and before flags are accessed by the program.
    252 // The return value will be flag.ErrHelp if -h or --help were used but not defined.
    253 func (f *FlagSet) Parse(args []string) error {
    254 	for len(args) > 0 {
    255 		arg := args[0]
    256 		if len(arg) < 2 || arg[0] != '-' {
    257 			break
    258 		}
    259 		args = args[1:]
    260 		if arg[:2] == "--" {
    261 			// Process single long option.
    262 			if arg == "--" {
    263 				break
    264 			}
    265 			name := arg[2:]
    266 			value := ""
    267 			haveValue := false
    268 			if i := strings.Index(name, "="); i >= 0 {
    269 				name, value = name[:i], name[i+1:]
    270 				haveValue = true
    271 			}
    272 			fg := f.Lookup(name)
    273 			if fg == nil {
    274 				if name == "h" || name == "help" {
    275 					// TODO ErrHelp
    276 				}
    277 				return f.failf("flag provided but not defined: --%s", name)
    278 			}
    279 			if b, ok := fg.Value.(boolFlag); ok && b.IsBoolFlag() {
    280 				if haveValue {
    281 					if err := fg.Value.Set(value); err != nil {
    282 						return f.failf("invalid boolean value %q for --%s: %v", value, name, err)
    283 					}
    284 				} else {
    285 					if err := fg.Value.Set("true"); err != nil {
    286 						return f.failf("invalid boolean flag %s: %v", name, err)
    287 					}
    288 				}
    289 				continue
    290 			}
    291 			if !haveValue {
    292 				if len(args) == 0 {
    293 					return f.failf("missing argument for --%s", name)
    294 				}
    295 				value, args = args[0], args[1:]
    296 			}
    297 			if err := fg.Value.Set(value); err != nil {
    298 				return f.failf("invalid value %q for flag --%s: %v", value, name, err)
    299 			}
    300 			continue
    301 		}
    302 
    303 		// Process one or more short options.
    304 		for arg = arg[1:]; arg != ""; {
    305 			r, size := utf8.DecodeRuneInString(arg)
    306 			if r == utf8.RuneError && size == 1 {
    307 				return f.failf("invalid UTF8 in command-line flags")
    308 			}
    309 			name := arg[:size]
    310 			arg = arg[size:]
    311 			fg := f.Lookup(name)
    312 			if fg == nil {
    313 				if name == "h" {
    314 					// TODO ErrHelp
    315 				}
    316 				return f.failf("flag provided but not defined: -%s", name)
    317 			}
    318 			if b, ok := fg.Value.(boolFlag); ok && b.IsBoolFlag() {
    319 				if err := fg.Value.Set("true"); err != nil {
    320 					return f.failf("invalid boolean flag %s: %v", name, err)
    321 				}
    322 				continue
    323 			}
    324 			if arg == "" {
    325 				if len(args) == 0 {
    326 					return f.failf("missing argument for -%s", name)
    327 				}
    328 				arg, args = args[0], args[1:]
    329 			}
    330 			if err := fg.Value.Set(arg); err != nil {
    331 				return f.failf("invalid value %q for flag -%s: %v", arg, name, err)
    332 			}
    333 			break // consumed arg
    334 		}
    335 	}
    336 
    337 	// Arrange for flag.NArg, flag.Args, etc to work properly.
    338 	f.FlagSet.Parse(append([]string{"--"}, args...))
    339 	return nil
    340 }
    341 
    342 // PrintDefaults is like flag.PrintDefaults but includes information
    343 // about short/long alias pairs and prints the correct syntax for
    344 // long flags.
    345 func PrintDefaults() {
    346 	CommandLine.PrintDefaults()
    347 }
    348 
    349 // PrintDefaults is like flag.PrintDefaults but includes information
    350 // about short/long alias pairs and prints the correct syntax for
    351 // long flags.
    352 func (f *FlagSet) PrintDefaults() {
    353 	f.FlagSet.VisitAll(func(fg *flag.Flag) {
    354 		name := fg.Name
    355 		short, long := "", ""
    356 		other := f.unalias[name]
    357 		if utf8.RuneCountInString(name) > 1 {
    358 			long, short = name, other
    359 		} else {
    360 			short, long = name, other
    361 		}
    362 		var s string
    363 		if short != "" {
    364 			s = fmt.Sprintf("  -%s", short) // Two spaces before -; see next two comments.
    365 			if long != "" {
    366 				s += ", --" + long
    367 			}
    368 		} else {
    369 			s = fmt.Sprintf("  --%s", long) // Two spaces before -; see next two comments.
    370 		}
    371 		name, usage := flag.UnquoteUsage(fg)
    372 		if len(name) > 0 {
    373 			s += " " + name
    374 		}
    375 
    376 		// Boolean flags of one ASCII letter are so common we
    377 		// treat them specially, putting their usage on the same line.
    378 		if len(s) <= 4 { // space, space, '-', 'x'.
    379 			s += "\t"
    380 		} else {
    381 			// Four spaces before the tab triggers good alignment
    382 			// for both 4- and 8-space tab stops.
    383 			s += "\n    \t"
    384 		}
    385 		s += usage
    386 		if !isZeroValue(fg, fg.DefValue) {
    387 			if strings.HasSuffix(reflect.TypeOf(fg.Value).String(), "stringValue") {
    388 				// put quotes on the value
    389 				s += fmt.Sprintf(" (default %q)", fg.DefValue)
    390 			} else {
    391 				s += fmt.Sprintf(" (default %v)", fg.DefValue)
    392 			}
    393 		}
    394 		fmt.Fprint(f.out(), s, "\n")
    395 	})
    396 }
    397 
    398 // isZeroValue guesses whether the string represents the zero
    399 // value for a flag. It is not accurate but in practice works OK.
    400 func isZeroValue(f *flag.Flag, value string) bool {
    401 	// Build a zero value of the flag's Value type, and see if the
    402 	// result of calling its String method equals the value passed in.
    403 	// This works unless the Value type is itself an interface type.
    404 	typ := reflect.TypeOf(f.Value)
    405 	var z reflect.Value
    406 	if typ.Kind() == reflect.Ptr {
    407 		z = reflect.New(typ.Elem())
    408 	} else {
    409 		z = reflect.Zero(typ)
    410 	}
    411 	if value == z.Interface().(flag.Value).String() {
    412 		return true
    413 	}
    414 
    415 	switch value {
    416 	case "false":
    417 		return true
    418 	case "":
    419 		return true
    420 	case "0":
    421 		return true
    422 	}
    423 	return false
    424 }