1 module jcli.argbinder.binder;
2 
3 import jcli.introspect, jcli.core, std;
4 
5 struct Binder {}
6 struct BindWith(alias Func_){ alias Func = Func_; }
7 struct PreValidator {}
8 struct PostValidator {}
9 
10 abstract class ArgBinder(Modules...)
11 {
12     alias ToBinder(alias M) = getSymbolsByUDA!(M, Binder);
13     alias Binders           = staticMap!(ToBinder, AliasSeq!(Modules, jcli.argbinder.binder));
14 
15     static Result bind(alias ArgIntrospectT)(string str, ref ArgIntrospectT.CommandT command)
16     {
17         alias ArgSymbol         = getArgSymbol!ArgIntrospectT;
18         alias PreValidators     = getValidators!(ArgSymbol, PreValidator);
19         alias PostValidators    = getValidators!(ArgSymbol, PostValidator);
20         alias BindWith          = getBindWith!(ArgSymbol, Binders);
21 
22         static foreach(v; PreValidators)
23         {{
24             const result = v.preValidate(str);
25             if(!result.isOk)
26                 return fail!void(result.error);
27         }}
28 
29         auto result = BindWith(str);
30         if(!result.isOk)
31             return fail!void(result.error);
32         getArg!ArgIntrospectT(command) = result.value;
33 
34         static foreach(v; PostValidators)
35         {{
36             const res = v.postValidate(getArg!ArgIntrospectT(command));
37             if(!res.isOk)
38                 return fail!void(res.error);
39         }}
40 
41         return ok();
42     }
43 }
44 
45 @Binder
46 ResultOf!string binderString(string value)
47 {
48     return ok(value);
49 }
50 ///
51 unittest
52 {
53     @CommandDefault
54     static struct S
55     {
56         @ArgPositional
57         string str;
58     }
59 
60     alias Info = commandInfoFor!S;
61     enum Param = Info.positionalArgs[0];
62     S s;
63     assert(ArgBinder!().bind!Param("hello", s).isOk);
64     assert(s.str == "hello");
65 }
66 
67 @Binder
68 ResultOf!T binderTo(T)(string value)
69 if(__traits(compiles, to!T(value)))
70 {
71     try return ok(value.to!T);
72     catch(ConvException msg) return fail!T(msg.msg);
73 }
74 ///
75 unittest
76 {
77     @CommandDefault
78     static struct S
79     {
80         @ArgPositional
81         int num;
82     }
83 
84     alias Info = commandInfoFor!S;
85     enum Param = Info.positionalArgs[0];
86     S s;
87     assert(ArgBinder!().bind!Param("256", s).isOk);
88     assert(s.num == 256);
89     assert(!ArgBinder!().bind!Param("two five six", s).isOk);
90 }
91 
92 @Binder
93 ResultOf!bool binderBool(string value)
94 {
95     try return ok(value.to!bool);
96     catch(ConvException msg) return fail!bool(msg.msg);
97 }
98 
99 private template getValidators(alias ArgSymbol, alias Validator)
100 {
101     alias Udas                  = __traits(getAttributes, ArgSymbol);
102     enum isValidator(alias Uda) = __traits(compiles, typeof(Uda)) && hasUDA!(typeof(Uda), Validator);
103     alias getValidators         = Filter!(isValidator, Udas);
104 }
105 
106 private template getBindWith(alias ArgSymbol, Binders...)
107 {
108     alias Udas                  = __traits(getAttributes, ArgSymbol);
109     enum isBindWith(alias Uda)  = isInstanceOf!(BindWith, Uda);
110     alias Found                 = Filter!(isBindWith, Udas);
111 
112     static if(isInstanceOf!(Nullable, typeof(ArgSymbol)))
113         alias ArgT = typeof(ArgSymbol.get());
114     else
115         alias ArgT = typeof(ArgSymbol);
116 
117     static assert(Found.length <= 1, "Only one @BindWith may exist.");
118     static if(Found.length == 0)
119     {
120         enum isValidBinder(alias Binder) = 
121             __traits(compiles, { ArgT a = Binder!(ArgT)("").value; })
122             || __traits(compiles, { ArgT a = Binder("").value; });
123         alias ValidBinders = Filter!(isValidBinder, Binders);
124 
125         static if(ValidBinders.length)
126         {
127             static if(__traits(compiles, Instantiate!(ValidBinders[0], ArgT)))
128                 alias getBindWith = Instantiate!(ValidBinders[0], ArgT);
129             else
130                 alias getBindWith = ValidBinders[0];
131         }
132         else
133             static assert(false, "No binders available for symbol "~__traits(identifier, ArgSymbol)~" of type "~ArgT.stringof);
134     }
135     else
136         alias getBindWith = Found[0].Func;
137 }