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 enum emptyObject = "---\n..."; 15 package static T parseInput(T, DeSiryulize flags, U)(U data) if (isInputRange!U && isSomeChar!(ElementType!U)) { 16 import std.conv : to; 17 import std.utf : byChar; 18 auto str = data.byChar.to!(char[]); 19 auto loader = Loader.fromString(str).load(); 20 return loader.fromYAML!(T, BitFlags!DeSiryulize(flags)); 21 } 22 package static string asString(Siryulize flags, T)(T data) { 23 import std.array : appender; 24 debug enum path = T.stringof; 25 else enum path = ""; 26 auto buf = appender!string; 27 auto dumper = dumper(buf); 28 auto representer = new Representer; 29 representer.defaultCollectionStyle = CollectionStyle.block; 30 representer.defaultScalarStyle = ScalarStyle.plain; 31 dumper.representer = representer; 32 dumper.explicitStart = false; 33 dumper.dump(toYAML!(BitFlags!Siryulize(flags))(data, path)); 34 return buf.data; 35 } 36 } 37 /++ 38 + Thrown on YAML deserialization errors 39 +/ 40 class YAMLDException : DeserializeException { 41 this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 42 super(msg, file, line); 43 } 44 } 45 /++ 46 + Thrown on YAML serialization errors 47 +/ 48 class YAMLSException : SerializeException { 49 this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 50 super(msg, file, line); 51 } 52 } 53 private T fromYAML(T, BitFlags!DeSiryulize flags)(Node node) if (!isInfinite!T) { 54 import std.conv : to; 55 import std.datetime : Date, DateTime, SysTime, TimeOfDay; 56 import std.exception : enforce; 57 import std.meta : AliasSeq; 58 import std.range : enumerate, isOutputRange; 59 import std.traits : arity, FieldNameTuple, ForeachType, getUDAs, hasIndirections, hasUDA, isArray, isAssociativeArray, isFloatingPoint, isIntegral, isPointer, isSomeString, isStaticArray, KeyType, OriginalType, Parameters, PointerTarget, TemplateArgsOf, ValueType; 60 import std.utf : byCodeUnit; 61 if (node.isNull) 62 return T.init; 63 try { 64 static if (is(T == struct) && hasDeserializationMethod!T) { 65 return deserializationMethod!T(fromYAML!(Parameters!(deserializationMethod!T), flags)(node)); 66 } else static if (is(T == enum)) { 67 enforce!YAMLDException(node.isScalar(), "Attempted to read a non-scalar as a "~T.stringof); 68 if (node.tag == `tag:yaml.org,2002:str`) 69 return node.get!string.to!T; 70 else 71 return node.fromYAML!(OriginalType!T, flags).to!T; 72 } else static if (isNullable!T) { 73 return node.isNull ? T.init : T(node.fromYAML!(TemplateArgsOf!T[0], flags)); 74 } else static if (isIntegral!T || isSomeString!T || isFloatingPoint!T) { 75 enforce!YAMLDException(node.isScalar(), "Attempted to read a non-scalar as a "~T.stringof); 76 if (node.tag == `tag:yaml.org,2002:str`) 77 return node.get!string.to!T; 78 static if (isIntegral!T) { 79 enforce!YAMLDException(node.tag == `tag:yaml.org,2002:int`, "Attempted to read a float as an integer"); 80 return node.get!T; 81 } else static if (isSomeString!T) { 82 enforce!YAMLDException(node.tag != `tag:yaml.org,2002:bool`, "Attempted to read a non-string as a string"); 83 return node.get!string.to!T; 84 } else { 85 return node.get!T; 86 } 87 } else static if (isSomeChar!T) { 88 import std.array : front; 89 enforce!YAMLDException(node.isScalar(), "Attempted to read a non-scalar as a "~T.stringof); 90 return cast(T)node.get!string.front; 91 } else static if (is(T == SysTime) || is(T == DateTime) || is(T == Date)) { 92 enforce!YAMLDException(node.isScalar(), "Attempted to read a non-scalar as a "~T.stringof); 93 return node.get!SysTime.to!T; 94 } else static if (is(T == TimeOfDay)) { 95 enforce!YAMLDException(node.isScalar(), "Attempted to read a non-scalar as a "~T.stringof); 96 return TimeOfDay.fromISOExtString(node.get!string); 97 } else static if (is(T == struct) || (isPointer!T && is(PointerTarget!T == struct))) { 98 enforce!YAMLDException(node.isMapping(), "Attempted to read a non-mapping as a "~T.stringof); 99 T output; 100 static if (isPointer!T) { 101 output = new PointerTarget!T; 102 alias Undecorated = PointerTarget!T; 103 } else { 104 alias Undecorated = T; 105 } 106 foreach (member; FieldNameTuple!Undecorated) { 107 static if (__traits(getProtection, __traits(getMember, Undecorated, member)) == "public") { 108 alias field = AliasSeq!(__traits(getMember, Undecorated, member)); 109 enum memberName = getMemberName!field; 110 static if ((hasUDA!(field, Optional) || (!!(flags & DeSiryulize.optionalByDefault))) || hasIndirections!(typeof(field))) { 111 if (!node.containsKey(memberName)) 112 continue; 113 } else { 114 enforce!YAMLDException(node.containsKey(memberName), "Missing non-@Optional "~memberName~" in node"); 115 } 116 alias fromFunc = getConvertFromFunc!(T, __traits(getMember, Undecorated, member)); 117 static if (hasUDA!(__traits(getMember, Undecorated, member), IgnoreErrors)) { 118 try { 119 __traits(getMember, output, member) = fromFunc(node[memberName].fromYAML!(Parameters!(fromFunc)[0], flags)); 120 } catch (YAMLDException) {} 121 } else 122 __traits(getMember, output, member) = fromFunc(node[memberName].fromYAML!(Parameters!(fromFunc)[0], flags)); 123 } 124 } 125 return output; 126 } else static if(isOutputRange!(T, ElementType!T)) { 127 enforce!YAMLDException(node.isSequence(), "Attempted to read a non-sequence as a "~T.stringof); 128 T output; 129 foreach (Node newNode; node) 130 output ~= fromYAML!(ElementType!T, flags)(newNode); 131 return output; 132 } else static if (isStaticArray!T && isSomeChar!(ElementType!T)) { 133 enforce!YAMLDException(node.isScalar(), "Attempted to read a non-scalar as a "~T.stringof); 134 T output; 135 foreach (i, chr; node.fromYAML!((ForeachType!T)[], flags).byCodeUnit.enumerate(0)) 136 output[i] = chr; 137 return output; 138 } else static if(isStaticArray!T) { 139 enforce!YAMLDException(node.isSequence(), "Attempted to read a non-sequence as a "~T.stringof); 140 T output; 141 size_t i; 142 foreach (Node newNode; node) 143 output[i++] = fromYAML!(ElementType!T, flags)(newNode); 144 return output; 145 } else static if(isAssociativeArray!T) { 146 enforce!YAMLDException(node.isMapping(), "Attempted to read a non-mapping as a "~T.stringof); 147 T output; 148 foreach (Node key, Node value; node) 149 output[fromYAML!(KeyType!T, flags)(key)] = fromYAML!(ValueType!T, flags)(value); 150 return output; 151 } else static if (is(T == bool)) { 152 enforce!YAMLDException(node.tag == `tag:yaml.org,2002:bool`, "Expecting a boolean value"); 153 return node.get!T; 154 } else 155 static assert(false, "Cannot read type "~T.stringof~" from YAML"); //unreachable, hopefully. 156 } catch (NodeException e) { 157 throw new YAMLDException(e.msg); 158 } 159 } 160 private @property Node toYAML(BitFlags!Siryulize flags, T)(T type, string path = "") if (!isInfinite!T) { 161 import std.conv : text, to; 162 import std.datetime : Date, DateTime, SysTime, TimeOfDay; 163 import std.meta : AliasSeq; 164 import std.traits : arity, FieldNameTuple, getSymbolsByUDA, getUDAs, hasUDA, isAssociativeArray, isPointer, isSomeString, isStaticArray, PointerTarget, Unqual; 165 static if (isPointer!T) { 166 alias Undecorated = Unqual!(PointerTarget!T); 167 } else { 168 alias Undecorated = Unqual!T; 169 } 170 static if (is(T == struct) && hasSerializationMethod!T) { 171 return toYAML!flags(mixin("type."~__traits(identifier, serializationMethod!T)), path); 172 } else static if (hasUDA!(type, AsString) || is(Undecorated == enum)) { 173 return Node(type.text); 174 } else static if (isNullable!Undecorated) { 175 if (type.isNull) { 176 return Node(YAMLNull()); 177 } else { 178 return toYAML!flags(type.get(), path); 179 } 180 } else static if (is(Undecorated == SysTime)) { 181 return Node(type.to!Undecorated, "tag:yaml.org,2002:timestamp"); 182 } else static if (is(Undecorated == DateTime) || is(Undecorated == Date)) { 183 return Node(type.toISOExtString(), "tag:yaml.org,2002:timestamp"); 184 } else static if (is(Undecorated == TimeOfDay)) { 185 return Node(type.toISOExtString()); 186 } else static if (isSomeChar!Undecorated) { 187 return toYAML!flags([type], path); 188 } else static if (canStoreUnchanged!Undecorated) { 189 return Node(type.to!Undecorated); 190 } else static if (isSomeString!Undecorated || (isStaticArray!Undecorated && isSomeChar!(ElementType!Undecorated))) { 191 import std.utf : toUTF8; 192 return toYAML!flags(type[].toUTF8().idup, path); 193 } else static if(isAssociativeArray!Undecorated) { 194 Node[Node] output; 195 foreach (key, value; type) 196 output[toYAML!flags(key, path)] = toYAML!flags(value, path); 197 return Node(output); 198 } else static if(isSimpleList!Undecorated) { 199 Node[] output; 200 foreach (value; type) 201 output ~= toYAML!flags(value, path); 202 return Node(output); 203 } else static if (is(Undecorated == struct)) { 204 static string[] empty; 205 Node output = Node(empty, empty); 206 foreach (member; FieldNameTuple!Undecorated) { 207 static if (__traits(getProtection, __traits(getMember, Undecorated, member)) == "public") { 208 debug string newPath = path~"."~member; 209 else string newPath = ""; 210 static if (!!(flags & Siryulize.omitInits)) { 211 static if (isNullable!(typeof(__traits(getMember, type, member)))) { 212 if (__traits(getMember, type, member).isNull) 213 continue; 214 } else { 215 if (__traits(getMember, type, member) == __traits(getMember, type, member).init) { 216 continue; 217 } 218 } 219 } 220 enum memberName = getMemberName!(__traits(getMember, Undecorated, member)); 221 try { 222 static if (isPointer!(typeof(mixin("type."~member))) && !!(flags & Siryulize.omitNulls)) { 223 if (mixin("type."~member) is null) { 224 continue; 225 } 226 } 227 static if (hasConvertToFunc!(T, __traits(getMember, Undecorated, member))) { 228 auto val = toYAML!flags(getConvertToFunc!(T, __traits(getMember, Undecorated, member))(mixin("type."~member)), newPath); 229 output.add(memberName, val); 230 } else { 231 output.add(memberName, toYAML!(flags)(mixin("type."~member), newPath)); 232 } 233 } catch (Exception e) { 234 e.msg = "Error serializing "~newPath~": "~e.msg; 235 throw e; 236 } 237 } 238 } 239 return output; 240 } else 241 static assert(false, "Cannot write type "~T.stringof~" to YAML"); //unreachable, hopefully 242 } 243 private template canStoreUnchanged(T) { 244 import std.traits : isFloatingPoint, isIntegral; 245 enum canStoreUnchanged = isIntegral!T || is(T == bool) || isFloatingPoint!T || is(T == string); 246 }