Page MenuHomec4science

safe_config.cpp
No OneTemporary

File Metadata

Created
Thu, Jan 2, 20:21

safe_config.cpp

/* =============================================================================
Copyright (c) 2014 - 2016
F. Georget <fabieng@princeton.edu> Princeton University
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
============================================================================= */
#include "safe_config.hpp"
#include "utils/string_algorithms.hpp"
#include <yaml-cpp/yaml.h>
#include <yaml-cpp/node/iterator.h>
#include <unordered_map>
#include <vector>
#include "utils/log.hpp"
#include "utils/compat.hpp"
#define SECTION_USER_VARIABLES "vars"
#define SECTION_INT_USER_VARIABLES "int"
#define SECTION_FLOAT_USER_VARIABLES "float"
#define NAME_PATH_ROOT "root"
namespace specmicp {
namespace io {
// ================
// ConfigFileHandle
// ================
struct YAMLConfigFileHandle
{
std::string m_filename;
std::unordered_map<std::string, scalar_t> m_float_vars;
std::unordered_map<std::string, index_t> m_int_vars;
YAMLConfigFileHandle(const std::string& filename):
m_filename(filename)
{}
};
// ============
// ConfigHandle
// ============
struct YAMLConfigHandle::YAMLConfigHandleImpl
{
YAML::Node m_node;
std::shared_ptr<YAMLConfigFileHandle> m_file;
std::string m_path;
std::string m_section_name;
std::vector<std::string> m_asked_attrs;
YAMLConfigHandleImpl(
const YAML::Node& node,
std::shared_ptr<YAMLConfigFileHandle> const file_handle,
const std::string& section,
const std::string& path):
m_node(node),
m_file(file_handle),
m_path(path),
m_section_name(section)
{
if (node.IsMap()) {
m_asked_attrs.reserve(node.size());
}
}
~YAMLConfigHandleImpl();
scalar_t parse_scalar_expr(const std::string& expr) {
return utils::parse_expression(
expr,
m_file->m_float_vars
);
}
index_t parse_int_expr(const std::string& expr) {
return utils::parse_expression(
expr,
m_file->m_int_vars
);
}
bool parse_bool_expr(const std::string& expr) {
return utils::string_to_bool(expr);
}
void record_attr(const std::string& attr) {
m_asked_attrs.push_back(attr);
}
void assert_open() {
if (m_file == nullptr) {
throw std::runtime_error( "Information from file '"
+ m_file->m_filename
+ "' are lost...");
}
}
template <typename T>
std::vector<T> list_to_vector(
const std::string& list,
YAMLConfigHandle& parent
);
};
YAMLConfigHandle::YAMLConfigHandle(const YAML::Node& node,
const std::shared_ptr<YAMLConfigFileHandle> file_handle,
const std::string& section,
const std::string& path):
m_impl(utils::make_pimpl<YAMLConfigHandleImpl>(
node, file_handle, section, path))
{
}
YAMLConfigHandle& YAMLConfigHandle::operator= (const YAMLConfigHandle& other)
{
m_impl = other.m_impl;
m_impl->assert_open();
return *this;
}
YAMLConfigHandle::YAMLConfigHandle(const YAMLConfigHandle& other):
m_impl(other.m_impl)
{
m_impl->assert_open();
}
YAMLConfigHandle::YAMLConfigHandle(YAMLConfigHandle&& other):
m_impl(std::move(other.m_impl))
{
m_impl->assert_open();
}
YAMLConfigHandle::~YAMLConfigHandle() = default;
uindex_t YAMLConfigHandle::size()
{
return static_cast<uindex_t>(m_impl->m_node.size());
}
void YAMLConfigHandle::set_file_handle(
std::shared_ptr<YAMLConfigFileHandle> file_handle
)
{
m_impl->m_file = file_handle;
m_impl->assert_open();
}
bool YAMLConfigHandle::is_map()
{
return m_impl->m_node.IsMap();
}
bool YAMLConfigHandle::is_map(const std::string& node)
{
bool val = false;
if (m_impl->m_node[node]) {
val = m_impl->m_node[node].IsMap();
}
return val;
}
bool YAMLConfigHandle::is_sequence()
{
return m_impl->m_node.IsSequence();
}
bool YAMLConfigHandle::is_sequence(const std::string& node)
{
bool val = false;
if (m_impl->m_node[node]) {
val = m_impl->m_node[node].IsSequence();
}
return val;
}
bool YAMLConfigHandle::has_node(const std::string& node) {
return (m_impl->m_node[node]);
}
bool YAMLConfigHandle::has_node(uindex_t index) {
return (m_impl->m_node[index]);
}
bool YAMLConfigHandle::has_section(const std::string& section)
{
bool val = false;
if (m_impl->m_node[section]) {
YAML::Node attr = m_impl->m_node[section];
val = (attr.IsMap() or attr.IsSequence());
}
return val;
}
bool YAMLConfigHandle::has_section(uindex_t index)
{
bool val = false;
if (m_impl->m_node[index]) {
YAML::Node attr = m_impl->m_node[index];
val = (attr.IsMap() or attr.IsSequence());
}
return val;
}
YAMLConfigHandle YAMLConfigHandle::get_section(const std::string& section)
{
if (not has_section(section))
{
report_error(YAMLConfigError::MissingRequiredSection, section);
}
m_impl->record_attr(section);
return YAMLConfigHandle(m_impl->m_node[section],
m_impl->m_file,
section,
m_impl->m_path+"->"+section);
}
YAMLConfigHandle YAMLConfigHandle::get_section(uindex_t index)
{
auto section_name = "["+std::to_string(index)+"]";
if (not has_section(index))
{
report_error(YAMLConfigError::MissingRequiredSection,
section_name
);
}
return YAMLConfigHandle(m_impl->m_node[index],
m_impl->m_file,
section_name,
m_impl->m_path+section_name);
}
bool YAMLConfigHandle::has_attribute(const std::string& attribute)
{
bool val = false;
if (m_impl->m_node[attribute]) {
YAML::Node attr = m_impl->m_node[attribute];
val = not (attr.IsMap() or attr.IsSequence());
}
return val;
}
#define report_conversion_error(attribute, type) \
report_error( \
YAMLConfigError::ConversionError, \
"Conversion of attribute '" \
+ attribute + "' to " type + " : " + e.what() \
);
template <>
scalar_t YAMLConfigHandle::get_attribute(const std::string& attribute)
{
m_impl->record_attr(attribute);
YAML::Node node = m_impl->m_node[attribute];
scalar_t val;
m_impl->assert_open();
try {
val = m_impl->parse_scalar_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_conversion_error(attribute, "scalar");
} catch (const std::out_of_range& e) {
report_error(YAMLConfigError::UnknownVariable,
"Conversion of attribute '"
+ attribute + "' to scalar : " + e.what()
);
}
return val;
}
template <>
index_t YAMLConfigHandle::get_attribute(const std::string& attribute)
{
m_impl->record_attr(attribute);
YAML::Node node = m_impl->m_node[attribute];
index_t val;
m_impl->assert_open();
try {
val = m_impl->parse_int_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_conversion_error(attribute, "integer");
} catch (const std::out_of_range& e) {
report_error(YAMLConfigError::UnknownVariable,
"Conversion of attribute '"
+ attribute + "' to integer : " + e.what()
);
}
return val;
}
template <>
uindex_t YAMLConfigHandle::get_attribute(const std::string& attribute)
{
m_impl->record_attr(attribute);
YAML::Node node = m_impl->m_node[attribute];
index_t val;
m_impl->assert_open();
try {
val = m_impl->parse_int_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_conversion_error(attribute, "integer");
} catch (const std::out_of_range& e) {
report_error(YAMLConfigError::UnknownVariable,
"Conversion of attribute '"
+ attribute + "' to integer : " + e.what()
);
}
if (val < 0) {
report_error(YAMLConfigError::InvalidArgument,
"Expected positive value for attribute "
+ attribute + ", got '"
+ std::to_string(val) + "'.");
}
return val;
}
template <>
bool YAMLConfigHandle::get_attribute(const std::string& attribute)
{
m_impl->record_attr(attribute);
YAML::Node node = m_impl->m_node[attribute];
bool val;
try {
val = m_impl->parse_bool_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_conversion_error(attribute, "boolean");
}
return val;
}
template <>
std::string YAMLConfigHandle::get_attribute(const std::string& attribute)
{
m_impl->record_attr(attribute);
return m_impl->m_node[attribute].as<std::string>();
}
#undef report_converssion_error
// list
#define report_list_conversion_error(index, type) \
report_error( \
YAMLConfigError::ConversionError, \
"Conversion of value at index '" \
+ std::to_string(index) + "' to " \
type + " : " + e.what() \
);
template <>
scalar_t YAMLConfigHandle::get_value(uindex_t ind)
{
YAML::Node node = m_impl->m_node[ind];
scalar_t val;
m_impl->assert_open();
try {
val = m_impl->parse_scalar_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_list_conversion_error(ind, "scalar");
} catch (const std::out_of_range& e) {
report_error(YAMLConfigError::UnknownVariable,
"Conversion of value at index '"
+ std::to_string(ind) + "' to scalar : " + e.what()
);
}
return val;
}
template <>
index_t YAMLConfigHandle::get_value(uindex_t ind)
{
YAML::Node node = m_impl->m_node[ind];
index_t val;
m_impl->assert_open();
try {
val = m_impl->parse_int_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_list_conversion_error(ind, "integer");
} catch (const std::out_of_range& e) {
report_error(YAMLConfigError::UnknownVariable,
"Conversion of value at index '"
+ std::to_string(ind) + "' to integer : " + e.what()
);
}
return val;
}
template <>
uindex_t YAMLConfigHandle::get_value(uindex_t ind)
{
YAML::Node node = m_impl->m_node[ind];
index_t val;
m_impl->assert_open();
try {
val = m_impl->parse_int_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_list_conversion_error(ind, "integer");
} catch (const std::out_of_range& e) {
report_error(YAMLConfigError::UnknownVariable,
"Conversion of value at index '"
+ std::to_string(ind) + "' to integer : " + e.what()
);
}
if (val < 0) {
report_error(YAMLConfigError::InvalidArgument,
"Expected positive value at index "
+ std::to_string(ind) + ", got '"
+ std::to_string(val) + "'.");
}
return val;
}
template <>
bool YAMLConfigHandle::get_value(uindex_t ind)
{
YAML::Node node = m_impl->m_node[ind];
bool val;
try {
val = m_impl->parse_bool_expr(node.as<std::string>());
} catch (const std::invalid_argument& e) {
report_list_conversion_error(ind, "boolean");
}
return val;
}
template <>
std::string YAMLConfigHandle::get_value(uindex_t ind)
{
return m_impl->m_node[ind].as<std::string>();
}
void YAMLConfigHandle::report_error(
YAMLConfigError error_type,
const std::string& error_msg
)
{
std::string header;
m_impl->assert_open();
if (m_impl->m_file->m_filename != "") {
header += "\n\t - in file " + m_impl->m_file->m_filename;
}
if (m_impl->m_path != "") {
header += "\n\t - in section " + m_impl->m_path + "\n";
}
switch (error_type) {
case YAMLConfigError::ConversionError:
throw std::invalid_argument("Conversion error : "
+ header + error_msg);
break;
case YAMLConfigError::UnknownVariable:
throw std::out_of_range("Unknown variable :"
+ header + error_msg);
break;
case YAMLConfigError::MissingRequiredAttribute:
throw std::out_of_range("Missing required attribute : "
+ error_msg + header);
break;
case YAMLConfigError::MissingRequiredSection:
throw std::out_of_range("Missing required section : "
+ error_msg + header);
break;
default:
throw std::runtime_error(error_msg + header);
break;
}
}
YAMLConfigHandle::YAMLConfigHandleImpl::~YAMLConfigHandleImpl()
{
if (m_node.IsMap())
{
assert_open();
// report untouched attribute
std::vector<std::string> unread;
for (auto it=m_node.begin(); it!=m_node.end(); ++it) {
auto is_read = std::find(
m_asked_attrs.cbegin(), m_asked_attrs.cend(),
it->first.as<std::string>()
);
if (is_read == m_asked_attrs.cend()) {
WARNING << "Unread key : " + it->first.as<std::string>()
+ " in file '" + m_file->m_filename + "'"
+ " in section '" + m_path + "'.";
}
}
}
}
// ===========
// MapIterator
// ===========
std::pair<std::string, std::string> YAMLConfigHandle::MapIterator::operator* ()
{
m_handle->record_attr((*m_true_it)->first.as<std::string>());
return {
(*m_true_it)->first.as<std::string>(),
(*m_true_it)->second.as<std::string>()
};
}
YAMLConfigHandle::MapIterator& YAMLConfigHandle::MapIterator::operator++ ()
{
m_true_it->operator++ ();
return *this;
}
bool YAMLConfigHandle::MapIterator::operator==(const MapIterator& other)
{
return (*m_true_it == *other.m_true_it);
}
bool YAMLConfigHandle::MapIterator::operator!=(const MapIterator& other)
{
return (*m_true_it != *other.m_true_it);
}
YAMLConfigHandle::MapIterator::MapIterator(
YAMLConfigHandleImpl* handle,
std::unique_ptr<YAML::iterator> it
):
m_handle(handle),
m_true_it(it.release())
{}
YAMLConfigHandle::MapIterator YAMLConfigHandle::map_begin() {
return MapIterator(
m_impl.get(),
make_unique<YAML::iterator>(m_impl->m_node.begin())
);
}
YAMLConfigHandle::MapIterator YAMLConfigHandle::map_end() {
return MapIterator(
m_impl.get(),
make_unique<YAML::iterator>(m_impl->m_node.end())
);
}
// List to vector
// -------------
// implementation of list_to_vector
template <typename T>
std::vector<T> YAMLConfigHandle::YAMLConfigHandleImpl::list_to_vector(
const std::string& list,
YAMLConfigHandle& parent
)
{
// check that it is a list as expected
if (not m_node[list]) {
parent.report_error(YAMLConfigError::MissingRequiredAttribute, list);
}
auto node = m_node[list];
if (not node.IsSequence()) {
parent.report_error(YAMLConfigError::ListExpected,
"A list was expected for attribute : " + list);
}
record_attr(list);
// parse the list
std::vector<T> vec;
vec.reserve(parent.size());
auto list_section = parent.get_section(list);
uindex_t size = list_section.size();
for (uindex_t ind=0; ind<size; ++ind) {
vec.push_back(list_section.get_value<T>(ind));
}
return vec;
}
// two specialization for indices => also read range of indices
template <>
std::vector<index_t> YAMLConfigHandle::YAMLConfigHandleImpl::list_to_vector(
const std::string& list,
YAMLConfigHandle& parent
)
{
if (not m_node[list]) {
parent.report_error(YAMLConfigError::MissingRequiredAttribute, list);
}
record_attr(list);
// parse the list
auto node = m_node[list];
std::vector<index_t> vec;
if (not node.IsSequence()) {
try { // it ;ay be our special way to generate list "1:10"
vec = utils::range_indices<index_t>(node.as<std::string>());
} catch (const std::invalid_argument& e) {
parent.report_error(YAMLConfigError::InvalidArgument,
"Expected valid list for attribute " + list
+ ". Error : " + e.what());
}
} else {
vec.reserve(parent.size());
auto list_section = parent.get_section(list);
uindex_t size = list_section.size();
for (uindex_t ind=0; ind<size; ++ind) {
vec.push_back(list_section.get_value<index_t>(ind));
}
}
return vec;
}
template <>
std::vector<uindex_t> YAMLConfigHandle::YAMLConfigHandleImpl::list_to_vector(
const std::string& list,
YAMLConfigHandle& parent
)
{
if (not m_node[list]) {
parent.report_error(YAMLConfigError::MissingRequiredAttribute, list);
}
record_attr(list);
// parse the list
auto node = m_node[list];
std::vector<uindex_t> vec;
if (not node.IsSequence()) {
try { // it may be the slice way to generate lists ":"
vec = utils::range_indices<uindex_t>(node.as<std::string>());
} catch (const std::invalid_argument& e) {
parent.report_error(YAMLConfigError::InvalidArgument,
"Expected valid list for attribute " + list
+ ". Error : " + e.what());
}
} else {
vec.reserve(parent.size());
auto list_section = parent.get_section(list);
uindex_t size = list_section.size();
for (uindex_t ind=0; ind<size; ++ind) {
vec.push_back(list_section.get_value<uindex_t>(ind));
}
}
return vec;
}
template <>
std::vector<scalar_t> YAMLConfigHandle::list_to_vector(const std::string& list)
{
return m_impl->list_to_vector<scalar_t>(list, *this);
}
template <>
std::vector<index_t> YAMLConfigHandle::list_to_vector(const std::string& list)
{
return m_impl->list_to_vector<index_t>(list, *this);
}
template <>
std::vector<uindex_t> YAMLConfigHandle::list_to_vector(const std::string& list)
{
return m_impl->list_to_vector<uindex_t>(list, *this);
}
template <>
std::vector<bool> YAMLConfigHandle::list_to_vector(const std::string& list)
{
return m_impl->list_to_vector<bool>(list, *this);
}
template <>
std::vector<std::string> YAMLConfigHandle::list_to_vector(const std::string& list)
{
return m_impl->list_to_vector<std::string>(list, *this);
}
// ==========
// ConfigFile
// ==========
YAMLConfigFile YAMLConfigFile::load(
const std::string& file
)
{
return YAMLConfigFile(YAML::LoadFile(file), file);
}
YAMLConfigFile YAMLConfigFile::load_from_string(
const std::string& str,
std::string name
)
{
return YAMLConfigFile(YAML::Load(str), name);
}
std::unique_ptr<YAMLConfigFile> YAMLConfigFile::make(
const std::string& file
)
{
// can't use make unique, YAMLConfigFile() is private....
return std::unique_ptr<YAMLConfigFile>(
new YAMLConfigFile(YAML::LoadFile(file), file)
);
}
std::unique_ptr<YAMLConfigFile> YAMLConfigFile::make_from_string(
const std::string& str,
std::string name
)
{
return std::unique_ptr<YAMLConfigFile>(
new YAMLConfigFile(YAML::Load(str), name)
);
}
YAMLConfigFile::YAMLConfigFile(
const YAML::Node& node,
const std::string name
):
YAMLConfigHandle(node, nullptr, NAME_PATH_ROOT, NAME_PATH_ROOT),
m_handle(std::make_shared<YAMLConfigFileHandle>(name))
{
set_file_handle(m_handle);
// read variables
if (has_section(SECTION_USER_VARIABLES)) {
auto& int_vars = m_handle->m_int_vars;
auto& float_vars = m_handle->m_float_vars;
auto var_section = get_section(SECTION_USER_VARIABLES);
if (var_section.has_section(SECTION_INT_USER_VARIABLES))
{
auto int_section = var_section.get_section(SECTION_INT_USER_VARIABLES);
int_vars.reserve( int_vars.size() + int_section.size());
float_vars.reserve(float_vars.size() + int_section.size());
for (
auto it=int_section.map_begin();
it!=int_section.map_end();
++it
) {
auto tp = *it;
auto key = utils::strip(tp.first);
index_t val = utils::parse_expression<index_t>(tp.second, int_vars);
int_vars.insert({key, val});
float_vars.insert({key, static_cast<scalar_t>(val)});
}
}
if (var_section.has_section(SECTION_FLOAT_USER_VARIABLES))
{
auto float_section = var_section.get_section(SECTION_FLOAT_USER_VARIABLES);
float_vars.reserve(float_vars.size()+float_section.size());
for (
auto it=float_section.map_begin();
it!=float_section.map_end();
++it
) {
auto tp = *it;
auto key = utils::strip(tp.first);
scalar_t val = utils::parse_expression<scalar_t>(tp.second);
float_vars.insert({key, val});
}
}
}
}
YAMLConfigFile::~YAMLConfigFile() = default;
} //end namespace io
} //end namespace specmicp

Event Timeline