1 2 // Copyright Ferdinand Majerech 2011. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 /** 8 * YAML dumper. 9 * 10 * Code based on $(LINK2 http://www.pyyaml.org, PyYAML). 11 */ 12 module dyaml.dumper; 13 14 15 //import std.stream; 16 import std.typecons; 17 18 import dyaml.stream; 19 import dyaml.anchor; 20 import dyaml.emitter; 21 import dyaml.encoding; 22 import dyaml.event; 23 import dyaml.exception; 24 import dyaml.linebreak; 25 import dyaml.node; 26 import dyaml.representer; 27 import dyaml.resolver; 28 import dyaml.serializer; 29 import dyaml.tagdirective; 30 31 32 /** 33 * Dumps YAML documents to files or streams. 34 * 35 * User specified Representer and/or Resolver can be used to support new 36 * tags / data types. 37 * 38 * Setters are provided to affect output details (style, encoding, etc.). 39 * 40 * Examples: 41 * 42 * Write to a file: 43 * -------------------- 44 * auto node = Node([1, 2, 3, 4, 5]); 45 * Dumper("file.yaml").dump(node); 46 * -------------------- 47 * 48 * Write multiple YAML documents to a file: 49 * -------------------- 50 * auto node1 = Node([1, 2, 3, 4, 5]); 51 * auto node2 = Node("This document contains only one string"); 52 * Dumper("file.yaml").dump(node1, node2); 53 * 54 * //Or with an array: 55 * //Dumper("file.yaml").dump([node1, node2]); 56 * 57 * 58 * -------------------- 59 * 60 * Write to memory: 61 * -------------------- 62 * import std.stream; 63 * auto stream = new YMemoryStream(); 64 * auto node = Node([1, 2, 3, 4, 5]); 65 * Dumper(stream).dump(node); 66 * -------------------- 67 * 68 * Use a custom representer/resolver to support custom data types and/or implicit tags: 69 * -------------------- 70 * auto node = Node([1, 2, 3, 4, 5]); 71 * auto representer = new Representer(); 72 * auto resolver = new Resolver(); 73 * 74 * //Add representer functions / resolver expressions here... 75 * 76 * auto dumper = Dumper("file.yaml"); 77 * dumper.representer = representer; 78 * dumper.resolver = resolver; 79 * dumper.dump(node); 80 * -------------------- 81 */ 82 struct Dumper 83 { 84 unittest 85 { 86 auto node = Node([1, 2, 3, 4, 5]); 87 Dumper(new YMemoryStream()).dump(node); 88 } 89 90 unittest 91 { 92 auto node1 = Node([1, 2, 3, 4, 5]); 93 auto node2 = Node("This document contains only one string"); 94 Dumper(new YMemoryStream()).dump(node1, node2); 95 } 96 97 unittest 98 { 99 //import std.stream; 100 auto stream = new YMemoryStream(); 101 auto node = Node([1, 2, 3, 4, 5]); 102 Dumper(stream).dump(node); 103 } 104 105 unittest 106 { 107 auto node = Node([1, 2, 3, 4, 5]); 108 auto representer = new Representer(); 109 auto resolver = new Resolver(); 110 auto dumper = Dumper(new YMemoryStream()); 111 dumper.representer = representer; 112 dumper.resolver = resolver; 113 dumper.dump(node); 114 } 115 116 private: 117 //Resolver to resolve tags. 118 Resolver resolver_; 119 //Representer to represent data types. 120 Representer representer_; 121 122 //Stream to write to. 123 YStream stream_; 124 //True if this Dumper owns stream_ and needs to destroy it in the destructor. 125 bool weOwnStream_ = false; 126 127 //Write scalars in canonical form? 128 bool canonical_; 129 //Indentation width. 130 int indent_ = 2; 131 //Preferred text width. 132 uint textWidth_ = 80; 133 //Line break to use. 134 LineBreak lineBreak_ = LineBreak.Unix; 135 //Character encoding to use. 136 Encoding encoding_ = Encoding.UTF_8; 137 //YAML version string. 138 string YAMLVersion_ = "1.1"; 139 //Tag directives to use. 140 TagDirective[] tags_ = null; 141 //Always write document start? 142 Flag!"explicitStart" explicitStart_ = No.explicitStart; 143 //Always write document end? 144 Flag!"explicitEnd" explicitEnd_ = No.explicitEnd; 145 146 //Name of the output file or stream, used in error messages. 147 string name_ = "<unknown>"; 148 149 public: 150 @disable this(); 151 @disable bool opEquals(ref Dumper); 152 @disable int opCmp(ref Dumper); 153 154 /** 155 * Construct a Dumper writing to a file. 156 * 157 * Params: filename = File name to write to. 158 * 159 * Throws: YAMLException if the file can not be dumped to (e.g. cannot be opened). 160 */ 161 this(string filename) @trusted 162 { 163 name_ = filename; 164 //try{this(new File(filename, FileMode.OutNew));} 165 try{this(new YFile(filename));} 166 //catch(StreamException e) 167 catch(Exception e) 168 { 169 throw new YAMLException("Unable to open file " ~ filename ~ 170 " for YAML dumping: " ~ e.msg); 171 } 172 // need to destroy the File we constructed. 173 weOwnStream_ = true; 174 } 175 176 ///Construct a Dumper writing to a _stream. This is useful to e.g. write to memory. 177 this(YStream stream) @safe 178 { 179 resolver_ = new Resolver(); 180 representer_ = new Representer(); 181 stream_ = stream; 182 } 183 184 ///Destroy the Dumper. 185 @trusted ~this() 186 { 187 YAMLVersion_ = null; 188 if(weOwnStream_) { destroy(stream_); } 189 } 190 191 ///Set stream _name. Used in debugging messages. 192 @property void name(string name) pure @safe nothrow 193 { 194 name_ = name; 195 } 196 197 ///Specify custom Resolver to use. 198 @property void resolver(Resolver resolver) @trusted 199 { 200 resolver_.destroy(); 201 resolver_ = resolver; 202 } 203 204 ///Specify custom Representer to use. 205 @property void representer(Representer representer) @trusted 206 { 207 representer_.destroy(); 208 representer_ = representer; 209 } 210 211 ///Write scalars in _canonical form? 212 @property void canonical(bool canonical) pure @safe nothrow 213 { 214 canonical_ = canonical; 215 } 216 217 ///Set indentation width. 2 by default. Must not be zero. 218 @property void indent(uint indent) pure @safe nothrow 219 in 220 { 221 assert(indent != 0, "Can't use zero YAML indent width"); 222 } 223 body 224 { 225 indent_ = indent; 226 } 227 228 ///Set preferred text _width. 229 @property void textWidth(uint width) pure @safe nothrow 230 { 231 textWidth_ = width; 232 } 233 234 ///Set line break to use. Unix by default. 235 @property void lineBreak(LineBreak lineBreak) pure @safe nothrow 236 { 237 lineBreak_ = lineBreak; 238 } 239 240 ///Set character _encoding to use. UTF-8 by default. 241 @property void encoding(Encoding encoding) pure @safe nothrow 242 { 243 encoding_ = encoding; 244 } 245 246 ///Always explicitly write document start? 247 @property void explicitStart(bool explicit) pure @safe nothrow 248 { 249 explicitStart_ = explicit ? Yes.explicitStart : No.explicitStart; 250 } 251 252 ///Always explicitly write document end? 253 @property void explicitEnd(bool explicit) pure @safe nothrow 254 { 255 explicitEnd_ = explicit ? Yes.explicitEnd : No.explicitEnd; 256 } 257 258 ///Specify YAML version string. "1.1" by default. 259 @property void YAMLVersion(string YAMLVersion) pure @safe nothrow 260 { 261 YAMLVersion_ = YAMLVersion; 262 } 263 264 /** 265 * Specify tag directives. 266 * 267 * A tag directive specifies a shorthand notation for specifying _tags. 268 * Each tag directive associates a handle with a prefix. This allows for 269 * compact tag notation. 270 * 271 * Each handle specified MUST start and end with a '!' character 272 * (a single character "!" handle is allowed as well). 273 * 274 * Only alphanumeric characters, '-', and '__' may be used in handles. 275 * 276 * Each prefix MUST not be empty. 277 * 278 * The "!!" handle is used for default YAML _tags with prefix 279 * "tag:yaml.org,2002:". This can be overridden. 280 * 281 * Params: tags = Tag directives (keys are handles, values are prefixes). 282 * 283 * Example: 284 * -------------------- 285 * Dumper dumper = Dumper("file.yaml"); 286 * string[string] directives; 287 * directives["!short!"] = "tag:long.org,2011:"; 288 * //This will emit tags starting with "tag:long.org,2011" 289 * //with a "!short!" prefix instead. 290 * dumper.tagDirectives(directives); 291 * dumper.dump(Node("foo")); 292 * -------------------- 293 */ 294 @property void tagDirectives(string[string] tags) pure @trusted 295 { 296 TagDirective[] t; 297 foreach(handle, prefix; tags) 298 { 299 assert(handle.length >= 1 && handle[0] == '!' && handle[$ - 1] == '!', 300 "A tag handle is empty or does not start and end with a " ~ 301 "'!' character : " ~ handle); 302 assert(prefix.length >= 1, "A tag prefix is empty"); 303 t ~= TagDirective(handle, prefix); 304 } 305 tags_ = t; 306 } 307 308 /** 309 * Dump one or more YAML _documents to the file/stream. 310 * 311 * Note that while you can call dump() multiple times on the same 312 * dumper, you will end up writing multiple YAML "files" to the same 313 * file/stream. 314 * 315 * Params: documents = Documents to _dump (root nodes of the _documents). 316 * 317 * Throws: YAMLException on error (e.g. invalid nodes, 318 * unable to write to file/stream). 319 */ 320 void dump(Node[] documents ...) @trusted 321 { 322 try 323 { 324 auto emitter = Emitter(stream_, canonical_, indent_, textWidth_, lineBreak_); 325 auto serializer = Serializer(emitter, resolver_, encoding_, explicitStart_, 326 explicitEnd_, YAMLVersion_, tags_); 327 foreach(ref document; documents) 328 { 329 representer_.represent(serializer, document); 330 } 331 } 332 catch(YAMLException e) 333 { 334 throw new YAMLException("Unable to dump YAML to stream " 335 ~ name_ ~ " : " ~ e.msg); 336 } 337 } 338 339 package: 340 /* 341 * Emit specified events. Used for debugging/testing. 342 * 343 * Params: events = Events to emit. 344 * 345 * Throws: YAMLException if unable to emit. 346 */ 347 void emit(Event[] events) @system 348 { 349 try 350 { 351 auto emitter = Emitter(stream_, canonical_, indent_, textWidth_, lineBreak_); 352 foreach(ref event; events) 353 { 354 emitter.emit(event); 355 } 356 } 357 catch(YAMLException e) 358 { 359 throw new YAMLException("Unable to emit YAML to stream " 360 ~ name_ ~ " : " ~ e.msg); 361 } 362 } 363 }