dyaml/source/dyaml/loader.d

327 lines
9.5 KiB
D
Raw Normal View History

2011-08-16 12:53:13 +00:00
// 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)
2014-07-19 12:32:16 +00:00
/// Class used to load YAML documents.
2011-08-16 12:53:13 +00:00
module dyaml.loader;
import std.exception;
import std.stream;
import dyaml.composer;
import dyaml.constructor;
import dyaml.event;
import dyaml.exception;
import dyaml.node;
2011-08-16 12:53:13 +00:00
import dyaml.parser;
import dyaml.reader;
import dyaml.resolver;
2011-08-16 12:53:13 +00:00
import dyaml.scanner;
import dyaml.token;
2014-07-19 12:32:16 +00:00
/// Loads YAML documents from files or streams.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// User specified Constructor and/or Resolver can be used to support new
/// tags / data types.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Examples:
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Load single YAML document from a file:
/// --------------------
/// auto rootNode = Loader("file.yaml").load();
/// ...
/// --------------------
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Load all YAML documents from a file:
/// --------------------
/// auto nodes = Loader("file.yaml").loadAll();
/// ...
/// --------------------
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Iterate over YAML documents in a file, lazily loading them:
/// --------------------
/// auto loader = Loader("file.yaml");
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// foreach(ref node; loader)
/// {
/// ...
/// }
/// --------------------
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Load YAML from memory:
/// --------------------
/// import std.stream;
/// import std.stdio;
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// string yaml_input = "red: '#ff0000'\n"
/// "green: '#00ff00'\n"
/// "blue: '#0000ff'";
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// auto colors = Loader.fromString(yaml_input).load();
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// foreach(string color, string value; colors)
/// {
/// writeln(color, " is ", value, " in HTML/CSS");
/// }
/// --------------------
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Use a custom constructor/resolver to support custom data types and/or implicit tags:
/// --------------------
/// auto constructor = new Constructor();
/// auto resolver = new Resolver();
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// //Add constructor functions / resolver expressions here...
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// auto loader = Loader("file.yaml");
/// loader.constructor = constructor;
/// loader.resolver = resolver;
/// auto rootNode = loader.load(node);
/// --------------------
2011-08-16 12:53:13 +00:00
struct Loader
{
private:
2014-07-19 12:32:16 +00:00
/// Reads character data from a stream.
2011-08-16 12:53:13 +00:00
Reader reader_;
2014-07-19 12:32:16 +00:00
/// Processes character data to YAML tokens.
2011-08-16 12:53:13 +00:00
Scanner scanner_;
2014-07-19 12:32:16 +00:00
/// Processes tokens to YAML events.
2011-08-16 12:53:13 +00:00
Parser parser_;
2014-07-19 12:32:16 +00:00
/// Resolves tags (data types).
2011-08-16 12:53:13 +00:00
Resolver resolver_;
2014-07-19 12:32:16 +00:00
/// Constructs YAML data types.
2011-08-16 12:53:13 +00:00
Constructor constructor_;
2014-07-19 12:32:16 +00:00
/// Name of the input file or stream, used in error messages.
string name_ = "<unknown>";
2014-07-19 12:32:16 +00:00
/// Are we done loading?
bool done_ = false;
2011-08-16 12:53:13 +00:00
public:
@disable this();
@disable int opCmp(ref Loader);
@disable bool opEquals(ref Loader);
2014-07-19 12:32:16 +00:00
/// Construct a Loader to load YAML from a file.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Params: filename = Name of the file to load from.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Throws: YAMLException if the file could not be opened or read.
this(string filename) @trusted
2011-08-16 12:53:13 +00:00
{
name_ = filename;
2014-07-19 12:38:35 +00:00
try { this(new File(filename)); }
2011-08-16 12:53:13 +00:00
catch(StreamException e)
{
2014-07-19 12:38:35 +00:00
throw new YAMLException("Unable to open file " ~ filename ~
" for YAML loading: " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
/// Construct a Loader to load YAML from a string.
///
/// Params: data = String to load YAML from.
///
/// Returns: Loader loading YAML from given string.
static Loader fromString(string data)
{
return Loader(new MemoryStream(cast(char[])data));
}
unittest
{
assert(Loader.fromString("42").load().as!int == 42);
}
2014-07-19 12:38:35 +00:00
2014-07-19 12:32:16 +00:00
/// Construct a Loader to load YAML from a _stream.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Params: stream = Stream to read from. Must be readable and seekable.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Throws: YAMLException if stream could not be read.
this(Stream stream) @safe
2011-08-16 12:53:13 +00:00
{
try
{
reader_ = new Reader(stream);
2011-08-16 12:53:13 +00:00
scanner_ = new Scanner(reader_);
parser_ = new Parser(scanner_);
2012-03-23 18:57:57 +00:00
resolver_ = new Resolver();
constructor_ = new Constructor();
2011-08-16 12:53:13 +00:00
}
catch(YAMLException e)
{
2014-07-19 12:38:35 +00:00
throw new YAMLException("Unable to open stream " ~ name_ ~
" for YAML loading: " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
2014-07-19 12:32:16 +00:00
/// Destroy the Loader.
@trusted ~this()
{
2014-07-19 02:17:18 +00:00
reader_.destroy();
scanner_.destroy();
parser_.destroy();
}
2014-07-19 12:32:16 +00:00
/// Set stream _name. Used in debugging messages.
2014-07-19 12:39:49 +00:00
void name(string name) pure @safe nothrow
{
name_ = name;
}
2014-07-19 12:32:16 +00:00
/// Specify custom Resolver to use.
2014-07-19 12:39:49 +00:00
void resolver(Resolver resolver) pure @safe nothrow
{
resolver_ = resolver;
}
2014-07-19 12:32:16 +00:00
/// Specify custom Constructor to use.
2014-07-19 12:39:49 +00:00
void constructor(Constructor constructor) pure @safe nothrow
{
constructor_ = constructor;
}
2014-07-19 12:32:16 +00:00
/// Load single YAML document.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// If none or more than one YAML document is found, this throws a YAMLException.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// This can only be called once; this is enforced by contract.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Returns: Root node of the document.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Throws: YAMLException if there wasn't exactly one document
/// or on a YAML parsing error.
Node load() @safe
in
{
assert(!done_, "Loader: Trying to load YAML twice");
}
body
2011-08-16 12:53:13 +00:00
{
try
{
2014-07-19 12:38:35 +00:00
scope(exit) { done_ = true; }
auto composer = new Composer(parser_, resolver_, constructor_);
enforce(composer.checkNode(), new YAMLException("No YAML document to load"));
return composer.getSingleNode();
2011-08-16 12:53:13 +00:00
}
catch(YAMLException e)
{
2014-07-19 12:38:35 +00:00
throw new YAMLException("Unable to load YAML from stream " ~
name_ ~ " : " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
2014-07-19 12:32:16 +00:00
/// Load all YAML documents.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// This is just a shortcut that iterates over all documents and returns
/// them all at once. Calling loadAll after iterating over the node or
/// vice versa will not return any documents, as they have all been parsed
/// already.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// This can only be called once; this is enforced by contract.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Returns: Array of root nodes of all documents in the file/stream.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Throws: YAMLException on a parsing error.
Node[] loadAll() @safe
{
Node[] nodes;
2014-07-19 12:38:35 +00:00
foreach(ref node; this) {nodes ~= node;}
return nodes;
}
2014-07-19 12:32:16 +00:00
/// Foreach over YAML documents.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Parses documents lazily, when they are needed.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Foreach over a Loader can only be used once; this is enforced by contract.
2014-07-19 12:38:35 +00:00
///
2014-07-19 12:32:16 +00:00
/// Throws: YAMLException on a parsing error.
int opApply(int delegate(ref Node) dg) @trusted
in
{
assert(!done_, "Loader: Trying to load YAML twice");
}
body
2011-08-16 12:53:13 +00:00
{
2014-07-19 12:38:35 +00:00
scope(exit) { done_ = true; }
2011-08-16 12:53:13 +00:00
try
{
auto composer = new Composer(parser_, resolver_, constructor_);
2011-08-16 12:53:13 +00:00
int result = 0;
while(composer.checkNode())
2011-08-16 12:53:13 +00:00
{
auto node = composer.getNode();
2011-08-16 12:53:13 +00:00
result = dg(node);
2014-07-19 12:38:35 +00:00
if(result) { break; }
2011-08-16 12:53:13 +00:00
}
return result;
}
catch(YAMLException e)
{
2014-07-19 12:38:35 +00:00
throw new YAMLException("Unable to load YAML from stream " ~
name_ ~ " : " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
package:
2014-07-19 12:32:16 +00:00
// Scan and return all tokens. Used for debugging.
Token[] scan() @safe
2011-08-16 12:53:13 +00:00
{
try
{
Token[] result;
2014-07-19 12:38:35 +00:00
while(scanner_.checkToken())
{
result ~= scanner_.getToken();
}
2011-08-16 12:53:13 +00:00
return result;
}
catch(YAMLException e)
{
2014-07-19 12:38:35 +00:00
throw new YAMLException("Unable to scan YAML from stream " ~
name_ ~ " : " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
2014-07-19 12:32:16 +00:00
// Parse and return all events. Used for debugging.
immutable(Event)[] parse() @safe
2011-08-16 12:53:13 +00:00
{
try
{
immutable(Event)[] result;
2014-07-19 12:38:35 +00:00
while(parser_.checkEvent())
{
result ~= parser_.getEvent();
}
2011-08-16 12:53:13 +00:00
return result;
}
catch(YAMLException e)
{
2014-07-19 12:38:35 +00:00
throw new YAMLException("Unable to parse YAML from stream " ~
name_ ~ " : " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
}
unittest
{
import std.stream;
import std.stdio;
2014-07-19 12:38:35 +00:00
string yaml_input = "red: '#ff0000'\n"
"green: '#00ff00'\n"
"blue: '#0000ff'";
2014-07-19 12:38:35 +00:00
auto colors = Loader(new MemoryStream(cast(char[])yaml_input)).load();
2014-07-19 12:38:35 +00:00
foreach(string color, string value; colors)
{
writeln(color, " is ", value, " in HTML/CSS");
}
}