| 53 | | } |
|---|
| 54 | | |
|---|
| 55 | | /* |
|---|
| 56 | | Options may be in two forms: long and short. Short options start with a single |
|---|
| 57 | | dash and are one letter long. Long options start with two dashes and may |
|---|
| 58 | | consist of any number of characters (so long as they don't start with a dash, |
|---|
| 59 | | though they may contain dashes). Options are case-sensitive. |
|---|
| 60 | | |
|---|
| 61 | | Short options may be combined. The following are equivalent: |
|---|
| 62 | | |
|---|
| 63 | | $ myapp -a -b -c |
|---|
| 64 | | $ myapp -abc |
|---|
| 65 | | $ myapp -ab -c |
|---|
| 66 | | |
|---|
| 67 | | If -f and --file are aliases of the same option, which accepts an argument, |
|---|
| 68 | | the following are equivalent: |
|---|
| 69 | | |
|---|
| 70 | | $ myapp -f somefile.txt |
|---|
| 71 | | $ myapp -fsomefile.txt |
|---|
| 72 | | $ myapp --file somefile.txt |
|---|
| 73 | | $ myapp --file=somefile.txt |
|---|
| 74 | | |
|---|
| 75 | | The following are also valid: |
|---|
| 76 | | |
|---|
| 77 | | $ myapp -abcf somefile.txt |
|---|
| 78 | | $ myapp -abcfsomefile.txt |
|---|
| 79 | | $ myapp -abc --file somefile.txt |
|---|
| 80 | | |
|---|
| 81 | | If an option occurs multiple times, the last one is the one recorded: |
|---|
| 82 | | |
|---|
| 83 | | $ myapp -f somefile.txt --file otherfile.txt |
|---|
| 84 | | |
|---|
| 85 | | Matches 'otherfile.txt'. |
|---|
| 86 | | */ |
|---|
| 87 | | |
|---|
| 88 | | bool startswith(char[] s, char[] start) { |
|---|
| 89 | | if (s.length < start.length) return false; |
|---|
| 90 | | return s[0 .. start.length] == start; |
|---|
| 91 | | } |
|---|
| 92 | | bool endswith(char[] s, char[] end) { |
|---|
| 93 | | if (s.length < end.length) return false; |
|---|
| 94 | | return s[$ - end.length .. $] == end; |
|---|
| 95 | | } |
|---|
| 96 | | |
|---|
| 97 | | /// Thrown if client code tries to set up an improper option. |
|---|
| 98 | | class OptionError : Exception { |
|---|
| 99 | | this(char[] msg) { super(msg); } |
|---|
| 100 | | } |
|---|
| 101 | | // Thrown if client code tries to extract the wrong type from an option. |
|---|
| 102 | | class OptionTypeError : Exception { |
|---|
| 103 | | this(char[] msg) { super(msg); } |
|---|
| 104 | | } |
|---|
| 105 | | |
|---|
| 106 | | /++ |
|---|
| 107 | | This class represents the results after parsing the command-line. |
|---|
| 108 | | +/ |
|---|
| 109 | | class Options { |
|---|
| 110 | | char[][][char[]] opts; |
|---|
| 111 | | int[char[]] counted_opts; |
|---|
| 112 | | /// By default, leftover arguments are placed in this array. |
|---|
| 113 | | char[][] args; |
|---|
| 114 | | |
|---|
| 115 | | /// Retrieves the results of the Store and StoreConst actions. |
|---|
| 116 | | char[] opIndex(char[] opt) { |
|---|
| 117 | | char[][]* o = opt in opts; |
|---|
| 118 | | if (o) { |
|---|
| 119 | | return (*o)[0]; |
|---|
| 120 | | } else { |
|---|
| 121 | | return ""; |
|---|
| 122 | | } |
|---|
| 123 | | } |
|---|
| 124 | | /// Retrieves the results of the Store action, when the type is Integer. |
|---|
| 125 | | int value(char[] opt) { |
|---|
| 126 | | char[][]* o = opt in opts; |
|---|
| 127 | | if (o) { |
|---|
| 128 | | return toInt((*o)[0]); |
|---|
| 129 | | } else { |
|---|
| 130 | | return 0; |
|---|
| 131 | | } |
|---|
| 132 | | } |
|---|
| 133 | | /// Retrieves the results of the Append and AppendConst actions. |
|---|
| 134 | | char[][] list(char[] opt) { |
|---|
| 135 | | char[][]* o = opt in opts; |
|---|
| 136 | | if (o) { |
|---|
| 137 | | return *o; |
|---|
| 138 | | } else { |
|---|
| 139 | | return null; |
|---|
| 140 | | } |
|---|
| 141 | | } |
|---|
| 142 | | /// Retrieves the results of the Append action, when the type is Integer. |
|---|
| 143 | | int[] valueList(char[] opt) { |
|---|
| 144 | | char[][]* o = opt in opts; |
|---|
| 145 | | int[] l; |
|---|
| 146 | | if (o) { |
|---|
| 147 | | l.length = (*o).length; |
|---|
| 148 | | foreach (i, s; *o) { |
|---|
| 149 | | l[i] = toInt(s); |
|---|
| 150 | | } |
|---|
| 151 | | } |
|---|
| 152 | | return l; |
|---|
| 153 | | } |
|---|
| 154 | | /// Retrieves the results of the Count action. |
|---|
| 155 | | int count(char[] opt) { |
|---|
| 156 | | int* c = opt in counted_opts; |
|---|
| 157 | | if (c) { |
|---|
| 158 | | return *c; |
|---|
| 159 | | } else { |
|---|
| 160 | | return 0; |
|---|
| 161 | | } |
|---|
| 162 | | } |
|---|
| 163 | | /// Retrieves the results of the SetTrue and SetFalse actions. |
|---|
| 164 | | bool flag(char[] opt) { |
|---|
| 165 | | char[][]* o = opt in opts; |
|---|
| 166 | | if (o) { |
|---|
| 167 | | return (*o)[0] == "1"; |
|---|
| 168 | | } else { |
|---|
| 169 | | return false; |
|---|
| 170 | | } |
|---|
| 171 | | } |
|---|
| 172 | | private { |
|---|
| 173 | | void storeArg(char[] name, char[] arg) { |
|---|
| 174 | | opts[name] = [arg]; |
|---|
| 175 | | } |
|---|
| 176 | | void storeArg(char[] name, bool arg) { |
|---|
| 177 | | if (arg) { |
|---|
| 178 | | opts[name] = ["1"]; |
|---|
| 179 | | } else { |
|---|
| 180 | | opts[name] = ["0"]; |
|---|
| 181 | | } |
|---|
| 182 | | } |
|---|
| 183 | | void appendArg(char[] name, char[] arg) { |
|---|
| 184 | | opts[name] ~= arg; |
|---|
| 185 | | } |
|---|
| 186 | | void increment(char[] name) { |
|---|
| 187 | | ++counted_opts[name]; |
|---|
| 188 | | } |
|---|
| 189 | | bool hasName(char[] name) { |
|---|
| 190 | | return name in opts || name in counted_opts; |
|---|
| 191 | | } |
|---|
| 192 | | } |
|---|
| 193 | | } |
|---|
| 194 | | |
|---|
| 195 | | // Options, args, this opt's index in args, name[, arg] |
|---|
| 196 | | /// |
|---|
| 197 | | alias void delegate(Options, inout char[][], inout int, char[], char[]) OptionCallbackFancyArg; |
|---|
| 198 | | /// |
|---|
| 199 | | alias void delegate(Options, inout char[][], inout int, char[], int) OptionCallbackFancyInt; |
|---|
| 200 | | /// |
|---|
| 201 | | alias void delegate(Options, inout char[][], inout int, char[]) OptionCallbackFancy; |
|---|
| 202 | | |
|---|
| 203 | | /// |
|---|
| 204 | | alias void delegate(char[]) OptionCallbackArg; |
|---|
| 205 | | /// |
|---|
| 206 | | alias void delegate(int) OptionCallbackInt; |
|---|
| 207 | | /// |
|---|
| 208 | | alias void delegate() OptionCallback; |
|---|
| 209 | | |
|---|
| 210 | | /// |
|---|
| 211 | | enum Action { /+++/Store, /+++/StoreConst, /+++/Append, /+++/AppendConst, /+++/Count, /+++/SetTrue, /+++/SetFalse, /+++/Callback, /+++/CallbackFancy, /+++/Help /+++/} |
|---|
| 212 | | /// |
|---|
| 213 | | enum ArgType { /+/+++/None,+/ /+++/String, /+++/Integer, /+++/Bool /+++/} |
|---|
| 214 | | |
|---|
| 215 | | /++ |
|---|
| 216 | | This class represents a single command-line option. |
|---|
| 217 | | +/ |
|---|
| 218 | | abstract class Option { |
|---|
| 219 | | char[][] shortopts, longopts; |
|---|
| 220 | | char[] name, argname; |
|---|
| 221 | | char[] helptext; |
|---|
| 222 | | this(char[][] options, char[] name) { |
|---|
| 223 | | dchar[] opt; |
|---|
| 224 | | foreach (_opt; options) { |
|---|
| 225 | | // (Unicode note: We convert to dchar[] so the length checks work |
|---|
| 226 | | // out in the event of a short opt with a >127 character.) |
|---|
| 227 | | opt = toUTF32(_opt); |
|---|
| 228 | | if (opt.length < 2) { |
|---|
| 229 | | throw new OptionError( |
|---|
| 230 | | "invalid option string '" ~ _opt ~ "': must be at least two characters long" |
|---|
| 231 | | ); |
|---|
| 232 | | } else if (opt.length > 2) { |
|---|
| 233 | | if (opt[0 .. 2] != "--" || opt[2] == '-') |
|---|
| 234 | | throw new OptionError( |
|---|
| 235 | | "invalid long option string '" ~ _opt ~ "': must start with --, followed by non-dash" |
|---|
| 236 | | ); |
|---|
| 237 | | longopts ~= _opt; |
|---|
| 238 | | } else { |
|---|
| 239 | | if (opt[0] != '-' || opt[1] == '-') |
|---|
| 240 | | throw new OptionError( |
|---|
| 241 | | "invalid short option string '" ~ _opt ~ "': must be of the form -x, where x is non-dash" |
|---|
| 242 | | ); |
|---|
| 243 | | shortopts ~= _opt; |
|---|
| 244 | | } |
|---|
| 245 | | } |
|---|
| 246 | | if (name is null) { |
|---|
| 247 | | // (Unicode note: We know '-' is a single code unit, so these |
|---|
| 248 | | // slices are okay.) |
|---|
| 249 | | if (longopts.length > 0) |
|---|
| 250 | | this.name = longopts[0][2 .. $]; |
|---|
| 251 | | else if (shortopts.length > 0) |
|---|
| 252 | | this.name = shortopts[0][1 .. 2]; |
|---|
| 253 | | else |
|---|
| 254 | | throw new OptionError( |
|---|
| 255 | | "No options provided to addOption!" |
|---|
| 256 | | ); |
|---|
| 257 | | } else { |
|---|
| 258 | | this.name = name; |
|---|
| 259 | | } |
|---|
| 260 | | argname = toupper(this.name.dup); |
|---|
| 261 | | } |
|---|
| 262 | | char[] toString() { |
|---|
| 263 | | int optCount = this.shortopts.length + this.longopts.length; |
|---|
| 264 | | char[] result; |
|---|
| 265 | | //bool printed_arg = false; |
|---|
| 266 | | foreach(i, opt; this.shortopts ~ this.longopts) { |
|---|
| 267 | | result ~= opt; |
|---|
| 268 | | if (i < optCount-1) { |
|---|
| 269 | | result ~= ", "; |
|---|
| 270 | | } else if (this.hasArg()) { |
|---|
| 271 | | result ~= "=" ~ toupper(this.argname.dup); |
|---|
| 272 | | } |
|---|
| 273 | | } |
|---|
| 274 | | return result; |
|---|
| 275 | | } |
|---|
| 276 | | //enum Action { Store, StoreConst, Append, AppendConst, Count, SetTrue, SetFalse, Callback, CallbackFancy, Help } |
|---|
| 277 | | bool supports_default() { |
|---|
| 278 | | return false; |
|---|
| 279 | | } |
|---|
| 280 | | ArgType def_type() { |
|---|
| 281 | | throw new OptionError("Option "~name~" does not support default arguments."); |
|---|
| 282 | | } |
|---|
| 283 | | bool has_default() { |
|---|
| 284 | | throw new OptionError("Option "~name~" does not support default arguments."); |
|---|
| 285 | | } |
|---|
| 286 | | void issue_default(Options results) { |
|---|
| 287 | | return; |
|---|
| 288 | | } |
|---|
| 289 | | /// Does whatever this option is supposed to do. |
|---|
| 290 | | abstract void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg); |
|---|
| 291 | | /// Returns whether this option accepts an argument. |
|---|
| 292 | | bool hasArg() { |
|---|
| 293 | | return false; |
|---|
| 294 | | } |
|---|
| 295 | | /// Sets the help text for this option. |
|---|
| 296 | | Option help(char[] help) { |
|---|
| 297 | | this.helptext = help; |
|---|
| 298 | | return this; |
|---|
| 299 | | } |
|---|
| 300 | | /// Sets the name of this option's argument, if it has one. |
|---|
| 301 | | Option argName(char[] argname) { |
|---|
| 302 | | this.argname = argname; |
|---|
| 303 | | return this; |
|---|
| 304 | | } |
|---|
| 305 | | Option def(char[] val) { |
|---|
| 306 | | throw new OptionError("Cannot specify string default for non-string option '"~this.name~"'"); |
|---|
| 307 | | } |
|---|
| 308 | | Option def(int val) { |
|---|
| 309 | | throw new OptionError("Cannot specify integer default for non-integer option '"~this.name~"'"); |
|---|
| 310 | | } |
|---|
| 311 | | Option def(bool val) { |
|---|
| 312 | | throw new OptionError("Cannot specify boolean default for non-flag option '"~this.name~"'"); |
|---|
| 313 | | } |
|---|
| 314 | | // Returns true if the passed option string matches this option. |
|---|
| 315 | | bool matches(char[] _arg) { |
|---|
| 316 | | dchar[] arg = toUTF32(_arg); |
|---|
| 317 | | if ( |
|---|
| 318 | | arg.length < 2 || |
|---|
| 319 | | arg.length == 2 && (arg[0] != '-' || arg[1] == '-') || |
|---|
| 320 | | arg.length > 2 && (arg[0 .. 2] != "--" || arg[2] == '-') |
|---|
| 321 | | ) { |
|---|
| 322 | | return false; |
|---|
| 323 | | } |
|---|
| 324 | | if (arg.length == 2) { |
|---|
| 325 | | foreach (opt; shortopts) { |
|---|
| 326 | | if (_arg == opt) { |
|---|
| 327 | | return true; |
|---|
| 328 | | } |
|---|
| 329 | | } |
|---|
| 330 | | } else { |
|---|
| 331 | | foreach (opt; longopts) { |
|---|
| 332 | | if (_arg == opt) { |
|---|
| 333 | | return true; |
|---|
| 334 | | } |
|---|
| 335 | | } |
|---|
| 336 | | } |
|---|
| 337 | | return false; |
|---|
| 338 | | } |
|---|
| 339 | | } |
|---|
| 340 | | |
|---|
| 341 | | abstract class ArgOption : Option { |
|---|
| 342 | | static const ArgType default_type = ArgType.String; |
|---|
| 343 | | ArgType type; |
|---|
| 344 | | bool _has_default = false; |
|---|
| 345 | | char[] default_string; |
|---|
| 346 | | this(char[][] options, char[] name, ArgType type) { |
|---|
| 347 | | super(options, name); |
|---|
| 348 | | if (type != ArgType.Integer && type != ArgType.String) { |
|---|
| 349 | | throw new OptionError("Argument type for Store and Append must be Integer or String."); |
|---|
| 350 | | } |
|---|
| 351 | | this.type = type; |
|---|
| 352 | | } |
|---|
| 353 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 354 | | if (this.type == ArgType.Integer) { |
|---|
| 355 | | // Verify that it's an int. |
|---|
| 356 | | int i = parser.toOptInt(arg); |
|---|
| 357 | | } |
|---|
| 358 | | } |
|---|
| 359 | | override ArgType def_type() { |
|---|
| 360 | | return this.type; |
|---|
| 361 | | } |
|---|
| 362 | | override Option def(char[] val) { |
|---|
| 363 | | if (this.type != ArgType.String) |
|---|
| 364 | | super.def(val); |
|---|
| 365 | | this._has_default = true; |
|---|
| 366 | | this.default_string = val; |
|---|
| 367 | | return this; |
|---|
| 368 | | } |
|---|
| 369 | | override Option def(int val) { |
|---|
| 370 | | if (this.type != ArgType.Integer) |
|---|
| 371 | | super.def(val); |
|---|
| 372 | | this._has_default = true; |
|---|
| 373 | | this.default_string = .toString(val); |
|---|
| 374 | | return this; |
|---|
| 375 | | } |
|---|
| 376 | | override bool supports_default() { |
|---|
| 377 | | return true; |
|---|
| 378 | | } |
|---|
| 379 | | override bool has_default() { |
|---|
| 380 | | return _has_default; |
|---|
| 381 | | } |
|---|
| 382 | | override void issue_default(Options results) { |
|---|
| 383 | | if (_has_default) results.storeArg(this.name, this.default_string); |
|---|
| 384 | | } |
|---|
| 385 | | override bool hasArg() { |
|---|
| 386 | | return true; |
|---|
| 387 | | } |
|---|
| 388 | | } |
|---|
| 389 | | |
|---|
| 390 | | class Store : ArgOption { |
|---|
| 391 | | this(char[][] options, char[] name, ArgType type=Store.default_type) { |
|---|
| 392 | | super(options, name, type); |
|---|
| 393 | | } |
|---|
| 394 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 395 | | super.performAction(parser, results, args, idx, arg); |
|---|
| 396 | | results.storeArg(this.name, arg); |
|---|
| 397 | | } |
|---|
| 398 | | } |
|---|
| 399 | | class Append : ArgOption { |
|---|
| 400 | | this(char[][] options, char[] name, ArgType type=Append.default_type) { |
|---|
| 401 | | super(options, name, type); |
|---|
| 402 | | } |
|---|
| 403 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 404 | | super.performAction(parser, results, args, idx, arg); |
|---|
| 405 | | results.appendArg(this.name, arg); |
|---|
| 406 | | } |
|---|
| 407 | | } |
|---|
| 408 | | abstract class ConstOption : Option { |
|---|
| 409 | | bool _has_default = false; |
|---|
| 410 | | char[] default_value; |
|---|
| 411 | | char[] const_value; |
|---|
| 412 | | this(char[][] options, char[] name, char[] const_value) { |
|---|
| 413 | | super(options, name); |
|---|
| 414 | | this.const_value = const_value; |
|---|
| 415 | | } |
|---|
| 416 | | override Option def(char[] val) { |
|---|
| 417 | | this._has_default = true; |
|---|
| 418 | | this.default_value = val; |
|---|
| 419 | | return this; |
|---|
| 420 | | } |
|---|
| 421 | | override ArgType def_type() { |
|---|
| 422 | | return ArgType.String; |
|---|
| 423 | | } |
|---|
| 424 | | override bool supports_default() { |
|---|
| 425 | | return true; |
|---|
| 426 | | } |
|---|
| 427 | | override bool has_default() { |
|---|
| 428 | | return _has_default; |
|---|
| 429 | | } |
|---|
| 430 | | override void issue_default(Options results) { |
|---|
| 431 | | if (_has_default) results.storeArg(this.name, this.default_value); |
|---|
| 432 | | } |
|---|
| 433 | | } |
|---|
| 434 | | class StoreConst : ConstOption { |
|---|
| 435 | | this(char[][] options, char[] name, char[] const_value) { |
|---|
| 436 | | super(options, name, const_value); |
|---|
| 437 | | } |
|---|
| 438 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 439 | | results.storeArg(this.name, this.const_value); |
|---|
| 440 | | } |
|---|
| 441 | | } |
|---|
| 442 | | class AppendConst : ConstOption { |
|---|
| 443 | | this(char[][] options, char[] name, char[] const_value) { |
|---|
| 444 | | super(options, name, const_value); |
|---|
| 445 | | } |
|---|
| 446 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 447 | | results.appendArg(this.name, this.const_value); |
|---|
| 448 | | } |
|---|
| 449 | | } |
|---|
| 450 | | abstract class SetBool : Option { |
|---|
| 451 | | bool _has_default = false; |
|---|
| 452 | | bool default_flag = false; |
|---|
| 453 | | this(char[][] options, char[] name) { |
|---|
| 454 | | super(options, name); |
|---|
| 455 | | } |
|---|
| 456 | | override Option def(bool val) { |
|---|
| 457 | | this._has_default = true; |
|---|
| 458 | | this.default_flag = val; |
|---|
| 459 | | return this; |
|---|
| 460 | | } |
|---|
| 461 | | override ArgType def_type() { |
|---|
| 462 | | return ArgType.Bool; |
|---|
| 463 | | } |
|---|
| 464 | | override bool supports_default() { |
|---|
| 465 | | return true; |
|---|
| 466 | | } |
|---|
| 467 | | override bool has_default() { |
|---|
| 468 | | return _has_default; |
|---|
| 469 | | } |
|---|
| 470 | | override void issue_default(Options results) { |
|---|
| 471 | | if (_has_default) results.storeArg(this.name, this.default_flag); |
|---|
| 472 | | } |
|---|
| 473 | | } |
|---|
| 474 | | class SetTrue : SetBool { |
|---|
| 475 | | this(char[][] options, char[] name) { |
|---|
| 476 | | super(options, name); |
|---|
| 477 | | } |
|---|
| 478 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 479 | | results.storeArg(this.name, true); |
|---|
| 480 | | } |
|---|
| 481 | | } |
|---|
| 482 | | class SetFalse : SetBool { |
|---|
| 483 | | this(char[][] options, char[] name) { |
|---|
| 484 | | super(options, name); |
|---|
| 485 | | } |
|---|
| 486 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 487 | | results.storeArg(this.name, false); |
|---|
| 488 | | } |
|---|
| 489 | | } |
|---|
| 490 | | class Count : Option { |
|---|
| 491 | | this(char[][] options, char[] name) { |
|---|
| 492 | | super(options, name); |
|---|
| 493 | | } |
|---|
| 494 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 495 | | results.increment(this.name); |
|---|
| 496 | | } |
|---|
| 497 | | } |
|---|
| 498 | | class Callback : Option { |
|---|
| 499 | | OptionCallback cb; |
|---|
| 500 | | this(char[][] options, OptionCallback cb) { |
|---|
| 501 | | super(options, null); |
|---|
| 502 | | this.cb = cb; |
|---|
| 503 | | } |
|---|
| 504 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 505 | | this.cb(); |
|---|
| 506 | | } |
|---|
| 507 | | } |
|---|
| 508 | | class CallbackArg : Option { |
|---|
| 509 | | OptionCallbackArg cb; |
|---|
| 510 | | this(char[][] options, OptionCallbackArg cb) { |
|---|
| 511 | | super(options, null); |
|---|
| 512 | | this.cb = cb; |
|---|
| 513 | | } |
|---|
| 514 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 515 | | this.cb(arg); |
|---|
| 516 | | } |
|---|
| 517 | | override bool hasArg() { |
|---|
| 518 | | return true; |
|---|
| 519 | | } |
|---|
| 520 | | } |
|---|
| 521 | | class CallbackInt : Option { |
|---|
| 522 | | OptionCallbackInt cb; |
|---|
| 523 | | this(char[][] options, OptionCallbackInt cb) { |
|---|
| 524 | | super(options, null); |
|---|
| 525 | | this.cb = cb; |
|---|
| 526 | | } |
|---|
| 527 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 528 | | int i = parser.toOptInt(arg); |
|---|
| 529 | | this.cb(i); |
|---|
| 530 | | } |
|---|
| 531 | | override bool hasArg() { |
|---|
| 532 | | return true; |
|---|
| 533 | | } |
|---|
| 534 | | } |
|---|
| 535 | | class FancyCallback : Option { |
|---|
| 536 | | OptionCallbackFancy cb; |
|---|
| 537 | | this(char[][] options, char[] name, OptionCallbackFancy cb) { |
|---|
| 538 | | super(options, name); |
|---|
| 539 | | this.cb = cb; |
|---|
| 540 | | } |
|---|
| 541 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 542 | | this.cb(results, args, idx, this.name); |
|---|
| 543 | | } |
|---|
| 544 | | } |
|---|
| 545 | | class FancyCallbackArg : Option { |
|---|
| 546 | | OptionCallbackFancyArg cb; |
|---|
| 547 | | this(char[][] options, char[] name, OptionCallbackFancyArg cb) { |
|---|
| 548 | | super(options, name); |
|---|
| 549 | | this.cb = cb; |
|---|
| 550 | | } |
|---|
| 551 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 552 | | this.cb(results, args, idx, this.name, arg); |
|---|
| 553 | | } |
|---|
| 554 | | override bool hasArg() { |
|---|
| 555 | | return true; |
|---|
| 556 | | } |
|---|
| 557 | | } |
|---|
| 558 | | class FancyCallbackInt : Option { |
|---|
| 559 | | OptionCallbackFancyInt cb; |
|---|
| 560 | | this(char[][] options, char[] name, OptionCallbackFancyInt cb) { |
|---|
| 561 | | super(options, name); |
|---|
| 562 | | this.cb = cb; |
|---|
| 563 | | } |
|---|
| 564 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 565 | | int i = parser.toOptInt(arg); |
|---|
| 566 | | this.cb(results, args, idx, this.name, i); |
|---|
| 567 | | } |
|---|
| 568 | | override bool hasArg() { |
|---|
| 569 | | return true; |
|---|
| 570 | | } |
|---|
| 571 | | } |
|---|
| 572 | | class Help : Option { |
|---|
| 573 | | this(char[][] options) { |
|---|
| 574 | | super(options, null); |
|---|
| 575 | | } |
|---|
| 576 | | override void performAction(OptionParser parser, Options results, inout char[][] args, inout int idx, char[] arg) { |
|---|
| 577 | | parser.helpText(); |
|---|
| 578 | | exit(EXIT_SUCCESS); |
|---|
| 579 | | } |
|---|
| | 62 | +/ |
|---|
| 680 | | } |
|---|
| 681 | | |
|---|
| 682 | | // Checks the passed arg against all the options in the parser. |
|---|
| 683 | | // Returns null if no match is found. |
|---|
| 684 | | Option matches(char[] arg) { |
|---|
| 685 | | foreach(o; options) { |
|---|
| 686 | | if (o.matches(arg)) { |
|---|
| 687 | | return o; |
|---|
| 688 | | } |
|---|
| 689 | | } |
|---|
| 690 | | return null; |
|---|
| 691 | | } |
|---|
| 692 | | version (Tango) { |
|---|
| 693 | | char[] getProgramName(char[] path) { |
|---|
| 694 | | version(Windows) { |
|---|
| 695 | | char delimiter = '\\'; |
|---|
| 696 | | } else { |
|---|
| 697 | | char delimiter = '/'; |
|---|
| 698 | | } |
|---|
| 699 | | uint idx = locatePrior(path, delimiter); |
|---|
| 700 | | if (idx == path.length) return path; |
|---|
| 701 | | return path[idx+1 .. $]; |
|---|
| 702 | | } |
|---|
| 703 | | } else { |
|---|
| 704 | | char[] getProgramName(char[] path) { |
|---|
| 705 | | version(Windows) { |
|---|
| 706 | | // (Unicode note: ".exe" only contains 4 code units, so this slice |
|---|
| 707 | | // should Just Work.) (Although it remains to be seen how robust |
|---|
| 708 | | // this code actually is.) |
|---|
| 709 | | assert(path[$-4 .. $] == ".exe"); |
|---|
| 710 | | path = path[0 .. $-4]; |
|---|
| 711 | | } |
|---|
| 712 | | return getBaseName(path); |
|---|
| 713 | | } |
|---|
| 714 | | } |
|---|
| 715 | | /// Parses the passed command-line arguments and returns the results. |
|---|
| 716 | | Options parse(char[][] args) { |
|---|
| 717 | | this.name = getProgramName(args[0]); |
|---|
| 718 | | args = args[1 .. $]; |
|---|
| 719 | | Options options = new Options; |
|---|
| 720 | | /* |
|---|
| 721 | | The issue is this: |
|---|
| 722 | | |
|---|
| 723 | | $ myapp -abc |
|---|
| 724 | | |
|---|
| 725 | | This might be three short opts, or one or two opts, the last of which |
|---|
| 726 | | accepts an argument. In the three-opt case, we want to get: |
|---|
| 727 | | |
|---|
| 728 | | $ myapp -a -b -c |
|---|
| 729 | | |
|---|
| 730 | | In the one-opt case, we want: |
|---|
| 731 | | |
|---|
| 732 | | $ myapp -a bc |
|---|
| 733 | | |
|---|
| 734 | | In the two-opt case, we want: |
|---|
| 735 | | |
|---|
| 736 | | $ myapp -a -b c |
|---|
| 737 | | |
|---|
| 738 | | We also want to parse apart "--file=somefile" into "--file somefile" |
|---|
| 739 | | */ |
|---|
| 740 | | char[] opt, newopt, arg; |
|---|
| 741 | | dchar[] opt32; |
|---|
| 742 | | int idx; |
|---|
| 743 | | Option match; |
|---|
| 744 | | |
|---|
| 745 | | foreach (o; this.options) { |
|---|
| 746 | | o.issue_default(options); |
|---|
| 747 | | } |
|---|
| 748 | | |
|---|
| 749 | | for (int i=0; i<args.length; ++i) { |
|---|
| 750 | | opt = args[i]; |
|---|
| 751 | | // -- ends the option list, the remainder is dumped into args |
|---|
| 752 | | if (opt == "--") { |
|---|
| 753 | | if (this.leftover_cb !is null) { |
|---|
| 754 | | foreach(a; args[i+1 .. $]) { |
|---|
| 755 | | this.leftover_cb(a); |
|---|
| 756 | | } |
|---|
| 757 | | } else { |
|---|
| 758 | | options.args ~= args[i+1 .. $]; |
|---|
| 759 | | } |
|---|
| 760 | | i = args.length; |
|---|
| 761 | | } else if (opt.startswith("--")) { |
|---|
| 762 | | idx = find(opt, '='); |
|---|
| 763 | | if (idx != getNotFound(opt)) { |
|---|
| 764 | | newopt = opt[0 .. idx]; |
|---|
| 765 | | // Stitch out the old arg, stitch in the newopt, arg pair. |
|---|
| 766 | | // (Unicode note: idx+1 works, since we know '=' is a |
|---|
| 767 | | // single code unit.) |
|---|
| 768 | | args = args[0 .. i] ~ [newopt, opt[idx+1 .. $]] ~ args[i+1 .. $]; |
|---|
| 769 | | } else { |
|---|
| 770 | | newopt = opt; |
|---|
| 771 | | } |
|---|
| 772 | | match = matches(newopt); |
|---|
| 773 | | if (match is null) { |
|---|
| 774 | | unknownOptError(newopt); |
|---|
| 775 | | } |
|---|
| 776 | | if (match.hasArg) { |
|---|
| 777 | | if (i == args.length-1) expectedArgError(match.name); |
|---|
| 778 | | arg = args[i+1]; |
|---|
| 779 | | ++i; |
|---|
| 780 | | } else { |
|---|
| 781 | | arg = null; |
|---|
| 782 | | } |
|---|
| 783 | | match.performAction(this, options, args, i, arg); |
|---|
| 784 | | } else if (opt.startswith("-")) { |
|---|
| 785 | | if (opt.length >= 2) { |
|---|
| 786 | | opt32 = toUTF32(opt[1 .. $]); |
|---|
| 787 | | foreach (j, c; opt32) { |
|---|
| 788 | | newopt = toUTF8("-" ~ [c]); |
|---|
| 789 | | match = matches(newopt); |
|---|
| 790 | | if (match is null) { |
|---|
| 791 | | unknownOptError(newopt); |
|---|
| 792 | | } |
|---|
| 793 | | if (match.hasArg) { |
|---|
| 794 | | // This is the last char in the group, look to the |
|---|
| 795 | | // next element of args for the arg. |
|---|
| 796 | | if (j == opt32.length-1) { |
|---|
| 797 | | if (i == args.length-1) expectedArgError(match.name); |
|---|
| 798 | | arg = args[i+1]; |
|---|
| 799 | | ++i; |
|---|
| 800 | | // Otherwise, consume the rest of this group for |
|---|
| 801 | | // the arg. |
|---|
| 802 | | } else { |
|---|
| 803 | | arg = toUTF8(opt32[j+1 .. $]); |
|---|
| 804 | | match.performAction(this, options, args, i, arg); |
|---|
| 805 | | break; |
|---|
| 806 | | } |
|---|
| 807 | | } else { |
|---|
| 808 | | arg = null; |
|---|
| 809 | | } |
|---|
| 810 | | match.performAction(this, options, args, i, arg); |
|---|
| 811 | | } |
|---|
| 812 | | } else { |
|---|
| 813 | | unknownOptError(opt); |
|---|
| 814 | | } |
|---|
| 815 | | } else { |
|---|
| 816 | | if (this.leftover_cb is null) { |
|---|
| 817 | | options.args ~= opt; |
|---|
| 818 | | } else { |
|---|
| 819 | | this.leftover_cb(opt); |
|---|
| 820 | | } |
|---|
| 821 | | } |
|---|
| 822 | | } |
|---|
| 823 | | return options; |
|---|
| 824 | | } |
|---|
| 825 | | |
|---|
| 826 | | /++ |
|---|
| 827 | | Overrides the default behavior of leftover arguments, calling this callback |
|---|
| 828 | | with them instead of adding them an array. |
|---|
| 829 | | +/ |
|---|
| 830 | | void leftoverCallback(OptionCallbackArg dg) { |
|---|
| 831 | | this.leftover_cb = dg; |
|---|
| 832 | | } |
|---|
| 833 | | private void wrong_args(char[][] options) { |
|---|
| 834 | | if (options.length > 0) { |
|---|
| 835 | | throw new OptionError( |
|---|
| 836 | | "Wrong arguments to addOption for option '" |
|---|
| 837 | | ~options[0]~"'." |
|---|
| 838 | | ); |
|---|
| 839 | | } else { |
|---|
| 840 | | throw new OptionError("Found an empty option!"); |
|---|
| 841 | | } |
|---|
| 842 | | } |
|---|
| 843 | | /// |
|---|
| 844 | | Option addOption(char[][] options ...) { |
|---|
| 845 | | return addOption(new Store(options, null)); |
|---|
| 846 | | } |
|---|
| 847 | | /// |
|---|
| 848 | | Option addOption(char[][] options, char[] name) { |
|---|
| 849 | | return addOption(new Store(options, name)); |
|---|
| 850 | | } |
|---|
| 851 | | /// |
|---|
| 852 | | Option addOption(char[][] options, Action action) { |
|---|
| 853 | | Option o; |
|---|
| 854 | | switch (action) { |
|---|
| 855 | | case Action.Store, Action.Append: |
|---|
| 856 | | return addOption(options, null, action, ArgOption.default_type); |
|---|
| 857 | | case Action.Count: |
|---|
| 858 | | return addOption(options, null, action); |
|---|
| 859 | | case Action.Help: |
|---|
| 860 | | return addOption(new Help(options)); |
|---|
| 861 | | default: |
|---|
| 862 | | wrong_args(options); |
|---|
| 863 | | break; |
|---|
| 864 | | } |
|---|
| 865 | | return addOption(o); |
|---|
| 866 | | } |
|---|
| 867 | | /// |
|---|
| 868 | | Option addOption(char[][] options, ArgType type) { |
|---|
| 869 | | return addOption(new Store(options, null, type)); |
|---|
| 870 | | } |
|---|
| 871 | | /// |
|---|
| 872 | | Option addOption(char[][] options, Action action, ArgType type) { |
|---|
| 873 | | return addOption(options, null, action, type); |
|---|
| 874 | | } |
|---|
| 875 | | /// |
|---|
| 876 | | Option addOption(char[][] options, char[] name, Action action) { |
|---|
| 877 | | Option o; |
|---|
| 878 | | switch (action) { |
|---|
| 879 | | case Action.Store, Action.Append: |
|---|
| 880 | | return addOption(options, name, action, ArgOption.default_type); |
|---|
| 881 | | case Action.Count: |
|---|
| 882 | | o = new Count(options, name); |
|---|
| 883 | | break; |
|---|
| 884 | | case Action.SetTrue: |
|---|
| 885 | | o = new SetTrue(options, name); |
|---|
| 886 | | break; |
|---|
| 887 | | case Action.SetFalse: |
|---|
| 888 | | o = new SetFalse(options, name); |
|---|
| 889 | | break; |
|---|
| 890 | | default: |
|---|
| 891 | | wrong_args(options); |
|---|
| 892 | | break; |
|---|
| 893 | | } |
|---|
| 894 | | return addOption(o); |
|---|
| 895 | | } |
|---|
| 896 | | /// |
|---|
| 897 | | Option addOption(char[][] options, char[] name, Action action, ArgType type) { |
|---|
| 898 | | Option o; |
|---|
| 899 | | switch (action) { |
|---|
| 900 | | case Action.Store: |
|---|
| 901 | | o = new Store(options, name, type); |
|---|
| 902 | | break; |
|---|
| 903 | | case Action.Append: |
|---|
| 904 | | o = new Append(options, name, type); |
|---|
| 905 | | break; |
|---|
| 906 | | default: |
|---|
| 907 | | wrong_args(options); |
|---|
| 908 | | break; |
|---|
| 909 | | } |
|---|
| 910 | | return addOption(o); |
|---|
| 911 | | } |
|---|
| 912 | | /// |
|---|
| 913 | | Option addOption(char[][] options, Action action, char[] const_value) { |
|---|
| 914 | | return addOption(options, null, action, const_value); |
|---|
| 915 | | } |
|---|
| 916 | | /// |
|---|
| 917 | | Option addOption(char[][] options, char[] name, char[] const_value) { |
|---|
| 918 | | return addOption(options, name, Action.StoreConst, const_value); |
|---|
| 919 | | } |
|---|
| 920 | | /// |
|---|
| 921 | | Option addOption(char[][] options, char[] name, Action action, char[] const_value) { |
|---|
| 922 | | Option o; |
|---|
| 923 | | switch (action) { |
|---|
| 924 | | case Action.StoreConst: |
|---|
| 925 | | o = new StoreConst(options, name, const_value); |
|---|
| 926 | | break; |
|---|
| 927 | | case Action.AppendConst: |
|---|
| 928 | | o = new AppendConst(options, name, const_value); |
|---|
| 929 | | break; |
|---|
| 930 | | default: |
|---|
| 931 | | wrong_args(options); |
|---|
| 932 | | break; |
|---|
| 933 | | } |
|---|
| 934 | | return addOption(o); |
|---|
| 935 | | } |
|---|
| 936 | | /// |
|---|
| 937 | | Option addOption(char[][] options, OptionCallback dg) { |
|---|
| 938 | | return addOption(new Callback(options, dg)); |
|---|
| 939 | | } |
|---|
| 940 | | /// |
|---|
| 941 | | Option addOption(char[][] options, OptionCallbackArg dg) { |
|---|
| 942 | | return addOption(new CallbackArg(options, dg)); |
|---|
| 943 | | } |
|---|
| 944 | | /// |
|---|
| 945 | | Option addOption(char[][] options, OptionCallbackInt dg) { |
|---|
| 946 | | return addOption(new CallbackInt(options, dg)); |
|---|
| 947 | | } |
|---|
| 948 | | /// |
|---|
| 949 | | Option addOption(char[][] options, OptionCallbackFancy dg) { |
|---|
| 950 | | return addOption(new FancyCallback(options, null, dg)); |
|---|
| 951 | | } |
|---|
| 952 | | /// |
|---|
| 953 | | Option addOption(char[][] options, OptionCallbackFancyArg dg) { |
|---|
| 954 | | return addOption(new FancyCallbackArg(options, null, dg)); |
|---|
| 955 | | } |
|---|
| 956 | | /// |
|---|
| 957 | | Option addOption(char[][] options, OptionCallbackFancyInt dg) { |
|---|
| 958 | | return addOption(new FancyCallbackInt(options, null, dg)); |
|---|
| 959 | | } |
|---|
| 960 | | /// |
|---|
| 961 | | Option addOption(char[][] options, char[] name, OptionCallbackFancy dg) { |
|---|
| 962 | | return addOption(new FancyCallback(options, name, dg)); |
|---|
| 963 | | } |
|---|
| 964 | | /// |
|---|
| 965 | | Option addOption(char[][] options, char[] name, OptionCallbackFancyArg dg) { |
|---|
| 966 | | return addOption(new FancyCallbackArg(options, name, dg)); |
|---|
| 967 | | } |
|---|
| 968 | | /// |
|---|
| 969 | | Option addOption(char[][] options, char[] name, OptionCallbackFancyInt dg) { |
|---|
| 970 | | return addOption(new FancyCallbackInt(options, name, dg)); |
|---|
| 971 | | } |
|---|
| 972 | | // Although users certainly /can/ call this, all those overloads are there |
|---|
| 973 | | // for a reason. |
|---|
| 974 | | Option addOption(Option option) { |
|---|
| 975 | | this.options ~= option; |
|---|
| 976 | | return option; |
|---|
| | 129 | return help; |
|---|