github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/elvish/getopt/getopt.go (about) 1 // Package getopt implements a command-line argument parser. 2 // 3 // It tries to cover all common styles of option syntaxes, and provides context 4 // information when given a partial input. It is mainly useful for writing 5 // completion engines and wrapper programs. 6 // 7 // If you are looking for an option parser for your go programm, consider using 8 // the flag package in the standard library instead. 9 package getopt 10 11 //go:generate stringer -type=Config,HasArg,ContextType -output=string.go 12 13 import "strings" 14 15 // Getopt specifies the syntax of command-line arguments. 16 type Getopt struct { 17 Options []*Option 18 Config Config 19 } 20 21 // Config configurates the parsing behavior. 22 type Config uint 23 24 const ( 25 // DoubleDashTerminatesOptions indicates that all elements after an argument 26 // "--" are treated as arguments. 27 DoubleDashTerminatesOptions Config = 1 << iota 28 // FirstArgTerminatesOptions indicates that all elements after the first 29 // argument are treated as arguments. 30 FirstArgTerminatesOptions 31 // LongOnly indicates that long options may be started by either one or two 32 // dashes, and short options are not allowed. Should replicate the behavior 33 // of getopt_long_only and the 34 // flag package of the Go standard library. 35 LongOnly 36 // GNUGetoptLong is a configuration that should replicate the behavior of 37 // GNU getopt_long. 38 GNUGetoptLong = DoubleDashTerminatesOptions 39 // POSIXGetopt is a configuration that should replicate the behavior of 40 // POSIX getopt. 41 POSIXGetopt = DoubleDashTerminatesOptions | FirstArgTerminatesOptions 42 ) 43 44 // HasAll tests whether a configuration has all specified flags set. 45 func (conf Config) HasAll(flags Config) bool { 46 return (conf & flags) == flags 47 } 48 49 // Option is a command-line option. 50 type Option struct { 51 // Short option. Set to 0 for long-only. 52 Short rune 53 // Long option. Set to "" for short-only. 54 Long string 55 // Whether the option takes an argument, and whether it is required. 56 HasArg HasArg 57 } 58 59 // HasArg indicates whether an option takes an argument, and whether it is 60 // required. 61 type HasArg uint 62 63 const ( 64 // NoArgument indicates that an option takes no argument. 65 NoArgument HasArg = iota 66 // RequiredArgument indicates that an option must take an argument. The 67 // argument can come either directly after a short option (-oarg), after a 68 // long option followed by an equal sign (--long=arg), or as a subsequent 69 // argument after the option (-o arg, --long arg). 70 RequiredArgument 71 // OptionalArgument indicates that an option takes an optional argument. 72 // The argument can come either directly after a short option (-oarg) or 73 // after a long option followed by an equal sign (--long=arg). 74 OptionalArgument 75 ) 76 77 // ParsedOption represents a parsed option. 78 type ParsedOption struct { 79 Option *Option 80 Long bool 81 Argument string 82 } 83 84 // Context indicates what may come after the supplied argument list. 85 type Context struct { 86 // The nature of the context. 87 Type ContextType 88 // Current option, with a likely incomplete Argument. Non-nil when Type is 89 // OptionArgument. 90 Option *ParsedOption 91 // Current partial long option name or argument. Non-empty when Type is 92 // LongOption or Argument. 93 Text string 94 } 95 96 // ContextType encodes what may be appended to the last element of the argument 97 // list. 98 type ContextType uint 99 100 const ( 101 // NewOptionOrArgument indicates that the last element may be either a new 102 // option or a new argument. Returned when it is an empty string. 103 NewOptionOrArgument ContextType = iota 104 // NewOption indicates that the last element must be new option, short or 105 // long. Returned when it is "-". 106 NewOption 107 // NewLongOption indicates that the last element must be a new long option. 108 // Returned when it is "--". 109 NewLongOption 110 // LongOption indicates that the last element is a long option, but not its 111 // argument. The partial name of the long option is stored in Context.Text. 112 LongOption 113 // ChainShortOption indicates that a new short option may be chained. 114 // Returned when the last element consists of a chain of options that take 115 // no arguments. 116 ChainShortOption 117 // OptionArgument indicates that the last element list must be an argument 118 // to an option. The option in question is stored in Context.Option. 119 OptionArgument 120 // Argument indicates that the last element is an argument. The partial 121 // argument is stored in Context.Text. 122 Argument 123 ) 124 125 func (g *Getopt) findShort(r rune) *Option { 126 for _, opt := range g.Options { 127 if r == opt.Short { 128 return opt 129 } 130 } 131 return nil 132 } 133 134 // parseShort parse short options, without the leading dash. It returns the 135 // parsed options and whether an argument is still to be seen. 136 func (g *Getopt) parseShort(s string) ([]*ParsedOption, bool) { 137 var opts []*ParsedOption 138 var needArg bool 139 for i, r := range s { 140 opt := g.findShort(r) 141 if opt != nil { 142 if opt.HasArg == NoArgument { 143 opts = append(opts, &ParsedOption{opt, false, ""}) 144 continue 145 } else { 146 parsed := &ParsedOption{opt, false, s[i+len(string(r)):]} 147 opts = append(opts, parsed) 148 needArg = parsed.Argument == "" && opt.HasArg == RequiredArgument 149 break 150 } 151 } 152 // Unknown option, treat as taking an optional argument 153 parsed := &ParsedOption{ 154 &Option{r, "", OptionalArgument}, false, s[i+len(string(r)):]} 155 opts = append(opts, parsed) 156 break 157 } 158 return opts, needArg 159 } 160 161 // parseLong parse a long option, without the leading dashes. It returns the 162 // parsed option and whether an argument is still to be seen. 163 func (g *Getopt) parseLong(s string) (*ParsedOption, bool) { 164 eq := strings.IndexRune(s, '=') 165 for _, opt := range g.Options { 166 if s == opt.Long { 167 return &ParsedOption{opt, true, ""}, opt.HasArg == RequiredArgument 168 } else if eq != -1 && s[:eq] == opt.Long { 169 return &ParsedOption{opt, true, s[eq+1:]}, false 170 } 171 } 172 // Unknown option, treat as taking an optional argument 173 if eq == -1 { 174 return &ParsedOption{&Option{0, s, OptionalArgument}, true, ""}, false 175 } 176 return &ParsedOption{&Option{0, s[:eq], OptionalArgument}, true, s[eq+1:]}, false 177 } 178 179 // Parse parses an argument list. 180 func (g *Getopt) Parse(elems []string) ([]*ParsedOption, []string, *Context) { 181 var ( 182 opts []*ParsedOption 183 args []string 184 // Non-nil only when the last element was an option with required 185 // argument, but the argument has not been seen. 186 opt *ParsedOption 187 // True if an option terminator has been seen. The criteria of option 188 // terminators is determined by the configuration. 189 noopt bool 190 ) 191 var elem string 192 hasPrefix := func(p string) bool { return strings.HasPrefix(elem, p) } 193 for _, elem = range elems[:len(elems)-1] { 194 if opt != nil { 195 opt.Argument = elem 196 opts = append(opts, opt) 197 opt = nil 198 } else if noopt { 199 args = append(args, elem) 200 } else if g.Config.HasAll(DoubleDashTerminatesOptions) && elem == "--" { 201 noopt = true 202 } else if hasPrefix("--") { 203 newopt, needArg := g.parseLong(elem[2:]) 204 if needArg { 205 opt = newopt 206 } else { 207 opts = append(opts, newopt) 208 } 209 } else if hasPrefix("-") { 210 if g.Config.HasAll(LongOnly) { 211 newopt, needArg := g.parseLong(elem[1:]) 212 if needArg { 213 opt = newopt 214 } else { 215 opts = append(opts, newopt) 216 } 217 } else { 218 newopts, needArg := g.parseShort(elem[1:]) 219 if needArg { 220 opts = append(opts, newopts[:len(newopts)-1]...) 221 opt = newopts[len(newopts)-1] 222 } else { 223 opts = append(opts, newopts...) 224 } 225 } 226 } else { 227 args = append(args, elem) 228 if g.Config.HasAll(FirstArgTerminatesOptions) { 229 noopt = true 230 } 231 } 232 } 233 elem = elems[len(elems)-1] 234 ctx := &Context{} 235 if opt != nil { 236 opt.Argument = elem 237 ctx.Type, ctx.Option = OptionArgument, opt 238 } else if noopt { 239 ctx.Type, ctx.Text = Argument, elem 240 } else if elem == "" { 241 ctx.Type = NewOptionOrArgument 242 } else if elem == "-" { 243 ctx.Type = NewOption 244 } else if elem == "--" { 245 ctx.Type = NewLongOption 246 } else if hasPrefix("--") { 247 if strings.IndexRune(elem, '=') == -1 { 248 ctx.Type, ctx.Text = LongOption, elem[2:] 249 } else { 250 newopt, _ := g.parseLong(elem[2:]) 251 ctx.Type, ctx.Option = OptionArgument, newopt 252 } 253 } else if hasPrefix("-") { 254 if g.Config.HasAll(LongOnly) { 255 if strings.IndexRune(elem, '=') == -1 { 256 ctx.Type, ctx.Text = LongOption, elem[1:] 257 } else { 258 newopt, _ := g.parseLong(elem[1:]) 259 ctx.Type, ctx.Option = OptionArgument, newopt 260 } 261 } else { 262 newopts, _ := g.parseShort(elem[1:]) 263 if newopts[len(newopts)-1].Option.HasArg == NoArgument { 264 opts = append(opts, newopts...) 265 ctx.Type = ChainShortOption 266 } else { 267 opts = append(opts, newopts[:len(newopts)-1]...) 268 ctx.Type, ctx.Option = OptionArgument, newopts[len(newopts)-1] 269 } 270 } 271 } else { 272 ctx.Type, ctx.Text = Argument, elem 273 } 274 return opts, args, ctx 275 }