1 module siryul.dyaml;
2 import dyaml;
3 import siryul.common;
4 import std.range.primitives : ElementType, isInfinite, isInputRange;
5 import std.traits : isSomeChar;
6 import std.typecons;
7 
8 /++
9  + YAML (YAML Ain't Markup Language) serialization format
10  +/
11 struct YAML {
12 	private import std.meta : AliasSeq;
13 	package alias types = AliasSeq!(".yml", ".yaml");
14 	package static T parseInput(T, DeSiryulize flags, U)(U data) if (isInputRange!U && isSomeChar!(ElementType!U)) {
15 		import std.conv : to;
16 		import std.utf : byChar;
17 		auto str = data.byChar.to!(char[]);
18 		auto loader = Loader.fromString(str).load();
19 		try {
20 			T result;
21 			deserialize!(YAML, BitFlags!DeSiryulize(flags))(loader, T.stringof, result);
22 			return result;
23 		} catch (NodeException e) {
24 			debug(norethrow) throw e;
25 			else throw new YAMLDException(e.msg);
26 		}
27 	}
28 	package static string asString(Siryulize flags, T)(T data) {
29 		import std.array : appender;
30 		auto buf = appender!string;
31 		auto dumper = dumper();
32 		dumper.defaultCollectionStyle = CollectionStyle.block;
33 		dumper.defaultScalarStyle = ScalarStyle.plain;
34 		dumper.explicitStart = false;
35 		dumper.dump(buf, serialize!(YAML, BitFlags!Siryulize(flags))(data));
36 		return buf.data;
37 	}
38 }
39 /++
40  + Thrown on YAML deserialization errors
41  +/
42 class YAMLDException : DeserializeException {
43 	this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow {
44 		super(msg, file, line);
45 	}
46 }
47 /++
48  + Thrown on YAML serialization errors
49  +/
50 class YAMLSException : SerializeException {
51 	this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow {
52 		super(msg, file, line);
53 	}
54 }
55 template deserialize(Serializer : YAML, BitFlags!DeSiryulize flags) {
56 	import std.conv : to;
57 	import std.datetime : Date, DateTime, SysTime, TimeOfDay;
58 	import std.exception : enforce;
59 	import std.range : enumerate, isOutputRange, put;
60 	import std.traits : arity, FieldNameTuple, ForeachType, getUDAs, hasIndirections, hasUDA, isArray, isAssociativeArray, isFloatingPoint, isIntegral, isPointer, isSomeString, isStaticArray, KeyType, OriginalType, Parameters, PointerTarget, TemplateArgsOf, ValueType;
61 	import std.utf : byCodeUnit;
62 	void deserialize(T)(Node value, string path, out T result) if (is(T == enum)) {
63 		import std.conv : to;
64 		import std.traits : OriginalType;
65 		enforce!YAMLDException(value.nodeID == NodeID.scalar, "Attempted to read a non-scalar as a "~T.stringof);
66 		if (value.tag == `tag:yaml.org,2002:str`) {
67 			result = value.get!string.to!T;
68 		} else {
69 			OriginalType!T tmp;
70 			deserialize(value, path, tmp);
71 			result = tmp.to!T;
72 		}
73 	}
74 	void deserialize(Node value, string path, out TimeOfDay result) {
75 		enforce!YAMLDException(value.nodeID == NodeID.scalar, "Attempted to read a non-scalar as a TimeOfDay");
76 		result = TimeOfDay.fromISOExtString(value.get!string);
77 	}
78 	void deserialize(T)(Node value, string path, out T result) if (isNullable!T) {
79 		if (value.type == NodeType.null_) {
80 			result.nullify();
81 		} else {
82 			typeof(result.get) tmp;
83 			deserialize(value, path, tmp);
84 			result = tmp;
85 		}
86 	}
87 	void deserialize(Node value, string path, out bool result) {
88 		enforce!YAMLDException(value.tag == `tag:yaml.org,2002:bool`, "Expecting a boolean value");
89 		result = value.get!bool;
90 	}
91 	void deserialize(V, K)(Node value, string path, out V[K] result) {
92 		enforce!YAMLDException(value.nodeID == NodeID.mapping, "Attempted to read a non-mapping as a "~(V[K]).stringof);
93 		foreach (Node k, Node v; value) {
94 			K key;
95 			V val;
96 			deserialize(k, path, key);
97 			deserialize(v, path, val);
98 			result[key] = val;
99 		}
100 	}
101 	void deserialize(T, size_t N)(Node value, string path, out T[N] result) {
102 		static if (isSomeChar!T) {
103 			enforce!YAMLDException(value.nodeID == NodeID.scalar, "Attempted to read a non-scalar as a "~(T[N]).stringof);
104 			ForeachType!(T[N])[] str;
105 			deserialize(value, path, str);
106 			foreach (i, chr; str.byCodeUnit.enumerate(0)) {
107 				enforce!YAMLDException(i < N, "Static array too small to contain all elements");
108 				result[i] = chr;
109 			}
110 			return;
111 		} else {
112 			enforce!YAMLDException(value.nodeID == NodeID.sequence, "Attempted to read a non-sequence as a "~(T[N]).stringof);
113 			size_t i;
114 			foreach (Node newNode; value) {
115 				enforce!YAMLDException(i < N, "Static array too small to contain all elements");
116 				deserialize(newNode, path, result[i++]);
117 			}
118 			return;
119 		}
120 	}
121 	void deserialize(T)(Node value, string path, out T result) if (isOutputRange!(T, ElementType!T) && !isSomeString!T) {
122 		if (value.type != NodeType.null_) {
123 			enforce!YAMLDException(value.nodeID == NodeID.sequence, "Attempted to read a non-sequence as a "~T.stringof);
124 			foreach (Node newNode; value) {
125 				ElementType!T ele;
126 				deserialize(newNode, path, ele);
127 				result ~= ele;
128 			}
129 		}
130 	}
131 	void deserialize(T)(Node value, string path, out T result) if (is(T == struct) && !isNullable!T && !isTimeType!T) {
132 		static if (hasDeserializationMethod!T) {
133 			Parameters!(deserializationMethod!T) tmp;
134 			deserialize(value, path, tmp);
135 			result = deserializationMethod!T(tmp);
136 			return;
137 		} else {
138 			import std.exception : enforce;
139 			import std.meta : AliasSeq;
140 			import std.traits : arity, FieldNameTuple, ForeachType, getUDAs, hasIndirections, hasUDA, isAssociativeArray, isFloatingPoint, isIntegral, isPointer, isSomeChar, isSomeString, isStaticArray, OriginalType, Parameters, PointerTarget, TemplateArgsOf, ValueType;
141 			enforce!YAMLDException(value.nodeID == NodeID.mapping, "Attempted to read a non-mapping as a "~T.stringof);
142 			foreach (member; FieldNameTuple!T) {
143 				static if (__traits(getProtection, __traits(getMember, T, member)) == "public") {
144 					debug string newPath = path~"."~member;
145 					else string newPath = path;
146 					alias field = AliasSeq!(__traits(getMember, T, member));
147 					enum memberName = getMemberName!field;
148 					static if ((hasUDA!(field, Optional) || (!!(flags & DeSiryulize.optionalByDefault)) && !hasUDA!(field, Required)) || hasIndirections!(typeof(field))) {
149 						if (memberName !in value) {
150 							continue;
151 						}
152 					} else {
153 						enforce!YAMLDException(memberName in value, "Missing non-@Optional "~memberName~" in node");
154 					}
155 					alias fromFunc = getConvertFromFunc!(T, field);
156 					try {
157 						Parameters!(fromFunc)[0] param;
158 						static if (hasUDA!(field, IgnoreErrors)) {
159 							try {
160 								deserialize(value[memberName], newPath, param);
161 								__traits(getMember, result, member) = fromFunc(param);
162 							} catch (YAMLDException) {} //just skip it
163 						} else {
164 							deserialize(value[memberName], newPath, param);
165 							__traits(getMember, result, member) = fromFunc(param);
166 						}
167 					} catch (Exception e) {
168 						e.msg = "Error deserializing "~newPath~": "~e.msg;
169 						throw e;
170 					}
171 				}
172 			}
173 		}
174 	}
175 	void deserialize(T)(Node value, string path, out T result) if (is(T == SysTime) || is(T == DateTime) || is(T == Date)) {
176 		enforce!YAMLDException(value.nodeID == NodeID.scalar, "Attempted to read a non-scalar as a "~T.stringof);
177 		result = value.get!SysTime.to!T;
178 	}
179 	void deserialize(T)(Node value, string path, out T result) if (isSomeChar!T) {
180 		import std.array : front;
181 		if (value.type != NodeType.null_) {
182 			enforce!YAMLDException(value.nodeID == NodeID.scalar, "Attempted to read a non-scalar as a "~T.stringof);
183 			result = cast(T)value.get!string.front;
184 		}
185 	}
186 	void deserialize(T)(Node value, string path, out T result) if (isSomeString!T && !canStoreUnchanged!T) {
187 		string str;
188 		deserialize(value, path, str);
189 		result = str.to!T;
190 	}
191 	void deserialize(T)(Node value, string path, out T result) if (canStoreUnchanged!T) {
192 		enforce!YAMLDException(value.nodeID == NodeID.scalar, "Attempted to read a non-scalar as a "~T.stringof);
193 		if (value.tag == `tag:yaml.org,2002:str`) {
194 			result = value.get!string.to!T;
195 		} else {
196 			static if (isIntegral!T) {
197 				enforce!YAMLDException(value.tag == `tag:yaml.org,2002:int`, "Attempted to read a float as an integer");
198 				result = value.get!T;
199 			} else static if (isSomeString!T) {
200 				enforce!YAMLDException(value.tag != `tag:yaml.org,2002:bool`, "Attempted to read a non-string as a string");
201 				if (value.type != NodeType.null_) {
202 					result = value.get!T;
203 				}
204 			} else {
205 				result = value.get!T;
206 			}
207 		}
208 	}
209 	void deserialize(T : P*, P)(Node value, string path, out T result) {
210 		result = new P;
211 		deserialize(value, path, *result);
212 	}
213 	void deserialize(Node, string, out typeof(null)) {}
214 }
215 template serialize(Serializer : YAML, BitFlags!Siryulize flags) {
216 	import std.conv : text, to;
217 	import std.datetime : Date, DateTime, SysTime, TimeOfDay;
218 	import std.traits : arity, FieldNameTuple, getSymbolsByUDA, getUDAs, hasUDA, isAssociativeArray, isPointer, isSomeString, isStaticArray, PointerTarget, Unqual;
219 	private Node serialize(const typeof(null) value) {
220 		return Node(YAMLNull());
221 	}
222 	private Node serialize(const SysTime value) {
223 		return Node(value.to!SysTime, "tag:yaml.org,2002:timestamp");
224 	}
225 	private Node serialize(const TimeOfDay value) {
226 		return Node(value.toISOExtString);
227 	}
228 	private Node serialize(T)(const T value) if (isSomeChar!T) {
229 		return serialize([value]);
230 	}
231 	private Node serialize(T)(const T value) if (canStoreUnchanged!T) {
232 		return Node(value.to!T);
233 	}
234 	private Node serialize(T)(const T value) if (!canStoreUnchanged!T && (isSomeString!T || (isStaticArray!T && isSomeChar!(ElementType!T)))) {
235 		import std.utf : toUTF8;
236 		return serialize(value[].toUTF8().idup);
237 	}
238 	private Node serialize(T)(const T value) if (hasUDA!(value, AsString) || is(T == enum)) {
239 		return Node(value.text);
240 	}
241 	private Node serialize(T)(const T value) if (isAssociativeArray!T) {
242 		Node[Node] output;
243 		foreach (k, v; value) {
244 			output[serialize(k)] = serialize(v);
245 		}
246 		return Node(output);
247 	}
248 	private Node serialize(T)(T values) if (isSimpleList!T && !isSomeChar!(ElementType!T)) {
249 		Node[] output;
250 		foreach (value; values) {
251 			output ~= serialize(value);
252 		}
253 		return Node(output);
254 	}
255 	private Node serialize(T)(auto ref const T value) if (isPointer!T) {
256 		return serialize(*value);
257 	}
258 	private Node serialize(T)(const T value) if (is(T == struct)) {
259 		static if (hasSerializationMethod!T) {
260 			return serialize(mixin("value."~__traits(identifier, serializationMethod!T)));
261 		} else static if (is(T == Date) || is(T == DateTime)) {
262 			return Node(value.toISOExtString, "tag:yaml.org,2002:timestamp");
263 		} else static if (isNullable!T) {
264 			if (value.isNull) {
265 				return serialize(null);
266 			} else {
267 				return serialize(value.get);
268 			}
269 		} else {
270 			static string[] empty;
271 			Node output = Node(empty, empty);
272 			foreach (member; FieldNameTuple!T) {
273 				static if (__traits(getProtection, __traits(getMember, T, member)) == "public") {
274 					static if (!!(flags & Siryulize.omitInits)) {
275 						static if (isNullable!(typeof(__traits(getMember, value, member)))) {
276 							if (__traits(getMember, value, member).isNull)
277 								continue;
278 						} else {
279 							if (__traits(getMember, value, member) == __traits(getMember, value, member).init) {
280 								continue;
281 							}
282 						}
283 					}
284 					enum memberName = getMemberName!(__traits(getMember, T, member));
285 					try {
286 						static if (isPointer!(typeof(mixin("value."~member))) && !!(flags & Siryulize.omitNulls)) {
287 							if (mixin("value."~member) is null) {
288 								continue;
289 							}
290 						}
291 						static if (hasConvertToFunc!(T, __traits(getMember, T, member))) {
292 							auto val = serialize(getConvertToFunc!(T, __traits(getMember, T, member))(mixin("value."~member)));
293 							output.add(memberName, val);
294 						} else {
295 							output.add(memberName, serialize(mixin("value."~member)));
296 						}
297 					} catch (Exception e) {
298 						e.msg = "Error serializing: "~e.msg;
299 						throw e;
300 					}
301 				}
302 			}
303 			return output;
304 		}
305 	}
306 }
307 private template expectedTag(T) {
308 	import std.datetime.systime : SysTime;
309 	import std.traits : isFloatingPoint, isIntegral;
310 	static if(isIntegral!T) {
311 		enum expectedTag = `tag:yaml.org,2002:int`;
312 	}
313 	static if(is(T == bool)) {
314 		enum expectedTag = `tag:yaml.org,2002:bool`;
315 	}
316 	static if(isFloatingPoint!T) {
317 		enum expectedTag = `tag:yaml.org,2002:float`;
318 	}
319 	static if(is(T == string)) {
320 		enum expectedTag = `tag:yaml.org,2002:str`;
321 	}
322 	static if(is(T == SysTime)) {
323 		enum expectedTag = `tag:yaml.org,2002:timestamp`;
324 	}
325 }
326 private template canStoreUnchanged(T) {
327 	import std.traits : isFloatingPoint, isIntegral;
328 	enum canStoreUnchanged = !is(T == enum) && (isIntegral!T || is(T == bool) || isFloatingPoint!T || is(T == string));
329 }