// Copyright Ferdinand Majerech 2011. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) /** * YAML _dumper. * * Code based on $(LINK2 http://www.pyyaml.org, PyYAML). */ module dyaml.dumper; import std.stream; import std.typecons; import dyaml.anchor; import dyaml.emitter; import dyaml.encoding; import dyaml.event; import dyaml.exception; import dyaml.linebreak; import dyaml.node; import dyaml.representer; import dyaml.resolver; import dyaml.serializer; import dyaml.tagdirective; /** * Dumps YAML documents to files or streams. * * User specified Representer and/or Resolver can be used to support new * tags / data types. * * Setters are provided to affect output details (style, encoding, etc.). * * Examples: * * Write to a file: * -------------------- * auto node = Node([1, 2, 3, 4, 5]); * Dumper("file.yaml").dump(node); * -------------------- * * Write multiple YAML documents to a file: * -------------------- * auto node1 = Node([1, 2, 3, 4, 5]); * auto node2 = Node("This document contains only one string"); * Dumper("file.yaml").dump(node1, node2); * * //Or with an array: * //Dumper("file.yaml").dump([node1, node2]); * * * -------------------- * * Write to memory: * -------------------- * import std.stream; * auto stream = new MemoryStream(); * auto node = Node([1, 2, 3, 4, 5]); * Dumper(stream).dump(node); * -------------------- * * Use a custom representer/resolver to support custom data types and/or implicit tags: * -------------------- * auto node = Node([1, 2, 3, 4, 5]); * auto representer = new Representer(); * auto resolver = new Resolver(); * * //Add representer functions / resolver expressions here... * * auto dumper = Dumper("file.yaml"); * dumper.representer = representer; * dumper.resolver = resolver; * dumper.dump(node); * -------------------- */ struct Dumper { unittest { auto node = Node([1, 2, 3, 4, 5]); Dumper(new MemoryStream()).dump(node); } unittest { auto node1 = Node([1, 2, 3, 4, 5]); auto node2 = Node("This document contains only one string"); Dumper(new MemoryStream()).dump(node1, node2); } unittest { import std.stream; auto stream = new MemoryStream(); auto node = Node([1, 2, 3, 4, 5]); Dumper(stream).dump(node); } unittest { auto node = Node([1, 2, 3, 4, 5]); auto representer = new Representer(); auto resolver = new Resolver(); auto dumper = Dumper(new MemoryStream()); dumper.representer = representer; dumper.resolver = resolver; dumper.dump(node); } private: ///Resolver to resolve tags. Resolver resolver_; ///Representer to represent data types. Representer representer_; ///Stream to write to. Stream stream_; ///Write scalars in canonical form? bool canonical_; ///Indentation width. int indent_ = 2; ///Preferred text width. uint textWidth_ = 80; ///Line break to use. LineBreak lineBreak_ = LineBreak.Unix; ///Character encoding to use. Encoding encoding_ = Encoding.UTF_8; ///YAML version string. string YAMLVersion_ = "1.1"; ///Tag directives to use. TagDirective[] tags_ = null; ///Always write document start? bool explicitStart_ = false; ///Always write document end? bool explicitEnd_ = false; ///Name of the output file or stream, used in error messages. string name_ = ""; public: @disable this(); @disable bool opEquals(ref Dumper); @disable int opCmp(ref Dumper); /** * Construct a Dumper writing to a file. * * Params: filename = File name to write to. * * Throws: YAMLException if the file can not be dumped to (e.g. cannot be opened). */ this(string filename) { name_ = filename; try{this(new File(filename, FileMode.OutNew));} catch(StreamException e) { throw new YAMLException("Unable to open file " ~ filename ~ " for YAML dumping: " ~ e.msg); } } ///Construct a Dumper writing to a _stream. This is useful to e.g. write to memory. this(Stream stream) { resolver_ = new Resolver(); representer_ = new Representer(); stream_ = stream; } ///Destroy the Dumper. ~this() { YAMLVersion_ = null; } ///Set stream _name. Used in debugging messages. @property void name(string name) { name_ = name; } ///Specify custom Resolver to use. @property void resolver(Resolver resolver) { clear(resolver_); resolver_ = resolver; } ///Specify custom Representer to use. @property void representer(Representer representer) { clear(representer_); representer_ = representer; } ///Write scalars in _canonical form? @property void canonical(bool canonical) { canonical_ = canonical; } ///Set indentation width. 2 by default. Must not be zero. @property void indent(uint indent) in { assert(indent != 0, "Can't use zero YAML indent width"); } body { indent_ = indent; } ///Set preferred text _width. @property void textWidth(uint width) { textWidth_ = width; } ///Set line break to use. Unix by default. @property void lineBreak(LineBreak lineBreak) { lineBreak_ = lineBreak; } ///Set character _encoding to use. UTF-8 by default. @property void encoding(Encoding encoding) { encoding_ = encoding; } ///Always explicitly write document start? @property void explicitStart(bool explicit) { explicitStart_ = explicit; } ///Always explicitly write document end? @property void explicitEnd(bool explicit) { explicitEnd_ = explicit; } ///Specify YAML version string. "1.1" by default. @property void YAMLVersion(string YAMLVersion) { YAMLVersion_ = YAMLVersion; } /** * Specify tag directives. * * A tag directive specifies a shorthand notation for specifying _tags. * Each tag directive associates a handle with a prefix. This allows for * compact tag notation. * * Each handle specified MUST start and end with a '!' character * (a single character "!" handle is allowed as well). * * Only alphanumeric characters, '-', and '__' may be used in handles. * * Each prefix MUST not be empty. * * The "!!" handle is used for default YAML _tags with prefix * "tag:yaml.org,2002:". This can be overridden. * * Params: tags = Tag directives (keys are handles, values are prefixes). * * Example: * -------------------- * Dumper dumper = Dumper("file.yaml"); * string[string] directives; * directives["!short!"] = "tag:long.org,2011:"; * //This will emit tags starting with "tag:long.org,2011" * //with a "!short!" prefix instead. * dumper.tagDirectives(directives); * dumper.dump(Node("foo")); * -------------------- */ @property void tagDirectives(string[string] tags) { TagDirective[] t; foreach(handle, prefix; tags) { assert(handle.length >= 1 && handle[0] == '!' && handle[$ - 1] == '!', "A tag handle is empty or does not start and end with a " "'!' character : " ~ handle); assert(prefix.length >= 1, "A tag prefix is empty"); t ~= TagDirective(handle, prefix); } tags_ = t; } /** * Dump one or more YAML _documents to the file/stream. * * Note that while you can call dump() multiple times on the same * dumper, you will end up writing multiple YAML "files" to the same * file/stream. * * Params: documents = Documents to _dump (root nodes of the _documents). * * Throws: YAMLException on error (e.g. invalid nodes, * unable to write to file/stream). */ void dump(Node[] documents ...) { try { auto emitter = Emitter(stream_, canonical_, indent_, textWidth_, lineBreak_); auto serializer = Serializer(emitter, resolver_, encoding_, explicitStart_, explicitEnd_, YAMLVersion_, tags_); foreach(ref document; documents) { representer_.represent(serializer, document); } } catch(YAMLException e) { throw new YAMLException("Unable to dump YAML to stream " ~ name_ ~ " : " ~ e.msg); } } package: /* * Emit specified events. Used for debugging/testing. * * Params: events = Events to emit. * * Throws: YAMLException if unable to emit. */ void emit(Event[] events) { try { auto emitter = Emitter(stream_, canonical_, indent_, textWidth_, lineBreak_); foreach(ref event; events) { emitter.emit(event); } } catch(YAMLException e) { throw new YAMLException("Unable to emit YAML to stream " ~ name_ ~ " : " ~ e.msg); } } }