dyaml/dyaml/loader.d

317 lines
8.7 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)
/**
* 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.anchor;
2011-08-16 12:53:13 +00:00
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.tagdirectives;
2011-08-16 12:53:13 +00:00
import dyaml.token;
/**
* Loads YAML documents from files or streams.
2011-08-16 12:53:13 +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
*
* Examples:
2011-08-16 12:53:13 +00:00
*
* Load single YAML document from a file:
* --------------------
* auto rootNode = Loader("file.yaml").load();
* ...
* --------------------
2011-08-16 12:53:13 +00:00
*
* Load all YAML documents from a file:
* --------------------
* auto nodes = Loader("file.yaml").loadAll();
* ...
* --------------------
2011-08-16 12:53:13 +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'";
*
* 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");
* }
* --------------------
*
* 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
*
* //Add constructor functions / resolver expressions here...
2011-08-16 12:53:13 +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:
///Resolver used by default.
static Resolver defaultResolver_;
///Constructor used by default.
static Constructor defaultConstructor_;
static this()
{
defaultResolver_ = new Resolver;
defaultConstructor_ = new Constructor;
}
2011-08-16 12:53:13 +00:00
///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.
string name_ = "<unknown>";
2011-08-16 12:53:13 +00:00
public:
@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.
*
* Throws: YAMLException if the file could not be opened or read.
2011-08-16 12:53:13 +00:00
*/
this(in string filename)
{
name_ = filename;
try{this(new File(filename));}
2011-08-16 12:53:13 +00:00
catch(StreamException e)
{
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 _stream.
2011-08-16 12:53:13 +00:00
*
* Params: stream = Stream to read from. Must be readable and seekable.
2011-08-16 12:53:13 +00:00
*
* Throws: YAMLException if stream could not be read.
2011-08-16 12:53:13 +00:00
*/
this(Stream stream)
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_);
resolver_ = defaultResolver_;
constructor_ = defaultConstructor_;
Anchor.addReference();
TagDirectives.addReference();
2011-08-16 12:53:13 +00:00
}
catch(YAMLException e)
{
throw new YAMLException("Unable to open stream " ~ name_ ~
" for YAML loading: " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
///Destroy the Loader.
~this()
{
Anchor.removeReference();
TagDirectives.removeReference();
clear(reader_);
clear(scanner_);
clear(parser_);
}
///Set stream _name. Used in debugging messages.
@property void name(string name)
{
name_ = name;
}
///Specify custom Resolver to use.
@property void resolver(Resolver resolver)
{
resolver_ = resolver;
}
///Specify custom Constructor to use.
@property void constructor(Constructor constructor)
{
constructor_ = constructor;
}
2011-08-16 12:53:13 +00:00
/**
* Load single YAML document.
*
* 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.
*/
Node load()
2011-08-16 12:53:13 +00:00
{
try
{
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)
{
throw new YAMLException("Unable to load YAML from stream " ~
name_ ~ " : " ~ e.msg);
2011-08-16 12:53:13 +00:00
}
}
/**
* Load all YAML documents.
*
* 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.
*
* Returns: Array of root nodes of all documents in the file/stream.
*
* Throws: YAMLException on a parsing error.
*/
Node[] loadAll()
{
Node[] nodes;
foreach(ref node; this){nodes ~= node;}
return nodes;
}
2011-08-16 12:53:13 +00:00
/**
* Foreach over YAML documents.
*
* 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
{
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);
if(result){break;}
}
return result;
}
catch(YAMLException e)
{
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)
{
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)
{
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;
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");
}
}