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 cmdflag handles flag processing common to several go tools. 6package cmdflag 7 8import ( 9 "errors" 10 "flag" 11 "fmt" 12 "strings" 13) 14 15// The flag handling part of go commands such as test is large and distracting. 16// We can't use the standard flag package because some of the flags from 17// our command line are for us, and some are for the binary we're running, 18// and some are for both. 19 20// ErrFlagTerminator indicates the distinguished token "--", which causes the 21// flag package to treat all subsequent arguments as non-flags. 22var ErrFlagTerminator = errors.New("flag terminator") 23 24// A FlagNotDefinedError indicates a flag-like argument that does not correspond 25// to any registered flag in a FlagSet. 26type FlagNotDefinedError struct { 27 RawArg string // the original argument, like --foo or -foo=value 28 Name string 29 HasValue bool // is this the -foo=value or --foo=value form? 30 Value string // only provided if HasValue is true 31} 32 33func (e FlagNotDefinedError) Error() string { 34 return fmt.Sprintf("flag provided but not defined: -%s", e.Name) 35} 36 37// A NonFlagError indicates an argument that is not a syntactically-valid flag. 38type NonFlagError struct { 39 RawArg string 40} 41 42func (e NonFlagError) Error() string { 43 return fmt.Sprintf("not a flag: %q", e.RawArg) 44} 45 46// ParseOne sees if args[0] is present in the given flag set and if so, 47// sets its value and returns the flag along with the remaining (unused) arguments. 48// 49// ParseOne always returns either a non-nil Flag or a non-nil error, 50// and always consumes at least one argument (even on error). 51// 52// Unlike (*flag.FlagSet).Parse, ParseOne does not log its own errors. 53func ParseOne(fs *flag.FlagSet, args []string) (f *flag.Flag, remainingArgs []string, err error) { 54 // This function is loosely derived from (*flag.FlagSet).parseOne. 55 56 raw, args := args[0], args[1:] 57 arg := raw 58 if strings.HasPrefix(arg, "--") { 59 if arg == "--" { 60 return nil, args, ErrFlagTerminator 61 } 62 arg = arg[1:] // reduce two minuses to one 63 } 64 65 switch arg { 66 case "-?", "-h", "-help": 67 return nil, args, flag.ErrHelp 68 } 69 if len(arg) < 2 || arg[0] != '-' || arg[1] == '-' || arg[1] == '=' { 70 return nil, args, NonFlagError{RawArg: raw} 71 } 72 73 name, value, hasValue := strings.Cut(arg[1:], "=") 74 75 f = fs.Lookup(name) 76 if f == nil { 77 return nil, args, FlagNotDefinedError{ 78 RawArg: raw, 79 Name: name, 80 HasValue: hasValue, 81 Value: value, 82 } 83 } 84 85 // Use fs.Set instead of f.Value.Set below so that any subsequent call to 86 // fs.Visit will correctly visit the flags that have been set. 87 88 failf := func(format string, a ...any) (*flag.Flag, []string, error) { 89 return f, args, fmt.Errorf(format, a...) 90 } 91 92 if fv, ok := f.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg 93 if hasValue { 94 if err := fs.Set(name, value); err != nil { 95 return failf("invalid boolean value %q for -%s: %v", value, name, err) 96 } 97 } else { 98 if err := fs.Set(name, "true"); err != nil { 99 return failf("invalid boolean flag %s: %v", name, err) 100 } 101 } 102 } else { 103 // It must have a value, which might be the next argument. 104 if !hasValue && len(args) > 0 { 105 // value is the next arg 106 hasValue = true 107 value, args = args[0], args[1:] 108 } 109 if !hasValue { 110 return failf("flag needs an argument: -%s", name) 111 } 112 if err := fs.Set(name, value); err != nil { 113 return failf("invalid value %q for flag -%s: %v", value, name, err) 114 } 115 } 116 117 return f, args, nil 118} 119 120type boolFlag interface { 121 IsBoolFlag() bool 122} 123