1 /** 2 * YAML configuration module 3 * 4 * Copyright: (c) 2015-2020, Milofon Project. 5 * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file. 6 * Author: <m.galanin@milofon.pro> Maksim Galanin 7 * Date: 2018-09-03 8 */ 9 10 module uniconf.yaml; 11 12 private 13 { 14 import std.array : appender, replace; 15 import std.datetime.systime : SysTime; 16 import std.datetime : DateTimeException; 17 18 import dyaml.loader : Loader; 19 import dyaml.node : Node, NodeType; 20 import dyaml.exception : YAMLException; 21 import dyaml.dumper : dumper; 22 import dyaml.style : ScalarStyle; 23 24 import uniconf.core : UniConfException; 25 import uninode.node : isUniNode; 26 } 27 28 29 /** 30 * Conert Yaml to UniConf 31 */ 32 UniConf toUniConf(UniConf)(auto ref const Node root) @safe 33 if (isUniNode!UniConf) 34 { 35 UniConf convert(ref const Node node) @safe 36 { 37 if (!node.isValid) 38 return UniConf(); 39 40 switch(node.type) 41 { 42 case NodeType.null_: 43 case NodeType.merge: 44 case NodeType.invalid: 45 return UniConf(); 46 case NodeType.boolean: 47 return UniConf(node.get!bool); 48 case NodeType.integer: 49 return UniConf(node.get!long); 50 case NodeType.decimal: 51 return UniConf(node.get!double); 52 case NodeType.binary: 53 return UniConf(node.get!(ubyte[])); 54 case NodeType.timestamp: 55 return UniConf(node.get!SysTime.toISOExtString); 56 case NodeType..string: 57 { 58 auto style = __traits(getMember, node, "scalarStyle"); 59 if (style == ScalarStyle.folded) 60 return UniConf(node.get!string.replace("\n", " ")); 61 return UniConf(node.get!string); 62 } 63 case NodeType.mapping: 64 { 65 UniConf[string] map; 66 foreach(string key, ref const Node value; node) 67 map[key] = convert(value); 68 return UniConf(map); 69 } 70 case NodeType.sequence: 71 { 72 UniConf[] arr = new UniConf[node.length]; 73 size_t i; 74 foreach(ref const Node value; node) 75 arr[i++] = convert(value); 76 return UniConf(arr); 77 } 78 default: 79 return UniConf(); 80 } 81 } 82 83 return convert(root); 84 } 85 86 87 /** 88 * Convert UniConf to Yaml 89 */ 90 Node toYaml(UniConf)(auto ref const UniConf root) @safe 91 if (isUniNode!UniConf) 92 { 93 Node convert(ref const UniConf node) @trusted 94 { 95 switch(node.tag) 96 { 97 case UniConf.Tag.nil: 98 return Node(); 99 case UniConf.Tag.boolean: 100 return Node(node.get!bool); 101 case UniConf.Tag.integer: 102 return Node(node.get!long); 103 case UniConf.Tag.uinteger: 104 return Node(node.get!ulong); 105 case UniConf.Tag.floating: 106 return Node(node.get!double); 107 case UniConf.Tag.text: 108 { 109 string val = node.get!string; 110 try 111 return Node(SysTime.fromISOExtString(val)); 112 catch (DateTimeException e) 113 return Node(node.get!string); 114 } 115 case UniConf.Tag.sequence: 116 { 117 size_t len = node.length; 118 Node[] arr = new Node[len]; 119 foreach(size_t i, ref const UniConf ch; node) 120 arr[i] = convert(ch); 121 return Node(arr); 122 } 123 case UniConf.Tag.mapping: 124 { 125 Node[string] map; 126 foreach (string key, ref const UniConf ch; node) 127 map[key] = convert(ch); 128 return Node(map); 129 } 130 default: 131 return Node(); 132 } 133 } 134 135 return convert(root); 136 } 137 138 139 @("Should work convert yaml to config and back") 140 @safe unittest 141 { 142 import uninode.node : UniNode; 143 const val = Node([Node(1), Node("hello"), Node(["one": Node(1)])]); 144 const conf = val.toUniConf!UniNode(); 145 const yml = conf.toYaml(); 146 assert (yml == val); 147 } 148 149 150 /** 151 * Parse UniConf from string 152 */ 153 UniConf parseYaml(UniConf)(string data) @safe 154 if (isUniNode!UniConf) 155 { 156 Node root; 157 try 158 root = Loader.fromString(data.idup).load(); 159 catch(YAMLException e) 160 throw new UniConfException("Error loading yaml from a string:", 161 e.file, e.line, e); 162 return toUniConf!UniConf(root); 163 } 164 165 166 /** 167 * Convert UniConf to json string 168 */ 169 string saveYaml(UniConf)(auto ref const UniConf node) @safe 170 if (isUniNode!UniConf) 171 { 172 const yml = toYaml!UniConf(node); 173 auto buffer = appender!string; 174 dumper().dump(buffer, yml); 175 return buffer.data; 176 } 177 178 @("Should work parseYaml and saveYaml method") 179 @safe unittest 180 { 181 import uninode.tree : UniTree; 182 183 enum yamlSrc = ` 184 client: 185 host: "localhost" 186 port: 404 187 can: 2001-12-15T02:59:43.1Z 188 collection: 189 - "one" 190 - "two" 191 `; 192 193 UniTree conf = parseYaml!UniTree(yamlSrc); 194 assert (conf.get!int("client.port") == 404); 195 assert (conf.get!string("client.host") == "localhost"); 196 const yml = saveYaml(conf); 197 conf = parseYaml!UniTree(yml); 198 assert (conf.get!int("client.port") == 404); 199 assert (conf.get!string("client.host") == "localhost"); 200 } 201