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 }