dyaml/source/dyaml/dumper.d
Jonathan M Davis 7a1e1ecce3 Another attempt at making d-yaml work with dub.
Creating a symlink in source to the dyaml directory does not actually
result in a symlink when another package grabs d-yaml as a dependency
via dub, and even if it did, it wouldn't work on Windows. So, this moves
the source into source so that it'll actually work, and cdc.d has been
adjusted accordingly so that building with it should still work.
2013-03-28 21:33:13 -07:00

356 lines
11 KiB
D

// 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?
Flag!"explicitStart" explicitStart_ = No.explicitStart;
///Always write document end?
Flag!"explicitEnd" explicitEnd_ = No.explicitEnd;
///Name of the output file or stream, used in error messages.
string name_ = "<unknown>";
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) @safe
{
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) pure @safe
{
resolver_ = new Resolver();
representer_ = new Representer();
stream_ = stream;
}
///Destroy the Dumper.
pure @safe nothrow ~this()
{
YAMLVersion_ = null;
}
///Set stream _name. Used in debugging messages.
@property void name(string name) pure @safe nothrow
{
name_ = name;
}
///Specify custom Resolver to use.
@property void resolver(Resolver resolver) @trusted
{
clear(resolver_);
resolver_ = resolver;
}
///Specify custom Representer to use.
@property void representer(Representer representer) @trusted
{
clear(representer_);
representer_ = representer;
}
///Write scalars in _canonical form?
@property void canonical(bool canonical) pure @safe nothrow
{
canonical_ = canonical;
}
///Set indentation width. 2 by default. Must not be zero.
@property void indent(uint indent) pure @safe nothrow
in
{
assert(indent != 0, "Can't use zero YAML indent width");
}
body
{
indent_ = indent;
}
///Set preferred text _width.
@property void textWidth(uint width) pure @safe nothrow
{
textWidth_ = width;
}
///Set line break to use. Unix by default.
@property void lineBreak(LineBreak lineBreak) pure @safe nothrow
{
lineBreak_ = lineBreak;
}
///Set character _encoding to use. UTF-8 by default.
@property void encoding(Encoding encoding) pure @safe nothrow
{
encoding_ = encoding;
}
///Always explicitly write document start?
@property void explicitStart(bool explicit) pure @safe nothrow
{
explicitStart_ = explicit ? Yes.explicitStart : No.explicitStart;
}
///Always explicitly write document end?
@property void explicitEnd(bool explicit) pure @safe nothrow
{
explicitEnd_ = explicit ? Yes.explicitEnd : No.explicitEnd;
}
///Specify YAML version string. "1.1" by default.
@property void YAMLVersion(string YAMLVersion) pure @safe nothrow
{
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) pure @trusted
{
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 ...) @trusted
{
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) @system
{
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);
}
}
}