1 /// Contains the action functions for arguments.
2 module jaster.cli.infogen.actions;
3 
4 import std.format, std.traits, std.typecons : Nullable;
5 import jaster.cli.infogen, jaster.cli.binder, jaster.cli.result;
6 
7 // ArgBinder knows how to safely discard UDAs it doesn't care about.
8 private alias getBinderUDAs(alias ArgT) = __traits(getAttributes, ArgT);
9 
10 // Now you may think: "But Bradley, this is module-level, why does this need to be marked static?"
11 //
12 // Well now little Timmy, what you don't seem to notice is that D, for some reason, is embedding a "this" pointer (as a consequence of `ArgT` referencing a member field),
13 // however because this is still technically a `function` I can call it *without* providing a context pointer, which has led to some very
14 // interesting errors.
15 
16 /// Sets the argument's value via `ArgBinder`.
17 static Result!void actionValueBind(alias CommandT, alias ArgT, alias ArgBinderInstance)(string value, ref CommandT commandInstance)
18 {
19     import std.typecons : Nullable; // Don't ask me why, but I need to repeat the import here for the amalgamation to compile properly.
20                                     // For some incredibly strange reason, if we don't do this, then `Nullable` evaluated to `void`.
21 
22     alias SymbolType = typeof(ArgT);
23 
24     static if(isInstanceOf!(Nullable, SymbolType))
25     {
26         // The Unqual removes the `inout` that `get` uses.
27         alias ResultT = Unqual!(ReturnType!(SymbolType.get));
28     }
29     else
30         alias ResultT = SymbolType;
31 
32     auto result = ArgBinderInstance.bind!(ResultT, getBinderUDAs!ArgT)(value);
33     if(!result.isSuccess)
34         return Result!void.failure(result.asFailure.error);
35 
36     mixin("commandInstance.%s = result.asSuccess.value;".format(__traits(identifier, ArgT)));
37     return Result!void.success();
38 }
39 
40 /// Increments the argument's value either by 1, or by the length of `value` if it is not null.
41 static Result!void actionCount(alias CommandT, alias ArgT, alias ArgBinderInstance)(string value, ref CommandT commandInstance)
42 {
43     static assert(__traits(compiles, {typeof(ArgT) a; a++;}), "Type "~typeof(ArgT).stringof~" does not implement the '++' operator.");
44 
45     // If parser passes null then the user's input was: -v or -vsome_value
46     // If parser passes value then the user's input was: -vv or -vvv(n+1)
47     const amount = (value is null) ? 1 : value.length;
48 
49     // Simplify implementation for user-defined types by only using ++.
50     foreach(i; 0..amount)
51         mixin("commandInstance.%s++;".format(__traits(identifier, ArgT)));
52 
53     return Result!void.success();
54 }
55 
56 /// Fails an assert if used.
57 static Result!void dummyAction(alias CommandT)(string value, ref CommandT commandInstance)
58 {
59     assert(false, "This action doesn't do anything.");
60 }