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)
|
|
|
|
|
|
|
|
/**
|
2011-10-14 08:34:53 +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;
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.anchor;
|
2011-08-16 12:53:13 +00:00
|
|
|
import dyaml.composer;
|
|
|
|
import dyaml.constructor;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.event;
|
|
|
|
import dyaml.exception;
|
|
|
|
import dyaml.node;
|
2011-08-16 12:53:13 +00:00
|
|
|
import dyaml.parser;
|
|
|
|
import dyaml.reader;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.resolver;
|
2011-08-16 12:53:13 +00:00
|
|
|
import dyaml.scanner;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.tagdirectives;
|
2011-08-16 12:53:13 +00:00
|
|
|
import dyaml.token;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-10-12 21:49:42 +00:00
|
|
|
* Loads YAML documents from files or streams.
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* User specified Constructor and/or Resolver can be used to support new
|
|
|
|
* tags / data types.
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* Examples:
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* Load single YAML document from a file:
|
|
|
|
* --------------------
|
|
|
|
* auto rootNode = Loader("file.yaml").load();
|
|
|
|
* ...
|
|
|
|
* --------------------
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* Load all YAML documents from a file:
|
|
|
|
* --------------------
|
|
|
|
* auto nodes = Loader("file.yaml").loadAll();
|
|
|
|
* ...
|
|
|
|
* --------------------
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* Iterate over YAML documents in a file, lazily loading them:
|
|
|
|
* --------------------
|
|
|
|
* auto loader = Loader("file.yaml");
|
|
|
|
*
|
|
|
|
* foreach(ref node; loader)
|
|
|
|
* {
|
|
|
|
* ...
|
|
|
|
* }
|
|
|
|
* --------------------
|
|
|
|
*
|
|
|
|
* Load YAML from memory:
|
2011-08-16 12:53:13 +00:00
|
|
|
* --------------------
|
|
|
|
* import std.stream;
|
|
|
|
* import std.stdio;
|
|
|
|
*
|
|
|
|
* string yaml_input = "red: '#ff0000'\n"
|
|
|
|
* "green: '#00ff00'\n"
|
|
|
|
* "blue: '#0000ff'";
|
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* auto colors = Loader(new MemoryStream(cast(char[])yaml_input)).load();
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
|
|
|
* foreach(string color, string value; colors)
|
|
|
|
* {
|
|
|
|
* writeln(color, " is ", value, " in HTML/CSS");
|
|
|
|
* }
|
|
|
|
* --------------------
|
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* Use a custom constructor/resolver to support custom data types and/or implicit tags:
|
|
|
|
* --------------------
|
|
|
|
* auto constructor = new Constructor();
|
|
|
|
* auto resolver = new Resolver();
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* //Add constructor functions / resolver expressions here...
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +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:
|
|
|
|
///Reads character data from a stream.
|
|
|
|
Reader reader_;
|
|
|
|
///Processes character data to YAML tokens.
|
|
|
|
Scanner scanner_;
|
|
|
|
///Processes tokens to YAML events.
|
|
|
|
Parser parser_;
|
|
|
|
///Resolves tags (data types).
|
|
|
|
Resolver resolver_;
|
|
|
|
///Constructs YAML data types.
|
|
|
|
Constructor constructor_;
|
|
|
|
///Name of the input file or stream, used in error messages.
|
2011-10-12 21:49:42 +00:00
|
|
|
string name_ = "<unknown>";
|
2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
public:
|
2011-10-18 14:12:22 +00:00
|
|
|
@disable this();
|
|
|
|
|
2011-08-16 12:53:13 +00:00
|
|
|
/**
|
|
|
|
* Construct a Loader to load YAML from a file.
|
|
|
|
*
|
|
|
|
* Params: filename = Name of the file to load from.
|
|
|
|
*
|
2011-10-18 14:12:22 +00:00
|
|
|
* Throws: YAMLException if the file could not be opened or read.
|
2011-08-16 12:53:13 +00:00
|
|
|
*/
|
|
|
|
this(in string filename)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
name_ = filename;
|
|
|
|
try{this(new File(filename));}
|
2011-08-16 12:53:13 +00:00
|
|
|
catch(StreamException e)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new YAMLException("Unable to open file " ~ filename ~
|
|
|
|
" for YAML loading: " ~ e.msg);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-10-14 08:34:53 +00:00
|
|
|
* Construct a Loader to load YAML from a _stream.
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-12 21:49:42 +00:00
|
|
|
* Params: stream = Stream to read from. Must be readable.
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
2011-10-18 14:12:22 +00:00
|
|
|
* Throws: YAMLException if stream could not be read.
|
2011-08-16 12:53:13 +00:00
|
|
|
*/
|
2011-10-12 21:49:42 +00:00
|
|
|
this(Stream stream)
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2011-10-12 21:49:42 +00:00
|
|
|
reader_ = new Reader(stream);
|
2011-08-16 12:53:13 +00:00
|
|
|
scanner_ = new Scanner(reader_);
|
|
|
|
parser_ = new Parser(scanner_);
|
2011-10-12 21:49:42 +00:00
|
|
|
resolver_ = new Resolver;
|
|
|
|
constructor_ = new Constructor;
|
2011-10-11 13:58:23 +00:00
|
|
|
Anchor.addReference();
|
|
|
|
TagDirectives.addReference();
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
catch(YAMLException e)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new YAMLException("Unable to open stream " ~ name_ ~
|
|
|
|
" for YAML loading: " ~ e.msg);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-12 21:49:42 +00:00
|
|
|
///Destroy the Loader.
|
|
|
|
~this()
|
|
|
|
{
|
|
|
|
Anchor.removeReference();
|
|
|
|
TagDirectives.removeReference();
|
|
|
|
clear(reader_);
|
|
|
|
clear(scanner_);
|
|
|
|
clear(parser_);
|
|
|
|
}
|
|
|
|
|
2011-10-14 08:34:53 +00:00
|
|
|
///Set stream _name. Used in debugging messages.
|
2011-10-12 21:49:42 +00:00
|
|
|
@property void name(string name)
|
|
|
|
{
|
|
|
|
name_ = name;
|
|
|
|
}
|
|
|
|
|
2011-10-14 08:34:53 +00:00
|
|
|
///Specify custom Resolver to use.
|
2011-10-12 21:49:42 +00:00
|
|
|
@property void resolver(Resolver resolver)
|
|
|
|
{
|
|
|
|
resolver_ = resolver;
|
|
|
|
}
|
|
|
|
|
2011-10-14 08:34:53 +00:00
|
|
|
///Specify custom Constructor to use.
|
2011-10-12 21:49:42 +00:00
|
|
|
@property void constructor(Constructor constructor)
|
|
|
|
{
|
|
|
|
constructor_ = constructor;
|
|
|
|
}
|
|
|
|
|
2011-08-16 12:53:13 +00:00
|
|
|
/**
|
|
|
|
* Load single YAML document.
|
|
|
|
*
|
2011-10-18 14:12:22 +00:00
|
|
|
* If none or more than one YAML document is found, this throws a YAMLException.
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
|
|
|
* Returns: Root node of the document.
|
|
|
|
*
|
|
|
|
* Throws: YAMLException if there wasn't exactly one document
|
|
|
|
* or on a YAML parsing error.
|
|
|
|
*/
|
2011-10-12 21:49:42 +00:00
|
|
|
Node load()
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2011-10-12 21:49:42 +00:00
|
|
|
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)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new YAMLException("Unable to load YAML from stream " ~
|
|
|
|
name_ ~ " : " ~ e.msg);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-12 21:49:42 +00:00
|
|
|
/**
|
|
|
|
* Load all YAML documents.
|
2011-10-20 18:01:52 +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.
|
2011-10-12 21:49:42 +00:00
|
|
|
*
|
|
|
|
* Returns: Array of root nodes of all documents in the file/stream.
|
|
|
|
*
|
2011-10-18 14:12:22 +00:00
|
|
|
* Throws: YAMLException on a parsing error.
|
2011-10-12 21:49:42 +00:00
|
|
|
*/
|
|
|
|
Node[] loadAll()
|
|
|
|
{
|
|
|
|
Node[] nodes;
|
2011-10-18 14:12:22 +00:00
|
|
|
foreach(ref node; this){nodes ~= node;}
|
2011-10-12 21:49:42 +00:00
|
|
|
return nodes;
|
|
|
|
}
|
|
|
|
|
2011-08-16 12:53:13 +00:00
|
|
|
/**
|
|
|
|
* Foreach over YAML documents.
|
|
|
|
*
|
2011-10-14 08:34:53 +00:00
|
|
|
* Parses documents lazily, when they are needed.
|
2011-08-16 12:53:13 +00:00
|
|
|
*
|
|
|
|
* Throws: YAMLException on a parsing error.
|
|
|
|
*/
|
|
|
|
int opApply(int delegate(ref Node) dg)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2011-10-12 21:49:42 +00:00
|
|
|
auto composer = new Composer(parser_, resolver_, constructor_);
|
|
|
|
|
2011-08-16 12:53:13 +00:00
|
|
|
int result = 0;
|
2011-10-12 21:49:42 +00:00
|
|
|
while(composer.checkNode())
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2011-10-12 21:49:42 +00:00
|
|
|
auto node = composer.getNode();
|
2011-08-16 12:53:13 +00:00
|
|
|
result = dg(node);
|
|
|
|
if(result){break;}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch(YAMLException e)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new YAMLException("Unable to load YAML from stream " ~
|
|
|
|
name_ ~ " : " ~ e.msg);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
package:
|
|
|
|
//Scan and return all tokens. Used for debugging.
|
|
|
|
Token[] scan()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Token[] result;
|
|
|
|
while(scanner_.checkToken()){result ~= scanner_.getToken();}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch(YAMLException e)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new YAMLException("Unable to scan YAML from stream " ~
|
|
|
|
name_ ~ " : " ~ e.msg);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Parse and return all events. Used for debugging.
|
|
|
|
Event[] parse()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Event[] result;
|
|
|
|
while(parser_.checkEvent()){result ~= parser_.getEvent();}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch(YAMLException e)
|
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new YAMLException("Unable to parse YAML from stream " ~
|
|
|
|
name_ ~ " : " ~ e.msg);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-10-12 21:49:42 +00:00
|
|
|
|
|
|
|
unittest
|
|
|
|
{
|
|
|
|
import std.stream;
|
|
|
|
import std.stdio;
|
|
|
|
|
|
|
|
string yaml_input = "red: '#ff0000'\n"
|
|
|
|
"green: '#00ff00'\n"
|
|
|
|
"blue: '#0000ff'";
|
|
|
|
|
|
|
|
auto colors = Loader(new MemoryStream(cast(char[])yaml_input)).load();
|
|
|
|
|
|
|
|
foreach(string color, string value; colors)
|
|
|
|
{
|
|
|
|
writeln(color, " is ", value, " in HTML/CSS");
|
|
|
|
}
|
|
|
|
}
|