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 }