Program Listing for File options.cpp

Return to documentation for file (lib/options/options.cpp)

// ---------------------------------------------------------------------
// This file is part of falcon-core.
//
// Copyright (C) 2015, 2016, 2017 Neuro-Electronics Research Flanders
//
// Falcon-server is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Falcon-server is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with falcon-core. If not, see <http://www.gnu.org/licenses/>.
// ---------------------------------------------------------------------


#include <vector>
#include "options.hpp"

using namespace options;

bool options::get_nested_yaml_node(const YAML::Node &root,
                                   const std::vector<std::string> &path,
                                   YAML::Node &out) {
  out.reset(root);
  for (auto &p : path) {

    if (out[p]) {
      out.reset(out[p]);
    } else {
      return false;
    }
  }
  return true;
}

void options::set_nested_yaml_node(YAML::Node &root,
                                   const std::vector<std::string> &path,
                                   const YAML::Node &value) {
  YAML::Node x;
  if (path.size() == 0) {
    // do nothing
  } else if (path.size() == 1) {
    root[path[0]] = value;
  } else {
    x.reset(root);
    for (auto it = path.begin(); it != path.end(); ++it) {
      if (std::next(it) == path.end()) {
        x[*it] = value;
      } else if (!x[*it]) {
        x[*it] = YAML::Node(YAML::NodeType::Map);
      }
      x.reset(x[*it]);
    }
  }
}

OptionBase::OptionBase(std::string name, ValueBase &value,
                       std::string description, bool required)
    : name_(name), description_(description), required_(required),
      value_(value) {
  if (name.size() == 0) {
    throw std::runtime_error("Option name cannot be empty.");
  }
  path_ = split(name, '/');
}

std::string OptionBase::name() const { return name_; }

std::string OptionBase::description() const { return description_; }

const std::vector<std::string> &OptionBase::path() const { return path_; }

bool OptionBase::is_required() const { return required_; }

void OptionBase::from_yaml(const YAML::Node &node) { value_.from_yaml(node); }

typename YAML::Node OptionBase::to_yaml() const {
  YAML::Node node;
  node = value_.to_yaml();
  return node;
}

OptionBase &OptionBase::required() {
  required_ = true;
  return *(this);
}

OptionBase &OptionBase::optional() {
  required_ = false;
  return *(this);
}

OptionBase &OptionBase::describe(std::string description) {
  description_ = description;
  return *(this);
}

OptionBase &OptionBase::set_null() {
  value_.set_null();
  return *(this);
}

bool OptionBase::is_null() const { return value_.is_null(); }

bool OptionBase::is_nullable() const { return value_.is_nullable(); }

OptionBase &OptionList::operator[](std::string key) {
  for (auto &option : options_) {
    if (option.name() == key) {
      return option;
    }
  }
  throw std::runtime_error("No such option.");
}

void OptionList::remove(std::string key) {
  options_.remove_if([key](const OptionBase &x) { return x.name() == key; });
}

std::vector<std::string> OptionList::options() const {
  std::vector<std::string> opts;
  opts.reserve(options_.size());

  for (auto &option : options_) {
    opts.push_back(option.name());
  }

  return opts;
}

std::vector<std::string> OptionList::required_options() const {
  std::vector<std::string> opts;
  for (auto &option : options_) {
    if (option.is_required()) {
      opts.push_back(option.name());
    }
  }
  return opts;
}

bool OptionList::has_option(std::string name) const noexcept{
  //name = std::regex_replace(name, std::regex("[ _]"), "-");
  return std::any_of(options_.begin(), options_.end(),
                     [name](const OptionBase &x) { return x.name() == name; });
}

void OptionList::from_yaml(const YAML::Node &node,
                           const option_error_handler &handler, bool check){
  if (!node.IsMap()) {
    throw std::runtime_error("Expecting YAML map.");
  }


  if(check){
      for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
          std::string basename = it->first.as<std::string>();
          bool exist = has_option(basename);
          if(it->second.IsMap() and !exist){
                  for (YAML::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
                      if(!has_option(basename + "/" + it2->first.as<std::string>()))
                         throw std::runtime_error("This is not a valid option: "+ basename + "/" + it2->first.as<std::string>()+ ".\n Possible values are: "+ list_options());
                  }
          }else if (!exist)
             throw std::runtime_error("This is not a valid option: "+ basename+ ".\n Possible values are: "+ list_options());
       }
  }

  YAML::Node x;
  // loop through options
  for (auto &option : options_) {
    // check if available in YAML node
    // treat "/" in option name special (e.g. recurse into maps)
    if (get_nested_yaml_node(node, option.path(), x)) {
      if (x.IsNull()) {
        if (option.is_nullable()) {
          option.set_null();
          continue;
        } else {
          throw std::runtime_error("Error setting option " + option.name() +
                                   ": value cannot be null");
        }
      }

      try {
        option.from_yaml(x);
      } catch (ConversionError &e) {
        if (!handler ||
            !handler(option.name(), option.is_required(),
                     OptionError::conversion_from_yaml_failed, e.what())) {
          throw std::runtime_error("Error setting option " + option.name() +
                                   ": " + e.what());
        }
      } catch (ValidationError &e) {
        if (!handler || !handler(option.name(), option.is_required(),
                                 OptionError::validation_failed, e.what())) {
          throw std::runtime_error("Error setting option " + option.name() +
                                   ": " + e.what());
        }
      }
    } else if (option.is_required()) {
      if (!handler || !handler(option.name(), option.is_required(),
                               OptionError::requirement_failed, "")) {
        throw std::runtime_error("Missing required option " + option.name() +
                                 ".");
      }
    }
  }
}

YAML::Node OptionList::to_yaml(const option_error_handler &handler) const {
  YAML::Node root = YAML::Node(YAML::NodeType::Map);

  for (auto &option : options_) {
    YAML::Node n;

    if (!option.is_nullable() || !option.is_null()) {
      try {
        n = option.to_yaml();
      } catch (ConversionError &e) {
        if (handler &&
            !handler(option.name(), option.is_required(),
                     OptionError::conversion_to_yaml_failed, e.what())) {
          throw std::runtime_error("Error exporting option " + option.name() +
                                   ": " + e.what());
        }
      } catch (SkipError &e) {
        continue;
      } catch (...) {
        throw std::runtime_error("Unknown error for option " + option.name());
      }
    }
    set_nested_yaml_node(root, option.path(), n);
  }
  return root;
}

void OptionList::load_yaml(std::string filename,
                           const option_error_handler &handler) {
  YAML::Node root = YAML::LoadFile(filename);
  from_yaml(root, handler);
}

void OptionList::save_yaml(std::string filename,
                           const option_error_handler &handler) const {
  auto node = to_yaml(handler);

  YAML::Emitter yaml_emitter;
  yaml_emitter << node;

  std::ofstream out(filename);
  out << yaml_emitter.c_str();
}