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> operator[](
const std::string &p)
429 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator i;
430 if ((i = children_.find(p)) != children_.end()) {
437 std::shared_ptr<YamlConfigurationNode>
438 operator+=(
const std::shared_ptr<YamlConfigurationNode> n)
441 return shared_from_this();
443 std::shared_ptr<YamlConfigurationNode> add_to = shared_from_this();
445 if (n->name() !=
"root") {
446 if (children_.find(n->name()) == children_.end()) {
447 auto new_val = std::make_shared<YamlConfigurationNode>(n->name());
448 new_val->set_type(n->get_type());
449 children_[n->name()] = new_val;
451 add_to = children_[n->name()];
454 if (add_to->is_scalar()) {
455 if (!n->is_scalar()) {
456 throw Exception(
"YamlConfig: cannot overwrite scalar value %s with non-scalar",
457 add_to->name().c_str());
459 add_to->set_scalar(n->get_scalar());
460 }
else if (add_to->is_list()) {
462 throw Exception(
"YamlConfig: cannot overwrite list value %s with non-list",
463 add_to->name().c_str());
465 if (is_type<unsigned int>()) {
469 add_to->set_list(n->get_list<
unsigned int>());
471 add_to->set_list(n->get_list<
int>());
473 }
catch (Exception &e) {
475 add_to->set_list(n->get_list<
unsigned int>());
477 }
else if (is_type<int>()) {
478 add_to->set_list(n->get_list<
int>());
479 }
else if (is_type<float>()) {
480 add_to->set_list(n->get_list<
float>());
481 }
else if (is_type<bool>()) {
482 add_to->set_list(n->get_list<
bool>());
483 }
else if (is_type<std::string>()) {
484 add_to->set_list(n->get_list<std::string>());
486 std::vector<std::string> empty;
487 add_to->set_list(empty);
489 }
else if (add_to->get_type() == Type::SEQUENCE_MAP) {
490 if (n->get_type() != Type::SEQUENCE_MAP) {
491 throw Exception(
"YamlConfig: cannot overwrite sequence map value %s with non-sequence-map",
492 add_to->name().c_str());
494 add_to->children_.clear();
495 for (
auto i = n->begin(); i != n->end(); ++i) {
496 *add_to += i->second;
499 for (
auto i = n->begin(); i != n->end(); ++i) {
500 *add_to += i->second;
504 return shared_from_this();
508 operator==(
const YamlConfigurationNode &n)
const
510 return (name_ == n.name_) && (type_ == n.type_) && (scalar_value_ == n.scalar_value_);
514 operator!=(
const YamlConfigurationNode &n)
const
516 return (name_ != n.name_) || (type_ != n.type_) || (scalar_value_ != n.scalar_value_);
528 static std::list<std::string>
529 diff(
const std::shared_ptr<YamlConfigurationNode> a,
530 const std::shared_ptr<YamlConfigurationNode> b)
532 std::list<std::string> rv;
534 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> na, nb;
538 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator i;
539 for (i = na.begin(); i != na.end(); ++i) {
540 if (nb.find(i->first) == nb.end()) {
543 rv.push_back(i->first);
544 }
else if (*i->second != *nb[i->first]) {
547 rv.push_back(i->first);
551 for (i = nb.begin(); i != nb.end(); ++i) {
552 if (na.find(i->first) == na.end()) {
555 rv.push_back(i->first);
568 template <
typename T>
572 if (type_ == Type::SEQUENCE) {
573 throw Exception(
"YamlConfiguration: value of %s is a list", name_.c_str());
576 if (yaml_utils::convert(scalar_value_, rv)) {
580 throw Exception(
"YamlConfig: value or type error on %s", name_.c_str());
589 get_list_as_string()
const
591 if (type_ != Type::SEQUENCE) {
592 throw fawkes::Exception(
"YamlConfiguration: value of %s is not a list", name_.c_str());
594 if (list_values_.empty())
598 bool is_string = (determine_scalar_type() == Type::STRING);
600 rv =
" \"" + list_values_[0] +
"\"";
601 for (
size_t i = 1; i < list_values_.size(); ++i) {
602 rv +=
" \"" + list_values_[i] +
"\"";
605 rv = list_values_[0];
606 for (
size_t i = 1; i < list_values_.size(); ++i) {
607 rv +=
" " + list_values_[i];
619 template <
typename T>
623 if (type_ != Type::SEQUENCE) {
624 throw Exception(
"YamlConfiguration: value of %s is not a list", name_.c_str());
627 const typename std::vector<T>::size_type N = list_values_.size();
629 for (
typename std::vector<T>::size_type i = 0; i < N; ++i) {
631 if (!yaml_utils::convert(list_values_[i], t)) {
633 throw Exception(
"YamlConfig: value or type error on %s[%zi]", name_.c_str(), i);
646 get_list_size()
const
648 if (type_ != Type::SEQUENCE) {
649 throw Exception(
"YamlConfiguration: value of %s is not a list", name_.c_str());
651 return list_values_.size();
660 template <
typename T>
662 set_value(
const char *path, T t)
664 std::shared_ptr<YamlConfigurationNode> n = find_or_insert(path);
665 if (n->has_children()) {
666 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", path);
677 template <
typename T>
679 set_list(
const char *path, std::vector<T> &t)
681 std::shared_ptr<YamlConfigurationNode> n = find_or_insert(path);
682 if (n->has_children()) {
683 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", path);
685 std::vector<std::string> v;
686 typename std::vector<T>::size_type N = t.size();
688 for (
typename std::vector<T>::size_type i = 0; i < N; ++i) {
691 n->set_scalar_list(v);
700 template <
typename T>
704 if (has_children()) {
705 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
716 template <
typename T>
718 set_list(
const std::vector<T> &t)
720 if (has_children()) {
721 throw Exception(
"YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
723 std::vector<std::string> v;
724 typename std::vector<T>::size_type N = t.size();
726 for (
typename std::vector<T>::size_type i = 0; i < N; ++i) {
738 template <
typename T>
743 if (type_ == Type::SEQUENCE) {
744 if (!list_values_.empty()) {
746 return (yaml_utils::convert(list_values_[0], rv));
751 return (yaml_utils::convert(scalar_value_, rv));
769 case Type::STRING:
return true;
770 default:
return false;
777 return (type_ == Type::SEQUENCE);
783 return get_value<float>();
789 return get_value<unsigned int>();
795 return get_value<int>();
801 return get_value<bool>();
807 return get_value<std::string>();
811 set_scalar(
const std::string &s)
814 type_ = determine_scalar_type();
818 set_scalar_list(
const std::vector<std::string> &s)
821 type_ = Type::SEQUENCE;
827 return scalar_value_;
831 set_sequence(
const YAML::Node &n)
833 if (n.Type() != YAML::NodeType::Sequence) {
834 #ifdef HAVE_YAMLCPP_NODE_MARK
835 throw Exception(
"Cannot initialize list from non-sequence (line %i, column %i)",
839 throw Exception(
"Cannot initialize list from non-sequence");
842 type_ = Type::SEQUENCE;
843 list_values_.resize(n.size());
845 if (n.begin()->Type() == YAML::NodeType::Scalar) {
847 for (YAML::const_iterator it = n.begin(); it != n.end(); ++it) {
848 list_values_[i++] = it->as<std::string>();
850 }
else if (n.begin()->Type() == YAML::NodeType::Map) {
851 type_ = Type::SEQUENCE_MAP;
852 for (
size_t i = 0; i < n.size(); ++i) {
853 std::string key{std::to_string(i)};
854 add_child(key, YamlConfigurationNode::create(n[i], key));
857 #ifdef HAVE_YAMLCPP_NODE_MARK
858 throw Exception(
"Sequence neither of type scalar nor map (line %i, column %i)",
862 throw Exception(
"Sequence neither of type scalar nor map");
869 set_map(
const YAML::Node &node)
871 for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
872 std::string key = it->first.as<std::string>();
873 std::shared_ptr<YamlConfigurationNode> in = shared_from_this();
874 if (key.find(
"/") != std::string::npos) {
876 std::vector<std::string> pel =
str_split(key);
877 for (
size_t i = 0; i < pel.size() - 1; ++i) {
878 std::shared_ptr<YamlConfigurationNode> n = (*in)[pel[i]];
880 n = std::make_shared<YamlConfigurationNode>(pel[i]);
881 in->add_child(pel[i], n);
889 if (children_.find(key) != children_.end()) {
891 auto new_value = YamlConfigurationNode::create(it->second, key);
892 if (new_value->get_type() != children_[key]->get_type()) {
893 #ifdef HAVE_YAMLCPP_NODE_MARK
895 "YamlConfig (line %d, column %d): overwriting value with incompatible type",
899 throw Exception(
"YamlConfig: overwriting value with incompatible type");
902 in->add_child(key, new_value);
904 in->add_child(key, YamlConfigurationNode::create(it->second, key));
912 return !children_.empty();
922 set_default(
const char *path,
bool is_default)
924 std::shared_ptr<YamlConfigurationNode> n = find(path);
925 n->set_default(is_default);
929 set_default(
bool is_default)
931 is_default_ = is_default;
935 enum_leafs(std::map<std::string, std::shared_ptr<YamlConfigurationNode>> &nodes,
936 std::string prefix =
"")
const
938 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::const_iterator c;
939 for (c = children_.begin(); c != children_.end(); ++c) {
940 std::string path = prefix +
"/" + c->first;
941 if (c->second->has_children()) {
942 c->second->enum_leafs(nodes, path);
944 nodes[path] = c->second;
950 print(std::string indent =
"")
952 std::cout << indent << name_;
953 if (!children_.empty()) {
954 std::cout << std::endl;
955 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator c;
956 for (c = children_.begin(); c != children_.end(); ++c) {
957 c->second->print(indent +
" ");
960 std::cout << indent << scalar_value_ <<
" (" << Type::to_string(get_type()) <<
")"
967 emit(YAML::Emitter &ye)
969 if (!children_.empty()) {
970 ye << YAML::BeginMap;
972 std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator c;
973 for (c = children_.begin(); c != children_.end(); ++c) {
974 if (c->second->has_children()) {
976 ye << YAML::Key << c->first << YAML::Value;
979 ye << YAML::Key << c->first << YAML::Value << c->second->get_scalar();
989 emit(std::string &filename)
991 if (access(filename.c_str(), W_OK) != 0) {
992 if (errno != ENOENT) {
993 throw Exception(errno,
"YamlConfig: cannot write host file");
997 std::ofstream fout(filename.c_str());
1011 set_name(
const std::string &name)
1017 set_type(Type::value type)
1023 determine_scalar_type()
const
1025 if (is_type<unsigned int>()) {
1029 return Type::UINT32;
1033 }
catch (Exception &e) {
1035 return Type::UINT32;
1037 }
else if (is_type<int>()) {
1039 }
else if (is_type<float>()) {
1041 }
else if (is_type<bool>()) {
1043 }
else if (is_type<std::string>()) {
1044 return Type::STRING;
1046 return Type::UNKNOWN;
1051 verify_scalar(
const YAML::Node &node)
const
1053 if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv4"
1054 || node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv6") {
1057 addr_s = get_string();
1058 }
catch (Exception &e) {
1059 #ifdef HAVE_YAMLCPP_NODE_MARK
1060 e.prepend(
"YamlConfig (line %d, column %d): Invalid IPv4 or IPv6 address (not a string)",
1062 node.Mark().column);
1064 e.prepend(
"YamlConfig: Invalid IPv4 or IPv6 address (not a string)");
1069 if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv4") {
1070 struct in_addr addr;
1071 if (inet_pton(AF_INET, addr_s.c_str(), &addr) != 1) {
1072 throw Exception(
"YamlConfig: %s is not a valid IPv4 address", addr_s.c_str());
1075 if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/ipv6") {
1076 struct in6_addr addr;
1077 if (inet_pton(AF_INET6, addr_s.c_str(), &addr) != 1) {
1078 throw Exception(
"YamlConfig: %s is not a valid IPv6 address", addr_s.c_str());
1082 }
else if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/tcp-port"
1083 || node.Tag() ==
"tag:fawkesrobotics.org,cfg/udp-port") {
1087 }
catch (Exception &e) {
1088 #ifdef HAVE_YAMLCPP_NODE_MARK
1090 "YamlConfig (line %d, column %d): Invalid TCP/UDP port number (not an unsigned int)",
1092 node.Mark().column);
1094 e.prepend(
"YamlConfig: Invalid TCP/UDP port number (not an unsigned int)");
1098 if (p <= 0 || p >= 65535) {
1099 throw Exception(
"YamlConfig: Invalid TCP/UDP port number "
1100 "(%u out of allowed range)",
1103 }
else if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/url") {
1104 std::string scalar = node.Scalar();
1105 if (!regex_match(scalar, yaml_utils::url_regex)) {
1106 #ifdef HAVE_YAMLCPP_NODE_MARK
1107 throw Exception(
"YamlConfig (line %d, column %d): %s is not a valid URL",
1112 throw Exception(
"YamlConfig: %s is not a valid URL", scalar.c_str());
1115 }
else if (node.Tag() ==
"tag:fawkesrobotics.org,cfg/frame") {
1116 std::string scalar = node.Scalar();
1117 if (!regex_match(scalar, yaml_utils::frame_regex)) {
1118 #ifdef HAVE_YAMLCPP_NODE_MARK
1119 throw Exception(
"YamlConfig (line %d, column %d): %s is not a valid frame ID",
1124 throw Exception(
"YamlConfig: %s is not a valid frame ID", scalar.c_str());
1133 std::string scalar_value_;
1134 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> children_;
1135 std::vector<std::string> list_values_;