23 #ifndef _CONFIG_YAML_NODE_H_
24 #define _CONFIG_YAML_NODE_H_
26 #ifndef _CONFIG_YAML_H_
27 # error Do not include yaml_node.h directly
30 #include <arpa/inet.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #include <utils/misc/string_conversions.h>
34 #include <utils/misc/string_split.h>
35 #include <yaml-cpp/traits.h>
52 #define PATH_REGEX "^[a-zA-Z0-9_-]+$"
53 #define YAML_REGEX "^[a-zA-Z0-9_-]+\\.yaml$"
55 #define URL_REGEX "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"
56 #define FRAME_REGEX "^([a-zA-Z_][a-zA-Z0-9_/-]*)+$"
58 namespace yaml_utils {
65 return 'a' <= ch && ch <=
'z';
70 return 'A' <= ch && ch <=
'Z';
75 return IsUpper(ch) ? ch +
'a' -
'A' : ch;
79 tolower(
const std::string &str)
82 std::transform(s.begin(), s.end(), s.begin(), ToLower);
88 IsEntirely(
const std::string &str, T func)
90 for (std::size_t i = 0; i < str.size(); i++)
103 IsFlexibleCase(
const std::string &str)
108 if (IsEntirely(str, IsLower))
111 bool firstcaps = IsUpper(str[0]);
112 std::string rest = str.substr(1);
113 return firstcaps && (IsEntirely(rest, IsLower) || IsEntirely(rest, IsUpper));
118 convert(
const std::string &input, std::string &output)
125 convert(
const std::string &input,
bool &output)
132 std::string truename, falsename;
140 if (!detail::IsFlexibleCase(input))
143 for (
unsigned i = 0; i <
sizeof(names) /
sizeof(names[0]); i++) {
144 if (names[i].truename == detail::tolower(input)) {
149 if (names[i].falsename == detail::tolower(input)) {
159 convert(
const std::string &input, YAML::_Null &output)
161 return input.empty() || input ==
"~" || input ==
"null" || input ==
"Null" || input ==
"NULL";
165 convert(
const std::string &input,
unsigned int &rhs)
169 long int l = strtol(input.c_str(), &endptr, 0);
171 if ((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) || (errno != 0 && l == 0)) {
174 if (endptr == input.c_str())
181 rhs = (
unsigned int)l;
186 template <
typename T>
188 convert(
const std::string &input, T &rhs,
typename YAML::enable_if<YAML::is_numeric<T>>::type * = 0)
190 std::stringstream stream(input);
191 stream.unsetf(std::ios::dec);
192 if ((stream >> rhs) && (stream >> std::ws).eof()) {
195 if (std::numeric_limits<T>::has_infinity) {
196 if (YAML::conversion::IsInfinity(input) || YAML::conversion::IsNegativeInfinity(input)) {
197 rhs = std::numeric_limits<T>::infinity();
202 if (std::numeric_limits<T>::has_quiet_NaN && YAML::conversion::IsNaN(input)) {
203 rhs = std::numeric_limits<T>::quiet_NaN();
210 static std::regex url_regex{URL_REGEX, std::regex_constants::extended};
211 static std::regex frame_regex{FRAME_REGEX, std::regex_constants::extended};
214 class YamlConfigurationNode :
public std::enable_shared_from_this<YamlConfigurationNode>
219 enum value { NONE, UINT32, INT32, FLOAT, BOOL, STRING, MAP, SEQUENCE, SEQUENCE_MAP, UNKNOWN };
224 case NONE:
return "NONE";
225 case UINT32:
return "unsigned int";
226 case INT32:
return "int";
227 case FLOAT:
return "float";
228 case BOOL:
return "bool";
229 case STRING:
return "string";
230 case SEQUENCE:
return "SEQUENCE";
231 case MAP:
return "MAP";
232 case SEQUENCE_MAP:
return "SEQUENCE_MAP";
233 default:
return "UNKNOWN";
238 YamlConfigurationNode() : name_(
"root"), type_(Type::UNKNOWN), is_default_(false)
242 YamlConfigurationNode(std::string name) : name_(name), type_(Type::NONE), is_default_(false)
246 YamlConfigurationNode(
const YamlConfigurationNode &n) =
delete;
248 ~YamlConfigurationNode()
252 static std::shared_ptr<YamlConfigurationNode>
253 create(
const YAML::Node &node,
const std::string &name =
"root")
255 auto n = std::make_shared<YamlConfigurationNode>(name);
257 switch (node.Type()) {
258 case YAML::NodeType::Null: n->set_type(Type::NONE);
break;
260 case YAML::NodeType::Scalar:
261 n->set_scalar(node.Scalar());
262 n->verify_scalar(node);
265 case YAML::NodeType::Sequence:
266 n->set_type(Type::SEQUENCE);
267 n->set_sequence(node);
270 case YAML::NodeType::Map:
271 n->set_type(Type::MAP);
275 default: n->set_type(Type::UNKNOWN);
break;
282 add_child(std::string &p, std::shared_ptr<YamlConfigurationNode> n)
284 if (type_ != Type::MAP && type_ != Type::SEQUENCE_MAP) {
290 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator
293 return children_.begin();
296 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator
299 return children_.end();
302 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::size_type
305 return children_.size();
308 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::const_iterator
311 return children_.begin();
314 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::const_iterator
317 return children_.end();
320 std::shared_ptr<YamlConfigurationNode>
321 find(std::queue<std::string> &q)
323 std::shared_ptr<YamlConfigurationNode> n = shared_from_this();
327 std::string pel = q.front();
331 if (n->children_.find(pel) == n->children_.end()) {
332 throw ConfigEntryNotFoundException(path.c_str());
334 n = n->children_[pel];
342 std::shared_ptr<YamlConfigurationNode>
343 find_or_insert(
const char *path)
347 std::shared_ptr<YamlConfigurationNode> n = shared_from_this();
349 std::string pel = q.front();
350 if (n->children_.find(pel) == n->children_.end()) {
351 n->add_child(pel, std::make_shared<YamlConfigurationNode>(pel));
353 n = n->children_[pel];
361 erase(
const char *path)
364 std::stack<std::shared_ptr<YamlConfigurationNode>> qs;
365 std::string full_path;
367 std::shared_ptr<YamlConfigurationNode> n = shared_from_this();
369 std::string pel = q.front();
370 full_path +=
"/" + pel;
372 if (n->children_.find(pel) == n->children_.end()) {
373 throw ConfigEntryNotFoundException(full_path.c_str());
376 n = n->children_[pel];
381 if (n->has_children()) {
382 throw Exception(
"YamlConfig: cannot erase non-leaf value");
385 std::shared_ptr<YamlConfigurationNode> child = n;
386 while (!qs.empty()) {
387 std::shared_ptr<YamlConfigurationNode> en = qs.top();
389 en->children_.erase(child->name());
392 if (en->has_children()) {
401 std::shared_ptr<YamlConfigurationNode>
402 find(
const char *path)
407 }
catch (Exception &e) {
413 operator=(
const YamlConfigurationNode &&n)
415 name_ = std::move(n.name_);
416 type_ = std::move(n.type_);
417 children_ = std::move(n.children_);
418 list_values_ = std::move(n.list_values_);
422 operator<(
const YamlConfigurationNode &n)
const
424 return this->name_ < n.name_;
427 std::shared_ptr<YamlConfigurationNode>
428 operator[](
const std::string &p)
430 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator i;
431 if ((i = children_.find(p)) != children_.end()) {
438 std::shared_ptr<YamlConfigurationNode>
439 operator+=(
const std::shared_ptr<YamlConfigurationNode> n)
442 return shared_from_this();
444 std::shared_ptr<YamlConfigurationNode> add_to = shared_from_this();
446 if (n->name() !=
"root") {
447 if (children_.find(n->name()) == children_.end()) {
448 auto new_val = std::make_shared<YamlConfigurationNode>(n->name());
449 new_val->set_type(n->get_type());
450 children_[n->name()] = new_val;
452 add_to = children_[n->name()];
455 if (add_to->is_scalar()) {
456 if (!n->is_scalar()) {
457 throw Exception(
"YamlConfig: cannot overwrite scalar value %s with non-scalar",
458 add_to->name().c_str());
460 add_to->set_scalar(n->get_scalar());
461 }
else if (add_to->is_list()) {
463 throw Exception(
"YamlConfig: cannot overwrite list value %s with non-list",
464 add_to->name().c_str());
466 if (is_type<unsigned int>()) {
470 add_to->set_list(n->get_list<
unsigned int>());
472 add_to->set_list(n->get_list<
int>());
474 }
catch (Exception &e) {
476 add_to->set_list(n->get_list<
unsigned int>());
478 }
else if (is_type<int>()) {
479 add_to->set_list(n->get_list<
int>());
480 }
else if (is_type<float>()) {
481 add_to->set_list(n->get_list<
float>());
482 }
else if (is_type<bool>()) {
483 add_to->set_list(n->get_list<
bool>());
484 }
else if (is_type<std::string>()) {
485 add_to->set_list(n->get_list<std::string>());
487 std::vector<std::string> empty;
488 add_to->set_list(empty);
490 }
else if (add_to->get_type() == Type::SEQUENCE_MAP) {
491 if (n->get_type() != Type::SEQUENCE_MAP) {
492 throw Exception(
"YamlConfig: cannot overwrite sequence map value %s with non-sequence-map",
493 add_to->name().c_str());
495 add_to->children_.clear();
496 for (
auto i = n->begin(); i != n->end(); ++i) {
497 *add_to += i->second;
500 for (
auto i = n->begin(); i != n->end(); ++i) {
501 *add_to += i->second;
505 return shared_from_this();
509 operator==(
const YamlConfigurationNode &n)
const
511 return (name_ == n.name_) && (type_ == n.type_) && (scalar_value_ == n.scalar_value_);
515 operator!=(
const YamlConfigurationNode &n)
const
517 return (name_ != n.name_) || (type_ != n.type_) || (scalar_value_ != n.scalar_value_);
529 static std::list<std::string>
530 diff(
const std::shared_ptr<YamlConfigurationNode> a,
531 const std::shared_ptr<YamlConfigurationNode> b)
533 std::list<std::string> rv;
535 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> na, nb;
539 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator i;
540 for (i = na.begin(); i != na.end(); ++i) {
541 if (nb.find(i->first) == nb.end()) {
544 rv.push_back(i->first);
545 }
else if (*i->second != *nb[i->first]) {
548 rv.push_back(i->first);
552 for (i = nb.begin(); i != nb.end(); ++i) {
553 if (na.find(i->first) == na.end()) {
556 rv.push_back(i->first);
569 template <
typename T>
573 if (type_ == Type::SEQUENCE) {
574 throw Exception(
"YamlConfiguration: value of %s is a list", name_.c_str());
577 if (yaml_utils::convert(scalar_value_, rv)) {
581 throw Exception(
"YamlConfig: value or type error on %s", name_.c_str());
590 get_list_as_string()
const
592 if (type_ != Type::SEQUENCE) {
593 throw fawkes::Exception(
"YamlConfiguration: value of %s is not a list", name_.c_str());
595 if (list_values_.empty())
599 bool is_string = (determine_scalar_type() == Type::STRING);
601 rv =
" \"" + list_values_[0] +
"\"";
602 for (
size_t i = 1; i < list_values_.size(); ++i) {
603 rv +=
" \"" + list_values_[i] +
"\"";
606 rv = list_values_[0];
607 for (
size_t i = 1; i < list_values_.size(); ++i) {
608 rv +=
" " + list_values_[i];
620 template <
typename T>
624 if (type_ != Type::SEQUENCE) {
625 throw Exception(
"YamlConfiguration: value of %s is not a list", name_.c_str());
628 const typename std::vector<T>::size_type N = list_values_.size();
630 for (
typename std::vector<T>::size_type i = 0; i < N; ++i) {
632 if (!yaml_utils::convert(list_values_[i], t)) {
634 throw Exception(
"YamlConfig: value or type error on %s[%zi]", name_.c_str(), i);
647 get_list_size()
const
649 if (type_ != Type::SEQUENCE) {
650 throw Exception(
"YamlConfiguration: value of %s is not a list", name_.c_str());
652 return list_values_.size();
661 template <
typename T>
663 set_value(
const char *path, T t)
665 std::shared_ptr<YamlConfigurationNode> n = find_or_insert(path);
666 if (n->has_children()) {
667 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", path);
678 template <
typename T>
680 set_list(
const char *path, std::vector<T> &t)
682 std::shared_ptr<YamlConfigurationNode> n = find_or_insert(path);
683 if (n->has_children()) {
684 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", path);
686 std::vector<std::string> v;
687 typename std::vector<T>::size_type N = t.size();
689 for (
typename std::vector<T>::size_type i = 0; i < N; ++i) {
692 n->set_scalar_list(v);
701 template <
typename T>
705 if (has_children()) {
706 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
717 template <
typename T>
719 set_list(
const std::vector<T> &t)
721 if (has_children()) {
722 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
724 std::vector<std::string> v;
725 typename std::vector<T>::size_type N = t.size();
727 for (
typename std::vector<T>::size_type i = 0; i < N; ++i) {
739 template <
typename T>
744 if (type_ == Type::SEQUENCE) {
745 if (!list_values_.empty()) {
747 return (yaml_utils::convert(list_values_[0], rv));
752 return (yaml_utils::convert(scalar_value_, rv));
770 case Type::STRING:
return true;
771 default:
return false;
778 return (type_ == Type::SEQUENCE);
784 return get_value<float>();
790 return get_value<unsigned int>();
796 return get_value<int>();
802 return get_value<bool>();
808 return get_value<std::string>();
812 set_scalar(
const std::string &s)
815 type_ = determine_scalar_type();
819 set_scalar_list(
const std::vector<std::string> &s)
822 type_ = Type::SEQUENCE;
828 return scalar_value_;
832 set_sequence(
const YAML::Node &n)
834 if (n.Type() != YAML::NodeType::Sequence) {
835 #ifdef HAVE_YAMLCPP_NODE_MARK
836 throw Exception(
"Cannot initialize list from non-sequence (line %i, column %i)",
840 throw Exception(
"Cannot initialize list from non-sequence");
843 type_ = Type::SEQUENCE;
844 list_values_.resize(n.size());
846 if (n.begin()->Type() == YAML::NodeType::Scalar) {
848 for (YAML::const_iterator it = n.begin(); it != n.end(); ++it) {
849 list_values_[i++] = it->as<std::string>();
851 }
else if (n.begin()->Type() == YAML::NodeType::Map) {
852 type_ = Type::SEQUENCE_MAP;
853 for (
size_t i = 0; i < n.size(); ++i) {
854 std::string key{std::to_string(i)};
855 add_child(key, YamlConfigurationNode::create(n[i], key));
858 #ifdef HAVE_YAMLCPP_NODE_MARK
859 throw Exception(
"Sequence neither of type scalar nor map (line %i, column %i)",
863 throw Exception(
"Sequence neither of type scalar nor map");
870 set_map(
const YAML::Node &node)
872 for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
873 std::string key = it->first.as<std::string>();
874 std::shared_ptr<YamlConfigurationNode> in = shared_from_this();
875 if (key.find(
"/") != std::string::npos) {
877 std::vector<std::string> pel =
str_split(key);
878 for (
size_t i = 0; i < pel.size() - 1; ++i) {
879 std::shared_ptr<YamlConfigurationNode> n = (*in)[pel[i]];
881 n = std::make_shared<YamlConfigurationNode>(pel[i]);
882 in->add_child(pel[i], n);
890 if (children_.find(key) != children_.end()) {
892 auto new_value = YamlConfigurationNode::create(it->second, key);
893 if (new_value->get_type() != children_[key]->get_type()) {
894 #ifdef HAVE_YAMLCPP_NODE_MARK
896 "YamlConfig (line %d, column %d): overwriting value with incompatible type",
900 throw Exception(
"YamlConfig: overwriting value with incompatible type");
903 in->add_child(key, new_value);
905 in->add_child(key, YamlConfigurationNode::create(it->second, key));
913 return !children_.empty();
923 set_default(
const char *path,
bool is_default)
925 std::shared_ptr<YamlConfigurationNode> n = find(path);
926 n->set_default(is_default);
930 set_default(
bool is_default)
932 is_default_ = is_default;
936 enum_leafs(std::map<std::string, std::shared_ptr<YamlConfigurationNode>> &nodes,
937 std::string prefix =
"")
const
939 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::const_iterator c;
940 for (c = children_.begin(); c != children_.end(); ++c) {
941 std::string path = prefix +
"/" + c->first;
942 if (c->second->has_children()) {
943 c->second->enum_leafs(nodes, path);
945 nodes[path] = c->second;
951 print(std::string indent =
"")
953 std::cout << indent << name_;
954 if (!children_.empty()) {
955 std::cout << std::endl;
956 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator c;
957 for (c = children_.begin(); c != children_.end(); ++c) {
958 c->second->print(indent +
" ");
961 std::cout << indent << scalar_value_ <<
" (" << Type::to_string(get_type()) <<
")"
968 emit(YAML::Emitter &ye)
970 if (!children_.empty()) {
971 ye << YAML::BeginMap;
973 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator c;
974 for (c = children_.begin(); c != children_.end(); ++c) {
975 if (c->second->has_children()) {
977 ye << YAML::Key << c->first << YAML::Value;
980 ye << YAML::Key << c->first << YAML::Value << c->second->get_scalar();
990 emit(std::string &filename)
992 if (access(filename.c_str(), W_OK) != 0) {
993 if (errno != ENOENT) {
994 throw Exception(errno,
"YamlConfig: cannot write host file");
998 std::ofstream fout(filename.c_str());
1012 set_name(
const std::string &name)
1018 set_type(Type::value type)
1024 determine_scalar_type()
const
1026 if (is_type<unsigned int>()) {
1030 return Type::UINT32;
1034 }
catch (Exception &e) {
1036 return Type::UINT32;
1038 }
else if (is_type<int>()) {
1040 }
else if (is_type<float>()) {
1042 }
else if (is_type<bool>()) {
1044 }
else if (is_type<std::string>()) {
1045 return Type::STRING;
1047 return Type::UNKNOWN;
1052 verify_scalar(
const YAML::Node &node)
const
1054 if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv4"
1055 || node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv6") {
1058 addr_s = get_string();
1059 }
catch (Exception &e) {
1060 #ifdef HAVE_YAMLCPP_NODE_MARK
1061 e.prepend(
"YamlConfig (line %d, column %d): Invalid IPv4 or IPv6 address (not a string)",
1063 node.Mark().column);
1065 e.prepend(
"YamlConfig: Invalid IPv4 or IPv6 address (not a string)");
1070 if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv4") {
1071 struct in_addr addr;
1072 if (inet_pton(AF_INET, addr_s.c_str(), &addr) != 1) {
1073 throw Exception(
"YamlConfig: %s is not a valid IPv4 address", addr_s.c_str());
1076 if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv6") {
1077 struct in6_addr addr;
1078 if (inet_pton(AF_INET6, addr_s.c_str(), &addr) != 1) {
1079 throw Exception(
"YamlConfig: %s is not a valid IPv6 address", addr_s.c_str());
1083 }
else if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/tcp-port"
1084 || node.Tag() ==
"tag:fawkesrobotics.org,cfg/udp-port") {
1088 }
catch (Exception &e) {
1089 #ifdef HAVE_YAMLCPP_NODE_MARK
1091 "YamlConfig (line %d, column %d): Invalid TCP/UDP port number (not an unsigned int)",
1093 node.Mark().column);
1095 e.prepend(
"YamlConfig: Invalid TCP/UDP port number (not an unsigned int)");
1099 if (p <= 0 || p >= 65535) {
1100 throw Exception(
"YamlConfig: Invalid TCP/UDP port number "
1101 "(%u out of allowed range)",
1104 }
else if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/url") {
1105 std::string scalar = node.Scalar();
1106 if (!regex_match(scalar, yaml_utils::url_regex)) {
1107 #ifdef HAVE_YAMLCPP_NODE_MARK
1108 throw Exception(
"YamlConfig (line %d, column %d): %s is not a valid URL",
1113 throw Exception(
"YamlConfig: %s is not a valid URL", scalar.c_str());
1116 }
else if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/frame") {
1117 std::string scalar = node.Scalar();
1118 if (!regex_match(scalar, yaml_utils::frame_regex)) {
1119 #ifdef HAVE_YAMLCPP_NODE_MARK
1120 throw Exception(
"YamlConfig (line %d, column %d): %s is not a valid frame ID",
1125 throw Exception(
"YamlConfig: %s is not a valid frame ID", scalar.c_str());
1134 std::string scalar_value_;
1135 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> children_;
1136 std::vector<std::string> list_values_;
Base class for exceptions in Fawkes.
static std::string to_string(unsigned int i)
Convert unsigned int value to a string.
Fawkes library namespace.
static std::queue< std::string > str_split_to_queue(const std::string &s, char delim='/')
Split string by delimiter.
static bool is_type(std::shared_ptr< YamlConfigurationNode > root, const char *path)
Check if value is of given type T.
static std::vector< std::string > str_split(const std::string &s, char delim='/')
Split string by delimiter.
static std::vector< T > get_list(std::shared_ptr< YamlConfigurationNode > root, const char *path)
Retrieve value casted to given type T.