1 /** 2 * SDLang 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.sdlang; 11 12 private 13 { 14 import std.algorithm.searching : any; 15 import std.array : empty; 16 17 import sdlang : Tag, parseSource, ParseException, Value, Attribute; 18 19 import uniconf.core : UniConfException; 20 import uninode.node : isUniNode; 21 } 22 23 24 /** 25 * Conert SDL to UniConf 26 */ 27 UniConf toUniConf(UniConf)(auto ref Tag root) @safe 28 if (isUniNode!UniConf) 29 { 30 UniConf convertVal(ref Value val) @trusted 31 { 32 if (val.convertsTo!bool) 33 return UniConf(val.get!bool); 34 else if (val.convertsTo!long) 35 return UniConf(val.get!long); 36 else if (val.convertsTo!string) 37 return UniConf(val.get!string); 38 else if (val.convertsTo!double) 39 return UniConf(val.get!double); 40 else if (val.convertsTo!(ubyte[])) 41 return UniConf(val.get!(ubyte[])); 42 else 43 return UniConf(); 44 } 45 46 UniConf convert(ref Tag tag) @trusted 47 { 48 // annonymous tag return values 49 if (tag.tags.empty && tag.attributes.empty && tag.values.length) 50 { 51 if (tag.values.length == 1) 52 return convertVal(tag.values[0]); 53 else 54 { 55 UniConf[] arr = new UniConf[tag.values.length]; 56 foreach (size_t i, Value v; tag.values) 57 arr[i] = convertVal(v); 58 return UniConf(arr); 59 } 60 } 61 62 // if exists tags or attributes then mapping 63 if (tag.tags.length || tag.attributes.length) 64 { 65 UniConf[string] map; 66 UniConf[] values; 67 68 foreach(Attribute a; tag.attributes) 69 map[a.name] = convertVal(a.value); 70 71 if (tag.values.length == 1 && tag.values[0].convertsTo!string) 72 map["__name"] = convertVal(tag.values[0]); 73 else 74 { 75 values.reserve(tag.values.length); 76 foreach (Value val; tag.values) 77 values ~= convertVal(val); 78 } 79 80 foreach (Tag subTag; tag.tags) 81 { 82 auto subNode = convert(subTag); 83 if (subTag.name.empty) 84 values ~= subNode; 85 else 86 { 87 if (auto exNode = subTag.name in map) 88 { 89 if (exNode.canSequence) 90 *exNode ~= subNode; 91 else if (subTag.name != "__name") 92 map[subTag.name] = UniConf([*exNode, subNode]); 93 } 94 else 95 map[subTag.name] = subNode; 96 } 97 } 98 99 if (values.length) 100 map["__values"] = UniConf(values); 101 102 return UniConf(map); 103 } 104 else 105 return UniConf(); 106 } 107 108 return convert(root); 109 } 110 111 112 /** 113 * Convert UniConf to SDLang 114 */ 115 Tag toSDLang(UniConf)(auto ref const UniConf root) @safe 116 if (isUniNode!UniConf) 117 { 118 Value convertNode(UniConf node) 119 { 120 switch(node.tag) 121 { 122 case UniConf.Tag.nil: 123 return Value(); 124 case UniConf.Tag.boolean: 125 return Value(cast()node.get!bool); 126 case UniConf.Tag.integer: 127 return Value(cast()node.get!long); 128 case UniConf.Tag.uinteger: 129 return Value(cast(long)node.get!ulong); 130 case UniConf.Tag.floating: 131 return Value(cast()node.get!double); 132 case UniConf.Tag.text: 133 return Value(cast()node.get!string); 134 case UniConf.Tag.raw: 135 return Value(cast(ubyte[])node.get!(ubyte[])); 136 default: 137 return Value(); 138 } 139 } 140 141 Tag convert(ref const UniConf node) @trusted 142 { 143 Tag ret = new Tag(); 144 145 if (node.canSequence) 146 { 147 foreach (ref const UniConf subNode; node) 148 { 149 if (subNode.canSequence || subNode.canMapping) 150 ret.add(convert(subNode)); 151 else 152 ret.add(convertNode(subNode)); 153 } 154 } 155 else if (node.canMapping) 156 { 157 foreach (string name, ref const UniConf subNode; node) 158 { 159 if (subNode.canSequence && subNode.getSequence.any!((n) => n.canMapping)) 160 { 161 foreach (ref const UniConf subSubNode; subNode) 162 { 163 Tag subTag = convert(subSubNode); 164 subTag.name = name; 165 ret.add(subTag); 166 } 167 } 168 else if (name == "__values") 169 { 170 foreach (ref const UniConf subVal; subNode) 171 { 172 if (subVal.canSequence || subVal.canMapping) 173 ret.add(convert(subVal)); 174 else 175 ret.add(convertNode(subVal)); 176 } 177 } 178 else if (name == "__name") 179 { 180 if (subNode.canSequence && subNode.getSequence.length) 181 ret.add(convertNode(subNode[0])); 182 else 183 ret.add(convertNode(subNode)); 184 } 185 else 186 { 187 Tag subTag = convert(subNode); 188 subTag.name = name; 189 ret.add(subTag); 190 } 191 } 192 } 193 else 194 ret.add(convertNode(node)); 195 196 return ret; 197 } 198 199 return () @trusted { 200 Tag ret = convert(root); 201 if (ret.tags.length == 0 && ret.values.length > 0) 202 { 203 Tag rootTag = new Tag(); 204 rootTag.add(ret); 205 return rootTag; 206 } 207 return ret; 208 } (); 209 } 210 211 @("Should work convert sdl to config and back") 212 @system unittest 213 { 214 import uninode.tree : UniTree; 215 216 enum sdl = ` 217 "anno1" 1 218 logger "console" { 219 appender "console" 220 level "debugv" 221 item "node1" { 222 order 1 223 } 224 item "node2" 225 item "node3" 226 } 227 `; 228 229 UniTree conf = parseSDLang!UniTree(sdl); 230 Tag sdlRoot = toSDLang(conf); 231 assert (sdlRoot.tags.length == 2); 232 assert (sdlRoot.tags[1].values.length == 2); 233 assert (sdlRoot.tags[1].values[0].get!string == "anno1"); 234 235 assert (sdlRoot.tags[0].values.length == 1); 236 assert (sdlRoot.tags[0].values[0].get!string == "console"); 237 assert (sdlRoot.tags[0].tags.length == 5); 238 } 239 240 241 /** 242 * Parse UniConf from string 243 */ 244 UniConf parseSDLang(UniConf)(string data) @trusted 245 if (isUniNode!UniConf) 246 { 247 Tag root; 248 try 249 root = parseSource(data); 250 catch(ParseException e) 251 throw new UniConfException("Error loading sdlang from string:", 252 e.file, e.line, e); 253 return toUniConf!UniConf(root); 254 } 255 256 257 /** 258 * Convert UniConf to json string 259 */ 260 string saveSDLang(UniConf)(auto ref const UniConf node, string indent=" ") @trusted 261 { 262 auto rootTag = toSDLang!UniConf(node); 263 return rootTag.toSDLDocument(indent); 264 } 265 266 @("Should work parseSDLang and saveSDLang method") 267 @safe unittest 268 { 269 import uninode.tree : UniTree; 270 271 enum sdl = `client { 272 host "localhost" 273 port 44L 274 } 275 `; 276 277 const conf = parseSDLang!UniTree(sdl); 278 assert (conf.get!int("client.port") == 44); 279 assert (conf.get!string("client.host") == "localhost"); 280 const sdls = saveSDLang(conf); 281 assert (sdl == sdls); 282 } 283