Add ReadTheDocs documentation

This commit is contained in:
Joao Paulo Magalhaes
2024-04-12 18:25:46 +01:00
parent a2527b5429
commit 7c790c4167
23 changed files with 4843 additions and 405 deletions

View File

@@ -201,7 +201,7 @@ jobs:
- {std: 20, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04} - {std: 20, cxx: clang++-10, bt: Release, vg: ON, san: ALL, bitlinks: static32, os: ubuntu-20.04}
env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"} env: {STD: "${{matrix.std}}", CXX_: "${{matrix.cxx}}", BT: "${{matrix.bt}}", BITLINKS: "${{matrix.bitlinks}}", VG: "${{matrix.vg}}", SAN: "${{matrix.san}}", LINT: "${{matrix.lint}}", OS: "${{matrix.os}}"}
steps: steps:
- {name: checkout, uses: actions/checkout@v3, with: {submodules: recursive}} - {name: checkout, uses: actions/checkout@v4, with: {submodules: recursive}}
- {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS} - {name: install requirements, run: source .github/reqs.sh && c4_install_test_requirements $OS}
- {name: show info, run: source .github/setenv.sh && c4_show_info} - {name: show info, run: source .github/setenv.sh && c4_show_info}
- name: shared64-configure--------------------------------------------------- - name: shared64-configure---------------------------------------------------

41
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,41 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "19"
# rust: "1.64"
# golang: "1.19"
commands:
- pip install -r doc/requirements.txt
- make -C doc html
- mkdir -p _readthedocs
- mv -v doc/_build/html _readthedocs/
# Build documentation in the "docs/" directory with Sphinx
#sphinx:
# configuration: doc/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: doc/requirements.txt
submodules:
include: all
recursive: true

456
README.md
View File

@@ -1,10 +1,11 @@
# Rapid YAML # Rapid YAML
[![MIT Licensed](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/biojppm/rapidyaml/blob/master/LICENSE.txt) [![MIT Licensed](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/biojppm/rapidyaml/blob/master/LICENSE.txt)
[![release](https://img.shields.io/github/v/release/biojppm/rapidyaml?color=g&include_prereleases&label=release%20&sort=semver)](https://github.com/biojppm/rapidyaml/releases) [![release](https://img.shields.io/github/v/release/biojppm/rapidyaml?color=g&include_prereleases&label=release%20&sort=semver)](https://github.com/biojppm/rapidyaml/releases)
[![Documentation Status](https://readthedocs.org/projects/rapidyaml/badge/?version=latest)](https://rapidyaml.readthedocs.io/en/latest/?badge=latest)
[![PyPI](https://img.shields.io/pypi/v/rapidyaml?color=g)](https://pypi.org/project/rapidyaml/) [![PyPI](https://img.shields.io/pypi/v/rapidyaml?color=g)](https://pypi.org/project/rapidyaml/)
[![Gitter](https://badges.gitter.im/rapidyaml/community.svg)](https://gitter.im/rapidyaml/community) [![Gitter](https://badges.gitter.im/rapidyaml/community.svg)](https://gitter.im/rapidyaml/community)
[![test](https://github.com/biojppm/rapidyaml/workflows/test/badge.svg?branch=master)](https://github.com/biojppm/rapidyaml/actions)
<!-- [![Coveralls](https://coveralls.io/repos/github/biojppm/rapidyaml/badge.svg?branch=master)](https://coveralls.io/github/biojppm/rapidyaml) --> <!-- [![Coveralls](https://coveralls.io/repos/github/biojppm/rapidyaml/badge.svg?branch=master)](https://coveralls.io/github/biojppm/rapidyaml) -->
[![Codecov](https://codecov.io/gh/biojppm/rapidyaml/branch/master/graph/badge.svg?branch=master)](https://codecov.io/gh/biojppm/rapidyaml) [![Codecov](https://codecov.io/gh/biojppm/rapidyaml/branch/master/graph/badge.svg?branch=master)](https://codecov.io/gh/biojppm/rapidyaml)
@@ -33,9 +34,9 @@ build->install->`find_package()`) or together with your project (ie with
ryml can use custom global and per-tree memory allocators and error ryml can use custom global and per-tree memory allocators and error
handler callbacks, and is exception-agnostic. ryml provides a default handler callbacks, and is exception-agnostic. ryml provides a default
implementation for the allocator (using `std::malloc()`) and error implementation for the allocator (using `std::malloc()`) and error
handlers (using using `std::abort()` is provided, but you can opt out handlers (using using either exceptions, `longjmp()` or
and provide your own memory allocation and eg, exception-throwing `std::abort()`), but you can opt out and provide your own memory
callbacks. allocation and eg, exception-throwing callbacks.
ryml does not depend on the STL, ie, it does not use any std container ryml does not depend on the STL, ie, it does not use any std container
as part of its data structures), but it can serialize and deserialize as part of its data structures), but it can serialize and deserialize
@@ -49,18 +50,19 @@ ryml is written in C++11, and compiles cleanly with:
* g++ 4.8 and later * g++ 4.8 and later
* Intel Compiler * Intel Compiler
ryml's API documentation is [available at
ReadTheDocs](https://rapidyaml.readthedocs.io/en/latest/).
ryml is [extensively unit-tested in Linux, Windows and ryml is [extensively unit-tested in Linux, Windows and
MacOS](https://github.com/biojppm/rapidyaml/actions). The tests cover MacOS](https://github.com/biojppm/rapidyaml/actions). The tests cover
x64, x86, wasm (emscripten), arm, aarch64, ppc64le and s390x x64, x86, wasm (emscripten), arm, aarch64, ppc64le and s390x
architectures, and include analysing ryml with: architectures, and include analysing ryml with:
* valgrind * valgrind
* clang-tidy * clang-tidy
* clang sanitizers: * gcc/clang sanitizers:
* memory * memory
* address * address
* undefined behavior * undefined behavior
* thread
* [LGTM.com](https://lgtm.com/projects/g/biojppm/rapidyaml)
ryml also [runs in ryml also [runs in
bare-metal](https://github.com/biojppm/rapidyaml/issues/193), and bare-metal](https://github.com/biojppm/rapidyaml/issues/193), and
@@ -219,9 +221,10 @@ workflow in the
CI](https://github.com/biojppm/rapidyaml/actions/workflows/benchmarks.yml) CI](https://github.com/biojppm/rapidyaml/actions/workflows/benchmarks.yml)
to scroll through the results for yourself. to scroll through the results for yourself.
Also, if you have a case where ryml behaves very nicely or not as nicely as Also, if you have a case where ryml behaves very nicely or not as
claimed above, we would definitely like to see it! Please submit a pull request nicely as claimed above, we would definitely like to see it! Please
adding the file to [bm/cases](bm/cases), or just send us the files. open an issue, or submit a pull request adding the file to
[bm/cases](bm/cases), or just send us the files.
------ ------
@@ -234,43 +237,19 @@ rapid is definitely NOT the same as being unpractical, so ryml was
written with easy AND efficient usage in mind, and comes with a two written with easy AND efficient usage in mind, and comes with a two
level API for accessing and traversing the data tree. level API for accessing and traversing the data tree.
The following snippet is a quick overview taken from [the quickstart The following snippet is a very quick overview taken from quickstart
sample](samples/quickstart.cpp). After cloning ryml (don't forget the sample ([see on
`--recursive` flag for git), you can very doxygen](https://rapidyaml.readthedocs.io/en/latest/group__doc__quickstart.html)/[see
easily build and run this executable using any of the build samples, on github](samples/quickstart.cpp). After cloning ryml
eg the [`add_subdirectory()` sample](samples/add_subdirectory/). (don't forget the `--recursive` flag for git), you can very easily
build and run this executable using any of the build samples, eg the
[`add_subdirectory()` sample](samples/add_subdirectory/) (see [the relevant section](#quickstart-samples)).
```c++ ```cpp
// Parse YAML code in place, potentially mutating the buffer: // Parse YAML code in place, potentially mutating the buffer:
char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}"; char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
ryml::Tree tree = ryml::parse_in_place(yml_buf); ryml::Tree tree = ryml::parse_in_place(yml_buf);
// The resulting tree contains only views to the parsed string. If
// the string was parsed in place, then the string must outlive
// the tree! This works in this case because `yml_buf` and `tree`
// live on the same scope, so have the same lifetime.
// It is also possible to:
//
// - parse a read-only buffer using parse_in_arena(). This
// copies the YAML buffer to the tree's arena, and spares the
// headache of the string's lifetime.
//
// - reuse an existing tree (advised)
//
// - reuse an existing parser (advised)
//
// Note: it will always be significantly faster to parse in place
// and reuse tree+parser.
//
// Below you will find samples that show how to achieve reuse; but
// please note that for brevity and clarity, many of the examples
// here are parsing in the arena, and not reusing tree or parser.
//------------------------------------------------------------------
// API overview
// ryml has a two-level API: // ryml has a two-level API:
// //
// The lower level index API is based on the indices of nodes, // The lower level index API is based on the indices of nodes,
@@ -288,191 +267,14 @@ ryml::ConstNodeRef bar = tree["bar"];
CHECK(root.is_map()); CHECK(root.is_map());
CHECK(bar.is_seq()); CHECK(bar.is_seq());
// A node ref is a lightweight handle to the tree and associated id: // The resulting tree stores only string views to the YAML source buffer.
CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount CHECK(root["foo"] == "1");
CHECK(root.id() == root_id); // a node ref's id is the index of the node CHECK(root["foo"].key().str == yml_buf + 1);
CHECK(bar.id() == bar_id); // a node ref's id is the index of the node CHECK(bar[0] == "2");
CHECK(root["john"] == "doe");
// The node API translates very cleanly to the index API, so most
// of the code examples below are using the node API.
// WARNING. A node ref holds a raw pointer to the tree. Care must
// be taken to ensure the lifetimes match, so that a node will
// never access the tree after the goes out of scope.
//------------------------------------------------------------------
// To read the parsed tree
// ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
CHECK(tree["foo"].is_keyval());
CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val)
CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key)
CHECK(tree["bar"].is_seq());
CHECK(tree["bar"].has_key());
CHECK(tree["bar"].key() == "bar");
// maps use string keys, seqs use index keys:
CHECK(tree["bar"][0].val() == "2");
CHECK(tree["bar"][1].val() == "3");
CHECK(tree["john"].val() == "doe");
// An index key is the position of the child within its parent,
// so even maps can also use int keys, if the key position is
// known.
CHECK(tree[0].id() == tree["foo"].id());
CHECK(tree[1].id() == tree["bar"].id());
CHECK(tree[2].id() == tree["john"].id());
// Tree::operator[](int) searches a ***root*** child by its position.
CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root
CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root
CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root
// NodeRef::operator[](int) searches a ***node*** child by its position:
CHECK(bar[0].val() == "2"); // 0 means first child of bar
CHECK(bar[1].val() == "3"); // 1 means second child of bar
// NodeRef::operator[](string):
// A string key is the key of the node: lookup is by name. So it
// is only available for maps, and it is NOT available for seqs,
// since seq members do not have keys.
CHECK(tree["foo"].key() == "foo");
CHECK(tree["bar"].key() == "bar");
CHECK(tree["john"].key() == "john");
CHECK(bar.is_seq());
// CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup
// Note that maps can also use index keys as well as string keys:
CHECK(root["foo"].id() == root[0].id());
CHECK(root["bar"].id() == root[1].id());
CHECK(root["john"].id() == root[2].id());
// IMPORTANT. The ryml tree uses an index-based linked list for
// storing children, so the complexity of
// `Tree::operator[csubstr]` and `Tree::operator[size_t]` is O(n),
// linear on the number of root children. If you use
// `Tree::operator[]` with a large tree where the root has many
// children, you will see a performance hit.
//
// To avoid this hit, you can create your own accelerator
// structure. For example, before doing a lookup, do a single
// traverse at the root level to fill an `map<csubstr,size_t>`
// mapping key names to node indices; with a node index, a lookup
// (via `Tree::get()`) is O(1), so this way you can get O(log n)
// lookup from a key. (But please do not use `std::map` if you
// care about performance; use something else like a flat map or
// sorted vector).
//
// As for node refs, the difference from `NodeRef::operator[]` and
// `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
// latter refers to the root node, whereas the former are invoked
// on their target node. But the lookup process works the same for
// both and their algorithmic complexity is the same: they are
// both linear in the number of direct children. But of course,
// depending on the data, that number may be very different from
// one to another.
//------------------------------------------------------------------
// Hierarchy:
{
ryml::ConstNodeRef foo = root.first_child();
ryml::ConstNodeRef john = root.last_child();
CHECK(tree.size() == 6); // O(1) number of nodes in the tree
CHECK(root.num_children() == 3); // O(num_children[root])
CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
CHECK(foo.parent().id() == root.id()); // parent() is O(1)
CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1)
CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1)
CHECK(john.first_sibling().id() == foo.id());
CHECK(foo.last_sibling().id() == john.id());
// prev_sibling(), next_sibling(): (both are O(1))
CHECK(foo.num_siblings() == root.num_children());
CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child()
CHECK(foo.next_sibling().key() == "bar");
CHECK(foo.next_sibling().next_sibling().key() == "john");
CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child()
}
//------------------------------------------------------------------
// Iterating:
{
ryml::csubstr expected_keys[] = {"foo", "bar", "john"};
// iterate children using the high-level node API:
{
size_t count = 0;
for(ryml::ConstNodeRef const& child : root.children())
CHECK(child.key() == expected_keys[count++]);
}
// iterate siblings using the high-level node API:
{
size_t count = 0;
for(ryml::ConstNodeRef const& child : root["foo"].siblings())
CHECK(child.key() == expected_keys[count++]);
}
// iterate children using the lower-level tree index API:
{
size_t count = 0;
for(size_t child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
CHECK(tree.key(child_id) == expected_keys[count++]);
}
// iterate siblings using the lower-level tree index API:
// (notice the only difference from above is in the loop
// preamble, which calls tree.first_sibling(bar_id) instead of
// tree.first_child(root_id))
{
size_t count = 0;
for(size_t child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
CHECK(tree.key(child_id) == expected_keys[count++]);
}
}
//------------------------------------------------------------------
// Gotchas:
// ryml uses assertions to prevent you from trying to obtain
// things that do not exist. For example:
{
ryml::ConstNodeRef seq_node = tree["bar"];
ryml::ConstNodeRef val_node = seq_node[0];
CHECK(seq_node.is_seq()); // seq is a container
CHECK(!seq_node.has_val()); // ... so it has no val
//CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior
CHECK(val_node.parent() == seq_node); // belongs to a seq
CHECK(!val_node.has_key()); // ... so it has no key
//CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior
CHECK(val_node.is_val()); // this node is a val
//CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior
// assertions are also present in methods that /may/ read the val:
CHECK(seq_node.is_seq()); // seq is a container
//CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check
}
// By default, assertions are enabled unless the NDEBUG macro is
// defined (which happens in release builds).
//
// This adheres to the pay-only-for-what-you-use philosophy: if
// you are sure that your intent is correct, why would you need to
// pay the runtime cost for the assertions?
//
// The downside, of course, is that when you are not sure, release
// builds may be doing something crazy.
//
// So you can override this behavior and enable/disable
// assertions, by defining the macro RYML_USE_ASSERT to a proper
// value (see c4/yml/common.hpp).
//
// Also, to be clear, this does not apply to parse errors
// happening when the YAML is parsed. Checking for these errors is
// always enabled and cannot be switched off.
//------------------------------------------------------------------ //------------------------------------------------------------------
// To get actual values, you need to deserialize the nodes.
// Deserializing: use operator>> // Deserializing: use operator>>
{ {
int foo = 0, bar0 = 0, bar1 = 0; int foo = 0, bar0 = 0, bar1 = 0;
@@ -481,7 +283,7 @@ CHECK(root["john"].id() == root[2].id());
root["foo"] >> foo; root["foo"] >> foo;
root["bar"][0] >> bar0; root["bar"][0] >> bar0;
root["bar"][1] >> bar1; root["bar"][1] >> bar1;
root["john"] >> john_str; // requires from_chars(std::string). see serialization samples below. root["john"] >> john_str; // requires from_chars(std::string). see API doc.
root["bar"] >> ryml::key(bar_str); // to deserialize the key, use the tag function ryml::key() root["bar"] >> ryml::key(bar_str); // to deserialize the key, use the tag function ryml::key()
CHECK(foo == 1); CHECK(foo == 1);
CHECK(bar0 == 2); CHECK(bar0 == 2);
@@ -490,15 +292,8 @@ CHECK(root["john"].id() == root[2].id());
CHECK(bar_str == "bar"); CHECK(bar_str == "bar");
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
// Modifying existing nodes: operator= vs operator<< // To modify existing nodes, use operator= or operator<<.
// As implied by its name, ConstNodeRef is a reference to a const
// node. It can be used to read from the node, but not write to it
// or modify the hierarchy of the node. If any modification is
// desired then a NodeRef must be used instead:
ryml::NodeRef wroot = tree.rootref();
// operator= assigns an existing string to the receiving node. // operator= assigns an existing string to the receiving node.
// The contents are NOT copied, and this pointer will be in effect // The contents are NOT copied, and this pointer will be in effect
@@ -535,19 +330,6 @@ CHECK(root["bar"][0].val() == "20");
CHECK(root["bar"][1].val() == "30"); CHECK(root["bar"][1].val() == "30");
CHECK(root["john"].val() == "deere"); CHECK(root["john"].val() == "deere");
CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena
// using operator<< instead of operator=, the crash above is avoided:
{
std::string ok("in_scope");
// root["john"] = ryml::to_csubstr(ok); // don't, will dangle
wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena
}
CHECK(root["john"].val() == "in_scope"); // OK!
// serializing floating points:
wroot["float"] << 2.4;
// to force a particular precision or float format:
// (see sample_float_precision() and sample_formatting())
wroot["digits"] << ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT);
CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -562,117 +344,6 @@ CHECK(root["newkeyval"].key() == "newkeyval");
CHECK(root["newkeyval"].val() == "shiny and new"); CHECK(root["newkeyval"].val() == "shiny and new");
CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)"); CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)"); CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
// adding a val node to a seq:
CHECK(root["bar"].num_children() == 2);
wroot["bar"][2] = "oh so nice";
wroot["bar"][3] << "oh so nice (serialized)";
CHECK(root["bar"].num_children() == 4);
CHECK(root["bar"][2].val() == "oh so nice");
CHECK(root["bar"][3].val() == "oh so nice (serialized)");
// adding a seq node:
CHECK(root.num_children() == 7);
wroot["newseq"] |= ryml::SEQ;
wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ;
CHECK(root.num_children() == 9);
CHECK(root["newseq"].num_children() == 0);
CHECK(root["newseq"].is_seq());
CHECK(root["newseq (serialized)"].num_children() == 0);
CHECK(root["newseq (serialized)"].is_seq());
// adding a map node:
CHECK(root.num_children() == 9);
wroot["newmap"] |= ryml::MAP;
wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::MAP;
CHECK(root.num_children() == 11);
CHECK(root["newmap"].num_children() == 0);
CHECK(root["newmap"].is_map());
CHECK(root["newmap (serialized)"].num_children() == 0);
CHECK(root["newmap (serialized)"].is_map());
//
// When the tree is mutable, operator[] first searches the tree
// for the does not mutate the tree until the returned node is
// written to.
//
// Until such time, the NodeRef object keeps in itself the required
// information to write to the proper place in the tree. This is
// called being in a "seed" state.
//
// This means that passing a key/index which does not exist will
// not mutate the tree, but will instead store (in the node) the
// proper place of the tree to be able to do so, if and when it is
// required. This is why the node is said to be in "seed" state -
// it allows creating the entry in the tree in the future.
//
// This is a significant difference from eg, the behavior of
// std::map, which mutates the map immediately within the call to
// operator[].
//
// All of the points above apply only if the tree is mutable. If
// the tree is const, then a NodeRef cannot be obtained from it;
// only a ConstNodeRef, which can never be used to mutate the
// tree.
//
CHECK(!root.has_child("I am not nothing"));
ryml::NodeRef nothing;
CHECK(nothing.invalid()); // invalid because it points at nothing
nothing = wroot["I am nothing"];
CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree
CHECK(nothing.is_seed()); // ... but nothing is there yet.
CHECK(!root.has_child("I am nothing")); // same as above
CHECK(!nothing.readable()); // ... and this node cannot be used to
// read anything from the tree
ryml::NodeRef something = wroot["I am something"];
ryml::ConstNodeRef constsomething = wroot["I am something"];
CHECK(!root.has_child("I am something")); // same as above
CHECK(!something.invalid());
CHECK(something.is_seed()); // same as above
CHECK(!something.readable()); // same as above
CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be
// used to mutate a tree, it is only valid()
// if it is pointing at an existing node.
something = "indeed"; // this will commit the seed to the tree, mutating at the proper place
CHECK(root.has_child("I am something"));
CHECK(root["I am something"].val() == "indeed");
CHECK(!something.invalid()); // it was already valid
CHECK(!something.is_seed()); // now the tree has this node, so the
// ref is no longer a seed
CHECK(something.readable()); // and it is now readable
//
// now the constref is also valid (but it needs to be reassigned):
ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
CHECK(!constsomethingnew.invalid());
CHECK(constsomethingnew.readable());
// note that the old constref is now stale, because it only keeps
// the state at creation:
CHECK(constsomething.invalid());
CHECK(!constsomething.readable());
//
// -----------------------------------------------------------
// Remember: a seed node cannot be used to read from the tree!
// -----------------------------------------------------------
//
// The seed node needs to be created and become readable first.
//
// Trying to invoke any tree-reading method on a node that is not
// readable will cause an assertion (see RYML_USE_ASSERT).
//
// It is your responsibility to verify that the preconditions are
// met. If you are not sure about the structure of your data,
// write your code defensively to signify your full intent:
//
ryml::NodeRef wbar = wroot["bar"];
if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable()
{
CHECK(wbar[0].readable() && wbar[0].val() == "20");
CHECK( ! wbar[100].readable());
CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val()
// this would work as well:
CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20");
CHECK(wbar[100].is_seed() || wbar[100].val() == "100");
}
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -711,45 +382,9 @@ ryml::csubstr buf_result = ryml::emit_yaml(tree, buf);
CHECK(buf_result == expected_result); CHECK(buf_result == expected_result);
CHECK(str_result == expected_result); CHECK(str_result == expected_result);
CHECK(stream_result == expected_result); CHECK(stream_result == expected_result);
// There are many possibilities to emit to buffer;
// please look at the emit sample functions below.
//------------------------------------------------------------------ //------------------------------------------------------------------
// ConstNodeRef vs NodeRef // UTF8
ryml::NodeRef noderef = tree["bar"][0];
ryml::ConstNodeRef constnoderef = tree["bar"][0];
// ConstNodeRef cannot be used to mutate the tree:
//constnoderef = "21"; // compile error
//constnoderef << "22"; // compile error
// ... but a NodeRef can:
noderef = "21"; // ok, can assign because it's not const
CHECK(tree["bar"][0].val() == "21");
noderef << "22"; // ok, can serialize and assign because it's not const
CHECK(tree["bar"][0].val() == "22");
// it is not possible to obtain a NodeRef from a ConstNodeRef:
// noderef = constnoderef; // compile error
// it is always possible to obtain a ConstNodeRef from a NodeRef:
constnoderef = noderef; // ok can assign const <- nonconst
// If a tree is const, then only ConstNodeRef's can be
// obtained from that tree:
ryml::Tree const& consttree = tree;
//noderef = consttree["bar"][0]; // compile error
noderef = tree["bar"][0]; // ok
constnoderef = consttree["bar"][0]; // ok
// ConstNodeRef and NodeRef can be compared for equality.
// Equality means they point at the same node.
CHECK(constnoderef == noderef);
CHECK(!(constnoderef != noderef));
//------------------------------------------------------------------
// Dealing with UTF8
ryml::Tree langs = ryml::parse_in_arena(R"( ryml::Tree langs = ryml::parse_in_arena(R"(
en: Planet (Gas) en: Planet (Gas)
fr: Planète (Gazeuse) fr: Planète (Gazeuse)
@@ -777,6 +412,7 @@ CHECK(langs["and this as well"].val() == "✅ 𝄞");
CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA"); CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA");
CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E"); CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E");
//------------------------------------------------------------------ //------------------------------------------------------------------
// Getting the location of nodes in the source: // Getting the location of nodes in the source:
// //
@@ -798,15 +434,29 @@ CHECK(loc.col == 4u);
### Package managers ### Package managers
If you opt for package managers, here's where ryml is available so far ryml is available in most package managers (thanks to all the
(thanks to all the contributors!): contributors!) and linux distributions. But please be aware: those
packages are maintained downstream of this repository, so if you have
issues with the package, file a report with the respective maintainer.
Here's a quick roundup (not maintained):
* Package managers:
* [conan](https://conan.io/center/recipes/rapidyaml)
* [vcpkg](https://vcpkg.io/en/packages.html): `vcpkg install ryml` * [vcpkg](https://vcpkg.io/en/packages.html): `vcpkg install ryml`
* [PyPI](https://pypi.org/project/rapidyaml/)
* Linux distributions:
* Arch Linux/Manjaro: * Arch Linux/Manjaro:
* [rapidyaml (aarch64)](https://archlinuxarm.org/packages/aarch64/rapidyaml)
* [rapidyaml-git (AUR)](https://aur.archlinux.org/packages/rapidyaml-git/) * [rapidyaml-git (AUR)](https://aur.archlinux.org/packages/rapidyaml-git/)
* [python-rapidyaml-git (AUR)](https://aur.archlinux.org/packages/python-rapidyaml-git/) * [python-rapidyaml-git (AUR)](https://aur.archlinux.org/packages/python-rapidyaml-git/)
* [Fedora Linux](https://getfedora.org/)/[EPEL](https://docs.fedoraproject.org/en-US/epel/): `dnf install rapidyaml-devel`, `dnf install python3-rapidyaml` * [Fedora Linux](https://getfedora.org/)/[EPEL](https://docs.fedoraproject.org/en-US/epel/):
* [PyPI](https://pypi.org/project/rapidyaml/) * `dnf install rapidyaml-devel`
* `dnf install python3-rapidyaml`
* [Gentoo](https://packages.gentoo.org/packages/dev-cpp/rapidyaml)
* [OpenSuse](https://build.openbuildservice.org/package/show/Emulators/rapidyaml)
* [Slackbuilds](https://slackbuilds.org/repository/15.0/libraries/rapidyaml/)
* [AltLinux](https://packages.altlinux.org/en/sisyphus/srpms/rapidyaml/3006055151670528141)
Although package managers are very useful for quickly getting up to Although package managers are very useful for quickly getting up to
speed, the advised way is still to bring ryml as a submodule of your speed, the advised way is still to bring ryml as a submodule of your
project, building both together. This makes it easy to track any project, building both together. This makes it easy to track any
@@ -1061,9 +711,9 @@ As for emitting, the improvement can be as high as 3000x:
## YAML standard conformance ## YAML standard conformance
ryml is close to feature complete. Most of the YAML features are well ryml is feature complete with regards to the YAML specification. All
covered in the unit tests, and expected to work, unless in the the YAML features are well covered in the unit tests, and expected to
exceptions noted below. work, unless in the exceptions noted below.
Of course, there are many dark corners in YAML, and there certainly Of course, there are many dark corners in YAML, and there certainly
can appear cases which ryml fails to parse. Your [bug reports or pull can appear cases which ryml fails to parse. Your [bug reports or pull

View File

@@ -1,3 +1,8 @@
### Add API documentation
- [PR#423](https://github.com/biojppm/rapidyaml/pull/423): **add Doxygen-based API documentation, now hosted in [https://rapidyaml.readthedocs.io/]!**.
- It uses the base doxygen docs, as I couldn't get doxyrest or breathe or exhale to produce anything meaningful using the groups already established in the source code.
### Error handling ### Error handling
Fix major error handling problem reported in [#389](https://github.com/biojppm/rapidyaml/issues/389) ([PR#411](https://github.com/biojppm/rapidyaml/pull/411)): Fix major error handling problem reported in [#389](https://github.com/biojppm/rapidyaml/issues/389) ([PR#411](https://github.com/biojppm/rapidyaml/pull/411)):
@@ -54,6 +59,9 @@ Fix major error handling problem reported in [#389](https://github.com/biojppm/r
- `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. This macro was already defined; the current PR adds the cmake option. - `RYML_USE_ASSERT` - enable assertions regardless of build type. This is disabled by default. This macro was already defined; the current PR adds the cmake option.
- `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS` - make the default error handler provided by ryml throw exceptions instead of calling `std::abort()`. This is disabled by default. - `RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS` - make the default error handler provided by ryml throw exceptions instead of calling `std::abort()`. This is disabled by default.
- Also, `RYML_DEBUG_BREAK()` is now enabled only if `RYML_DBG` is defined, as reported in [#362](https://github.com/biojppm/rapidyaml/issues/362). - Also, `RYML_DEBUG_BREAK()` is now enabled only if `RYML_DBG` is defined, as reported in [#362](https://github.com/biojppm/rapidyaml/issues/362).
- As part of [PR#423](https://github.com/biojppm/rapidyaml/pull/423), to improve linters and codegen:
- annotate the error handlers with `[[noreturn]]`/`C4_NORETURN`
- annotate some error sites with `C4_UNREACHABLE_AFTER_ERR()`
### More fixes ### More fixes

2
doc/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
_build/*
doxygen/*

2948
doc/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

269
doc/DoxygenLayout.xml Normal file
View File

@@ -0,0 +1,269 @@
<?xml version="1.0" encoding="UTF-8"?>
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.10.0 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="topics" visible="yes" title="" intro=""/>
<tab type="modules" visible="yes" title="" intro="">
<tab type="modulelist" visible="yes" title="" intro=""/>
<tab type="modulemembers" visible="yes" title="" intro=""/>
</tab>
<tab type="namespaces" visible="yes" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>
<tab type="namespacemembers" visible="yes" title="" intro=""/>
</tab>
<tab type="concepts" visible="yes" title="">
</tab>
<tab type="interfaces" visible="yes" title="">
<tab type="interfacelist" visible="yes" title="" intro=""/>
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="classes" visible="yes" title="">
<tab type="classlist" visible="yes" title="" intro=""/>
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="hierarchy" visible="yes" title="" intro=""/>
<tab type="classmembers" visible="yes" title="" intro=""/>
</tab>
<tab type="structs" visible="yes" title="">
<tab type="structlist" visible="yes" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
<tab type="exceptions" visible="yes" title="">
<tab type="exceptionlist" visible="yes" title="" intro=""/>
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="files" visible="yes" title="">
<tab type="filelist" visible="yes" title="" intro=""/>
<tab type="globals" visible="yes" title="" intro=""/>
</tab>
<tab type="examples" visible="yes" title="" intro=""/>
</navindex>
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<inheritancegraph visible="yes"/>
<collaborationgraph visible="yes"/>
<memberdecl>
<nestedclasses visible="yes" title=""/>
<publictypes title=""/>
<services title=""/>
<interfaces title=""/>
<publicslots title=""/>
<signals title=""/>
<publicmethods title=""/>
<publicstaticmethods title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
<protectedtypes title=""/>
<protectedslots title=""/>
<protectedmethods title=""/>
<protectedstaticmethods title=""/>
<protectedattributes title=""/>
<protectedstaticattributes title=""/>
<packagetypes title=""/>
<packagemethods title=""/>
<packagestaticmethods title=""/>
<packageattributes title=""/>
<packagestaticattributes title=""/>
<properties title=""/>
<events title=""/>
<privatetypes title=""/>
<privateslots title=""/>
<privatemethods title=""/>
<privatestaticmethods title=""/>
<privateattributes title=""/>
<privatestaticattributes title=""/>
<friends title=""/>
<related title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<enums title=""/>
<services title=""/>
<interfaces title=""/>
<constructors title=""/>
<functions title=""/>
<related title=""/>
<variables title=""/>
<properties title=""/>
<events title=""/>
</memberdef>
<allmemberslink visible="yes"/>
<usedfiles visible="$SHOW_USED_FILES"/>
<authorsection visible="yes"/>
</class>
<!-- Layout definition for a namespace page -->
<namespace>
<briefdescription visible="yes"/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<concepts visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<properties title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<properties title=""/>
</memberdef>
<authorsection visible="yes"/>
</namespace>
<!-- Layout definition for a concept page -->
<concept>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<definition visible="yes" title=""/>
<detaileddescription title=""/>
<authorsection visible="yes"/>
</concept>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="yes"/>
<includedbygraph visible="yes"/>
<sourcelink visible="yes"/>
<memberdecl>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<properties title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<properties title=""/>
</memberdef>
<authorsection/>
</file>
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="yes"/>
<groupgraph visible="yes"/>
<memberdecl>
<nestedgroups visible="yes" title=""/>
<modules visible="yes" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<pagedocs/>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
</memberdef>
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a C++20 module page -->
<module>
<briefdescription visible="yes"/>
<exportedmodules visible="yes"/>
<memberdecl>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<enums title=""/>
<typedefs title=""/>
<functions title=""/>
<variables title=""/>
<membergroups title=""/>
</memberdecl>
<detaileddescription title=""/>
<memberdecl>
<files visible="yes"/>
</memberdecl>
</module>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>
<directorygraph visible="yes"/>
<memberdecl>
<dirs visible="yes"/>
<files visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
</directory>
</doxygenlayout>

35
doc/Makefile Normal file
View File

@@ -0,0 +1,35 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile doxy
@echo "building from the makefile"
$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@echo "overwrite doxygen files"
mkdir -p "$(BUILDDIR)"
if [ -d "$(BUILDDIR)/html/doxygen" ] ; then rm -rf "$(BUILDDIR)/html/doxygen" ; fi
cp -favr doxygen/html "$(BUILDDIR)/html/doxygen"
doxy:
# ensure submodules are checked out
if [ ! -f $(shell pwd)/../ext/c4core/cmake/c4Project.cmake ] ; then echo "ERROR: submodules not present" ; exit 1 ; fi
if [ ! -f $(shell pwd)/../ext/c4core/src/c4/charconv.hpp ] ; then echo "ERROR: submodules not present" ; exit 1 ; fi
doxygen Doxyfile
clean:
rm -rf doxygen/
if [ ! -z "$(BUILDDIR)" ] ; then rm -rf "$(BUILDDIR)" ; fi

129
doc/conf.py Normal file
View File

@@ -0,0 +1,129 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'rapidyaml'
copyright = '2018-2024 Joao Paulo Magalhaes <dev@jpmag.me>'
author = 'Joao Paulo Magalhaes <dev@jpmag.me>'
release = '0.5.0'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"myst_parser",
]
templates_path = ['_templates']
exclude_patterns = ['_build', 'doxygen', 'Thumbs.db', '.DS_Store']
# Tell sphinx what the primary language being documented is.
primary_domain = 'cpp'
# Tell sphinx what the pygments highlight language should be.
highlight_language = 'cpp'
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
#html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
exclude_patterns += ["doxy_*.md"]
# ----------------------------------------------------------------------------
# Overwrite with the Doxygen documentation
# https://stackoverflow.com/a/41199722
"""
exclude_patterns += ["main.md", "include/*rst"]
import subprocess
this_dir = os.path.dirname(__file__)
subprocess.check_call(['doxygen', 'Doxyfile'], cwd=this_dir)
html_extra_path = ['./doxygen/html']
"""
# ----------------------------------------------------------------------------
# Setup the breathe extension
# https://breathe.readthedocs.io/en/latest/
# disabled as it was not working well with the groups
"""
extensions.append("breathe")
this_dir = os.path.dirname(__file__)
xml_dir = os.path.join(this_dir, "doxygen/xml")
assert os.path.exists(xml_dir)
breathe_projects = {
"rapidyaml": xml_dir
}
breathe_default_project = "rapidyaml"
"""
# ----------------------------------------------------------------------------
# Setup the doxyrest extension
# https://vovkos.github.io/doxyrest/manual/basic.html
# https://github.com/vovkos/doxyrest
'''
# To get the binary:
# git clone --recursive https://github.com/vovkos/doxyrest_b
# cd doxyrest_b
# cmany i
# put the doxyrest dir in the path
import shutil
import sys
doxyrest_exe = shutil.which('doxyrest')
doxyrest_dir = os.path.abspath(os.path.dirname(os.path.dirname(doxyrest_exe)))
doxyrest_sphinx_dir = os.path.join(doxyrest_dir, "share/doxyrest/sphinx")
sys.path.insert(1, doxyrest_sphinx_dir)
extensions += ['doxyrest', 'cpplexer']
# commands to run
"""
set -xe ; \
cd ~/proj/rapidyaml ; \
cmany c -t Debug -c clang++ -V RYML_DEV=OFF -V RYML_BUILD_TESTS=ON ; \
cp -fav build/linux-x86_64-clangxx16.0-Debug/compile_commands.json doc ; \
cd doc ; \
make doxy ; \
export PATH=/home/jpmag/proj/doxyrest_b/install/linux-x86_64-gxx13.2-Release/bin:$PATH ; \
doxyrest -h ; \
doxyrest -c doxyrest-config.lua ; \
make html
"""
'''
# ----------------------------------------------------------------------------
# Setup the exhale extension
# https://exhale.readthedocs.io/en/latest/quickstart.html
# disabled as it was just not working will
"""
#extensions.append("exhale")
exhale_args = {
# These arguments are required
"containmentFolder": "./api",
"rootFileName": "library_root.rst",
"doxygenStripFromPath": "..",
# Heavily encouraged optional argument (see docs)
"rootFileTitle": "Library API",
# Suggested optional arguments
"createTreeView": True,
# TIP: if using the sphinx-bootstrap-theme, you need
# "treeViewIsBootstrap": True,
"exhaleExecutesDoxygen": True,
"exhaleDoxygenStdin": "INPUT = ../src"
}
"""

55
doc/doxy_main.md Normal file
View File

@@ -0,0 +1,55 @@
# rapidyaml
* Begin by looking at the [project's README](https://github.com/biojppm/rapidyaml/blob/v0.5.0/README.md)
* [Documentation page](https://rapidyaml.readthedocs.org)
* Next, skim the docs for the @ref doc_quickstart sample.
* Good! Now the main ryml topics:
* @ref doc_parse - how to parse YAML/JSON into a tree
* @ref doc_emit - how to emit YAML/JSON from an existing tree
* How to visit and create ryml trees:
* @ref doc_node_type
* @ref doc_tree
* @ref doc_node_classes
* For serialization/deserialization:
* See @ref sample_scalar_types for when the type is scalar (a leaf node in the YAML tree, containing a string representation):
* See examples on how to @ref sample_to_chars_scalar
* See examples on how to @ref sample_from_chars_scalar
* See the sample @ref sample::sample_user_scalar_types
* When serializing floating point values in C++ earlier than 17,
be aware that there may be a truncation of the precision with
the default to_chars implementation. To enforce a particular
precision, use for example @ref c4::fmt::real, or call
directly @ref c4::ftoa or @ref c4::dtoa, or any other method
(remember that ryml only stores the final string, so nothing
prevents you from creating it). See the relevant sample: @ref
sample::sample_float_precision.
* See @ref sample_container_types for when the type is a container (ie, a node which has children, which may themselves be containers).
* See the sample @ref sample::sample_user_container_types
* ryml does not use any STL containers internally, but it can be
used to serialize and deserialize these containers. See @ref
sample::sample_std_types for an example. See the header @ref
ryml_std.hpp and also the headers it includes.
* @ref doc_tag_utils - how to resolve tags
* @ref doc_callbacks - how to set up error/allocation/deallocation
callbacks either globally for the library, or for specific objects
such as @ref c4::yml::Tree or @ref c4::yml::Parser
* rapidyaml uses these facilities from [c4core](https://github.com/biojppm/c4core), so their documentation
is also shown here:
* @ref doc_substr
* @ref doc_charconv
* @ref doc_format_utils
* @ref doc_base64
You should also be aware that the @ref doc_charconv facilities in
[c4core](https://github.com/biojppm/c4core) are extremely fast, and
generally outperform the fastest equivalent facilities in the standard
library by a significant margin; refer to the documentation of @ref
doc_charconv for further details. For example:
<table>
<caption id="xtoa-i64">xtoa,int64_t</caption>
<tr><th>g++12, linux <th>Visual Studio 2019
<tr><td> \image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png <td> \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png
</table>

463
doc/doxyrest-config.lua Normal file
View File

@@ -0,0 +1,463 @@
--------------------------------------------------------------------------------
--
-- This file is part of the Doxyrest toolkit.
--
-- Doxyrest is distributed under the MIT license.
-- For details see accompanying license.txt file,
-- the public copy of which is also available at:
-- http://tibbo.com/downloads/archive/doxyrest/license.txt
--
--------------------------------------------------------------------------------
--!
--! \defgroup frame-config
--! \grouporder 2
--! \title Frame Settings
--!
--! This section describes frame settings controlling input and output paths,
--! titles, force-includes, declaration coding style, etc.
--!
--! A default ``doxyrest-config.lua`` file for standard frames can be found at
--! ``$DOXYREST_FRAME_DIR/doxyrest-config.lua``. Copy it to your project
--! directory and then adjust all the necessary parameters.
--!
--! @{
--!
--!
--! Table containing a list of frame directories. All frame files will be
--! searched in directories -- and in the sequence -- specified here.
--!
FRAME_DIR_LIST = {"include"}
--!
--! The output master (index) reStructuredText file. Usually, the index frame
--! also generates auxillary files -- they will be placed next to the master
--! file. The command line option ``-f`` *overrides* this value.
--! If neither ``FRAME_FILE`` nor ``-f`` is specified, ``index.rst.in`` will be
--! used as the default frame file.
--!
FRAME_FILE = "index.rst"
--!
--! The input master (index) XML file. Specifying it here allows calling
--! ``doxyrest`` without parameters; otherwise, the master XML *must* be passed
--! via the command line. If both ``INPUT_FILE`` and command line parameter are
--! specified, the command line takes precedence.
--!
INPUT_FILE = "doxygen/xml/index.xml"
--!
--! The output master (index) reStructuredText file. Usually, the index frame
--! also generates auxillary files -- they will be placed next to the master
--! file. The command line option ``-o`` *overrides* this value. If neither
--! ``OUTPUT_FILE`` nor ``-o`` is specified, ``index.rst`` will be used as
--! the default output master file.
--!
OUTPUT_FILE = nil
--!
--! File with project-specific reStructuredText definitions. When non``nil``,
--! this file will be included at the top of every generated ``.rst`` file.
--!
FORCE_INCLUDE_FILE = nil
--!
--! If you want to add extra reStructuredText documentation pages, do so
--! by adding them to this list.
--!
EXTRA_PAGE_LIST = {}
--!
--! The title of the main (index) page. This only is used when ``INTRO_FILE``
--! is not set (otherwise, the title of intro file will be used).
--!
INDEX_TITLE = "My Project Documentation asdasdasdxs"
--!
--! File with project introduction (reStructuredText). When non-nil, this file
--! will be included into ``index.rst`` file and NOT added to the list of other
--! documentation pages.
--!
INTRO_FILE = nil
--!
--! Specify whether to sort groups lexicographically (by ``title``) or
--! logically (by ``id``). To maintain the original order (in which a group has
--! been seen in the XML database), use ``originalIdx``.
--!
--! Omitting ``SORT_GROUPS_BY`` (or setting it to ``nil``) results in groups
--! being sorted by ``title``.
--!
SORT_GROUPS_BY = "title"
--[[!
By default, the page for the global namespace page will be called
"Global Namespace" and will contain no description except that for the
global compounds and members.
It's possible to override this behaviour by defining an auxillary compound
(page or group) with a special ``id``; this page/group may contain a
user-defined title, a brief description and a detailed description. Use
``GLOBAL_AUX_COMPOUND_ID`` to define this special id.
.. note::
To make sure you use the correct **Doxygen** XML ID of the group/page,
find the definition of the group in one of ``.xml`` files and copy
the value of ``id`` attribute.
For example, if the group was declared as ``\defgroup global`` then
the its ``id`` will probably be either ``group_<your-group-name>`` or
``group__<your-group-name>``.
]]
GLOBAL_AUX_COMPOUND_ID = "group_global"
--[[!
Doxyrest offers a workaround for the lack of footnotes in Doxygen by
getting documentation blocks for specially named pseudo-members and
converting those into footnotes.
``FOOTNOTE_MEMBER_PREFIX`` specifies the name prefix for such
pseudo-members. If it is set to ``nil`` or an empty string, Doxyrest
will not attempt to convert any members to footnotes.
\sa :ref:`footnotes`
]]
FOOTNOTE_MEMBER_PREFIX = nil
--!
--! Specify the main language of your project; this string will be used for
--! the reStructuredText ``.. code-block::`` sections and for conditional
--! formatting of module item declarations.
--!
LANGUAGE = cpp
--!
--! Convert ``\verbatim`` sections in doxy-comments to ``.. code-block::``
--! sections in the output reStructuredText. The string value of
--! ``VERBATIM_TO_CODE_BLOCK`` will be used as the language of
--! ``.. code-block::`` section. By default, it's ``"none"`` which results in
--! no syntax highlighting. To disable conversion at all, use ``nil``.
--!
VERBATIM_TO_CODE_BLOCK = "none"
--!
--! If the original doxy comments contain asterisks, they have to be escaped in
--! reStructuredText (asterisks are used to mark **bold** or *italic* blocks).
--!
ESCAPE_ASTERISKS = false
--!
--! If the original doxy comments contain pipe characters ``|``, they have to be
--! escaped in reStructuredText (pipes are used for substitution references).
--!
ESCAPE_PIPES = false
--!
--! If the original doxy comments contain trailingasterisks, they have to be
--! escaped in reStructuredText (trailing underscores are used for internal
--! links).
--!
ESCAPE_TRAILING_UNDERSCORES = false
--!
--! Exclude items declared in specific locations. Use a regular expression to
--! define a mask of directories/source files to completely exclude from the
--! final documentation. For example, ``.*/impl/.*lua$`` will exclude all
--! ``.lua`` files located in ``impl/`` directory.
--!
EXCLUDE_LOCATION_PATTERN = nil
--!
--! Exclude variables and functions without any documentation (no doxy-comments).
--!
EXCLUDE_UNDOCUMENTED_ITEMS = false
--!
--! \subgroup
--!
--! By default, Doxyrest tries to translate Doxygen ``\section``,
--! ``\subsection``, and ``\subsubsection`` commands (as well as the
--! ``<heading level="n">`` blocks generated by Doxygen from Markdown titles
--! inside comments) into reStructuredText titles.
--!
--! This sometimes leads to problems because Doxygen headings may appear outside
--! of the global scope (e.g. inside lists) while reStructuredText titles are
--! only allowed at the global scope. Another issue is Doxygen headings may
--! yield inconsistent title structure (e.g. a title level 1 followed by level
--! 3).
--!
--! If you run into these issues, use ``SECTION_TO_RUBRIC`` or
--! ``HEADING_TO_RUBRIC`` to always convert Doxygen sections or ``<heading>``
--! blocks or into reStructuredText ``.. rubric::`` directives. This yields
--! uni-level headings, but solves both aforementioned problems.
--!
SECTION_TO_RUBRIC = false
HEADING_TO_RUBRIC = false
--[[!
By default, Doxyrest frames build a Python dictionary to be used later on by
the ``:cref:`` (code-reference) role. This database maps language-specific
qualified names of items to their IDs.
This, together with setting the Sphinx ``default_role`` to ``cref``, allows
to conveniently reference items from Doxy-comments or regular ``.rst`` files
as such:
.. code-block:: none
The `ui::Dialog` class is a base to the `ui::FileDialog` class.
However, if this facility is not used, building (and loading) of the
cref-database can be omitted to save both space and time.
]]
CREF_DB = false
--[[!
Exclude items with higher protection level than ``PROTECTION_FILTER``:
1. ``public``
2. ``protected``
3. ``private``
4. ``package``
By default, only public items are included into documentation.
]]
PROTECTION_FILTER = "public"
--!
--! In many projects empty defines are *only* used as include-guards (and as
--! such, should be excluded from the documentation). If this is not the case
--! and empty defines should be kept in the final documentation, change this
--! setting to ``false``.
--!
EXCLUDE_EMPTY_DEFINES = true
--!
--! If non-``nil``, each define will be checked using this regular expression
--! and if its name matches, this define will be excluded from the documentation.
--!
EXCLUDE_DEFINE_PATTERN = nil
--!
--! Usually providing documentation blocks for default constructors is
--! not necessary (as to avoid redundant meaningless "Constructs a new object"
--! paragraphs). Change this to ``false`` if default constructors *should* be
--! included.
--!
EXCLUDE_DEFAULT_CONSTRUCTORS = true
--!
--! Usually providing documentation blocks for a destructors is
--! not necessary (as to avoid redundant meaningless "Destructs an object"
--! paragraphs). Change this to ``false`` if destructors *should* be
--! included.
--!
EXCLUDE_DESTRUCTORS = true
--[[!
Usually providing documentation blocks for primitive C typedefs such as:
.. code-block:: C
typedef struct S S;
is not necessary. Change this to ``false`` if such typedefs *should* be
included.
]]
EXCLUDE_PRIMITIVE_TYPEDEFS = true
--!
--! For a base class/struct, show all the types directly derived from it.
--!
SHOW_DIRECT_DESCENDANTS = true
--[[!
\subgroup
Insert space between function name and parameter list like this:
.. code-block:: C
void foo ();
By default, ``PRE_PARAM_LIST_SPACE`` is ``false`` which yields:
.. code-block:: C
void foo();
]]
PRE_PARAM_LIST_SPACE = false
PRE_OPERATOR_NAME_SPACE = true
PRE_OPERATOR_PARAM_LIST_SPACE = true
--[[!
Insert a new line before the body of a enum, struct, class, etc.
When ``PRE_BODY_NL`` is ``false``:
.. code-block:: cpp
class MyClass {
...
}
When ``PRE_BODY_NL`` is ``true``:
.. code-block:: cpp
class MyClass
{
...
}
]]
PRE_BODY_NL = false
--[[!
Use multi-line parameter lists in function declarations if parameter count is
greater than this threshold. ``nil`` means don't use parameter count
threshold.
For example, when ``ML_PARAM_LIST_COUNT_THRESHOLD`` is ``2``, the function
declarations will look as such:
.. code-block:: C
void fn0();
void fn1(int a);
void fn2(int a, int b);
void fn3(
int a,
int b
int c
);
]]
ML_PARAM_LIST_COUNT_THRESHOLD = nil
--[[!
Use multi-line parameter lists in function declarations if single-line
declaration length parameter count is greater than this threshold.
``nil`` means don't use length threshold.
Similar to ``ML_PARAM_LIST_COUNT_THRESHOLD``, but the threshold parameter
here is *declaration length* and not *declaration parameter count*.
Example:
.. code-block:: C
void foo(int a, int b, int c);
void bar(
int veryLongParameterName,
int anotherVeryLongParameterName
);
]]
ML_PARAM_LIST_LENGTH_THRESHOLD = 80
--[[!
Use multi-line specifier-modifier lists in function declarations, i.e
allocate a dedicated line for each type specifier/morifier.
For example, when ``ML_SPECIFIER_MODIFIER_LIST`` is ``true``, the function
declarations will look as such:
.. code-block:: C
void
foo();
static
bool
__cdecl
bar(int a);
]]
ML_SPECIFIER_MODIFIER_LIST = false
--[[!
Use the new C++11 syntax for ``typedef``-s:
When ``TYPEDEF_TO_USING`` is ``false``:
.. code-block:: cpp
typedef unsigned int uint_t;
typedef void Foo(int);
typedef void (*FooPtr)(int);
When ``TYPEDEF_TO_USING`` is ``true``:
.. code-block:: cpp
using uint_t = unsigned int;
using Foo typedef void (int);
using FooPtr typedef void (*)(int);
]]
TYPEDEF_TO_USING = false
--[[!
Sometimes, it's required to redirect a Doxygen link to some external location.
In this case, add an entry to ``EXTERNAL_CREF_DB`` with the target URL, e.g.:
.. code-block:: lua
EXTERNAL_CREF_DB =
{
[ "jnc.Scheduler" ] = "https://vovkos.github.io/jancy/stdlib/class_jnc_Scheduler.html",
[ "strlen"] = "https://vovkos.github.io/jancy/stdlib/group_crt.html#doxid-function-strlen"
-- ...
}
The key of the map is an ``importid`` attribute. This is a non-standard Doxygen
attribute; Jancy compiler generates is when a referenced item is contained in an
imported extensions library (``.jncx``)
]]
EXTERNAL_CREF_DB = {}
--[[!
Usually providing documentation blocks for Lua ``local`` variables or
functions is not desired. Change this to ``false`` if local items
*should* be included in the final documentation.
]]
EXCLUDE_LUA_LOCALS = true
--! @}

62
doc/index.rst Normal file
View File

@@ -0,0 +1,62 @@
rapidyaml
=========
Or ryml, for short. ryml is a C++ library to parse and emit YAML,
and do it fast, on everything from x64 to bare-metal chips without
operating system.
And if you're wondering whether ryml's speed comes at the cost of
usability, you need not: with ryml, you can have your cake
and eat it too. Being rapid is definitely NOT the same as being
unpractical, so ryml was written with easy AND efficient usage in
mind.
ryml is available as a single header file, or it can be used as a
simple library with cmake -- both separately (ie
build -> install -> ``find_package()``) or together with your project (ie with
``add_subdirectory()``). (See below for examples).
ryml can use custom global and per-tree memory allocators and error
handler callbacks, and is exception-agnostic. ryml provides a default
implementation for the allocator (using ``std::malloc()``) and error
handlers (using either exceptions, ``longjmp()`` or ``std::abort()``),
but you can opt out of and provide your own memory allocation and
error callbacks.
For maximum portability, ryml does not depend on the STL, ie, it does
not use any std container as part of its data structures. But ryml can
serialize and deserialize these containers into the data tree, with
the use of optional headers. ryml ships with `c4core
<https://github.com/biojppm/c4core>`_, a small C++ utilities
multiplatform library.
ryml is written in C++11, and compiles cleanly with:
* Visual Studio 2015 and later
* clang++ 3.9 and later
* g++ 4.8 and later
* Intel Compiler
.. note::
If you are looking to use an YAML tree with override facilities
as the configuration for your programs, take a look at `c4conf
<https://github.com/biojppm/c4conf>`_.
Table of contents
=================
.. toctree::
:maxdepth: 3
./sphinx_api_documentation
./sphinx_quicklinks
./sphinx_is_it_rapid
./sphinx_yaml_standard
./sphinx_using
./sphinx_other_languages
./sphinx_alternative_libraries

5
doc/requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
sphinx-rtd-theme
sphinx-mdinclude
myst_parser
breathe
exhale

View File

@@ -0,0 +1,36 @@
Alternative libraries
=====================
Why this library? Because none of the existing libraries was quite what
I wanted. When I started this project in 2018, I was aware of these two
alternative C/C++ libraries:
- `libyaml <https://github.com/yaml/libyaml>`__. This is a bare C
library. It does not create a representation of the data tree, so I
dont see it as practical. My initial idea was to wrap parsing and
emitting around libyamls convenient event handling, but to my
surprise I found out it makes heavy use of allocations and string
duplications when parsing. I briefly pondered on sending PRs to
reduce these allocation needs, but not having a permanent tree to
store the parsed data was too much of a downside.
- `yaml-cpp <https://github.com/jbeder/yaml-cpp>`__. This library may
be full of functionality, but is heavy on the use of
node-pointer-based structures like ``std::map``, allocations, string
copies, polymorphism and slow C++ stream serializations. This is
generally a sure way of making your code slower, and strong evidence
of this can be seen in the benchmark results above.
Recently `libfyaml <https://github.com/pantoniou/libfyaml>`__ appeared.
This is a newer C library, fully conformant to the YAML standard with an
amazing 100% success in the test suite; it also offers the tree as a
data structure. As a downside, it does not work in Windows, and it is
also multiple times slower parsing and emitting.
When performance and low latency are important, using contiguous
structures for better cache behavior and to prevent the library from
trampling caches, parsing in place and using non-owning strings is of
central importance. Hence this Rapid YAML library which, with minimal
compromise, bridges the gap from efficiency to usability. This library
takes inspiration from
`RapidJSON <https://github.com/Tencent/rapidjson>`__ and
`RapidXML <http://rapidxml.sourceforge.net/>`__.

View File

@@ -0,0 +1,52 @@
API documentation
=================
.. note::
Please refer to the `Doxygen documentation
<./doxygen/index.html>`_, and please read the `overview sample
<doxygen/group__doc__quickstart.html#ga1118e0fb55403d0e061626cf8a07cc0c>`_;
it will quickly teach you the basic concepts and caveats, which
will save you a lot of time.
.. include:: sphinx_try_quickstart.rst
Here's a short selection from the quickstart overview (`see on
doxygen <doxygen/group__doc__quickstart.html>`_ / `see full code
on github <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/quickstart.cpp>`_):
.. code-block:: c++
// Parse YAML code in place, potentially mutating the buffer:
char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
ryml::Tree tree = ryml::parse_in_place(yml_buf);
// read from the tree:
ryml::NodeRef bar = tree["bar"];
CHECK(bar[0].val() == "2");
CHECK(bar[1].val() == "3");
CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
CHECK(bar[1].val().str == yml_buf + 17);
// deserializing:
int bar0 = 0, bar1 = 0;
bar[0] >> bar0;
bar[1] >> bar1;
CHECK(bar0 == 2);
CHECK(bar1 == 3);
// serializing:
bar[0] << 10; // creates a string in the tree's arena
bar[1] << 11;
CHECK(bar[0].val() == "10");
CHECK(bar[1].val() == "11");
// add nodes
bar.append_child() << 12;
CHECK(bar[1].val() == "12");
// emit to stdout
std::cout << tree;
// emit to std::string
CHECK(emitrs_yaml<std::string>(tree) == "{foo: 1,bar: [10,11,12],john: doe}");

160
doc/sphinx_is_it_rapid.rst Normal file
View File

@@ -0,0 +1,160 @@
Is it rapid?
============
You bet!
Compared against the other existing YAML libraries for
C/C++
- ryml is in general between 2 and 3 times faster than
`libyaml <https://github.com/yaml/libyaml>`__
- ryml is in general between 10 and 70 times faster than
`yaml-cpp <https://github.com/jbeder/yaml-cpp>`__, and in some cases as
much as 100x and `even
200x <https://github.com/biojppm/c4core/pull/16#issuecomment-700972614>`__
faster.
.. Note::
While a more effective way of showing the benchmark results is not
available yet, you can browse through the `runs of the benchmark
workflow in the CI
<https://github.com/biojppm/rapidyaml/actions/workflows/benchmarks.yml>`__
to scroll through the results for yourself.
Also, if you have a case where ryml behaves very nicely or not as
nicely as claimed here, we would definitely like to see it! Please
open an issue, or submit a pull request adding the file to
`bm/cases
<https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/cases>`__, or
just send us the files.
`Heres a parsing benchmark
<https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/bm_parse.cpp>`__. Using
different approaches within ryml (in-situ/read-only vs. with/without
reuse), a YAML / JSON buffer is repeatedly parsed, and compared
against other libraries.
Comparison with yaml-cpp
------------------------
The first result set is for Windows, and is using a `appveyor.yml config
file <https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/cases/appveyor.yml>`__. A comparison of these results is
summarized on the table below:
=========================== ===== ======= ==========
Read rates (MB/s) ryml yamlcpp compared
=========================== ===== ======= ==========
appveyor / vs2017 / Release 101.5 5.3 20x / 5.2%
appveyor / vs2017 / Debug 6.4 0.0844 76x / 1.3%
=========================== ===== ======= ==========
The next set of results is taken in Linux, comparing g++ 8.2 and clang++
7.0.1 in parsing a YAML buffer from a `travis.yml config
file <https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/cases/travis.yml>`__ or a JSON buffer from a
`compile_commands.json file <https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/cases/compile_commands.json>`__. You
can `see the full results
here <https://github.com/biojppm/rapidyaml/blob/v0.5.0/results/parse.linux.i7_6800K.md>`__. Summarizing:
========================== ===== ======= ========
Read rates (MB/s) ryml yamlcpp compared
========================== ===== ======= ========
json / clang++ / Release 453.5 15.1 30x / 3%
json / g++ / Release 430.5 16.3 26x / 4%
json / clang++ / Debug 61.9 1.63 38x / 3%
json / g++ / Debug 72.6 1.53 47x / 2%
travis / clang++ / Release 131.6 8.08 16x / 6%
travis / g++ / Release 176.4 8.23 21x / 5%
travis / clang++ / Debug 10.2 1.08 9x / 1%
travis / g++ / Debug 12.5 1.01 12x / 8%
========================== ===== ======= ========
The 450MB/s read rate for JSON puts ryml squarely in the same ballpark
as `RapidJSON <https://github.com/Tencent/rapidjson>`__ and other fast
json readers (`data from
here <https://lemire.me/blog/2018/05/03/how-fast-can-you-parse-json/>`__).
Even parsing full YAML is at ~150MB/s, which is still in that
performance ballpark, albeit at its lower end. This is something to be
proud of, as the YAML specification is much more complex than JSON:
`23449 vs 1969
words <https://www.arp242.net/yaml-config.html#its-pretty-complex>`__.
Performance reading JSON
------------------------
So how does ryml compare against other JSON readers? Well, it may not
be the fastest, but it's definitely ahead of the pack!
The benchmark is the `same as above <https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/bm_parse.cpp>`__, and it is
reading the
`compile_commands.json <https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/cases/compile_commands.json>`__, The
``_arena`` suffix notes parsing a read-only buffer (so buffer copies are
performed), while the ``_inplace`` suffix means that the source buffer
can be parsed in place. The ``_reuse`` means the data tree and/or parser
are reused on each benchmark repeat.
Heres what we get with g++ 8.2:
=================== ============ ==========
Benchmark Release,MB/s Debug,MB/s
=================== ============ ==========
rapidjson_arena 509.9 43.4
rapidjson_inplace 1329.4 68.2
sajson_inplace 434.2 176.5
sajson_arena 430.7 175.6
jsoncpp_arena 183.6 ? 187.9
nlohmann_json_arena 115.8 21.5
yamlcpp_arena 16.6 1.6
libyaml_arena 113.9 35.7
libyaml_arena_reuse 114.6 35.9
ryml_arena 388.6 36.9
ryml_inplace 393.7 36.9
ryml_arena_reuse 446.2 74.6
ryml_inplace_reuse 457.1 74.9
=================== ============ ==========
You can verify that (at least for this test) ryml beats most json
parsers at their own game, with the only exception of
`rapidjson <https://github.com/Tencent/rapidjson>`__. And actually, in
Debug, `rapidjson <https://github.com/Tencent/rapidjson>`__ is slower
than ryml, and `sajson <https://github.com/chadaustin/sajson>`__ manages
to be faster (but not sure about jsoncpp; need to scrutinize there the
suspicious fact that the Debug result is faster than the Release
result).
Performance emitting
--------------------
`Emitting benchmarks <https://github.com/biojppm/rapidyaml/blob/v0.5.0/bm/bm_emit.cpp>`__ also show similar speedups from
the existing libraries, also anecdotally reported by some users `(eg,
heres a user reporting 25x speedup from
yaml-cpp) <https://github.com/biojppm/rapidyaml/issues/28#issue-553855608>`__.
Also, in some cases (eg, block folded multiline scalars), the speedup is
as high as 200x (eg, 7.3MB/s -> 1.416MG/s).
Serialization performance
-------------------------
ryml uses the `charconv facilities
<doxygen/group__doc__charconv.html>`__ from `c4core
<https://github.com/biojppm/c4core>`__; these functions are blazing
fast, and generally outperform the fastest equivalent facilities in
the standard library by a significant margin; refer to the
documentation of `charconv facilities
<doxygen/group__doc__charconv.html>`__ for further details. For
example, here are some results for `ryml::xtoa<int64>` (`documentation
<doxygen/group__doc__xtoa.html>`__):
.. |xtoa64_linux| image:: doxygen/linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png
:target: doxygen/linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png
.. |xtoa64_windows| image:: doxygen/windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png
:target: doxygen/windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png
+----------------+--------------------+
| g++12, linux | Visual Studio 2019 |
+================+====================+
| |xtoa64_linux| | |xtoa64_windows| |
+----------------+--------------------+

View File

@@ -0,0 +1,118 @@
Other languages
===============
One of the aims of ryml is to provide an efficient YAML API for other
languages. JavaScript is fully available, and there is already a cursory
implementation for Python using only the low-level API. After ironing
out the general approach, other languages are likely to follow (all of
this is possible because were using `SWIG <http://www.swig.org/>`__,
which makes it easy to do so).
JavaScript
----------
A JavaScript+WebAssembly port is available, compiled through
`emscripten <https://emscripten.org/>`__.
Python
------
(Note that this is a work in progress. Additions will be made and things
will be changed.) With that said, heres an example of the Python API:
.. code:: python
import ryml
# ryml cannot accept strings because it does not take ownership of the
# source buffer; only bytes or bytearrays are accepted.
src = b"{HELLO: a, foo: b, bar: c, baz: d, seq: [0, 1, 2, 3]}"
def check(tree):
# for now, only the index-based low-level API is implemented
assert tree.size() == 10
assert tree.root_id() == 0
assert tree.first_child(0) == 1
assert tree.next_sibling(1) == 2
assert tree.first_sibling(5) == 2
assert tree.last_sibling(1) == 5
# use bytes objects for queries
assert tree.find_child(0, b"foo") == 1
assert tree.key(1) == b"foo")
assert tree.val(1) == b"b")
assert tree.find_child(0, b"seq") == 5
assert tree.is_seq(5)
# to loop over children:
for i, ch in enumerate(ryml.children(tree, 5)):
assert tree.val(ch) == [b"0", b"1", b"2", b"3"][i]
# to loop over siblings:
for i, sib in enumerate(ryml.siblings(tree, 5)):
assert tree.key(sib) == [b"HELLO", b"foo", b"bar", b"baz", b"seq"][i]
# to walk over all elements
visited = [False] * tree.size()
for n, indentation_level in ryml.walk(tree):
# just a dumb emitter
left = " " * indentation_level
if tree.is_keyval(n):
print("{}{}: {}".format(left, tree.key(n), tree.val(n))
elif tree.is_val(n):
print("- {}".format(left, tree.val(n))
elif tree.is_keyseq(n):
print("{}{}:".format(left, tree.key(n))
visited[inode] = True
assert False not in visited
# NOTE about encoding!
k = tree.get_key(5)
print(k) # '<memory at 0x7f80d5b93f48>'
assert k == b"seq" # ok, as expected
assert k != "seq" # not ok - NOTE THIS!
assert str(k) != "seq" # not ok
assert str(k, "utf8") == "seq" # ok again
# parse immutable buffer
tree = ryml.parse_in_arena(src)
check(tree) # OK
# parse mutable buffer.
# requires bytearrays or objects offering writeable memory
mutable = bytearray(src)
tree = ryml.parse_in_place(mutable)
check(tree) # OK
As expected, the performance results so far are encouraging. In a
`timeit benchmark <api/python/parse_bm.py>`__ compared against
`PyYaml <https://pyyaml.org/>`__ and
`ruamel.yaml <https://yaml.readthedocs.io/en/latest/>`__, ryml parses
quicker by generally 100x and up to 400x:
::
+----------------------------------------+-------+----------+----------+-----------+
| style_seqs_blck_outer1000_inner100.yml | count | time(ms) | avg(ms) | avg(MB/s) |
+----------------------------------------+-------+----------+----------+-----------+
| parse:RuamelYamlParse | 1 | 4564.812 | 4564.812 | 0.173 |
| parse:PyYamlParse | 1 | 2815.426 | 2815.426 | 0.280 |
| parse:RymlParseInArena | 38 | 588.024 | 15.474 | 50.988 |
| parse:RymlParseInArenaReuse | 38 | 466.997 | 12.289 | 64.202 |
| parse:RymlParseInPlace | 38 | 579.770 | 15.257 | 51.714 |
| parse:RymlParseInPlaceReuse | 38 | 462.932 | 12.182 | 64.765 |
+----------------------------------------+-------+----------+----------+-----------+
(Note that the parse timings above are somewhat biased towards ryml,
because it does not perform any type conversions in Python-land: return
types are merely ``memoryviews`` to the source buffer, possibly copied
to the trees arena).
As for emitting, the improvement can be as high as 3000x:
::
+----------------------------------------+-------+-----------+-----------+-----------+
| style_maps_blck_outer1000_inner100.yml | count | time(ms) | avg(ms) | avg(MB/s) |
+----------------------------------------+-------+-----------+-----------+-----------+
| emit_yaml:RuamelYamlEmit | 1 | 18149.288 | 18149.288 | 0.054 |
| emit_yaml:PyYamlEmit | 1 | 2683.380 | 2683.380 | 0.365 |
| emit_yaml:RymlEmitToNewBuffer | 88 | 861.726 | 9.792 | 99.976 |
| emit_yaml:RymlEmitReuse | 88 | 437.931 | 4.976 | 196.725 |
+----------------------------------------+-------+-----------+-----------+-----------+

23
doc/sphinx_quicklinks.rst Normal file
View File

@@ -0,0 +1,23 @@
Quick links
===========
* `API documentation (Doxygen) <./doxygen/index.html>`_
* `Github repo <https://github.com/biojppm/rapidyaml>`_
* `Issues <https://github.com/biojppm/rapidyaml/issues>`_
* `Pull Requests <https://github.com/biojppm/rapidyaml/pull>`_
* Latest release: `0.5.0 <https://github.com/biojppm/rapidyaml/releases/tag/v0.5.0>`_
* `Release page [0.5.0] <https://github.com/biojppm/rapidyaml/releases/tag/v0.5.0>`_
* `README [0.5.0] <https://github.com/biojppm/rapidyaml/blob/v0.5.0/README.md>`_
* Since latest release (master branch):
* `README [master] <https://github.com/biojppm/rapidyaml/blob/master/README.md>`_
* `Changelog [master] <https://github.com/biojppm/rapidyaml/blob/master/changelog/current.md>`_

View File

@@ -0,0 +1,33 @@
.. note::
Try the quickstart!
First create this ``CMakeLists.txt``:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.13)
project(my-quickstart LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(ryml
GIT_REPOSITORY https://github.com/biojppm/rapidyaml.git
GIT_TAG v0.5.0
GIT_SHALLOW FALSE # ensure submodules are checked out
)
FetchContent_MakeAvailable(ryml)
add_executable(my-quickstart ${ryml_SOURCE_DIR}/samples/quickstart.cpp)
target_link_libraries(my-quickstart ryml::ryml)
add_custom_target(run my-quickstart
COMMAND $<TARGET_FILE:my-quickstart>
DEPENDS my-quickstart)
Now run the following commands in the same folder:
.. code-block:: bash
# configure the project
cmake -S . -B build
# build and run
cmake --build build --target ryml-quickstart -j
# optionally, open in your IDE
cmake --open build

221
doc/sphinx_using.rst Normal file
View File

@@ -0,0 +1,221 @@
Using ryml in your project
==========================
Quickstart build samples
------------------------
These samples show different ways of getting ryml into your application.
All the samples use `the same quickstart executable
source <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/quickstart.cpp>`__, but are built in different ways,
showing several alternatives to integrate ryml into your project. We
also encourage you to refer to the `quickstart
docs <doxygen/group__doc__quickstart.html>`__, which extensively cover
the functionality that you may want out of ryml.
If you're in a rush to start, try this:
.. include:: sphinx_try_quickstart.rst
For alternative ways, continue reading this section.
Each sample brings a ``run.sh`` script with the sequence of commands
required to successfully build and run the application (this is a bash
script and runs in Linux and MacOS, but it is also possible to run in
Windows via Git Bash or the WSL). Click on the links below to find out
more about each sample:
+-------------------------------------------------------------------------------------------------+----------------------------------+--------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------+
| Sample name | ryml is part of build? | cmake file | commands |
+=================================================================================================+==================================+==============================================================================================================+=============================================================================================================+
| `singleheader <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheader>`_ | | **yes** | `CMakeLists.txt <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheader/CMakeLists.txt>`_ | `run.sh <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheader/run.sh>`_ |
| | | ryml brought as a single | | |
| | | header, not as a library | | |
+-------------------------------------------------------------------------------------------------+----------------------------------+--------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------+
| `singleheaderlib <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheaderlib>`_ | | **yes** | `CMakeLists.txt <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheaderlib/CMakeLists.txt>`_ | | `run_shared.sh <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheaderlib/run_shared.sh>`_ |
| | | ryml brought as library | | | `run_static.sh <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/singleheaderlib/run_static.sh>`_ |
| | | but from the single header | | |
+-------------------------------------------------------------------------------------------------+----------------------------------+--------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------+
| `add_subdirectory <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/add_subdirectory>`_ | **yes** | `CMakeLists.txt <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/add_subdirectory/CMakeLists.txt>`_ | `run.sh <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/add_subdirectory/run.sh>`_ |
+-------------------------------------------------------------------------------------------------+----------------------------------+--------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------+
| `fetch_content <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/fetch_content>`_ | **yes** | `CMakeLists.txt <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/fetch_content/CMakeLists.txt>`_ | `run.sh <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/fetch_content/run.sh>`_ |
+-------------------------------------------------------------------------------------------------+----------------------------------+--------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------+
| `find_package <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/find_package>`_ | | **no** | `CMakeLists.txt <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/find_package/CMakeLists.txt>`_ | `run.sh <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/find_package/run.sh>`_ |
| | | needs prior install or package | | |
+-------------------------------------------------------------------------------------------------+----------------------------------+--------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------+
As a single-header
------------------
ryml is provided chiefly as a cmake library project, but it can also
be used as a single header file, and there is a `tool to amalgamate
<https://github.com/biojppm/rapidyaml/blob/v0.5.0/tools/amalgamate.py>`__
the code into a single header file. The amalgamated header file is
provided with each release, but you can also generate a customized
file suiting your particular needs (or commit):
.. code-block:: console
[user@host rapidyaml]$ python3 tools/amalgamate.py -h
usage: amalgamate.py [-h] [--c4core | --no-c4core] [--fastfloat | --no-fastfloat] [--stl | --no-stl] [output]
positional arguments:
output output file. defaults to stdout
optional arguments:
-h, --help show this help message and exit
--c4core amalgamate c4core together with ryml. this is the default.
--no-c4core amalgamate c4core together with ryml. the default is --c4core.
--fastfloat enable fastfloat library. this is the default.
--no-fastfloat enable fastfloat library. the default is --fastfloat.
--stl enable stl interop. this is the default.
--no-stl enable stl interop. the default is --stl.
The amalgamated header file contains all the function declarations and
definitions. To use it in the project, ``#include`` the header at will
in any header or source file in the project, but in one source file,
and only in that one source file, ``#define`` the macro
``RYML_SINGLE_HDR_DEFINE_NOW`` **before including the header**. This
will enable the function definitions. For example:
.. code-block:: console
// foo.h
#include <ryml_all.hpp>
// foo.cpp
// ensure that foo.h is not included before this define!
#define RYML_SINGLE_HDR_DEFINE_NOW
#include <foo.h>
If you wish to package the single header into a shared library, then
you will need to define the preprocessor symbol ``RYML_SHARED`` during
compilation.
From a package manager
----------------------
ryml is available in most package managers (thanks to all the
contributors!) and linux distributions.
.. note::
These packages are maintained downstream of this repository, so if
you have issues with the package, file a report with the respective
package maintainer.
Here's a quick roundup (possibly outdated):
* Package managers
* `conan <https://conan.io/center/recipes/rapidyaml>`_
* `vcpkg <https://vcpkg.io/en/packages.html>`_: ``vcpkg install ryml``
* `PyPI <https://pypi.org/project/rapidyaml/>`_
* Linux distributions:
* Arch Linux/Manjaro:
* `rapidyaml (aarch64) <https://archlinuxarm.org/packages/aarch64/rapidyaml>`_
* `rapidyaml-git (AUR) <https://aur.archlinux.org/packages/rapidyaml-git/>`_
* `python-rapidyaml-git (AUR) <https://aur.archlinux.org/packages/python-rapidyaml-git/>`_
* `Fedora Linux <https://getfedora.org/>`_/`EPEL <https://docs.fedoraproject.org/en-US/epel/>`_:
* ``dnf install rapidyaml-devel``
* ``dnf install python3-rapidyaml``
* `Gentoo <https://packages.gentoo.org/packages/dev-cpp/rapidyaml>`_
* `OpenSuse <https://build.openbuildservice.org/package/show/Emulators/rapidyaml>`_
* `Slackbuilds <https://slackbuilds.org/repository/15.0/libraries/rapidyaml/>`_
* `AltLinux <https://packages.altlinux.org/en/sisyphus/srpms/rapidyaml/3006055151670528141>`_
Although package managers are very useful for quickly getting up to
speed, the advised way is still to bring ryml as a submodule of your
project, building both together. This makes it easy to track any
upstream changes in ryml. Also, ryml is small and quick to build, so
there's not much of a cost for building it with your project.
As a library
------------
The single header file is a good approach to quickly try the library,
but if you wish to make good use of CMake and its tooling ecosystem,
(and get better compile times), then ryml has you covered.
As with any other cmake library, you have the option to integrate ryml into
your project's build setup, thereby building ryml together with your
project, or -- prior to configuring your project -- you can have ryml
installed either manually or through package managers.
Currently `cmake <https://cmake.org/>`_ is required to build ryml; we
recommend a recent cmake version, at least 3.13.
Note that ryml uses submodules. Take care to use the `--recursive` flag
when cloning the repo, to ensure ryml's submodules are checked out as well:
.. code:: bash
git clone --recursive https://github.com/biojppm/rapidyaml
If you omit `--recursive`, after cloning you
will have to do `git submodule update --init --recursive`
to ensure ryml's submodules are checked out.
CMake build settings for ryml
-----------------------------
The following cmake variables can be used to control the build behavior
of ryml:
- ``RYML_WITH_TAB_TOKENS=ON/OFF``. Enable/disable support for tabs as
valid container tokens after ``:`` and ``-``. Defaults to ``OFF``,
because this may cost up to 10% in processing time.
- ``RYML_DEFAULT_CALLBACKS=ON/OFF``. Enable/disable rymls default
implementation of error and allocation callbacks. Defaults to ``ON``.
- ``RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=ON/OFF`` - Enable/disable the
same-named macro, which will make the default error handler provided
by ryml throw a ``std::runtime_error`` exception.
- ``RYML_USE_ASSERT`` - enable assertions in the code regardless of
build type. This is disabled by default. Failed assertions will
trigger a call to the error callback.
- ``RYML_STANDALONE=ON/OFF``. ryml uses
`c4core <https://github.com/biojppm/c4core>`__, a C++ library with
low-level multi-platform utilities for C++. When
``RYML_STANDALONE=ON``, c4core is incorporated into ryml as if it is
the same library. Defaults to ``ON``.
If youre developing ryml or just debugging problems with ryml itself,
the following cmake variables can be helpful:
- ``RYML_DEV=ON/OFF``: a bool variable which enables development targets
such as unit tests, benchmarks, etc. Defaults to ``OFF``.
- ``RYML_DBG=ON/OFF``: a bool variable which enables verbose prints from
parsing code; can be useful to figure out parsing problems. Defaults
to ``OFF``.
Forcing ryml to use a different c4core version
----------------------------------------------
ryml is strongly coupled to c4core, and this is reinforced by the fact
that c4core is a submodule of the current repo. However, it is still
possible to use a c4core version different from the one in the repo (of
course, only if there are no incompatibilities between the versions).
You can find out how to achieve this by looking at the
`custom_c4core sample <https://github.com/biojppm/rapidyaml/blob/v0.5.0/samples/custom_c4core/CMakeLists.txt>`__.

View File

@@ -0,0 +1,112 @@
YAML standard conformance
=========================
ryml is feature complete with regards to the YAML specification. All the
YAML features are well covered in the unit tests, and expected to work,
unless in the exceptions noted below.
Of course, there are many dark corners in YAML, and there certainly can
appear cases which ryml fails to parse. Your `bug reports or pull
requests <https://github.com/biojppm/rapidyaml/issues>`__ are very
welcome.
See also `the roadmap <https://github.com/biojppm/rapidyaml/tree/master/ROADMAP.md>`__ for a list of future work.
Known limitations
-----------------
ryml deliberately makes no effort to follow the standard in the
following situations:
- Containers are not accepted as mapping keys: keys must be scalars.
- Tab characters after ``:`` and ``-`` are not accepted tokens, unless
ryml is compiled with the macro ``RYML_WITH_TAB_TOKENS``. This
requirement exists because checking for tabs introduces branching
into the parsers hot code and in some cases costs as much as 10% in
parsing time.
- Anchor names must not end with a terminating colon: eg
``&anchor: key: val``.
- Non-unique map keys are allowed. Enforcing key uniqueness in the
parser or in the tree would cause log-linear parsing complexity (for
root children on a mostly flat tree), and would increase code size
through added structural, logical and cyclomatic complexity. So
enforcing uniqueness in the parser would hurt users who may not care
about it (they may not care either because non-uniqueness is OK for
their use case, or because it is impossible to occur). On the other
hand, any user who requires uniqueness can easily enforce it by doing
a post-parse walk through the tree. So choosing to not enforce key
uniqueness adheres to the spirit of “dont pay for what you dont
use”.
- ``%YAML`` directives have no effect and are ignored.
- ``%TAG`` directives are limited to a default maximum of 4 instances
per ``Tree``. To increase this maximum, define the preprocessor
symbol ``RYML_MAX_TAG_DIRECTIVES`` to a suitable value. This
arbitrary limit reflects the usual practice of having at most 1 or 2
tag directives; also, be aware that this feature is under
consideration for removal in YAML 1.3.
Also, ryml tends to be on the permissive side where the YAML standard
dictates there should be an error; in many of these cases, ryml will
tolerate the input. This may be good or bad, but in any case is being
improved on (meaning ryml will grow progressively less tolerant of YAML
errors in the coming releases). So we strongly suggest to stay away from
those dark corners of YAML which are generally a source of problems,
which is a good practice anyway.
.. note::
If you do run into trouble and would like to investigate
conformance of your YAML code, **do not use existing online YAML
linters**, many of which are not fully conformant; instead, try
using `https://play.yaml.io
<https://play.yaml.io/main/parser?input=IyBFZGl0IE1lIQoKJVlBTUwgMS4yCi0tLQpmb286IEhlbGxvLCBZQU1MIQpiYXI6IFsxMjMsIHRydWVdCmJhejoKLSBvbmUKLSB0d28KLSBudWxsCg==>`__,
an amazing tool which lets you dynamically input your YAML and
continuously see the results from all the existing parsers (kudos
to @ingydotnet and the people from the YAML test suite). And of
course, if you detect anything wrong with ryml, please `open an
issue <https://github.com/biojppm/rapidyaml/issues>`__ so that we
can improve.
YAML test suite
===============
As part of its CI testing, ryml uses the `YAML test
suite <https://github.com/yaml/yaml-test-suite>`__. This is an extensive
set of reference cases covering the full YAML spec. Each of these cases
have several subparts:
- ``in-yaml``: mildly, plainly or extremely difficult-to-parse YAML
- ``in-json``: equivalent JSON (where possible/meaningful)
- ``out-yaml``: equivalent standard YAML
- ``emit-yaml``: equivalent standard YAML
- ``events``: reference results (ie, expected tree)
When testing, ryml parses each of the 4 yaml/json parts, then emits the
parsed tree, then parses the emitted result and verifies that emission
is idempotent, ie that the emitted result is semantically the same as
its input without any loss of information. To ensure consistency, this
happens over four levels of parse/emission pairs. And to ensure
correctness, each of the stages is compared against the ``events`` spec
from the test, which constitutes the reference. The tests also check for
equality between the reference events in the test case and the events
emitted by ryml from the data tree parsed from the test case input. All
of this is then carried out combining several variations: both unix
``\n`` vs windows ``\r\n`` line endings, emitting to string, file or
streams, which results in ~250 tests per case part. With multiple parts
per case and ~400 reference cases in the test suite, this makes over
several hundred thousand individual tests to which ryml is subjected,
which are added to the unit tests in ryml, which also employ the same
extensive combinatorial approach.
Also, note that in `their own words <http://matrix.yaml.io/>`__, the
tests from the YAML test suite *contain a lot of edge cases that dont
play such an important role in real world examples*. And yet, despite
the extreme focus of the test suite, currently ryml only fails a minor
fraction of the test cases, mostly related with the deliberate
limitations noted above. Other than those limitations, by far the main
issue with ryml is that several standard-mandated parse errors fail to
materialize. For the up-to-date list of ryml failures in the test-suite,
refer to the `list of known
exceptions <https://github.com/biojppm/rapidyaml/blob/v0.5.0/test/test_suite/test_suite_parts.cpp>`__ from rymls test
suite runner, which is used as part of rymls CI process.

View File

@@ -34,6 +34,22 @@ search = "c4_project\\(VERSION {current_version}"
[[file]] [[file]]
src = "test/test_singleheader/CMakeLists.txt" src = "test/test_singleheader/CMakeLists.txt"
search = "c4_project\\(VERSION {current_version}" search = "c4_project\\(VERSION {current_version}"
[[file]]
src = "doc/conf.py"
search = "release\\s*=\\s*['\"]{current_version}['\"]"
[[file]]
src = "doc/Doxyfile"
search = "PROJECT_NUMBER\\s*=\\s*{current_version}"
[[file]]
src = "samples/quickstart.cpp"
search = "GIT_TAG \\s*v{current_version}"
[[file]]
src = "doc/doxy_main.md"
search = ".*{current_version}.*"
[[file]]
src = "doc/sphinx_*.rst"
search = ".*{current_version}.*"
# You can specify a list of commands to # You can specify a list of commands to
# run after the files have been patched # run after the files have been patched