Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ set(LIB_INSTALL_DIR lib)
if(NOT BUILD_TESTS)
option(BUILD_TESTS "Build Unit tests" ON)
endif(NOT BUILD_TESTS)
option(BUILD_DOC "Build documentation (requires doxygen)" OFF)
if(CMAKE_CXX_STANDARD LESS 17)
message(STATUS "C++ less than 17, Boost::filesystem is required")
set(BOOST_FILESYSTEM_LIB 1)
Expand Down Expand Up @@ -136,6 +137,25 @@ endif()
# find the installed library relative to the installed executable (works for any CMAKE_INSTALL_PREFIX)
set_target_properties(yaml_template_generator PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")

# ------ DOCUMENTATION ------
# 'make doc' generates the documentation in ${PROJECT_BINARY_DIR}/doc/html (if doxygen is found).
# With BUILD_DOC=ON, it is also generated by 'make'.
find_package(Doxygen QUIET)
if(DOXYGEN_FOUND AND NOT TARGET doc)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in ${PROJECT_BINARY_DIR}/Doxyfile @ONLY)
if(BUILD_DOC)
message(STATUS "Will build documentation.")
set(DOC_ALL ALL)
endif()
add_custom_target(doc ${DOC_ALL}
COMMAND Doxygen::doxygen ${PROJECT_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Generating documentation in ${PROJECT_BINARY_DIR}/doc/html"
VERBATIM)
elseif(BUILD_DOC AND NOT DOXYGEN_FOUND)
message(WARNING "BUILD_DOC is ON but doxygen was not found: the documentation will not be built.")
endif()

# ------ INSTALL ------
#install headers
install(
Expand Down Expand Up @@ -186,6 +206,7 @@ install(FILES
# So that it can be used without installing
export(EXPORT ${PROJECT_NAME}-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-targets.cmake"
NAMESPACE yaml-schema-cpp::
)

# ---- UNINSTALL ----
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ sudo make install
```
You can run the tests with `ctest -j4`.

## Documentation

If [doxygen](https://www.doxygen.nl) is installed (`sudo apt install doxygen`), the API documentation can be generated
from the build folder with:

```bash
make doc
```

and then opened at `build/doc/html/index.html`. Configure with `cmake -DBUILD_DOC=ON ..` to generate it also with `make`.

# Using `yaml_schema_cpp`

## The `.schema` file
Expand Down Expand Up @@ -146,6 +157,8 @@ The `_type` can be:

If the type string ends with `[]`, the input field is specified to be a sequence.

Note that null values (e.g. `key:` without value) are not accepted for string types. Use `''` for an empty string.

### `_mandatory`
A **bool** value or expression (see below) specifying if the input field is required or just optional.

Expand All @@ -171,7 +184,7 @@ In both input and schema files, the key `follow` is interpreted as a copy-paste
We allow the use of [exprtk](http://www.partow.net/programming/exprtk/index.html) expressions in `_mandatory` field.
There are some rules for the expressions:
1. It should start by a `$` symbol.
2. All parameters involved in an expression have to be at the same level of the specified node.
2. All parameters involved in an expression have to be at the same level of the specified node (they can be declared before or after it).
3. All parameters involved in an expression have to be mandatory at all cases (i.e. true, not an expression) or have a default value defined.
4. The exprtk syntax has to be followed (see [exprtk readme](https://www.partow.net/programming/exprtk/code/readme.txt) for more information).
5. The result of the expression will be casted to `bool`. If the result is a numeric value, it will be converted to `true` except for 0 (`false`).
Expand Down Expand Up @@ -283,7 +296,7 @@ This library handles relative paths parameters. In case a string value starts by
This is useful in case of using `follow` to a YAML in a different folder that contains a path parameter.

### Tree structure
This library is intended to be used to validate a given YAML file with parameters. Then, loops are not suported in the input YAML file. It is assumed to have tree structure. Note that `follow` copies the content of the other file, so multiple `follow` instantes to the same file is supported.
This library is intended to be used to validate a given YAML file with parameters. Then, loops are not suported in the input YAML file. It is assumed to have tree structure. Note that `follow` copies the content of the other file, so multiple `follow` instantes to the same file is supported. A `follow` cycle (a file that follows, directly or indirectly, itself) raises an error.

# YAML template generator

Expand Down
35 changes: 35 additions & 0 deletions doc/Doxyfile.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Doxygen configuration of yaml-schema-cpp (configured by CMake, see 'doc' target in CMakeLists.txt).
# Only the tags different from the doxygen defaults are set.

PROJECT_NAME = "yaml-schema-cpp"
PROJECT_NUMBER = @PROJECT_VERSION@
PROJECT_BRIEF = "Specification and validation of YAML files via schemas"
OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc
STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include

# Input: README (main page) and public headers (without the redistributed exprtk)
INPUT = @PROJECT_SOURCE_DIR@/README.md \
@PROJECT_SOURCE_DIR@/include/yaml-schema-cpp
FILE_PATTERNS = *.hpp \
*.md
RECURSIVE = YES
EXCLUDE = @PROJECT_SOURCE_DIR@/include/yaml-schema-cpp/exprtk
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md

# Extraction
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES

# Preprocessing: document the Eigen parts
ENABLE_PREPROCESSING = YES
PREDEFINED = _EIGEN_FOUND=1

# Warnings
QUIET = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES

# Output
GENERATE_HTML = YES
GENERATE_LATEX = NO
48 changes: 47 additions & 1 deletion include/yaml-schema-cpp/expression.hpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,63 @@
/**
* @file expression.hpp
* @brief Boolean expressions in the MANDATORY key of schema specifications.
*
* Expressions start with '$' and follow the exprtk syntax (http://www.partow.net/programming/exprtk). The symbols are
* the names of other parameters at the same level in the schema/input (e.g. "$mode == 'auto' and n_threads > 0").
*/
#pragma once

#include "yaml-cpp/yaml.h"

namespace yaml_schema_cpp
{
/**
* @brief Check if a node is an expression.
* @param node YAML node
* @return true if node is a scalar starting with '$'
*/
bool isExpression(const YAML::Node& node);

/**
* @brief Check if a string is an expression.
* @param expression string
* @return true if expression starts with '$'
*/
bool isExpression(const std::string& expression);

/**
* @brief Check the syntax of an expression in a schema.
*
* All symbols in the expression should be parameters specified in node_schema_parent (i.e. at the same level) of
* basic type (see isBasicType()) or string type, either mandatory (MANDATORY true) or with DEFAULT.
*
* @param node_expression node containing the expression (starting with '$')
* @param node_schema_parent schema node containing the specifications of the parameters in the expression
* @param err OUTPUT error message (if not valid)
* @return true if the expression is valid
*/
bool checkExpression(const YAML::Node& node_expression, const YAML::Node& node_schema_parent, std::string& err);

bool evalExpression(std::string expression_str, const YAML::Node& node_input_parent);
/**
* @brief Evaluate an expression with the values of an input node.
*
* The values of the symbols are taken from node_input_parent. Their type (bool, number or string) is taken from the
* specifications in node_schema_parent if available, otherwise it is deduced from the value.
*
* @param expression_str expression (starting with '$')
* @param node_input_parent input node containing the parameters in the expression
* @param node_schema_parent schema node containing the specifications of the parameters (undefined if not available)
* @return result of the expression casted to bool (numeric values are true except for 0)
* @throws std::runtime_error if the expression cannot be compiled (e.g. wrong syntax or a parameter is missing)
*/
bool evalExpression(std::string expression_str,
const YAML::Node& node_input_parent,
const YAML::Node& node_schema_parent = YAML::Node(YAML::NodeType::Undefined));

/**
* @brief Remove the leading '$' of an expression (if any).
* @param expression_str INPUT/OUTPUT expression
*/
void preProcessExpression(std::string& expression_str);

} // namespace yaml_schema_cpp
6 changes: 5 additions & 1 deletion include/yaml-schema-cpp/filesystem_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @file filesystem_wrapper.hpp
* @brief Namespace alias 'filesystem': std::filesystem (C++17 or later) or boost::filesystem (older standards).
*/
#pragma once

#include "yaml-schema-cpp/internal/config.h"
Expand All @@ -8,4 +12,4 @@ namespace filesystem = boost::filesystem;
#else
#include <filesystem>
namespace filesystem = std::filesystem;
#endif
#endif
139 changes: 71 additions & 68 deletions include/yaml-schema-cpp/type_check.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* @file type_check.hpp
* @brief Checking if YAML nodes can be interpreted as a given type (specified as a string).
*
* Types are specified as strings: basic types ("bool", "char", "int", "unsigned int", "long int",
* "long unsigned int", "float", "double"), string types ("string", "std::string"), Eigen types ("VectorXd",
* "MatrixXd", "Vector<N>d", "Matrix<N>d" for N in 1..10, with or without "Eigen::", only if Eigen was found) and array
* types ("<type>[]" or "<type>[<size>]", e.g. "double[3][]").
*/
#pragma once

#include "yaml-schema-cpp/internal/config.h"
Expand All @@ -11,64 +20,6 @@

namespace yaml_schema_cpp
{
#define CHECK_TYPE_CASE(TypeName) \
if (type == #TypeName) \
{ \
node.as<TypeName>(); \
return true; \
}

#if _EIGEN_FOUND == 1
#define CHECK_TYPE_EIGEN_CASE(size) \
if (type == "Vector" #size "d" or type == "Eigen::Vector" #size "d") \
{ \
node.as<Eigen::Matrix<double, size, 1> >(); \
return true; \
} \
if (type == "Matrix" #size "d" or type == "Eigen::Matrix" #size "d") \
{ \
node.as<Eigen::Matrix<double, size, size> >(); \
return true; \
}

#define CHECK_TYPE_EIGEN_CASES \
CHECK_TYPE_EIGEN_CASE(1) \
CHECK_TYPE_EIGEN_CASE(2) \
CHECK_TYPE_EIGEN_CASE(3) \
CHECK_TYPE_EIGEN_CASE(4) \
CHECK_TYPE_EIGEN_CASE(5) \
CHECK_TYPE_EIGEN_CASE(6) \
CHECK_TYPE_EIGEN_CASE(7) \
CHECK_TYPE_EIGEN_CASE(8) \
CHECK_TYPE_EIGEN_CASE(9) \
CHECK_TYPE_EIGEN_CASE(10) \
CHECK_TYPE_CASE(Eigen::VectorXd) \
CHECK_TYPE_CASE(Eigen::VectorXd) \
CHECK_TYPE_CASE(Eigen::MatrixXd) \
{ \
using namespace Eigen; \
CHECK_TYPE_CASE(VectorXd) \
CHECK_TYPE_CASE(MatrixXd) \
}
#endif

#define CHECK_TYPE_BASIC_CASES \
CHECK_TYPE_CASE(bool) \
CHECK_TYPE_CASE(char) \
CHECK_TYPE_CASE(int) \
CHECK_TYPE_CASE(unsigned int) \
CHECK_TYPE_CASE(long int) \
CHECK_TYPE_CASE(long unsigned int) \
CHECK_TYPE_CASE(float) \
CHECK_TYPE_CASE(double)

#define CHECK_TYPE_STRING_CASES \
CHECK_TYPE_CASE(std::string) \
{ \
using std::string; \
CHECK_TYPE_CASE(string) \
}

/**
* @brief check the sizes of node and type
* If the type is an array type, it checks the node is a sequence.
Expand All @@ -81,43 +32,95 @@ namespace yaml_schema_cpp
bool checkSizes(const YAML::Node& node, const std::string& type);

/**
* @brief check if a node can be interpreted as a given type
* NOTE: This function may raise exceptions
* @brief check if a node can be interpreted as a given basic type (or array of basic types)
* NOTE: This function may raise exceptions (if the type is basic but the conversion fails)
*
* @param node YAML node to be converted
* @param type string defining the type
* @return false if type not checked, true if check ok.
* @return false if type is not basic (or sizes do not match), true if check ok.
*/
bool checkNodeAsBasic(const YAML::Node& node, const std::string& type);

/**
* @brief check if a node can be interpreted as a given string type (or array of string types)
* NOTE: This function may raise exceptions (if the type is string but the conversion fails)
*
* @param node YAML node to be converted
* @param type string defining the type
* @return false if type is not string (or sizes do not match), true if check ok.
*/
bool checkNodeAsString(const YAML::Node& node, const std::string& type);

#if _EIGEN_FOUND == 1
/**
* @brief check if a node can be interpreted as a given Eigen type (or array of Eigen types)
* NOTE: This function may raise exceptions (if the type is Eigen but the conversion fails)
*
* @param node YAML node to be converted
* @param type string defining the type
* @return false if type is not Eigen (or sizes do not match), true if check ok.
*/
bool checkNodeAsEigen(const YAML::Node& node, const std::string& type);
#endif

/**
* @brief check if a node can be interpreted as a given trivial type (or array of trivial types)
* NOTE: This function may raise exceptions (if the type is trivial but the conversion fails)
*
* @param node YAML node to be converted
* @param type string defining the type
* @return false if type is not trivial (or sizes do not match), true if check ok.
*/
bool checkNodeAs(const YAML::Node& node, const std::string& type);

/**
* @brief try if a node can be interpreted as a given type
* NOTE: This function does not raise exceptions
* NOTE: A null node can be interpreted as a string ("null"), see applySchema() for input validation.
*
* @param node YAML node to be converted
* @param type string defining the type
* @param type string defining the type (trivial type or array of trivial types)
* @return if the node can be interpreted as the type
*/
bool tryNodeAs(const YAML::Node& node, const std::string& type);

/** Functions to evaluate if a string definig type is in these groups:
* - Basic: int, float, string, Eigen types (if eigen installed))
* - String
* - Eigen: Vector or Matrix dynamic of fixed size (until 10)
* - Trivial: Basic, string or Eigen
* - NonTrivial: rest of types that require schemas to be specified
/**
* @brief check if a type is trivial: basic, string or Eigen (not requiring a schema)
* @param type string defining the type
* @return true if trivial (false for array types and malformed type strings)
*/
bool isTrivialType(const std::string& type);

/**
* @brief check if a type is basic: bool, char, int, unsigned int, long int, long unsigned int, float or double
* @param type string defining the type
* @return true if basic (false for array types and malformed type strings)
*/
bool isBasicType(const std::string& type);

/**
* @brief check if a type is string: string or std::string
* @param type string defining the type
* @return true if string (false for array types and malformed type strings)
*/
bool isStringType(const std::string& type);

#if _EIGEN_FOUND == 1
/**
* @brief check if a type is Eigen: VectorXd, MatrixXd, Vector<N>d or Matrix<N>d (N from 1 to 10), with or without
* "Eigen::"
* @param type string defining the type
* @return true if Eigen (false for array types and malformed type strings)
*/
bool isEigenType(const std::string& type);
#endif

/**
* @brief check if a type is non-trivial: a schema file with that name exists in folders (see findSchema())
* @param type string defining the type (name of the schema)
* @param folders folders where the schema files are searched (recursively)
* @return true if the schema file is found
*/
bool isNonTrivialType(const std::string& type, const std::vector<std::string>& folders);

} // namespace yaml_schema_cpp
Loading
Loading