Implemented the resolver unittest.

Changed Loader API to be in line with Dumper,
and updated examples, tutorials and docs.
This commit is contained in:
Ferdinand Majerech 2011-10-12 23:49:42 +02:00
parent 934df763ad
commit 34b11405d4
16 changed files with 262 additions and 279 deletions

View file

@ -9,7 +9,7 @@ are caused by difficulty of implementation of some features, such as multiple
Unicode encodings within single stream, and some by unnecessary restrictions or Unicode encodings within single stream, and some by unnecessary restrictions or
ambiguities in the specification. ambiguities in the specification.
Still, D:YAML tries to be as close to the specification as possible. D:YAML should Still, D:YAML tries to be as close to the specification as possible. It should
never load documents with different meaning than according to the specification, never load documents with different meaning than according to the specification,
and documents that fail to load should be very rare (for instance, very few and documents that fail to load should be very rare (for instance, very few
files use multiple Unicode encodings). files use multiple Unicode encodings).
@ -21,10 +21,6 @@ List of known differences:
Differences that can cause valid YAML documents not to load: Differences that can cause valid YAML documents not to load:
* At the moment, all mappings in the internal representation are ordered,
and a comparison for equality between equal mappings with differing order
will return false. This will be fixed once Phobos has a usable map type or
D associative arrays work with variants.
* No support for byte order marks and multiple Unicode encodings in a stream. * No support for byte order marks and multiple Unicode encodings in a stream.
* Plain scalars in flow context cannot contain ``,``, ``:`` and ``?``. * Plain scalars in flow context cannot contain ``,``, ``:`` and ``?``.
This might change with ``:`` in the future. This might change with ``:`` in the future.

View file

@ -2,10 +2,10 @@
Custom YAML data types Custom YAML data types
====================== ======================
Often you will want to serialize complex data types such as classes. You can use Often you might want to serialize complex data types such as classes. You can
functions to process nodes; e.g. a mapping containing class data members indexed use functions to process nodes such as a mapping containing class data members
by name. Alternatively, YAML supports custom data types using identifiers called indexed by name. Alternatively, YAML supports custom data types using
*tags*. That is the topic of this tutorial. identifiers called *tags*. That is the topic of this tutorial.
Each YAML node has a tag specifying its type. For instance: strings use the tag Each YAML node has a tag specifying its type. For instance: strings use the tag
``tag:yaml.org,2002:str``. Tags of most default types are *implicitly resolved* ``tag:yaml.org,2002:str``. Tags of most default types are *implicitly resolved*
@ -19,9 +19,9 @@ Constructor
D:YAML uses the *Constructor* class to process each node to hold data type D:YAML uses the *Constructor* class to process each node to hold data type
corresponding to its tag. *Constructor* stores a function for each supported corresponding to its tag. *Constructor* stores a function for each supported
tag to process it. These functions can be supplied by the user using the tag to process it. These functions are supplied by the user using the
*addConstructor()* method. *Constructor* is then passed to *Loader*, which will *addConstructor()* method. *Constructor* is then passed to *Loader*, which
parse YAML input. parses YAML input.
We will implement support for an RGB color type. It is implemented as the We will implement support for an RGB color type. It is implemented as the
following struct: following struct:
@ -111,7 +111,7 @@ RRGGBB, or from a mapping, where we use the following format:
return Color(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b); return Color(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b);
} }
Next, we need some YAML code using our new tag. Create a file called input.yaml Next, we need some YAML data using our new tag. Create a file called input.yaml
with the following contents: with the following contents:
.. code-block:: yaml .. code-block:: yaml
@ -144,9 +144,10 @@ Finally, the code to put it all together:
constructor.addConstructor("!color", &constructColorScalar); constructor.addConstructor("!color", &constructColorScalar);
constructor.addConstructor("!color-mapping", &constructColorMapping); constructor.addConstructor("!color-mapping", &constructColorMapping);
auto loader = new Loader("input.yaml", constructor, new Resolver); auto loader = Loader("input.yaml");
loader.constructor = constructor;
auto root = loader.loadSingleDocument(); auto root = loader.load();
if(root["scalar-red"].get!Color == red && if(root["scalar-red"].get!Color == red &&
root["mapping-red"].get!Color == red && root["mapping-red"].get!Color == red &&
@ -166,10 +167,9 @@ Finally, the code to put it all together:
} }
First, we create a *Constructor* and pass functions to handle the ``!color`` First, we create a *Constructor* and pass functions to handle the ``!color``
and ``!color-mapping`` tag. We construct a *Loader* using the *Constructor*. and ``!color-mapping`` tag. We construct a *Loader*m and pass the *Constructor*
We also need a *Resolver*, but for now we just default-construct it. We then to it. We then load the YAML document, and finally, read the colors using
load the YAML document, and finally, read the colors using *get()* method to *get()* method to test if they were loaded as expected.
test if they were loaded as expected.
You can find the source code for what we've done so far in the You can find the source code for what we've done so far in the
``examples/constructor`` directory in the D:YAML package. ``examples/constructor`` directory in the D:YAML package.
@ -180,8 +180,8 @@ Resolver
-------- --------
Specifying tag for every color value can be tedious. D:YAML can implicitly Specifying tag for every color value can be tedious. D:YAML can implicitly
resolve tag of a scalar using a regular expression. This is how default types, resolve scalar tags using regular expressions. This is how default types such as
e.g. int, are resolved. We will use the *Resolver* class to add implicit tag int are resolved. We will use the *Resolver* class to add implicit tag
resolution for the Color data type (in its scalar form). resolution for the Color data type (in its scalar form).
We use the *addImplicitResolver* method of *Resolver*, passing the tag, regular We use the *addImplicitResolver* method of *Resolver*, passing the tag, regular
@ -203,11 +203,14 @@ Add this to your code to add implicit resolution of ``!color``.
resolver.addImplicitResolver("!color", std.regex.regex("[0-9a-fA-F]{6}", resolver.addImplicitResolver("!color", std.regex.regex("[0-9a-fA-F]{6}",
"0123456789abcdefABCDEF")); "0123456789abcdefABCDEF"));
auto loader = new Loader("input.yaml", constructor, resolver); auto loader = Loader("input.yaml");
loader.constructor = constructor;
loader.resolver = resolver;
//code from the previous example... //code from the previous example...
Now, change contents of input.dyaml to this: Now, change contents of input.yaml to this:
.. code-block:: yaml .. code-block:: yaml

View file

@ -26,10 +26,8 @@ Download the version of DMD for your operating system and install it.
.. note:: .. note::
Other D compilers exist, such as Other D compilers exist, such as
`GDC <http://bitbucket.org/goshawk/gdc/wiki/Home>`_ and `GDC <http://bitbucket.org/goshawk/gdc/wiki/Home>`_ and
`LDC <http://www.dsource.org/projects/ldc/>`_. `LDC <http://www.dsource.org/projects/ldc/>`_. Setting up with either one of
Setting up with either one of them should be similar to DMD, them should be similar to DMD, but they are not yet as stable as DMD.
however, at the moment they are not as up to date as DMD.
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Download and compile D:YAML Download and compile D:YAML
@ -84,7 +82,7 @@ into the file:
void main() void main()
{ {
yaml.Node root = yaml.load("input.yaml"); Node root = Loader("input.yaml").load();
foreach(string word; root["Hello World"]) foreach(string word; root["Hello World"])
{ {
writeln(word); writeln(word);
@ -100,22 +98,23 @@ Explanation of the code
First, we import the *yaml* module. This is the only module you need to import First, we import the *yaml* module. This is the only module you need to import
to use D:YAML - it automatically imports all needed modules. to use D:YAML - it automatically imports all needed modules.
Next we load the file using the *yaml.load()* function - this loads the file as Next we load the file using the *Loader.load()* method. *Loader* is the struct
used for parsing YAML documents, and *load()* is a method that loads the file as
**one** YAML document and throws *YAMLException*, D:YAML exception type, if the **one** YAML document and throws *YAMLException*, D:YAML exception type, if the
file could not be parsed or does not contain exactly one document. Note that we file could not be parsed or does not contain exactly one document. Note that we
don't do any error checking here in order to keep the example as simple as don't do any error checking here in order to keep the example as simple as
possible. possible.
*yaml.Node* represents a node in a YAML document. It can be a sequence (array), *Node* represents a node in a YAML document. It can be a sequence (array),
mapping (associative array) or a scalar (value). Here the root node is a mapping (associative array) or a scalar (value). Here the root node is a
mapping, and we use the index operator to get subnodes with keys "Hello World" mapping, and we use the index operator to get subnodes with keys "Hello World"
and "Answer". We iterate over the first, as it is a sequence, and use the and "Answer". We iterate over the first, as it is a sequence, and use the
*yaml.Node.get()* method on the second to get its value as an integer. *Node.get()* method on the second to get its value as an integer.
You can iterate over a mapping or sequence as if it was an associative or normal You can iterate over a mapping or sequence as if it was an associative or normal
array. If you try to iterate over a scalar, it will throw a *YAMLException*. array. If you try to iterate over a scalar, it will throw a *YAMLException*.
You can iterate over subnodes using yaml.Node as the iterated type, or specify You can iterate over subnodes using *Node* as the iterated type, or specify
the type subnodes are expected to have. D:YAML will automatically convert the type subnodes are expected to have. D:YAML will automatically convert
iterated subnodes to that type if possible. Here we specify the *string* type, iterated subnodes to that type if possible. Here we specify the *string* type,
so we iterate over the "Hello World" sequence as an array of strings. If it is so we iterate over the "Hello World" sequence as an array of strings. If it is
@ -123,8 +122,8 @@ not possible to convert to iterated type, a *YAMLException* is thrown. For
instance, if we specified *int* here, we would get an error, as "Hello" instance, if we specified *int* here, we would get an error, as "Hello"
cannot be converted to an integer. cannot be converted to an integer.
The *yaml.Node.get()* method is used to get value of a scalar node as specified The *Node.get()* method is used to get value of a scalar node, allowing to
type. D:YAML will try to return the scalar as specified type, converting if specify type. D:YAML will try to return the scalar as this type, converting if
needed, throwing *YAMLException* if not possible. needed, throwing *YAMLException* if not possible.
@ -135,16 +134,15 @@ Compiling
To compile your project, you must give DMD the directories containing import To compile your project, you must give DMD the directories containing import
modules and the library. You also need to tell it to link with D:YAML. The import modules and the library. You also need to tell it to link with D:YAML. The import
directory should be the D:YAML package directory. You can specify it using the directory should be the D:YAML package directory. You can specify it using the
``-I`` option of DMD. The library directory should point to where you put the ``-I`` option of DMD. The library directory should be where you put the compiled
compiled D:YAML library. On Unix/Linux you can specify it using the ``-L-L`` D:YAML library. On Unix/Linux you can specify it using the ``-L-L`` option, and
option, and link with D:YAML using the ``-L-l`` option. On Windows, the import link with D:YAML using the ``-L-l`` option. On Windows, the import directory is
directory is used as the library directory. To link with the library on Windows, used as the library directory. To link with the library on Windows, just add the
just add the path to it relative to the current directory. path to it relative to the current directory.
For example, if you extracted D:YAML to ``/home/xxx/dyaml`` and compiled it in For example, if you extracted and compiled D:YAML in ``/home/xxx/dyaml``, your
that directory, your project is in ``/home/xxx/dyaml-project``, and you are project is in ``/home/xxx/dyaml-project``, and you are currently in that
currently in that directory, you can compile the project with the following directory, you can compile the project with the following command on Unix/Linux::
command on Unix/Linux::
dmd -I../dyaml -L-L../dyaml -L-ldyaml main.d dmd -I../dyaml -L-L../dyaml -L-ldyaml main.d

View file

@ -8,14 +8,14 @@ which this article is based on,
`Chapter 2 of the YAML specification <http://yaml.org/spec/1.1/#id857168>`_ `Chapter 2 of the YAML specification <http://yaml.org/spec/1.1/#id857168>`_
or the `Wikipedia page <http://en.wikipedia.org/wiki/YAML>`_. or the `Wikipedia page <http://en.wikipedia.org/wiki/YAML>`_.
YAML is a data serialization format designed to be as human readable as YAML is a data serialization format designed for human readability. YAML is a
possible. YAML is a recursive acronym for "YAML Ain't Markup Language". recursive acronym for "YAML Ain't Markup Language".
YAML is similar to JSON, and in fact, JSON is a subset of YAML 1.2; but YAML has YAML is similar to JSON, and in fact, JSON is a subset of YAML 1.2; but YAML has
some more advanced features and is easier to read. However, YAML is also more some more advanced features and is easier to read. However, it is also more
difficult to parse (and probably somewhat slower). Data is stored in mappings difficult to parse (and probably somewhat slower). Data is stored in mappings
(associative arrays), sequences (lists) and scalars (single values). Data (associative arrays), sequences (lists) and scalars (single values). Data
structure hierarchy either depends on indentation (block context, similar to structure hierarchy depends either on indentation (block context, similar to
Python code), or nesting of brackets and braces (flow context, similar to JSON). Python code), or nesting of brackets and braces (flow context, similar to JSON).
YAML comments begin with ``#`` and continue until the end of line. YAML comments begin with ``#`` and continue until the end of line.
@ -25,8 +25,7 @@ Documents
--------- ---------
A YAML stream consists of one or more documents starting with ``---`` and A YAML stream consists of one or more documents starting with ``---`` and
optionally ending with ``...`` . If there is only one document, ``---`` can be optionally ending with ``...`` . ``---`` can be left out for the first document.
left out.
Single document with no explicit start or end: Single document with no explicit start or end:

View file

@ -40,14 +40,14 @@ import dyaml.tagdirectives;
* Write to a file: * Write to a file:
* -------------------- * --------------------
* auto node = Node([1, 2, 3, 4, 5]); * auto node = Node([1, 2, 3, 4, 5]);
* Dumper("file.txt").dump(node); * Dumper("file.yaml").dump(node);
* -------------------- * --------------------
* *
* Write multiple YAML documents to a file: * Write multiple YAML documents to a file:
* -------------------- * --------------------
* auto node1 = Node([1, 2, 3, 4, 5]); * auto node1 = Node([1, 2, 3, 4, 5]);
* auto node2 = Node("This document contains only one string"); * auto node2 = Node("This document contains only one string");
* Dumper("file.txt").dump(node1, node2); * Dumper("file.yaml").dump(node1, node2);
* -------------------- * --------------------
* *
* Write to memory: * Write to memory:
@ -65,8 +65,8 @@ import dyaml.tagdirectives;
* auto resolver = new Resolver(); * auto resolver = new Resolver();
* *
* //Add representer functions / resolver expressions here... * //Add representer functions / resolver expressions here...
* -------------------- *
* auto dumper = Dumper("file.txt"); * auto dumper = Dumper("file.yaml");
* dumper.representer = representer; * dumper.representer = representer;
* dumper.resolver = resolver; * dumper.resolver = resolver;
* dumper.dump(node); * dumper.dump(node);
@ -259,7 +259,7 @@ struct Dumper
* *
* Example: * Example:
* -------------------- * --------------------
* Dumper dumper = Dumper("file.txt"); * Dumper dumper = Dumper("file.yaml");
* //This will emit tags starting with "tag:long.org,2011" * //This will emit tags starting with "tag:long.org,2011"
* //with a "!short!" prefix instead. * //with a "!short!" prefix instead.
* dumper.tags("short", "tag:long.org,2011:"); * dumper.tags("short", "tag:long.org,2011:");

View file

@ -28,43 +28,36 @@ import dyaml.token;
/** /**
* Load single YAML document from a file. * Loads YAML documents from files or streams.
* *
* If there is no or more than one YAML document in the file, this will throw. * User specified Constructor and/or Resolver can be used to support new
* Use $(LREF loadAll) for such files. * tags / data types.
*
* Params: filename = Name of the file to _load from.
*
* Returns: Root node of the document.
*
* Throws: YAMLException if there wasn't exactly one document in the file,
* the file could not be opened or on a YAML parsing error.
*/
Node load(in string filename)
{
auto loader = Loader(filename);
return loader.loadSingleDocument();
}
/**
* Load single YAML document from a stream.
*
* You can use this to e.g _load YAML from memory.
*
* If there is no or more than one YAML document in the stream, this will throw.
* Use $(LREF loadAll) for such files.
*
* Params: input = Stream to read from. Must be readable.
* name = Name of the stream, used in error messages.
*
* Returns: Root node of the document.
*
* Throws: YAMLException if there wasn't exactly one document in the stream,
* the stream could not be read from or on a YAML parsing error.
* *
* Examples: * Examples:
* *
* Loading YAML from memory: * Load single YAML document from a file:
* --------------------
* auto rootNode = Loader("file.yaml").load();
* ...
* --------------------
*
* Load all YAML documents from a file:
* --------------------
* auto nodes = Loader("file.yaml").loadAll();
* ...
* --------------------
*
* Iterate over YAML documents in a file, lazily loading them:
* --------------------
* auto loader = Loader("file.yaml");
*
* foreach(ref node; loader)
* {
* ...
* }
* --------------------
*
* Load YAML from memory:
* -------------------- * --------------------
* import std.stream; * import std.stream;
* import std.stdio; * import std.stdio;
@ -73,74 +66,27 @@ Node load(in string filename)
* "green: '#00ff00'\n" * "green: '#00ff00'\n"
* "blue: '#0000ff'"; * "blue: '#0000ff'";
* *
* auto colors = yaml.load(new MemoryStream(cast(char[])yaml_input)); * auto colors = Loader(new MemoryStream(cast(char[])yaml_input)).load();
* *
* foreach(string color, string value; colors) * foreach(string color, string value; colors)
* { * {
* writeln(color, " is ", value, " in HTML/CSS"); * 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();
*
* //Add constructor functions / resolver expressions here...
*
* auto loader = Loader("file.yaml");
* loader.constructor = constructor;
* loader.resolver = resolver;
* auto rootNode = loader.load(node);
* --------------------
*/ */
Node load(Stream input, in string name = "<unknown>")
{
auto loader = Loader(input, name, new Constructor, new Resolver);
return loader.loadSingleDocument();
}
unittest
{
import std.stream;
import std.stdio;
string yaml_input = "red: '#ff0000'\n"
"green: '#00ff00'\n"
"blue: '#0000ff'";
auto colors = load(new MemoryStream(cast(char[])yaml_input));
foreach(string color, string value; colors)
{
writeln(color, " is ", value, " in HTML/CSS");
}
}
/**
* Load all YAML documents from a file.
*
* Params: filename = Name of the file to load from.
*
* Returns: Array of root nodes of documents in the stream.
* If the stream is empty, empty array will be returned.
*
* Throws: YAMLException if the file could not be opened or on a YAML parsing error.
*/
Node[] loadAll(in string filename)
{
auto loader = Loader(filename);
Node[] result;
foreach(ref node; loader){result ~= node;}
return result;
}
/**
* Load all YAML documents from a stream.
*
* Params: input = Stream to read from. Must be readable.
* name = Name of the stream, used in error messages.
*
* Returns: Array of root nodes of documents in the file.
* If the file is empty, empty array will be returned.
*
* Throws: YAMLException if the stream could not be read from or on a YAML parsing error.
*/
Node[] loadAll(Stream input, in string name = "<unknown>")
{
auto loader = Loader(input, name, new Constructor, new Resolver);
Node[] result;
foreach(ref node; loader){result ~= node;}
return result;
}
///Loads YAML documents from files or streams.
struct Loader struct Loader
{ {
private: private:
@ -154,12 +100,8 @@ struct Loader
Resolver resolver_; Resolver resolver_;
///Constructs YAML data types. ///Constructs YAML data types.
Constructor constructor_; Constructor constructor_;
///Composes YAML nodes.
Composer composer_;
///Name of the input file or stream, used in error messages. ///Name of the input file or stream, used in error messages.
string name_; string name_ = "<unknown>";
///Input file stream, if the stream is created by Loader itself.
File file_ = null;
public: public:
/** /**
@ -171,60 +113,33 @@ struct Loader
*/ */
this(in string filename) this(in string filename)
{ {
try{file_ = new File(filename);} try
{
name = filename;
this(new File(filename));
}
catch(StreamException e) catch(StreamException e)
{ {
throw new YAMLException("Unable to load YAML file " ~ filename ~ " : " ~ e.msg); throw new YAMLException("Unable to load YAML file " ~ filename ~ " : " ~ e.msg);
} }
this(file_, filename, new Constructor, new Resolver);
} }
/** /**
* Construct a Loader to load YAML from a file, with provided _constructor and _resolver. * Construct a Loader to load YAML from a stream.
* *
* Constructor and _resolver can be used to implement custom data types in YAML. * Params: stream = Stream to read from. Must be readable.
*
* Params: filename = Name of the file to load from.
* constructor = Constructor to use.
* resolver = Resolver to use.
*
* Throws: YAMLException if the file could not be opened or read from.
*/
this(in string filename, Constructor constructor, Resolver resolver)
{
try{file_ = new File(filename);}
catch(StreamException e)
{
throw new YAMLException("Unable to load YAML file " ~ filename ~ " : " ~ e.msg);
}
this(file_, filename, constructor, resolver);
}
/**
* Construct a Loader to load YAML from a stream with provided _constructor and _resolver.
*
* Stream can be used to load YAML from memory and other sources.
* Constructor and _resolver can be used to implement custom data types in YAML.
*
* Params: input = Stream to read from. Must be readable.
* name = Name of the stream. Used in error messages.
* constructor = Constructor to use.
* resolver = Resolver to use.
* *
* Throws: YAMLException if the stream could not be read from. * Throws: YAMLException if the stream could not be read from.
*/ */
this(Stream input, in string name, Constructor constructor, Resolver resolver) this(Stream stream)
{ {
try try
{ {
reader_ = new Reader(input); reader_ = new Reader(stream);
scanner_ = new Scanner(reader_); scanner_ = new Scanner(reader_);
parser_ = new Parser(scanner_); parser_ = new Parser(scanner_);
resolver_ = resolver; resolver_ = new Resolver;
constructor_ = constructor; constructor_ = new Constructor;
composer_ = new Composer(parser_, resolver_, constructor_);
name_ = name;
Anchor.addReference(); Anchor.addReference();
TagDirectives.addReference(); TagDirectives.addReference();
} }
@ -235,58 +150,6 @@ struct Loader
} }
} }
/**
* Load single YAML document.
*
* If no or more than one YAML document is found, this will throw a YAMLException.
*
* Returns: Root node of the document.
*
* Throws: YAMLException if there wasn't exactly one document
* or on a YAML parsing error.
*/
Node loadSingleDocument()
{
try
{
enforce(composer_.checkNode(), new YAMLException("No YAML document to load"));
return composer_.getSingleNode();
}
catch(YAMLException e)
{
e.name = name_;
throw e;
}
}
/**
* Foreach over YAML documents.
*
* Parses documents lazily, as they are needed.
*
* Throws: YAMLException on a parsing error.
*/
int opApply(int delegate(ref Node) dg)
{
try
{
int result = 0;
while(composer_.checkNode())
{
auto node = composer_.getNode();
result = dg(node);
if(result){break;}
}
return result;
}
catch(YAMLException e)
{
e.name = name_;
throw e;
}
}
///Destroy the Loader. ///Destroy the Loader.
~this() ~this()
{ {
@ -295,9 +158,97 @@ struct Loader
clear(reader_); clear(reader_);
clear(scanner_); clear(scanner_);
clear(parser_); clear(parser_);
clear(composer_); }
//Can't clear constructor, resolver: they might be supplied by the user.
if(file_ !is null){file_.close();} ///Set stream name. Used in debugging messages.
@property void name(string name)
{
name_ = name;
}
///Set Resolver to use.
@property void resolver(Resolver resolver)
{
resolver_ = resolver;
}
///Set Constructor to use.
@property void constructor(Constructor constructor)
{
constructor_ = constructor;
}
/**
* Load single YAML document.
*
* If no or more than one YAML document is found, this will throw a YAMLException.
*
* Returns: Root node of the document.
*
* Throws: YAMLException if there wasn't exactly one document
* or on a YAML parsing error.
*/
Node load()
{
try
{
auto composer = new Composer(parser_, resolver_, constructor_);
enforce(composer.checkNode(), new YAMLException("No YAML document to load"));
return composer.getSingleNode();
}
catch(YAMLException e)
{
e.name = name_;
throw e;
}
}
/**
* Load all YAML documents.
*
* Returns: Array of root nodes of all documents in the file/stream.
*
* Throws: YAMLException if there wasn't exactly one document
* or on a YAML parsing error.
*/
Node[] loadAll()
{
Node[] nodes;
foreach(ref node; this)
{
nodes ~= node;
}
return nodes;
}
/**
* Foreach over YAML documents.
*
* Parses documents lazily, as they are needed.
*
* Throws: YAMLException on a parsing error.
*/
int opApply(int delegate(ref Node) dg)
{
try
{
auto composer = new Composer(parser_, resolver_, constructor_);
int result = 0;
while(composer.checkNode())
{
auto node = composer.getNode();
result = dg(node);
if(result){break;}
}
return result;
}
catch(YAMLException e)
{
e.name = name_;
throw e;
}
} }
package: package:
@ -333,3 +284,20 @@ struct Loader
} }
} }
} }
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");
}
}

View file

@ -88,9 +88,10 @@ void main()
constructor.addConstructor("!color", &constructColorScalar); constructor.addConstructor("!color", &constructColorScalar);
constructor.addConstructor("!color-mapping", &constructColorMapping); constructor.addConstructor("!color-mapping", &constructColorMapping);
auto loader = new Loader("input.yaml", constructor, new Resolver); auto loader = Loader("input.yaml");
loader.constructor = constructor;
auto root = loader.loadSingleDocument(); auto root = loader.load();
if(root["scalar-red"].get!Color == red && if(root["scalar-red"].get!Color == red &&
root["mapping-red"].get!Color == red && root["mapping-red"].get!Color == red &&

View file

@ -3,7 +3,7 @@ import yaml;
void main() void main()
{ {
yaml.Node root = yaml.load("input.yaml"); yaml.Node root = Loader("input.yaml").load();
foreach(string word; root["Hello World"]) foreach(string word; root["Hello World"])
{ {
writeln(word); writeln(word);

View file

@ -92,9 +92,11 @@ void main()
resolver.addImplicitResolver("!color", std.regex.regex("[0-9a-fA-F]{6}"), resolver.addImplicitResolver("!color", std.regex.regex("[0-9a-fA-F]{6}"),
"0123456789abcdefABCDEF"); "0123456789abcdefABCDEF");
auto loader = new Loader("input.yaml", constructor, resolver); auto loader = Loader("input.yaml");
loader.constructor = constructor;
loader.resolver = resolver;
auto root = loader.loadSingleDocument(); auto root = loader.load();
if(root["scalar-red"].get!Color == red && if(root["scalar-red"].get!Color == red &&
root["mapping-red"].get!Color == red && root["mapping-red"].get!Color == red &&

View file

@ -42,8 +42,8 @@ void testParser(bool verbose, string dataFilename, string canonicalFilename)
*/ */
void testLoader(bool verbose, string dataFilename, string canonicalFilename) void testLoader(bool verbose, string dataFilename, string canonicalFilename)
{ {
auto data = loadAll(dataFilename); auto data = Loader(dataFilename).loadAll;
auto canonical = loadAll(canonicalFilename); auto canonical = Loader(canonicalFilename).loadAll;
assert(data.length == canonical.length, "Unequal node count"); assert(data.length == canonical.length, "Unequal node count");
foreach(n; 0 .. data.length) foreach(n; 0 .. data.length)

View file

@ -398,8 +398,9 @@ void testConstructor(bool verbose, string dataFilename, string codeDummy)
constructor.addConstructor("!tag1", &constructClass); constructor.addConstructor("!tag1", &constructClass);
constructor.addConstructor("!tag2", &constructStruct); constructor.addConstructor("!tag2", &constructStruct);
auto resolver = new Resolver; auto loader = Loader(dataFilename);
auto loader = Loader(dataFilename, constructor, resolver); loader.constructor = constructor;
loader.resolver = new Resolver;
Node[] exp = expected[base]; Node[] exp = expected[base];

View file

@ -92,8 +92,12 @@ void testEmitterOnData(bool verbose, string dataFilename, string canonicalFilena
writeln("ORIGINAL:\n", readText(dataFilename)); writeln("ORIGINAL:\n", readText(dataFilename));
writeln("OUTPUT:\n", cast(string)emitStream.data); writeln("OUTPUT:\n", cast(string)emitStream.data);
} }
auto loadStream = new MemoryStream(emitStream.data);
auto newEvents = Loader(loadStream, "DUMMY", new Constructor, new Resolver).parse(); auto loader2 = Loader(new MemoryStream(emitStream.data));
loader2.name = "TEST";
loader2.constructor = new Constructor;
loader2.resolver = new Resolver;
auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents)); assert(compareEvents(events, newEvents));
} }
@ -121,8 +125,11 @@ void testEmitterOnCanonical(bool verbose, string canonicalFilename)
writeln("OUTPUT (canonical=", canonical, "):\n", writeln("OUTPUT (canonical=", canonical, "):\n",
cast(string)emitStream.data); cast(string)emitStream.data);
} }
auto loadStream = new MemoryStream(emitStream.data); auto loader2 = Loader(new MemoryStream(emitStream.data));
auto newEvents = Loader(loadStream, "DUMMY", new Constructor, new Resolver).parse(); loader2.name = "TEST";
loader2.constructor = new Constructor;
loader2.resolver = new Resolver;
auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents)); assert(compareEvents(events, newEvents));
} }
} }
@ -179,8 +186,11 @@ void testEmitterStyles(bool verbose, string dataFilename, string canonicalFilena
to!string(style), ")"); to!string(style), ")");
writeln(emitStream.data); writeln(emitStream.data);
} }
auto loadStream = new MemoryStream(emitStream.data); auto loader2 = Loader(new MemoryStream(emitStream.data));
auto newEvents = Loader(loadStream, "DUMMY", new Constructor, new Resolver).parse(); loader2.name = "TEST";
loader2.constructor = new Constructor;
loader2.resolver = new Resolver;
auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents)); assert(compareEvents(events, newEvents));
} }
} }

View file

@ -22,7 +22,7 @@ void testLoaderError(bool verbose, string errorFilename)
scope(exit){file.close();} scope(exit){file.close();}
Node[] nodes; Node[] nodes;
try{nodes = loadAll(file, errorFilename);} try{nodes = Loader(file).loadAll();}
catch(YAMLException e) catch(YAMLException e)
{ {
if(verbose){writeln(typeid(e).toString(), "\n", e);} if(verbose){writeln(typeid(e).toString(), "\n", e);}
@ -48,7 +48,7 @@ void testLoaderErrorString(bool verbose, string errorFilename)
try try
{ {
auto nodes = loadAll(new MemoryStream(buffer), errorFilename); auto nodes = Loader(new MemoryStream(buffer)).loadAll();
} }
catch(YAMLException e) catch(YAMLException e)
{ {
@ -66,7 +66,7 @@ void testLoaderErrorString(bool verbose, string errorFilename)
*/ */
void testLoaderErrorFilename(bool verbose, string errorFilename) void testLoaderErrorFilename(bool verbose, string errorFilename)
{ {
try{auto nodes = loadAll(errorFilename);} try{auto nodes = Loader(errorFilename).loadAll();}
catch(YAMLException e) catch(YAMLException e)
{ {
if(verbose){writeln(typeid(e).toString(), "\n", e);} if(verbose){writeln(typeid(e).toString(), "\n", e);}
@ -83,7 +83,7 @@ void testLoaderErrorFilename(bool verbose, string errorFilename)
*/ */
void testLoaderErrorSingle(bool verbose, string errorFilename) void testLoaderErrorSingle(bool verbose, string errorFilename)
{ {
try{auto nodes = load(errorFilename);} try{auto nodes = Loader(errorFilename).load();}
catch(YAMLException e) catch(YAMLException e)
{ {
if(verbose){writeln(typeid(e).toString(), "\n", e);} if(verbose){writeln(typeid(e).toString(), "\n", e);}

View file

@ -57,13 +57,13 @@ void testUnicodeInput(bool verbose, string unicodeFilename)
string data = readText(unicodeFilename); string data = readText(unicodeFilename);
string expected = data.split().join(" "); string expected = data.split().join(" ");
Node output = load(new MemoryStream(to!(char[])(data)), unicodeFilename); Node output = Loader(new MemoryStream(to!(char[])(data))).load();
assert(output.get!string == expected); assert(output.get!string == expected);
foreach(stream; [new MemoryStream(cast(byte[])(bom16() ~ to!(wchar[])(data))), foreach(stream; [new MemoryStream(cast(byte[])(bom16() ~ to!(wchar[])(data))),
new MemoryStream(cast(byte[])(bom32() ~ to!(dchar[])(data)))]) new MemoryStream(cast(byte[])(bom32() ~ to!(dchar[])(data)))])
{ {
output = load(stream, unicodeFilename); output = Loader(stream).load();
assert(output.get!string == expected); assert(output.get!string == expected);
} }
} }
@ -82,7 +82,7 @@ void testUnicodeInputErrors(bool verbose, string unicodeFilename)
new MemoryStream(cast(byte[])(bom16(true) ~ to!(wchar[])(data))), new MemoryStream(cast(byte[])(bom16(true) ~ to!(wchar[])(data))),
new MemoryStream(cast(byte[])(bom32(true) ~ to!(dchar[])(data)))]) new MemoryStream(cast(byte[])(bom32(true) ~ to!(dchar[])(data)))])
{ {
try{load(stream, unicodeFilename);} try{Loader(stream).load();}
catch(YAMLException e) catch(YAMLException e)
{ {
if(verbose){writeln(typeid(e).toString(), "\n", e);} if(verbose){writeln(typeid(e).toString(), "\n", e);}

View file

@ -61,9 +61,10 @@ void testRepresenterTypes(bool verbose, string codeFilename)
constructor.addConstructor("!tag1", &constructClass); constructor.addConstructor("!tag1", &constructClass);
constructor.addConstructor("!tag2", &constructStruct); constructor.addConstructor("!tag2", &constructStruct);
auto resolver = new Resolver; auto loader = Loader(loadStream);
auto loader = Loader(loadStream, "DUMMY", constructor, resolver); loader.name = "TEST";
foreach(node; loader){readNodes ~= node;} loader.constructor = constructor;
readNodes = loader.loadAll();
assert(expectedNodes.length == readNodes.length); assert(expectedNodes.length == readNodes.length);
foreach(n; 0 .. expectedNodes.length) foreach(n; 0 .. expectedNodes.length)

View file

@ -17,27 +17,31 @@ import dyaml.testcommon;
* Implicit tag resolution unittest. * Implicit tag resolution unittest.
* *
* Params: verbose = Print verbose output? * Params: verbose = Print verbose output?
* dataFilename = TODO * dataFilename = File with unittest data.
* detectFilename = TODO * detectFilename = Dummy filename used to specify which data filenames to use.
*/ */
void testImplicitResolver(bool verbose, string dataFilename, string detectFilename) void testImplicitResolver(bool verbose, string dataFilename, string detectFilename)
{ {
string correctTag; string correctTag;
Node node; Node node;
scope(exit) scope(failure)
{ {
if(verbose) if(true)
{ {
writeln("Correct tag: ", correctTag); writeln("Correct tag: ", correctTag);
writeln("Node: ", node.debugString); writeln("Node: ", node.debugString);
assert(node.isSequence);
assert(node.tag.get == correctTag);
} }
} }
correctTag = readText(dataFilename).strip(); correctTag = readText(detectFilename).strip();
node = yaml.load(dataFilename); node = Loader(dataFilename).load();
assert(node.isSequence);
foreach(ref Node scalar; node)
{
assert(scalar.isScalar);
assert(scalar.tag.get == correctTag);
}
} }