Program Listing for File options.hpp

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

// ---------------------------------------------------------------------
// 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/>.
// ---------------------------------------------------------------------

#pragma once

#include <fstream>
#include <functional>
#include <list>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include "yaml-cpp/yaml.h"

#include "../utilities/string.hpp"
#include "value.hpp"

namespace options {

enum class OptionError {
  validation_failed,
  conversion_to_yaml_failed,
  conversion_from_yaml_failed,
  requirement_failed
};

class SkipError : public std::runtime_error {
 public:
  SkipError(std::string msg = "") : std::runtime_error(msg) {}
};

bool get_nested_yaml_node(const YAML::Node &root,
                          const std::vector<std::string> &path,
                          YAML::Node &out);

void set_nested_yaml_node(YAML::Node &root,
                          const std::vector<std::string> &path,
                          const YAML::Node &value);

class OptionBase {
 public:
  OptionBase(std::string name, ValueBase &value, std::string description = "",
             bool required = false);

  OptionBase(const OptionBase &other)
      : OptionBase(other.name_, other.value_, other.description_,
                   other.required_) {}

  OptionBase &operator=(const options::OptionBase &other) {
    name_ = other.name_;
    path_ = other.path_;
    description_ = other.description_;
    value_ = other.value_;
    required_ = other.required_;
    return *this;
  }

  std::string name() const;

  std::string description() const;

  const std::vector<std::string> &path() const;

  bool is_required() const;

  void from_yaml(const YAML::Node &node);

  typename YAML::Node to_yaml() const;

  OptionBase &required();

  OptionBase &optional();

  OptionBase &describe(std::string description);

  OptionBase &set_null();

  bool is_null() const;

  bool is_nullable() const;

 protected:
  std::string name_;
  std::string description_;
  std::vector<std::string> path_;
  bool required_ = false;
  ValueBase &value_;
};

template <typename T> class Option : public OptionBase {
  static_assert(std::is_base_of<ValueBase, T>::value,
                "Option requires a Value derived from ValueBase.");

 public:
  Option(std::string name, T &value, std::string description = "",
         bool required = false)
      : OptionBase(name, value, description, required) {}

  Option<T> &init(const typename T::ValueType &value) {
    static_cast<T &>(this->value_).set_value(value);
    return *(this);
  }

  Option<T> &
  validate(std::function<typename T::ValueType(typename T::ValueType)>
               validator = {}) {
    static_cast<T &>(this->value_).set_validator(validator);
    return *(this);
  }

  template <typename... Args> Option<T> &dependencies(const Args &&... args) {
    static_cast<T &>(this->value_)
        .set_dependencies(std::forward<const Args>(args)...);
    return *(this);
  }

  Option<T> &required() {
    return static_cast<Option<T> &>(OptionBase::required());
  }

  Option<T> &optional() {
    return static_cast<Option<T> &>(OptionBase::optional());
  }

  Option<T> &describe(std::string description) {
    return static_cast<Option<T> &>(OptionBase::describe(description));
  }

  Option<T> &set_null() {
    return static_cast<Option<T> &>(OptionBase::set_null());
  }

  const typename T::ValueType &get_value() const {
    return static_cast<T &>(this->value_).operator()();
  }

  void set_value(const typename T::ValueType &value) {
    static_cast<T &>(this->value_).set_value(value);
  }

  void operator=(const typename T::ValueType &value) { set_value(value); }

  const typename T::ValueType &operator()() const { return get_value(); }
};

typedef std::function<bool(std::string name, bool required, OptionError error,
                           std::string msg)>
    option_error_handler;

class OptionList {
 public:
  template <typename TValue>
  void add(std::string name, TValue &value, std::string description = "",
           bool required = false) {
    //name = std::regex_replace(name, std::regex("[ _]"), "-");
    add(Option<TValue>(name, value, description, required));
  }

  template <typename TValue> void add(const Option<TValue> &value) {
    if(!has_option(value.name())){
          options_.push_back(value);
     }else{
          throw std::runtime_error("Option with same name "+ value.name() + " already exists.");
     }
  }

  OptionBase &operator[](std::string key);

  void remove(std::string key);

  std::vector<std::string> options() const;

  std::vector<std::string> required_options() const;

  bool has_option(std::string name) const noexcept;

  void from_yaml(const YAML::Node &node,
                 const option_error_handler &handler = {},  bool check=true);

  YAML::Node to_yaml(const option_error_handler &handler = {}) const;

  void load_yaml(std::string filename,
                 const option_error_handler &handler = {});

  void save_yaml(std::string filename,
                 const option_error_handler &handler = {}) const;

  std::string list_options(){
      std::string name;
      for (auto &option : options_) {
          name += option.name() + ", ";
      }
      return name;
  };

 protected:
  std::list<OptionBase> options_;
};
}  // namespace options