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 }