1 /*
2     Meta utils.
3 */
4 module automapper.meta;
5 
6 
7 package:
8 
9 import std.traits;
10 public import std.meta;
11 
12 /**
13     Get an alias on the member type (work with nested member like "foo.bar").
14     Params:
15         T = the type where the member is
16         members = the member to alias
17 */
18 template MemberType(T, string members)
19 {
20     import std..string : split, join;
21     enum string[] memberSplited = members.split(".");
22 
23     static if (is(T t == T)) {
24         static if (memberSplited.length > 1)
25             alias MemberType = MemberType!(MemberType!(T, memberSplited[0]), memberSplited[1..$].join("."));
26         else
27             alias MemberType = typeof(__traits(getMember, t, members));
28     }
29 }
30 
31 ///
32 unittest
33 {
34     static class A {
35         int bar;
36         string baz;
37     }
38 
39     static class B {
40         A foo = new A();
41     }
42 
43     static assert(is(MemberType!(A, "bar") == int));
44     static assert(is(MemberType!(A, "baz") == string));
45     static assert(is(MemberType!(B, "foo.bar") == int));
46 }
47 
48 /** Test existance of the nested member (or not nested).
49 Params:
50     T = The type where member are nested
51     members = the member to test (e.g. "foo.bar.baz") */
52 template hasNestedMember(T, string members)
53 {
54     import std..string : split, join;
55     enum string[] memberSplited = members.split(".");
56 
57     static if (is(T t == T)) {
58         static if (memberSplited.length > 1)
59             static if (__traits(hasMember, T, memberSplited[0]))
60                 enum bool hasNestedMember = hasNestedMember!(MemberType!(T, memberSplited[0]), memberSplited[1..$]
61                     .join("."));
62             else
63                 enum bool hasNestedMember = false;
64         else
65             enum bool hasNestedMember = __traits(hasMember, t, members);
66     }
67 }
68 
69 ///
70 unittest
71 {
72     static class A {
73         int bar;
74     }
75 
76     static class B {
77         A foo = new A();
78     }
79 
80     static class C {
81         B bar = new B();
82     }
83 
84     static assert(hasNestedMember!(B, "foo.bar"));
85     static assert(!hasNestedMember!(B, "data.bar"));
86     static assert(!hasNestedMember!(B, "foo.baz"));
87     static assert(hasNestedMember!(B, "foo"));
88     static assert(!hasNestedMember!(B, "fooz"));
89     static assert(!hasNestedMember!(C, "bar.foo.baz"));
90     static assert(hasNestedMember!(C, "bar.foo.bar"));
91 }
92 
93 template isPublicMember(T, string M)
94 {
95 	import std.algorithm, std.typetuple : TypeTuple;
96 
97 	static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false;
98 	else {
99 		alias MEM = TypeTuple!(__traits(getMember, T, M));
100 		static if (__traits(compiles, __traits(getProtection, MEM)))
101 			enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
102 		else
103 			enum isPublicMember = true;
104 	}
105 }
106 ///
107 unittest
108 {
109     static class A {
110         public int bar;
111         protected string foo;
112         private int baz;
113     }
114 
115     static assert(isPublicMember!(A, "bar"));
116     static assert(!isPublicMember!(A, "foo"));
117     static assert(!isPublicMember!(A, "baz"));
118 }
119 
120 ///
121 string GetMember(alias T, string member)()
122 {
123     import std.format : format;
124     return (q{%s.%s}.format(T.stringof, member));
125 }
126 
127 ///
128 unittest
129 {
130     static class A {
131         int bar;
132     }
133 
134     static class B {
135         A foo = new A();
136     }
137 
138     auto b = new B();
139 
140     mixin(GetMember!(b, "foo.bar")) = 42;
141 }
142 
143 /** Get a list of all public class member.
144 Params:
145     T = The class where to list member */
146 template ClassMembers(T) if (isClassOrStruct!T)
147 {
148     import std.algorithm : canFind;
149 
150     static immutable MembersToIgnore = [__traits(allMembers, Object)];
151 
152     private template ClassMembersImpl(size_t idx)
153     {
154         static if (idx < [__traits(allMembers, T)].length) {
155             enum M = __traits(allMembers, T)[idx];
156             static if (!isCallable!M && !MembersToIgnore.canFind(M) && isPublicMember!(T, M))
157                 enum string[] ClassMembersImpl = M ~ ClassMembersImpl!(idx + 1);
158             else
159                 enum string[] ClassMembersImpl = ClassMembersImpl!(idx + 1); // skip
160         }
161         else {
162             enum string[] ClassMembersImpl = [];
163         }
164     }
165 
166     enum string[] ClassMembers = ClassMembersImpl!0;
167 }
168 
169 
170 ///
171 unittest
172 {
173     static class Base {
174         int baz;
175     }
176 
177     static class A : Base {
178         int bar;
179         string foo;
180         private int priv;
181     }
182 
183     static assert(ClassMembers!A == ["bar", "foo", "baz"]);
184     static assert(ClassMembers!(A) != ["bar", "foo"]);
185 }
186 
187 /** Get a list of flatenned class member. */
188 template FlattenedMembers(T) if (isClassOrStruct!T)
189 {
190     import std..string : join;
191 
192     private template FlattenedMembersImpl(U, size_t idx, string Prefix)
193     {
194         static if (idx < ClassMembers!U.length) {
195             enum M = ClassMembers!U[idx];
196             enum P = (Prefix == "" ? "" : Prefix ~ "."); // prefix
197 
198             // it's a class: recurse
199             static if (isClassOrStruct!(MemberType!(U, M)))
200                 enum string[] FlattenedMembersImpl = (P ~ M) ~
201                     FlattenedMembersImpl!(MemberType!(U, M), 0, P ~ M) ~
202                     FlattenedMembersImpl!(U, idx + 1, Prefix);
203             else
204                 enum string[] FlattenedMembersImpl = P ~ M ~ FlattenedMembersImpl!(U, idx + 1, Prefix);
205         }
206         else {
207             enum string[] FlattenedMembersImpl = [];
208         }
209     }
210 
211     enum string[] FlattenedMembers = FlattenedMembersImpl!(T, 0, "");
212 }
213 
214 ///
215 unittest
216 {
217     static class A {
218         int bar;
219         string str;
220         private int dum;
221     }
222 
223     static class B {
224         A foo;
225         int mid;
226     }
227 
228     static class C {
229         B baz;
230         int top;
231     }
232 
233     static class Address {
234         int zipcode;
235     }
236 
237     static class D {
238         Address address;
239     }
240 
241     static struct E {
242         int foo;
243     }
244 
245     static struct F {
246         E bar;
247     }
248 
249     static assert(FlattenedMembers!C == ["baz", "baz.foo", "baz.foo.bar", "baz.foo.str", "baz.mid", "top"]);
250     static assert(FlattenedMembers!D == ["address", "address.zipcode"]);
251     static assert(FlattenedMembers!F == ["bar", "bar.foo"]);
252 }
253 
254 template Alias(alias T)
255 {
256     alias Alias = T;
257 }
258 
259 template isClass(T)
260 {
261     enum bool isClass = (is(T == class));
262 }
263 
264 template isClassOrStruct(T)
265 {
266     enum bool isClassOrStruct = (is(T == class) || is(T == struct));
267 }
268 
269 string[] splitOnCase(string str)
270 {
271     import std.ascii : isUpper;
272     import std.uni : toLower;
273 
274     string[] res;
275     string tmp;
276     int k = 0;
277     foreach(c; str) {
278         if (c.isUpper && tmp != "") {
279             res ~= tmp;
280             tmp = "";
281         }
282         tmp ~= c.toLower;
283         k++;
284     }
285 
286     foreach(c; str[k..$])
287         tmp ~= c.toLower;
288     res ~= tmp;
289 
290     return res;
291 }
292 
293 /// unittest
294 unittest
295 {
296     static assert("fooBarBaz".splitOnCase() == ["foo", "bar", "baz"]);
297 }
298 
299 /// Returns true if its a RT function(P)
300 template isSpecifiedCallable(alias D, P, RT)
301 {
302     static if (isCallable!D)
303         enum bool isSpecifiedCallable = (is(ReturnType!D == RT) && (Parameters!D.length > 0) &&
304             is(Parameters!D[0] == P));
305     else
306         enum bool isSpecifiedCallable = false;
307 }
308 
309 ///
310 unittest
311 {
312     import std.conv : to;
313     static assert(isSpecifiedCallable!((long ts) => ts.to!string, long, string));
314 }
315 
316 /// Returns true if T has the specified callable
317 template hasSpecifiedCallable(T, string callable, P, RT)
318 {
319     static if (__traits(hasMember, T, callable)) {
320         static if (is(T t : T)) {
321             enum hasSpecifiedCallable = (isSpecifiedCallable!(__traits(getMember, t, callable), P, RT));
322         }
323         else
324             enum hasSpecifiedCallable = false;
325     }
326     else
327         enum hasSpecifiedCallable = false;
328 }
329 
330 unittest
331 {
332     struct A
333     {
334         string foo(string a)
335         {
336             return a;
337         }
338     }
339 
340     static assert(hasSpecifiedCallable!(A, "foo", string, string));
341     static assert(!hasSpecifiedCallable!(A, "foo", int, string));
342     static assert(!hasSpecifiedCallable!(A, "foo", string, int));
343     static assert(!hasSpecifiedCallable!(A, "bar", string, string));
344 }
345 
346 /// Find a type matching a criteria or return default
347 template findOrDefault(alias Criteria, Default, T...) {
348     alias Found = Filter!(Criteria, T);
349 
350     static if (Found.length == 1)
351         alias findOrDefault = Found[0];
352     else
353         alias findOrDefault = Default;
354 }
355 
356 /// Check if a type matching a criteria exists and only one
357 template onlyOneExists(alias Criteria, T...) {
358     alias Found = Filter!(Criteria, T);
359 
360     static if (Found.length == 1)
361         enum onlyOneExists = true;
362     else
363         enum onlyOneExists = false;
364 }