1 /** 2 * Copyright: Copyright (c) 2011-2012 Jacob Carlborg. All rights reserved. 3 * Authors: Jacob Carlborg 4 * Version: Initial created: Apr 3, 2011 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module mambo.arguments.Arguments; 8 9 import std.conv; 10 11 import tango.util.container.HashMap; 12 13 import Internal = mambo.arguments.internal.Arguments; 14 import mambo.arguments.Formatter; 15 import mambo.arguments.Options; 16 import mambo.core._; 17 import mambo.util.Traits; 18 19 class Arguments 20 { 21 immutable string shortPrefix; 22 immutable string longPrefix; 23 immutable char assignment; 24 25 bool sloppy; 26 bool passThrough; 27 28 string header; 29 string footer = "Use the `-h' flag for help."; 30 31 package Internal.Arguments internalArguments; 32 33 private 34 { 35 Options optionProxy; 36 ArgumentBase[string] positionalArguments_; 37 ArgumentProxy proxy; 38 ArgumentBase[] sortedPosArgs_; 39 Formatter formatter_; 40 41 string[] originalArgs_; 42 string[] rawArgs_; 43 } 44 45 alias option this; 46 47 this (string shortPrefix = "-", string longPrefix = "--", char assignment = '=') 48 { 49 this.shortPrefix = shortPrefix; 50 this.longPrefix = longPrefix; 51 this.assignment = assignment; 52 53 internalArguments = new Internal.Arguments(shortPrefix, longPrefix, assignment); 54 optionProxy = Options(this); 55 proxy = ArgumentProxy.create(this); 56 } 57 58 @property Formatter formatter () 59 { 60 return formatter_ = formatter_ ? formatter_ : Formatter.instance(this); 61 } 62 63 Option!(T) opIndex (T = string) (string name) 64 { 65 return option.opIndex!(T)(name); 66 } 67 68 string opIndex () (size_t index) 69 { 70 if (index > rawArgs.length || isEmpty) 71 assert(0, "throw MissingArgumentException - Missing argument(s)"); 72 73 return rawArgs[index]; 74 } 75 76 @property Options option () 77 { 78 return optionProxy; 79 } 80 81 @property ArgumentProxy argument () () 82 { 83 return proxy; 84 } 85 86 Argument!(T) argument (T = string) (string name, string helpText) 87 { 88 return proxy.opCall!(T)(name, helpText); 89 } 90 91 bool parse (string[] input) 92 { 93 originalArgs = input; 94 internalArguments.passThrough = passThrough; 95 auto result = internalArguments.parse(originalArgs, sloppy); 96 97 if (!result) 98 return false; 99 100 rawArgs = cast(string[]) internalArguments(null).assigned; 101 return result && parsePositionalArguments(); 102 } 103 104 @property string first () 105 { 106 return this[0]; 107 } 108 109 @property string last () 110 { 111 return this[rawArgs.length - 1]; 112 } 113 114 @property bool isEmpty () 115 { 116 return rawArgs.isEmpty; 117 } 118 119 @property bool any () 120 { 121 return rawArgs.any; 122 } 123 124 @property string helpText () 125 { 126 return formatter.helpText(); 127 } 128 129 @property ArgumentBase[] positionalArguments () 130 { 131 return positionalArguments_.values; 132 } 133 134 @property Option!(int)[] options () 135 { 136 return optionProxy.options; 137 } 138 139 @property string[] rawArgs () 140 { 141 return rawArgs_; 142 } 143 144 private @property string[] rawArgs (string[] rawArgs) 145 { 146 return rawArgs_ = rawArgs; 147 } 148 149 @property string[] originalArgs () 150 { 151 return originalArgs_; 152 } 153 154 private @property string[] originalArgs (string[] args) 155 { 156 return originalArgs_ = args; 157 } 158 159 string errors (char[] delegate (char[] buffer, const(char)[] format, ...) dg) 160 { 161 return internalArguments.errors(dg).assumeUnique; 162 } 163 164 private: 165 166 @property ArgumentBase[] sortedPosArgs () 167 { 168 if (sortedPosArgs_.any) 169 return sortedPosArgs_; 170 171 sortedPosArgs_ = positionalArguments; 172 sortedPosArgs_.sort!((a, b) => a.position < b.position)(); 173 174 return sortedPosArgs_; 175 } 176 177 bool parsePositionalArguments () 178 { 179 import std.algorithm : remove; 180 181 int error; 182 auto posArgs = sortedPosArgs; 183 184 if (posArgs.isEmpty) 185 return true; 186 187 auto arg = posArgs.first; 188 size_t numArgs; 189 auto len = rawArgs.length; 190 191 for (size_t i = 1; i < len; i++) 192 { 193 auto value = rawArgs[i]; 194 195 if (value == "--") 196 break; 197 198 else if (value.startsWith(longPrefix) || value.startsWith(shortPrefix)) 199 continue; 200 201 arg.values_ ~= value; 202 rawArgs = rawArgs.remove(i); 203 len--; 204 i--; 205 206 if (++numArgs == arg.max) 207 { 208 if (numArgs >= posArgs.length) 209 break; 210 211 arg = posArgs[i - numArgs]; 212 numArgs = 0; 213 } 214 } 215 216 foreach (e ; posArgs) 217 error |= e.validate(); 218 219 return error == 0; 220 } 221 } 222 223 struct ArgumentProxy 224 { 225 private Arguments arguments; 226 227 static ArgumentProxy create (Arguments arguments) 228 { 229 ArgumentProxy proxy; 230 proxy.arguments = arguments; 231 232 return proxy; 233 } 234 235 Argument!(T) opCall (T = string) (string name, string helpText) 236 { 237 assert(name !in arguments.positionalArguments_); 238 239 auto arg = new Argument!(T)(arguments.positionalArguments_.length, name); 240 arg.help(helpText); 241 arguments.positionalArguments_[name] = arg; 242 243 return arg; 244 } 245 246 template opDispatch (string name) 247 { 248 @property Argument!(T) opDispatch (T = string) () 249 { 250 return opIndex!(T)(name); 251 } 252 } 253 254 Argument!(T) opIndex (T = string) (string name) 255 { 256 return cast(Argument!(T)) arguments.positionalArguments_[name]; 257 } 258 } 259 260 class ArgumentBase 261 { 262 int min = 1; 263 int max = 1; 264 265 private 266 { 267 enum maxParams = 42; 268 size_t position_; 269 string[] values_; 270 int error_ = Internal.Arguments.Argument.None; 271 272 string name_; 273 string helpText_; 274 string defaults_; 275 bool required_; 276 } 277 278 private this (size_t position, string name) 279 { 280 this.position_ = position; 281 this.name_ = name; 282 } 283 284 @property string name () 285 { 286 return name_; 287 } 288 289 @property string helpText () 290 { 291 return helpText_; 292 } 293 294 private @property string helpText (string value) 295 { 296 return helpText_ = value; 297 } 298 299 @property size_t position () 300 { 301 return position_; 302 } 303 304 @property int error () 305 { 306 return error_; 307 } 308 309 private @property int error (int value) 310 { 311 return error_ = value; 312 } 313 314 @property bool hasValue () 315 { 316 return values_.any; 317 } 318 319 @property U as (U) () 320 { 321 static if (!isString!(U) && isArray!(U)) 322 return to!(U)(values_); 323 324 else 325 { 326 assert(hasValue, "Missing value"); 327 return to!(U)(rawValue); 328 } 329 } 330 331 @property string rawValue () 332 { 333 return hasValue ? values_.first : ""; 334 } 335 336 @property string[] rawValues () 337 { 338 return values_; 339 } 340 341 private int validate () 342 { 343 if (!hasValue && required_) 344 return error = Internal.Arguments.Argument.Required; 345 346 if (!hasValue) 347 return error; 348 349 if (rawValues.length < min) 350 error = Internal.Arguments.Argument.ParamLo; 351 352 else if (rawValues.length > max) 353 error = Internal.Arguments.Argument.ParamHi; 354 355 return error; 356 } 357 } 358 359 class Argument (T) : ArgumentBase 360 { 361 alias value this; 362 363 private this (size_t position, string name) 364 { 365 super(position, name); 366 } 367 368 @property T value () 369 { 370 return as!(T); 371 } 372 373 @property T[] values () 374 { 375 return as!(T[]); 376 } 377 378 Argument help (string text) 379 { 380 helpText = text; 381 return this; 382 } 383 384 Argument defaults (string values) 385 { 386 defaults_ = values; 387 return this; 388 } 389 390 Argument params () 391 { 392 return params(1, maxParams); 393 } 394 395 Argument params (int count) 396 { 397 return params(count, count); 398 } 399 400 Argument params (int min, int max) 401 { 402 this.min = min; 403 this.max = max; 404 405 return this; 406 } 407 408 Argument required () 409 { 410 required_ = true; 411 return this; 412 } 413 }