1 /**
2  * Program and command entries.
3  *
4  * This module contains interfaces and implementations of various entries.
5  * Generally, most APIs expose builder-like pattern, where setters return
6  * class instance to allow chaining.
7  *
8  * Entries are all things that can be added to program or command - that is
9  * flags, options and arguments. All entries contain a name - which is an unique
10  * identifier for every entry. Names must be a valid alpha numeric identifier.
11  *
12  * Result of parsing arguments (instance of `ProgramArgs`) allows reading
13  * argument values by entry `name` (not by `-short` or `--long-forms`).
14  *
15  * See_Also:
16  *  Flag, Option, Argument
17  */
18 module commandr.option;
19 
20 import commandr.validators;
21 import commandr.program : InvalidProgramException;
22 
23 
24 /**
25  * Interface for all program or command entries - flags, options and arguments.
26  */
27 interface IEntry {
28     /**
29      * Sets entry name.
30      */
31     public typeof(this) name(string name) pure @safe;
32 
33     /**
34      * Entry name.
35      */
36     public string name() const pure nothrow @safe;
37 
38     /**
39      * Display name for entry.
40      *
41      * For arguments, this is argument tag value.
42      * For options and flags, this is either abbrevation with single dash prefix
43      * or full name with double dash prefix.
44      */
45     public string displayName() const pure nothrow @safe;
46 
47     /**
48      * Sets entry help description (one-liner).
49      */
50     public typeof(this) description(string description) pure @safe;
51 
52     /**
53      * Entry help description (one-liner).
54      */
55     public string description() const pure nothrow @safe @nogc;
56 
57     /**
58      * Sets whenever entry can be repeated.
59      */
60     public typeof(this) repeating(bool repeating = true) pure @safe;
61 
62     /**
63      * Gets whenever entry can be repeated.
64      */
65     public bool isRepeating() const pure nothrow @safe @nogc;
66 
67     /**
68      * Sets entry required flag.
69      */
70     public typeof(this) required(bool required = true) pure @safe;
71 
72     /**
73      * Sets entry optional flag.
74      */
75     public typeof(this) optional(bool optional = true) pure @safe;
76 
77     /**
78      * Whenever entry is required.
79      */
80     public bool isRequired() const pure nothrow @safe @nogc;
81 
82     /**
83      * Sets entry default value.
84      */
85     public typeof(this) defaultValue(string defaultValue) pure @safe;
86 
87     /**
88      * Sets entry default value array.
89      */
90     public typeof(this) defaultValue(string[] defaultValue) pure @safe;
91 
92     /**
93      * Entry default value array.
94      */
95     public string[] defaultValue() pure nothrow @safe @nogc;
96 
97     /**
98      * Adds entry validator.
99      */
100     public typeof(this) validate(IValidator validator) pure @safe;
101 
102     /**
103      * Entry validators.
104      */
105     public IValidator[] validators() pure nothrow @safe @nogc;
106 }
107 
108 mixin template EntryImpl() {
109     private string _name;
110     private string _description;
111     private bool _repeating = false;
112     private bool _required = false;
113     private string[] _default;
114     private IValidator[] _validators;
115 
116     ///
117     public typeof(this) name(string name) pure nothrow @safe @nogc {
118         this._name = name;
119         return this;
120     }
121 
122     ///
123     public string name() const pure nothrow @safe @nogc {
124         return this._name;
125     }
126 
127     ///
128     public typeof(this) description(string description) pure nothrow @safe @nogc {
129         this._description = description;
130         return this;
131     }
132 
133     ///
134     public string description() const pure nothrow @safe @nogc {
135         return this._description;
136     }
137 
138     ///
139     public typeof(this) repeating(bool repeating = true) pure nothrow @safe @nogc {
140         this._repeating = repeating;
141         return this;
142     }
143 
144     ///
145     public bool isRepeating() const pure nothrow @safe @nogc {
146         return this._repeating;
147     }
148 
149     ///
150     public typeof(this) required(bool required = true) pure @safe {
151         this._required = required;
152 
153         return this;
154     }
155 
156     ///
157     public typeof(this) optional(bool optional = true) pure @safe {
158         this.required(!optional);
159         return this;
160     }
161 
162     ///
163     public bool isRequired() const pure nothrow @safe @nogc {
164         return this._required;
165     }
166 
167     ///
168     public typeof(this) defaultValue(string defaultValue) pure @safe {
169         return this.defaultValue([defaultValue]);
170     }
171 
172     ///
173     public typeof(this) defaultValue(string[] defaultValue) pure @safe {
174         this._default = defaultValue;
175         this._required = false;
176         return this;
177     }
178 
179     ///
180     public string[] defaultValue() pure nothrow @safe @nogc {
181         return this._default;
182     }
183 
184     ///
185     public typeof(this) validate(IValidator validator) pure @safe {
186         this._validators ~= validator;
187         return this;
188     }
189 
190     ///
191     public IValidator[] validators() pure nothrow @safe @nogc {
192         return this._validators;
193     }
194 }
195 
196 /**
197  * Option interface.
198  *
199  * Used by flags and options, which both contain short and long names.
200  * Either can be null but not both.
201  */
202 interface IOption: IEntry {
203     /**
204      * Sets option full name (long-form).
205      *
206      * Set to null to disable long form.
207      */
208     public typeof(this) full(string full) pure nothrow @safe @nogc;
209 
210     /**
211      * Option full name (long-form).
212      */
213     public string full() const pure nothrow @safe @nogc;
214 
215     /// ditto
216     public alias long_ = full;
217 
218     /**
219      * Sets option abbrevation (short-form).
220      *
221      * Set to null to disable short form.
222      */
223     public typeof(this) abbrev(string abbrev) pure nothrow @safe @nogc;
224 
225     /**
226      * Sets option abbrevation (short-form).
227      */
228     public string abbrev() const pure nothrow @safe @nogc;
229 
230     /// ditto
231     public alias short_ = abbrev;
232 }
233 
234 mixin template OptionImpl() {
235     private string _abbrev;
236     private string _full;
237 
238     ///
239     public string displayName() const nothrow pure @safe {
240         if (_abbrev) {
241             return "-" ~ _abbrev;
242         }
243         return "--" ~ _full;
244     }
245 
246     ///
247     public typeof(this) full(string full) pure nothrow @safe @nogc {
248         this._full = full;
249         return this;
250     }
251 
252     ///
253     public string full() const pure nothrow @safe @nogc {
254         return this._full;
255     }
256 
257     ///
258     public alias long_ = full;
259 
260     ///
261     public typeof(this) abbrev(string abbrev) pure nothrow @safe @nogc {
262         this._abbrev = abbrev;
263         return this;
264     }
265 
266     ///
267     public string abbrev() const pure nothrow @safe @nogc {
268         return this._abbrev;
269     }
270 
271     ///
272     public alias short_ = abbrev;
273 }
274 
275 
276 /**
277  * Represents a flag.
278  *
279  * Flag hold a single boolean value.
280  * Flags are optional and cannot be set as required.
281  */
282 public class Flag: IOption {
283     mixin EntryImpl;
284     mixin OptionImpl;
285 
286     /**
287      * Creates new flag.
288      *
289      * Full flag name (long-form) is set to name parameter value.
290      *
291      * Params:
292      *   name - flag unique name.
293      */
294     public this(string name) pure nothrow @safe @nogc {
295         this._name = name;
296         this._full = name;
297     }
298 
299 
300     /**
301      * Creates new flag.
302      *
303      * Name defaults to long form value.
304      *
305      * Params:
306      *   abbrev - Flag short name (null for none)
307      *   full - Flag full name (null for none)
308      *   description - Flag help description
309      */
310     public this(string abbrev, string full, string description) pure nothrow @safe @nogc {
311         this._name = full;
312         this._full = full;
313         this._abbrev = abbrev;
314         this._description = description;
315     }
316 }
317 
318 /**
319  * Represents an option.
320  *
321  * Options hold any value as string (or array of strings).
322  * Options by default are optional, but can be marked as required.
323  *
324  * Order in which options are passed does not matter.
325  */
326 public class Option: IOption {
327     mixin OptionImpl;
328     mixin EntryImpl;
329 
330     private string _tag = "value";
331 
332 
333     /**
334      * Creates new option.
335      *
336      * Full option name (long-form) is set to `name` parameter value.
337      *
338      * Params:
339      *   name - option unique name.
340      */
341     public this(string name) pure nothrow @safe @nogc {
342         this._name = name;
343         this._full = name;
344     }
345 
346     /**
347      * Creates new option.
348      *
349      * Name defaults to long form value.
350      *
351      * Params:
352      *   abbrev - Option short name (null for none)
353      *   full - Option full name (null for none)
354      *   description - Option help description
355      */
356     public this(string abbrev, string full, string description) pure nothrow @safe @nogc {
357         this._name = full;
358         this._full = full;
359         this._abbrev = abbrev;
360         this._description = description;
361     }
362 
363     /**
364      * Sets option value tag.
365      *
366      * A tag is a token displayed in place of option value.
367      * Default tag is `value`.
368      *
369      * For example, for a option that takes path to configuration file,
370      * one can create `--config` option and set `tag` to `config-path`, so that in
371      * help it is displayed as `--config=config-path` instead of `--config=value`
372      */
373     public typeof(this) tag(string tag) pure nothrow @safe @nogc {
374         this._tag = tag;
375         return this;
376     }
377 
378     /**
379      * Option value tag.
380      */
381     public string tag() const pure nothrow @safe @nogc {
382         return this._tag;
383     }
384 }
385 
386 /**
387  * Represents an argument.
388  *
389  * Arguments are positional parameters passed to program and are required by default.
390  * Only last argument can be repeating or optional.
391  */
392 public class Argument: IEntry {
393     mixin EntryImpl;
394 
395     private string _tag;
396 
397     /**
398      * Creates new argument.
399      *
400      * Params:
401      *  name - Argument name
402      *  description - Help description
403      */
404     public this(string name, string description = null) nothrow pure @safe @nogc {
405         this._name = name;
406         this._description = description;
407         this._required = true;
408         this._tag = name;
409     }
410 
411     /**
412      * Gets argument display name (tag or name).
413      */
414     public string displayName() const nothrow pure @safe {
415         return this._tag;
416     }
417 
418     /**
419      * Sets argument tag.
420      *
421      * A tag is a token displayed in place of argument.
422      * By default it is name of the argument.
423      */
424     public typeof(this) tag(string tag) pure nothrow @safe @nogc {
425         this._tag = tag;
426         return this;
427     }
428 
429     /**
430      * Argument tag
431      */
432     public string tag() const pure nothrow @safe @nogc {
433         return this._tag;
434     }
435 }
436 
437 /**
438  * Thrown when user-passed data is invalid.
439  *
440  * This exception is thrown during parsing phase when user passed arguments (e.g. invalid option, invalid value).
441  *
442  * This exception is automatically caught if using `parse` function.
443  */
444 public class InvalidArgumentsException: Exception {
445     /**
446      * Creates new InvalidArgumentException
447      */
448     public this(string msg) nothrow pure @safe @nogc {
449         super(msg);
450     }
451 }
452 
453 // options
454 unittest {
455     assert(!new Option("t", "test", "").isRequired);
456 }
457 
458 // arguments
459 unittest {
460     assert(new Argument("test", "").isRequired);
461 }