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;
|
2014-07-31 00:01:08 +00:00
|
|
|
import std.file;
|
2018-06-15 09:01:43 +00:00
|
|
|
import std.stdio : File;
|
2014-07-31 00:01:08 +00:00
|
|
|
import std.string;
|
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;
|
|
|
|
import dyaml.token;
|
|
|
|
|
|
|
|
|
2016-03-16 23:43:58 +00:00
|
|
|
/** Loads YAML documents from files or char[].
|
2014-08-01 00:51:35 +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
|
|
|
struct Loader
|
|
|
|
{
|
|
|
|
private:
|
2015-02-21 13:31:55 +00:00
|
|
|
// Reads character data from a stream.
|
2011-08-16 12:53:13 +00:00
|
|
|
Reader reader_;
|
2015-02-21 13:31:55 +00:00
|
|
|
// Processes character data to YAML tokens.
|
2011-08-16 12:53:13 +00:00
|
|
|
Scanner scanner_;
|
2015-02-21 13:31:55 +00:00
|
|
|
// Processes tokens to YAML events.
|
2011-08-16 12:53:13 +00:00
|
|
|
Parser parser_;
|
2015-02-21 13:31:55 +00:00
|
|
|
// Resolves tags (data types).
|
2011-08-16 12:53:13 +00:00
|
|
|
Resolver resolver_;
|
2015-02-21 13:31:55 +00:00
|
|
|
// Constructs YAML data types.
|
2011-08-16 12:53:13 +00:00
|
|
|
Constructor constructor_;
|
2015-02-21 13:31:55 +00:00
|
|
|
// Name of the input file or stream, used in error messages.
|
2011-10-12 21:49:42 +00:00
|
|
|
string name_ = "<unknown>";
|
2015-02-21 13:31:55 +00:00
|
|
|
// Are we done loading?
|
2018-06-20 09:51:23 +00:00
|
|
|
bool done_;
|
2018-09-04 15:01:56 +00:00
|
|
|
// Last node read from stream
|
|
|
|
Node currentNode;
|
|
|
|
// Has the range interface been initialized yet?
|
|
|
|
bool rangeInitialized;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
public:
|
2011-10-18 14:12:22 +00:00
|
|
|
@disable this();
|
2011-10-30 19:24:43 +00:00
|
|
|
@disable int opCmp(ref Loader);
|
|
|
|
@disable bool opEquals(ref Loader);
|
2011-10-18 14:12:22 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Construct a Loader to load YAML from a file.
|
|
|
|
*
|
|
|
|
* Params: filename = Name of the file to load from.
|
2018-06-15 09:01:43 +00:00
|
|
|
* file = Already-opened file to load from.
|
2014-08-01 00:51:35 +00:00
|
|
|
*
|
|
|
|
* Throws: YAMLException if the file could not be opened or read.
|
|
|
|
*/
|
2018-05-31 07:02:21 +00:00
|
|
|
static Loader fromFile(string filename) @trusted
|
|
|
|
{
|
2014-07-31 00:01:08 +00:00
|
|
|
try
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-05-31 07:02:21 +00:00
|
|
|
auto loader = Loader(std.file.read(filename));
|
|
|
|
loader.name_ = filename;
|
|
|
|
return loader;
|
2014-07-31 00:01:08 +00:00
|
|
|
}
|
|
|
|
catch(FileException e)
|
|
|
|
{
|
|
|
|
throw new YAMLException("Unable to open file %s for YAML loading: %s"
|
2018-07-17 23:41:59 +00:00
|
|
|
.format(filename, e.msg), e.file, e.line);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-05-31 07:02:21 +00:00
|
|
|
}
|
2018-06-15 09:01:43 +00:00
|
|
|
/// ditto
|
|
|
|
static Loader fromFile(File file) @system
|
|
|
|
{
|
|
|
|
auto loader = Loader(file.byChunk(4096).join);
|
|
|
|
loader.name_ = file.name;
|
|
|
|
return loader;
|
|
|
|
}
|
2012-11-02 13:31:50 +00:00
|
|
|
|
2018-05-31 07:02:21 +00:00
|
|
|
/** Construct a Loader to load YAML from a string.
|
2014-08-01 00:51:35 +00:00
|
|
|
*
|
2018-05-31 07:02:21 +00:00
|
|
|
* Params: data = String to load YAML from. The char[] version $(B will)
|
|
|
|
* overwrite its input during parsing as D:YAML reuses memory.
|
2014-08-01 00:51:35 +00:00
|
|
|
*
|
|
|
|
* Returns: Loader loading YAML from given string.
|
|
|
|
*
|
|
|
|
* Throws:
|
|
|
|
*
|
|
|
|
* YAMLException if data could not be read (e.g. a decoding error)
|
|
|
|
*/
|
2014-07-31 00:08:08 +00:00
|
|
|
static Loader fromString(char[] data) @safe
|
|
|
|
{
|
|
|
|
return Loader(cast(ubyte[])data);
|
|
|
|
}
|
2018-05-31 07:02:21 +00:00
|
|
|
/// Ditto
|
|
|
|
static Loader fromString(string data) @safe
|
|
|
|
{
|
|
|
|
return fromString(data.dup);
|
|
|
|
}
|
|
|
|
/// Load a char[].
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2012-11-02 13:31:50 +00:00
|
|
|
{
|
2018-03-23 21:35:16 +00:00
|
|
|
assert(Loader.fromString("42".dup).load().as!int == 42);
|
2012-11-02 13:31:50 +00:00
|
|
|
}
|
2018-05-31 07:02:21 +00:00
|
|
|
/// Load a string.
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
assert(Loader.fromString("42").load().as!int == 42);
|
|
|
|
}
|
2014-07-19 12:38:35 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Construct a Loader to load YAML from a buffer.
|
|
|
|
*
|
|
|
|
* Params: yamlData = Buffer with YAML data to load. This may be e.g. a file
|
|
|
|
* loaded to memory or a string with YAML data. Note that
|
|
|
|
* buffer $(B will) be overwritten, as D:YAML minimizes
|
|
|
|
* memory allocations by reusing the input _buffer.
|
2014-08-05 20:07:35 +00:00
|
|
|
* $(B Must not be deleted or modified by the user as long
|
|
|
|
* as nodes loaded by this Loader are in use!) - Nodes may
|
|
|
|
* refer to data in this buffer.
|
2014-08-01 00:51:35 +00:00
|
|
|
*
|
|
|
|
* Note that D:YAML looks for byte-order-marks YAML files encoded in
|
|
|
|
* UTF-16/UTF-32 (and sometimes UTF-8) use to specify the encoding and
|
|
|
|
* endianness, so it should be enough to load an entire file to a buffer and
|
|
|
|
* pass it to D:YAML, regardless of Unicode encoding.
|
|
|
|
*
|
|
|
|
* Throws: YAMLException if yamlData contains data illegal in YAML.
|
|
|
|
*/
|
2018-05-31 07:02:21 +00:00
|
|
|
static Loader fromBuffer(ubyte[] yamlData) @safe
|
|
|
|
{
|
|
|
|
return Loader(yamlData);
|
|
|
|
}
|
|
|
|
/// Ditto
|
|
|
|
static Loader fromBuffer(void[] yamlData) @system
|
|
|
|
{
|
|
|
|
return Loader(yamlData);
|
|
|
|
}
|
|
|
|
/// Ditto
|
|
|
|
private this(void[] yamlData) @system
|
|
|
|
{
|
|
|
|
this(cast(ubyte[])yamlData);
|
|
|
|
}
|
|
|
|
/// Ditto
|
|
|
|
private this(ubyte[] yamlData) @safe
|
2014-07-31 00:01:08 +00:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2018-05-31 07:02:21 +00:00
|
|
|
reader_ = new Reader(yamlData);
|
2014-07-31 00:16:53 +00:00
|
|
|
scanner_ = new Scanner(reader_);
|
|
|
|
parser_ = new Parser(scanner_);
|
2014-07-31 00:01:08 +00:00
|
|
|
}
|
|
|
|
catch(YAMLException e)
|
|
|
|
{
|
2014-07-31 00:16:53 +00:00
|
|
|
throw new YAMLException("Unable to open %s for YAML loading: %s"
|
2018-06-22 03:59:10 +00:00
|
|
|
.format(name_, e.msg), e.file, e.line);
|
2014-07-31 00:01:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-12 21:49:42 +00:00
|
|
|
|
2014-07-19 12:32:16 +00:00
|
|
|
/// Set stream _name. Used in debugging messages.
|
2014-07-19 12:41:30 +00:00
|
|
|
void name(string name) pure @safe nothrow @nogc
|
2011-10-12 21:49:42 +00:00
|
|
|
{
|
|
|
|
name_ = name;
|
|
|
|
}
|
|
|
|
|
2014-07-19 12:32:16 +00:00
|
|
|
/// Specify custom Resolver to use.
|
2014-07-19 12:41:30 +00:00
|
|
|
void resolver(Resolver resolver) pure @safe nothrow @nogc
|
2011-10-12 21:49:42 +00:00
|
|
|
{
|
|
|
|
resolver_ = resolver;
|
|
|
|
}
|
|
|
|
|
2014-07-19 12:32:16 +00:00
|
|
|
/// Specify custom Constructor to use.
|
2014-07-19 12:41:30 +00:00
|
|
|
void constructor(Constructor constructor) pure @safe nothrow @nogc
|
2011-10-12 21:49:42 +00:00
|
|
|
{
|
|
|
|
constructor_ = constructor;
|
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Load single YAML document.
|
|
|
|
*
|
|
|
|
* If none or more than one YAML document is found, this throws a YAMLException.
|
|
|
|
*
|
|
|
|
* This can only be called once; this is enforced by contract.
|
|
|
|
*
|
|
|
|
* Returns: Root node of the document.
|
|
|
|
*
|
|
|
|
* Throws: YAMLException if there wasn't exactly one document
|
|
|
|
* or on a YAML parsing error.
|
|
|
|
*/
|
2012-09-08 23:42:13 +00:00
|
|
|
Node load() @safe
|
2011-11-16 02:10:29 +00:00
|
|
|
{
|
2018-09-04 15:01:56 +00:00
|
|
|
enforce!YAMLException(!empty, "Zero documents in stream");
|
|
|
|
auto output = front;
|
|
|
|
popFront();
|
|
|
|
enforce!YAMLException(empty, "More than one document in stream");
|
|
|
|
return output;
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
|
2018-09-04 15:01:56 +00:00
|
|
|
/** Implements the empty range primitive.
|
|
|
|
*
|
|
|
|
* If there's no more documents left in the stream, this will be true.
|
|
|
|
*
|
|
|
|
* Returns: `true` if no more documents left, `false` otherwise.
|
|
|
|
*/
|
|
|
|
bool empty() @safe
|
2018-06-10 15:22:26 +00:00
|
|
|
{
|
2018-09-04 15:01:56 +00:00
|
|
|
// currentNode and done_ are both invalid until popFront is called once
|
|
|
|
if (!rangeInitialized)
|
|
|
|
{
|
|
|
|
popFront();
|
|
|
|
}
|
|
|
|
return done_;
|
2011-11-16 02:10:29 +00:00
|
|
|
}
|
2018-09-04 15:01:56 +00:00
|
|
|
/** Implements the popFront range primitive.
|
|
|
|
*
|
|
|
|
* Reads the next document from the stream, if possible.
|
|
|
|
*/
|
|
|
|
void popFront() @safe
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-09-04 15:01:56 +00:00
|
|
|
// Composer initialization is done here in case the constructor is
|
|
|
|
// modified, which is a pretty common case.
|
|
|
|
static Composer composer;
|
|
|
|
if (!rangeInitialized)
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2014-08-04 00:14:01 +00:00
|
|
|
lazyInitConstructorResolver();
|
2018-09-04 15:01:56 +00:00
|
|
|
composer = new Composer(parser_, resolver_, constructor_);
|
|
|
|
rangeInitialized = true;
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-09-04 15:01:56 +00:00
|
|
|
assert(!done_, "Loader.popFront called on empty range");
|
|
|
|
if (composer.checkNode())
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-09-04 15:01:56 +00:00
|
|
|
currentNode = composer.getNode();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
done_ = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/** Implements the front range primitive.
|
|
|
|
*
|
|
|
|
* Returns: the current document as a Node.
|
|
|
|
*/
|
|
|
|
Node front() @safe
|
|
|
|
{
|
|
|
|
// currentNode and done_ are both invalid until popFront is called once
|
|
|
|
if (!rangeInitialized)
|
|
|
|
{
|
|
|
|
popFront();
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-09-04 15:01:56 +00:00
|
|
|
return currentNode;
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2014-07-19 12:32:16 +00:00
|
|
|
// Scan and return all tokens. Used for debugging.
|
2018-03-23 21:35:16 +00:00
|
|
|
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 " ~
|
2018-07-17 23:41:59 +00:00
|
|
|
name_ ~ " : " ~ e.msg, e.file, e.line);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-02 21:26:46 +00:00
|
|
|
// Scan all tokens, throwing them away. Used for benchmarking.
|
|
|
|
void scanBench() @safe
|
|
|
|
{
|
|
|
|
try while(scanner_.checkToken())
|
|
|
|
{
|
|
|
|
scanner_.getToken();
|
|
|
|
}
|
|
|
|
catch(YAMLException e)
|
|
|
|
{
|
|
|
|
throw new YAMLException("Unable to scan YAML from stream " ~
|
2018-07-17 23:41:59 +00:00
|
|
|
name_ ~ " : " ~ e.msg, e.file, e.line);
|
2014-08-02 21:26:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-07-19 12:32:16 +00:00
|
|
|
// Parse and return all events. Used for debugging.
|
2018-07-12 02:59:35 +00:00
|
|
|
auto parse() @safe
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-07-12 02:59:35 +00:00
|
|
|
return parser_;
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2014-08-04 00:14:01 +00:00
|
|
|
|
|
|
|
// Construct default constructor/resolver if the user has not yet specified
|
|
|
|
// their own.
|
|
|
|
void lazyInitConstructorResolver() @safe
|
|
|
|
{
|
|
|
|
if(resolver_ is null) { resolver_ = new Resolver(); }
|
|
|
|
if(constructor_ is null) { constructor_ = new Constructor(); }
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-04-07 20:36:38 +00:00
|
|
|
/// Load single YAML document from a file:
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
write("example.yaml", "Hello world!");
|
2018-05-31 07:02:21 +00:00
|
|
|
auto rootNode = Loader.fromFile("example.yaml").load();
|
2018-04-07 20:36:38 +00:00
|
|
|
assert(rootNode == "Hello world!");
|
|
|
|
}
|
2018-06-15 09:01:43 +00:00
|
|
|
/// Load single YAML document from an already-opened file:
|
|
|
|
@system unittest
|
|
|
|
{
|
|
|
|
// Open a temporary file
|
|
|
|
auto file = File.tmpfile;
|
|
|
|
// Write valid YAML
|
|
|
|
file.write("Hello world!");
|
|
|
|
// Return to the beginning
|
|
|
|
file.seek(0);
|
|
|
|
// Load document
|
|
|
|
auto rootNode = Loader.fromFile(file).load();
|
|
|
|
assert(rootNode == "Hello world!");
|
|
|
|
}
|
2018-04-07 20:36:38 +00:00
|
|
|
/// Load all YAML documents from a file:
|
|
|
|
@safe unittest
|
|
|
|
{
|
2018-09-04 15:01:56 +00:00
|
|
|
import std.array : array;
|
2018-04-07 20:36:38 +00:00
|
|
|
import std.file : write;
|
|
|
|
write("example.yaml",
|
|
|
|
"---\n"~
|
|
|
|
"Hello world!\n"~
|
|
|
|
"...\n"~
|
|
|
|
"---\n"~
|
|
|
|
"Hello world 2!\n"~
|
|
|
|
"...\n"
|
|
|
|
);
|
2018-09-04 15:01:56 +00:00
|
|
|
auto nodes = Loader.fromFile("example.yaml").array;
|
2018-04-07 20:36:38 +00:00
|
|
|
assert(nodes.length == 2);
|
|
|
|
}
|
|
|
|
/// Iterate over YAML documents in a file, lazily loading them:
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import std.file : write;
|
|
|
|
write("example.yaml",
|
|
|
|
"---\n"~
|
|
|
|
"Hello world!\n"~
|
|
|
|
"...\n"~
|
|
|
|
"---\n"~
|
|
|
|
"Hello world 2!\n"~
|
|
|
|
"...\n"
|
|
|
|
);
|
2018-05-31 07:02:21 +00:00
|
|
|
auto loader = Loader.fromFile("example.yaml");
|
2011-10-12 21:49:42 +00:00
|
|
|
|
2018-04-07 20:36:38 +00:00
|
|
|
foreach(ref node; loader)
|
|
|
|
{
|
|
|
|
//Do something
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// Load YAML from a string:
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-10-12 21:49:42 +00:00
|
|
|
{
|
2018-05-31 07:02:21 +00:00
|
|
|
string yaml_input = ("red: '#ff0000'\n" ~
|
2016-11-15 18:07:48 +00:00
|
|
|
"green: '#00ff00'\n" ~
|
2018-05-31 07:02:21 +00:00
|
|
|
"blue: '#0000ff'");
|
2014-07-19 12:38:35 +00:00
|
|
|
|
2014-07-31 00:22:42 +00:00
|
|
|
auto colors = Loader.fromString(yaml_input).load();
|
2014-07-19 12:38:35 +00:00
|
|
|
|
2011-10-12 21:49:42 +00:00
|
|
|
foreach(string color, string value; colors)
|
|
|
|
{
|
2018-04-30 22:11:36 +00:00
|
|
|
// Do something with the color and its value...
|
2011-10-12 21:49:42 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-07 20:36:38 +00:00
|
|
|
|
|
|
|
/// Load a file into a buffer in memory and then load YAML from that buffer:
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import std.file : read, write;
|
|
|
|
import std.stdio : writeln;
|
|
|
|
// Create a yaml document
|
|
|
|
write("example.yaml",
|
|
|
|
"---\n"~
|
|
|
|
"Hello world!\n"~
|
|
|
|
"...\n"~
|
|
|
|
"---\n"~
|
|
|
|
"Hello world 2!\n"~
|
|
|
|
"...\n"
|
|
|
|
);
|
|
|
|
try
|
|
|
|
{
|
2018-05-31 07:02:21 +00:00
|
|
|
string buffer = readText("example.yaml");
|
|
|
|
auto yamlNode = Loader.fromString(buffer);
|
2018-04-07 20:36:38 +00:00
|
|
|
|
|
|
|
// Read data from yamlNode here...
|
|
|
|
}
|
|
|
|
catch(FileException e)
|
|
|
|
{
|
|
|
|
writeln("Failed to read file 'example.yaml'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// Use a custom constructor/resolver to support custom data types and/or implicit tags:
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import std.file : write;
|
|
|
|
// Create a yaml document
|
|
|
|
write("example.yaml",
|
|
|
|
"---\n"~
|
|
|
|
"Hello world!\n"~
|
|
|
|
"...\n"
|
|
|
|
);
|
|
|
|
auto constructor = new Constructor();
|
|
|
|
auto resolver = new Resolver();
|
|
|
|
|
|
|
|
// Add constructor functions / resolver expressions here...
|
|
|
|
|
2018-05-31 07:02:21 +00:00
|
|
|
auto loader = Loader.fromFile("example.yaml");
|
2018-04-07 20:36:38 +00:00
|
|
|
loader.constructor = constructor;
|
|
|
|
loader.resolver = resolver;
|
|
|
|
auto rootNode = loader.load();
|
|
|
|
}
|