1 module plist.conv;
2 import plist.types;
3 import dxml.dom;
4 import std.traits : EnumMembers, isAssignable;
5 
6 bool typeIsCoercible(T)(PlistElementType type) {
7     // Can't avoid boilerplate here, as we need to hardcode the actual element types
8     if (type == PlistElementType.PLIST_ELEMENT_DATA) {
9         if (isAssignable!(T, ubyte[])) {
10             return true;
11         }
12     } 
13 
14     if (type == PlistElementType.PLIST_ELEMENT_STRING || 
15         type == PlistElementType.PLIST_ELEMENT_KEY) {
16         if (isAssignable!(T, string)) {
17             return true;
18         }
19     }
20 
21     if (type == PlistElementType.PLIST_ELEMENT_DATE) {
22         import std.datetime : SysTime;
23         if (isAssignable!(T, SysTime)) {
24             return true;
25         } 
26     }
27 
28     if (type == PlistElementType.PLIST_ELEMENT_BOOLEAN_TRUE || 
29         type == PlistElementType.PLIST_ELEMENT_BOOLEAN_FALSE) {
30         if (isAssignable!(T, bool)) {
31             return true;
32         }
33     }
34 
35     if (type == PlistElementType.PLIST_ELEMENT_INTEGER) {
36         if (isAssignable!(T, long)) {
37             return true;
38         }
39     }
40 
41     if (type == PlistElementType.PLIST_ELEMENT_REAL) {
42         if (is(T == real)) {
43             return true;
44         }
45     }
46 
47     return false;
48 }
49 
50 bool validateDataType(DOMEntity!string _entity) {
51     import std.algorithm.searching;
52 
53     auto members = cast(string[])[EnumMembers!PlistElementType]; // this makes a compile-time array of every value possible in the enum
54     if (members.canFind(_entity.name)) { // verify that it's one of them
55         return true;
56     }
57     return false;
58 }
59 
60 PlistElement coerce(T)(DOMEntity!string _entity) {
61     auto element = new T(); // object creation needed because type() cannot be static
62     if (_entity.name != "true" && _entity.name != "false" && _entity.name != "key") { // booleans are the exception, <bool>false</bool> is just <false/>
63         assert(_entity.name == element.type(), "Entity name " ~ _entity.name ~ " did not match " ~ element.type());
64     }
65 
66     element.instantiate(_entity);
67 
68     return element;
69 }
70 
71 PlistElement coerceRuntime(PlistElementType type, DOMEntity!string _entity) {
72     assert(_entity.type == EntityType.elementStart || _entity.type == EntityType.elementEmpty, "Encountered invalid type"); // this should never happen
73     assert(_entity.name == type);
74     assert(validateDataType(_entity), "Invalid type " ~ _entity.name);
75 
76     PlistElement element;
77 
78 
79     static foreach(etype; [EnumMembers!PlistElementType]) {
80         if (type == etype) { // Check has to be done at runtime 
81             mixin("element = coerce!(" ~ getElementClassFromType(etype) ~ ")(_entity);");
82         }
83     }
84 
85     return element;
86 }
87 
88 auto getElementClassFromType(PlistElementType type) {
89     static foreach(etype; [EnumMembers!PlistElementType]) {
90         if (type == etype) {
91             static if (etype == PlistElementType.PLIST_ELEMENT_BOOLEAN_TRUE || etype == PlistElementType.PLIST_ELEMENT_BOOLEAN_FALSE) {
92                 return "PlistElementBoolean";
93             } else static if (etype == PlistElementType.PLIST_ELEMENT_KEY) {
94                 return "PlistElementString";
95             } else {
96                 import std.string : capitalize;
97                 return "PlistElement" ~ capitalize(cast(string)etype);
98             }
99         }
100     }
101     
102     assert(0, "Should never reach this point");
103 }