dyaml/source/dyaml/loader.d

408 lines
12 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.file;
import std.stdio : File;
import std.string;
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.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
// Name of the input file or stream, used in error messages.
string name_ = "<unknown>";
2015-02-21 13:31:55 +00:00
// Are we done loading?
bool done_;
// 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:
@disable this();
@disable int opCmp(ref Loader);
@disable bool opEquals(ref Loader);
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.
* 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.
*/
static Loader fromFile(string filename) @trusted
{
try
2011-08-16 12:53:13 +00:00
{
auto loader = Loader(std.file.read(filename));
loader.name_ = filename;
return loader;
}
catch(FileException e)
{
throw new YAMLException("Unable to open file %s for YAML loading: %s"
.format(filename, e.msg), e.file, e.line);
2011-08-16 12:53:13 +00:00
}
}
/// ditto
static Loader fromFile(File file) @system
{
auto loader = Loader(file.byChunk(4096).join);
loader.name_ = file.name;
return loader;
}
/** Construct a Loader to load YAML from a string.
2014-08-01 00:51:35 +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);
}
/// Ditto
static Loader fromString(string data) @safe
{
return fromString(data.dup);
}
/// Load a char[].
2018-03-23 21:35:16 +00:00
@safe unittest
{
2018-03-23 21:35:16 +00:00
assert(Loader.fromString("42".dup).load().as!int == 42);
}
/// 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.
*/
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
{
resolver_ = Resolver.withDefaultResolvers;
try
{
reader_ = new Reader(yamlData);
2019-01-15 09:31:05 +00:00
scanner_ = Scanner(reader_);
2014-07-31 00:16:53 +00:00
parser_ = new Parser(scanner_);
}
catch(YAMLException e)
{
2014-07-31 00:16:53 +00:00
throw new YAMLException("Unable to open %s for YAML loading: %s"
.format(name_, e.msg), e.file, e.line);
}
}
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
{
name_ = name;
}
2014-07-19 12:32:16 +00:00
/// Specify custom Resolver to use.
auto ref resolver() pure @safe nothrow @nogc
{
return resolver_;
}
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.
*/
Node load() @safe
{
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
}
/** 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
{
// currentNode and done_ are both invalid until popFront is called once
if (!rangeInitialized)
{
popFront();
}
return done_;
}
/** 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
{
// 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
{
composer = Composer(parser_, resolver_);
rangeInitialized = true;
2011-08-16 12:53:13 +00:00
}
assert(!done_, "Loader.popFront called on empty range");
if (composer.checkNode())
2011-08-16 12:53:13 +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
}
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.
2019-01-24 06:53:40 +00:00
const(Token)[] scan() @safe
2011-08-16 12:53:13 +00:00
{
try
{
2019-01-24 06:53:40 +00:00
const(Token)[] result;
foreach (token; scanner_)
2014-07-19 12:38:35 +00:00
{
2019-01-24 06:53:40 +00:00
result ~= token;
2014-07-19 12:38:35 +00:00
}
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, e.file, e.line);
2011-08-16 12:53:13 +00:00
}
}
// Scan all tokens, throwing them away. Used for benchmarking.
void scanBench() @safe
{
2019-01-24 06:53:40 +00:00
try
{
2019-01-24 06:53:40 +00:00
while(!scanner_.empty)
{
scanner_.popFront();
}
}
catch(YAMLException e)
{
throw new YAMLException("Unable to scan YAML from stream " ~
name_ ~ " : " ~ e.msg, e.file, e.line);
}
}
2014-07-19 12:32:16 +00:00
// Parse and return all events. Used for debugging.
auto parse() @safe
2011-08-16 12:53:13 +00:00
{
return parser_;
2011-08-16 12:53:13 +00:00
}
}
/// Load single YAML document from a file:
@safe unittest
{
write("example.yaml", "Hello world!");
auto rootNode = Loader.fromFile("example.yaml").load();
assert(rootNode == "Hello world!");
}
/// 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!");
}
/// Load all YAML documents from a file:
@safe unittest
{
import std.array : array;
import std.file : write;
write("example.yaml",
"---\n"~
"Hello world!\n"~
"...\n"~
"---\n"~
"Hello world 2!\n"~
"...\n"
);
auto nodes = Loader.fromFile("example.yaml").array;
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"
);
auto loader = Loader.fromFile("example.yaml");
foreach(ref node; loader)
{
//Do something
}
}
/// Load YAML from a string:
2018-03-23 21:35:16 +00:00
@safe unittest
{
string yaml_input = ("red: '#ff0000'\n" ~
"green: '#00ff00'\n" ~
"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
foreach(string color, string value; colors)
{
2018-04-30 22:11:36 +00:00
// Do something with the color and its value...
}
}
/// 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
{
string buffer = readText("example.yaml");
auto yamlNode = Loader.fromString(buffer);
// Read data from yamlNode here...
}
catch(FileException e)
{
writeln("Failed to read file 'example.yaml'");
}
}
/// Use a custom 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 loader = Loader.fromFile("example.yaml");
// Add resolver expressions here...
// loader.resolver.addImplicitResolver(...);
auto rootNode = loader.load();
}