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 }