Fawkes API  Fawkes Development Version
yaml_node.h
1 
2 /***************************************************************************
3  * yaml_node.h - Utility class for internal YAML config handling
4  *
5  * Created: Thu Aug 09 14:08:18 2012
6  * Copyright 2006-2018 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version. A runtime exception applies to
13  * this software (see LICENSE.GPL_WRE file mentioned below for details).
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL_WRE file in the doc directory.
21  */
22 
23 #ifndef _CONFIG_YAML_NODE_H_
24 #define _CONFIG_YAML_NODE_H_
25 
26 #ifndef _CONFIG_YAML_H_
27 # error Do not include yaml_node.h directly
28 #endif
29 
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>
36 
37 #include <algorithm>
38 #include <cerrno>
39 #include <climits>
40 #include <fstream>
41 #include <iostream>
42 #include <limits>
43 #include <memory>
44 #include <regex>
45 #include <stack>
46 #include <unistd.h>
47 
48 namespace fawkes {
49 
50 /// @cond INTERNALS
51 
52 #define PATH_REGEX "^[a-zA-Z0-9_-]+$"
53 #define YAML_REGEX "^[a-zA-Z0-9_-]+\\.yaml$"
54 // from https://www.ietf.org/rfc/rfc3986.txt
55 #define URL_REGEX "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"
56 #define FRAME_REGEX "^([a-zA-Z_][a-zA-Z0-9_/-]*)+$"
57 
58 namespace yaml_utils {
59 
60 namespace detail {
61 // we're not gonna mess with the mess that is all the isupper/etc. functions
62 inline bool
63 IsLower(char ch)
64 {
65  return 'a' <= ch && ch <= 'z';
66 }
67 inline bool
68 IsUpper(char ch)
69 {
70  return 'A' <= ch && ch <= 'Z';
71 }
72 inline char
73 ToLower(char ch)
74 {
75  return IsUpper(ch) ? ch + 'a' - 'A' : ch;
76 }
77 
78 inline std::string
79 tolower(const std::string &str)
80 {
81  std::string s(str);
82  std::transform(s.begin(), s.end(), s.begin(), ToLower);
83  return s;
84 }
85 
86 template <typename T>
87 inline bool
88 IsEntirely(const std::string &str, T func)
89 {
90  for (std::size_t i = 0; i < str.size(); i++)
91  if (!func(str[i]))
92  return false;
93 
94  return true;
95 }
96 
97 // IsFlexibleCase
98 // . Returns true if 'str' is:
99 // . UPPERCASE
100 // . lowercase
101 // . Capitalized
102 inline bool
103 IsFlexibleCase(const std::string &str)
104 {
105  if (str.empty())
106  return true;
107 
108  if (IsEntirely(str, IsLower))
109  return true;
110 
111  bool firstcaps = IsUpper(str[0]);
112  std::string rest = str.substr(1);
113  return firstcaps && (IsEntirely(rest, IsLower) || IsEntirely(rest, IsUpper));
114 }
115 } // namespace detail
116 
117 inline bool
118 convert(const std::string &input, std::string &output)
119 {
120  output = input;
121  return true;
122 }
123 
124 inline bool
125 convert(const std::string &input, bool &output)
126 {
127  // we can't use iostream bool extraction operators as they don't
128  // recognize all possible values in the table below (taken from
129  // http://yaml.org/type/bool.html)
130  static const struct
131  {
132  std::string truename, falsename;
133  } names[] = {
134  {"y", "n"},
135  {"yes", "no"},
136  {"true", "false"},
137  {"on", "off"},
138  };
139 
140  if (!detail::IsFlexibleCase(input))
141  return false;
142 
143  for (unsigned i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
144  if (names[i].truename == detail::tolower(input)) {
145  output = true;
146  return true;
147  }
148 
149  if (names[i].falsename == detail::tolower(input)) {
150  output = false;
151  return true;
152  }
153  }
154 
155  return false;
156 }
157 
158 inline bool
159 convert(const std::string &input, YAML::_Null &output)
160 {
161  return input.empty() || input == "~" || input == "null" || input == "Null" || input == "NULL";
162 }
163 
164 inline bool
165 convert(const std::string &input, unsigned int &rhs)
166 {
167  errno = 0;
168  char * endptr;
169  long int l = strtol(input.c_str(), &endptr, 0);
170 
171  if ((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN)) || (errno != 0 && l == 0)) {
172  return false;
173  }
174  if (endptr == input.c_str())
175  return false;
176  if (*endptr != 0)
177  return false;
178  if (l < 0)
179  return false;
180 
181  rhs = (unsigned int)l;
182 
183  return true;
184 }
185 
186 template <typename T>
187 inline bool
188 convert(const std::string &input, T &rhs, typename YAML::enable_if<YAML::is_numeric<T>>::type * = 0)
189 {
190  std::stringstream stream(input);
191  stream.unsetf(std::ios::dec);
192  if ((stream >> rhs) && (stream >> std::ws).eof()) {
193  return true;
194  }
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();
198  return true;
199  }
200  }
201 
202  if (std::numeric_limits<T>::has_quiet_NaN && YAML::conversion::IsNaN(input)) {
203  rhs = std::numeric_limits<T>::quiet_NaN();
204  return true;
205  }
206 
207  return false;
208 }
209 
210 static std::regex url_regex{URL_REGEX, std::regex_constants::extended};
211 static std::regex frame_regex{FRAME_REGEX, std::regex_constants::extended};
212 } // namespace yaml_utils
213 
214 class YamlConfigurationNode : public std::enable_shared_from_this<YamlConfigurationNode>
215 {
216 public:
217  struct Type
218  {
219  enum value { NONE, UINT32, INT32, FLOAT, BOOL, STRING, MAP, SEQUENCE, SEQUENCE_MAP, UNKNOWN };
220  static const char *
221  to_string(value v)
222  {
223  switch (v) {
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";
234  }
235  }
236  };
237 
238  YamlConfigurationNode() : name_("root"), type_(Type::UNKNOWN), is_default_(false)
239  {
240  }
241 
242  YamlConfigurationNode(std::string name) : name_(name), type_(Type::NONE), is_default_(false)
243  {
244  }
245 
246  YamlConfigurationNode(const YamlConfigurationNode &n) = delete;
247 
248  ~YamlConfigurationNode()
249  {
250  }
251 
252  static std::shared_ptr<YamlConfigurationNode>
253  create(const YAML::Node &node, const std::string &name = "root")
254  {
255  auto n = std::make_shared<YamlConfigurationNode>(name);
256 
257  switch (node.Type()) {
258  case YAML::NodeType::Null: n->set_type(Type::NONE); break;
259 
260  case YAML::NodeType::Scalar:
261  n->set_scalar(node.Scalar());
262  n->verify_scalar(node);
263  break;
264 
265  case YAML::NodeType::Sequence:
266  n->set_type(Type::SEQUENCE);
267  n->set_sequence(node);
268  break;
269 
270  case YAML::NodeType::Map:
271  n->set_type(Type::MAP);
272  n->set_map(node);
273  break;
274 
275  default: n->set_type(Type::UNKNOWN); break;
276  }
277 
278  return n;
279  }
280 
281  void
282  add_child(std::string &p, std::shared_ptr<YamlConfigurationNode> n)
283  {
284  if (type_ != Type::MAP && type_ != Type::SEQUENCE_MAP) {
285  type_ = Type::MAP;
286  }
287  children_[p] = n;
288  }
289 
290  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator
291  begin()
292  {
293  return children_.begin();
294  }
295 
296  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator
297  end()
298  {
299  return children_.end();
300  }
301 
302  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::size_type
303  size() const
304  {
305  return children_.size();
306  }
307 
308  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::const_iterator
309  begin() const
310  {
311  return children_.begin();
312  }
313 
314  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::const_iterator
315  end() const
316  {
317  return children_.end();
318  }
319 
320  std::shared_ptr<YamlConfigurationNode>
321  find(std::queue<std::string> &q)
322  {
323  std::shared_ptr<YamlConfigurationNode> n = shared_from_this();
324  std::string path;
325 
326  while (!q.empty()) {
327  std::string pel = q.front();
328 
329  path += "/" + pel;
330 
331  if (n->children_.find(pel) == n->children_.end()) {
332  throw ConfigEntryNotFoundException(path.c_str());
333  }
334  n = n->children_[pel];
335 
336  q.pop();
337  }
338 
339  return n;
340  }
341 
342  std::shared_ptr<YamlConfigurationNode>
343  find_or_insert(const char *path)
344  {
345  std::queue<std::string> q = str_split_to_queue(path);
346 
347  std::shared_ptr<YamlConfigurationNode> n = shared_from_this();
348  while (!q.empty()) {
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));
352  }
353  n = n->children_[pel];
354  q.pop();
355  }
356 
357  return n;
358  }
359 
360  void
361  erase(const char *path)
362  {
363  std::queue<std::string> q = str_split_to_queue(path);
364  std::stack<std::shared_ptr<YamlConfigurationNode>> qs;
365  std::string full_path;
366 
367  std::shared_ptr<YamlConfigurationNode> n = shared_from_this();
368  while (!q.empty()) {
369  std::string pel = q.front();
370  full_path += "/" + pel;
371 
372  if (n->children_.find(pel) == n->children_.end()) {
373  throw ConfigEntryNotFoundException(full_path.c_str());
374  }
375  qs.push(n);
376  n = n->children_[pel];
377 
378  q.pop();
379  }
380 
381  if (n->has_children()) {
382  throw Exception("YamlConfig: cannot erase non-leaf value");
383  }
384 
385  std::shared_ptr<YamlConfigurationNode> child = n;
386  while (!qs.empty()) {
387  std::shared_ptr<YamlConfigurationNode> en = qs.top();
388 
389  en->children_.erase(child->name());
390 
391  // The node had more nodes than just the child, stop erasing
392  if (en->has_children()) {
393  break;
394  }
395 
396  child = en;
397  qs.pop();
398  }
399  }
400 
401  std::shared_ptr<YamlConfigurationNode>
402  find(const char *path)
403  {
404  try {
405  std::queue<std::string> pel_q = str_split_to_queue(path);
406  return find(pel_q);
407  } catch (Exception &e) {
408  throw;
409  }
410  }
411 
412  void
413  operator=(const YamlConfigurationNode &&n)
414  {
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_);
419  }
420 
421  bool
422  operator<(const YamlConfigurationNode &n) const
423  {
424  return this->name_ < n.name_;
425  }
426 
427  std::shared_ptr<YamlConfigurationNode>
428  operator[](const std::string &p)
429  {
430  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator i;
431  if ((i = children_.find(p)) != children_.end()) {
432  return i->second;
433  } else {
434  return NULL;
435  }
436  }
437 
438  std::shared_ptr<YamlConfigurationNode>
439  operator+=(const std::shared_ptr<YamlConfigurationNode> n)
440  {
441  if (!n)
442  return shared_from_this();
443 
444  std::shared_ptr<YamlConfigurationNode> add_to = shared_from_this();
445 
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;
451  }
452  add_to = children_[n->name()];
453  }
454 
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());
459  }
460  add_to->set_scalar(n->get_scalar());
461  } else if (add_to->is_list()) {
462  if (!n->is_list()) {
463  throw Exception("YamlConfig: cannot overwrite list value %s with non-list",
464  add_to->name().c_str());
465  }
466  if (is_type<unsigned int>()) {
467  try {
468  int v = get_int();
469  if (v >= 0) {
470  add_to->set_list(n->get_list<unsigned int>());
471  } else {
472  add_to->set_list(n->get_list<int>());
473  }
474  } catch (Exception &e) {
475  // can happen if value > MAX_INT
476  add_to->set_list(n->get_list<unsigned int>());
477  }
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>());
486  } else {
487  std::vector<std::string> empty;
488  add_to->set_list(empty);
489  }
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());
494  }
495  add_to->children_.clear();
496  for (auto i = n->begin(); i != n->end(); ++i) {
497  *add_to += i->second;
498  }
499  } else {
500  for (auto i = n->begin(); i != n->end(); ++i) {
501  *add_to += i->second;
502  }
503  }
504 
505  return shared_from_this();
506  }
507 
508  bool
509  operator==(const YamlConfigurationNode &n) const
510  {
511  return (name_ == n.name_) && (type_ == n.type_) && (scalar_value_ == n.scalar_value_);
512  }
513 
514  bool
515  operator!=(const YamlConfigurationNode &n) const
516  {
517  return (name_ != n.name_) || (type_ != n.type_) || (scalar_value_ != n.scalar_value_);
518  }
519 
520  /** Check for differences in two trees.
521  * This returns a list of all changes that have occured in b opposed
522  * to b. This means keys which have been added or changed in b compared to
523  * a. It also includes keys which have been removed from a, i.e. which exist
524  * in b but not in a.
525  * @param a root node of first tree
526  * @param b root node of second tree
527  * @return list of paths to leaf nodes that changed
528  */
529  static std::list<std::string>
530  diff(const std::shared_ptr<YamlConfigurationNode> a,
531  const std::shared_ptr<YamlConfigurationNode> b)
532  {
533  std::list<std::string> rv;
534 
535  std::map<std::string, std::shared_ptr<YamlConfigurationNode>> na, nb;
536  a->enum_leafs(na);
537  b->enum_leafs(nb);
538 
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()) {
542  // this is a new key in a
543  // printf("A %s NOT in B\n", i->first.c_str());
544  rv.push_back(i->first);
545  } else if (*i->second != *nb[i->first]) {
546  // different values/types
547  // printf("A %s modified\n", i->first.c_str());
548  rv.push_back(i->first);
549  }
550  }
551 
552  for (i = nb.begin(); i != nb.end(); ++i) {
553  if (na.find(i->first) == na.end()) {
554  // this is a new key in b
555  // printf("B %s NOT in A\n", i->first.c_str());
556  rv.push_back(i->first);
557  }
558  }
559 
560  return rv;
561  }
562 
563  /** Retrieve value casted to given type T.
564  * @param path path to query
565  * @return value casted as desired
566  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
567  * a different type.
568  */
569  template <typename T>
570  T
571  get_value() const
572  {
573  if (type_ == Type::SEQUENCE) {
574  throw Exception("YamlConfiguration: value of %s is a list", name_.c_str());
575  }
576  T rv;
577  if (yaml_utils::convert(scalar_value_, rv)) {
578  return rv;
579  } else {
580  // might want to have custom exception here later
581  throw Exception("YamlConfig: value or type error on %s", name_.c_str());
582  }
583  }
584 
585  /** Get the list elements as string.
586  * The first element determines the type of the list.
587  * @return value string as list, i.e. as space-separated list of items
588  */
589  std::string
590  get_list_as_string() const
591  {
592  if (type_ != Type::SEQUENCE) {
593  throw fawkes::Exception("YamlConfiguration: value of %s is not a list", name_.c_str());
594  }
595  if (list_values_.empty())
596  return "";
597 
598  std::string rv = "";
599  bool is_string = (determine_scalar_type() == Type::STRING);
600  if (is_string) {
601  rv = " \"" + list_values_[0] + "\"";
602  for (size_t i = 1; i < list_values_.size(); ++i) {
603  rv += " \"" + list_values_[i] + "\"";
604  }
605  } else {
606  rv = list_values_[0];
607  for (size_t i = 1; i < list_values_.size(); ++i) {
608  rv += " " + list_values_[i];
609  }
610  }
611 
612  return rv;
613  }
614 
615  /** Retrieve value casted to given type T.
616  * @return value casted as desired
617  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
618  * a different type.
619  */
620  template <typename T>
621  std::vector<T>
622  get_list() const
623  {
624  if (type_ != Type::SEQUENCE) {
625  throw Exception("YamlConfiguration: value of %s is not a list", name_.c_str());
626  }
627  std::vector<T> rv;
628  const typename std::vector<T>::size_type N = list_values_.size();
629  rv.resize(N);
630  for (typename std::vector<T>::size_type i = 0; i < N; ++i) {
631  T t;
632  if (!yaml_utils::convert(list_values_[i], t)) {
633  // might want to have custom exception here later
634  throw Exception("YamlConfig: value or type error on %s[%zi]", name_.c_str(), i);
635  }
636  rv[i] = t;
637  }
638  return rv;
639  }
640 
641  /** Retrieve list size.
642  * @return size of list
643  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
644  * a different type.
645  */
646  size_t
647  get_list_size() const
648  {
649  if (type_ != Type::SEQUENCE) {
650  throw Exception("YamlConfiguration: value of %s is not a list", name_.c_str());
651  }
652  return list_values_.size();
653  }
654 
655  /** Set value of given type T.
656  * @param path path to query
657  * @return value casted as desired
658  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
659  * a different type.
660  */
661  template <typename T>
662  void
663  set_value(const char *path, T t)
664  {
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);
668  }
669  n->set_scalar(StringConversions::to_string(t));
670  }
671 
672  /** Set list of given type T.
673  * @param path path to query
674  * @return value casted as desired
675  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
676  * a different type.
677  */
678  template <typename T>
679  void
680  set_list(const char *path, std::vector<T> &t)
681  {
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);
685  }
686  std::vector<std::string> v;
687  typename std::vector<T>::size_type N = t.size();
688  v.resize(N);
689  for (typename std::vector<T>::size_type i = 0; i < N; ++i) {
690  v[i] = StringConversions::to_string(t[i]);
691  }
692  n->set_scalar_list(v);
693  }
694 
695  /** Set value of given type T.
696  * @param path path to query
697  * @return value casted as desired
698  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
699  * a different type.
700  */
701  template <typename T>
702  void
703  set_value(T t)
704  {
705  if (has_children()) {
706  throw Exception("YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
707  }
708  set_scalar(StringConversions::to_string(t));
709  }
710 
711  /** Set list of values of given type T.
712  * @param path path to query
713  * @return value casted as desired
714  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
715  * a different type.
716  */
717  template <typename T>
718  void
719  set_list(const std::vector<T> &t)
720  {
721  if (has_children()) {
722  throw Exception("YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
723  }
724  std::vector<std::string> v;
725  typename std::vector<T>::size_type N = t.size();
726  v.resize(N);
727  for (typename std::vector<T>::size_type i = 0; i < N; ++i) {
728  v[i] = StringConversions::to_string(t[i]);
729  }
730  set_scalar_list(v);
731  }
732 
733  /** Check if value is of given type T.
734  * @param path path to query
735  * @return value casted as desired
736  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
737  * a different type.
738  */
739  template <typename T>
740  bool
741  is_type() const
742  {
743  T rv;
744  if (type_ == Type::SEQUENCE) {
745  if (!list_values_.empty()) {
746  T rv;
747  return (yaml_utils::convert(list_values_[0], rv));
748  } else {
749  return true;
750  }
751  } else {
752  return (yaml_utils::convert(scalar_value_, rv));
753  }
754  }
755 
756  Type::value
757  get_type() const
758  {
759  return type_;
760  }
761 
762  bool
763  is_scalar() const
764  {
765  switch (type_) {
766  case Type::UINT32:
767  case Type::INT32:
768  case Type::FLOAT:
769  case Type::BOOL:
770  case Type::STRING: return true;
771  default: return false;
772  }
773  }
774 
775  bool
776  is_list() const
777  {
778  return (type_ == Type::SEQUENCE);
779  }
780 
781  float
782  get_float() const
783  {
784  return get_value<float>();
785  }
786 
787  unsigned int
788  get_uint() const
789  {
790  return get_value<unsigned int>();
791  }
792 
793  int
794  get_int() const
795  {
796  return get_value<int>();
797  }
798 
799  bool
800  get_bool() const
801  {
802  return get_value<bool>();
803  }
804 
805  std::string
806  get_string() const
807  {
808  return get_value<std::string>();
809  }
810 
811  void
812  set_scalar(const std::string &s)
813  {
814  scalar_value_ = s;
815  type_ = determine_scalar_type();
816  }
817 
818  void
819  set_scalar_list(const std::vector<std::string> &s)
820  {
821  list_values_ = s;
822  type_ = Type::SEQUENCE;
823  }
824 
825  const std::string &
826  get_scalar() const
827  {
828  return scalar_value_;
829  }
830 
831  void
832  set_sequence(const YAML::Node &n)
833  {
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)",
837  n.Mark().line,
838  n.Mark().column);
839 #else
840  throw Exception("Cannot initialize list from non-sequence");
841 #endif
842  }
843  type_ = Type::SEQUENCE;
844  list_values_.resize(n.size());
845  if (n.size() > 0) {
846  if (n.begin()->Type() == YAML::NodeType::Scalar) {
847  unsigned int i = 0;
848  for (YAML::const_iterator it = n.begin(); it != n.end(); ++it) {
849  list_values_[i++] = it->as<std::string>();
850  }
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));
856  }
857  } else {
858 #ifdef HAVE_YAMLCPP_NODE_MARK
859  throw Exception("Sequence neither of type scalar nor map (line %i, column %i)",
860  n.Mark().line,
861  n.Mark().column);
862 #else
863  throw Exception("Sequence neither of type scalar nor map");
864 #endif
865  }
866  }
867  }
868 
869  void
870  set_map(const YAML::Node &node)
871  {
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) {
876  // we need to split and find the proper insertion node
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]];
880  if (!n) {
881  n = std::make_shared<YamlConfigurationNode>(pel[i]);
882  in->add_child(pel[i], n);
883  }
884  in = n;
885  }
886 
887  key = pel.back();
888  }
889 
890  if (children_.find(key) != children_.end()) {
891  // we are updating a value
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
895  throw Exception(
896  "YamlConfig (line %d, column %d): overwriting value with incompatible type",
897  node.Mark().line,
898  node.Mark().column);
899 #else
900  throw Exception("YamlConfig: overwriting value with incompatible type");
901 #endif
902  }
903  in->add_child(key, new_value);
904  } else {
905  in->add_child(key, YamlConfigurationNode::create(it->second, key));
906  }
907  }
908  }
909 
910  bool
911  has_children() const
912  {
913  return !children_.empty();
914  }
915 
916  bool
917  is_default() const
918  {
919  return is_default_;
920  }
921 
922  void
923  set_default(const char *path, bool is_default)
924  {
925  std::shared_ptr<YamlConfigurationNode> n = find(path);
926  n->set_default(is_default);
927  }
928 
929  void
930  set_default(bool is_default)
931  {
932  is_default_ = is_default;
933  }
934 
935  void
936  enum_leafs(std::map<std::string, std::shared_ptr<YamlConfigurationNode>> &nodes,
937  std::string prefix = "") const
938  {
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);
944  } else {
945  nodes[path] = c->second;
946  }
947  }
948  }
949 
950  void
951  print(std::string indent = "")
952  {
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 + " ");
959  }
960  } else {
961  std::cout << indent << scalar_value_ << " (" << Type::to_string(get_type()) << ")"
962  << std::endl;
963  }
964  }
965 
966 private:
967  void
968  emit(YAML::Emitter &ye)
969  {
970  if (!children_.empty()) {
971  ye << YAML::BeginMap;
972 
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()) {
976  // recurse
977  ye << YAML::Key << c->first << YAML::Value;
978  c->second->emit(ye);
979  } else {
980  ye << YAML::Key << c->first << YAML::Value << c->second->get_scalar();
981  }
982  }
983 
984  ye << YAML::EndMap;
985  }
986  }
987 
988 public:
989  void
990  emit(std::string &filename)
991  {
992  if (access(filename.c_str(), W_OK) != 0) {
993  if (errno != ENOENT) {
994  throw Exception(errno, "YamlConfig: cannot write host file");
995  }
996  }
997 
998  std::ofstream fout(filename.c_str());
999  YAML::Emitter ye;
1000  emit(ye);
1001  fout << ye.c_str();
1002  }
1003 
1004  const std::string &
1005  name() const
1006  {
1007  return name_;
1008  }
1009 
1010 private:
1011  void
1012  set_name(const std::string &name)
1013  {
1014  name_ = name;
1015  }
1016 
1017  void
1018  set_type(Type::value type)
1019  {
1020  type_ = type;
1021  }
1022 
1023  Type::value
1024  determine_scalar_type() const
1025  {
1026  if (is_type<unsigned int>()) {
1027  try {
1028  int v = get_int();
1029  if (v >= 0) {
1030  return Type::UINT32;
1031  } else {
1032  return Type::INT32;
1033  }
1034  } catch (Exception &e) {
1035  // can happen if value > MAX_INT
1036  return Type::UINT32;
1037  }
1038  } else if (is_type<int>()) {
1039  return Type::INT32;
1040  } else if (is_type<float>()) {
1041  return Type::FLOAT;
1042  } else if (is_type<bool>()) {
1043  return Type::BOOL;
1044  } else if (is_type<std::string>()) {
1045  return Type::STRING;
1046  } else {
1047  return Type::UNKNOWN;
1048  }
1049  }
1050 
1051  void
1052  verify_scalar(const YAML::Node &node) const
1053  {
1054  if (node.Tag() == "tag:fawkesrobotics.org,cfg/ipv4"
1055  || node.Tag() == "tag:fawkesrobotics.org,cfg/ipv6") {
1056  std::string addr_s;
1057  try {
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)",
1062  node.Mark().line,
1063  node.Mark().column);
1064 #else
1065  e.prepend("YamlConfig: Invalid IPv4 or IPv6 address (not a string)");
1066 #endif
1067  throw;
1068  }
1069 
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());
1074  }
1075  }
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());
1080  }
1081  }
1082 
1083  } else if (node.Tag() == "tag:fawkesrobotics.org,cfg/tcp-port"
1084  || node.Tag() == "tag:fawkesrobotics.org,cfg/udp-port") {
1085  unsigned int p = 0;
1086  try {
1087  p = get_uint();
1088  } catch (Exception &e) {
1089 #ifdef HAVE_YAMLCPP_NODE_MARK
1090  e.prepend(
1091  "YamlConfig (line %d, column %d): Invalid TCP/UDP port number (not an unsigned int)",
1092  node.Mark().line,
1093  node.Mark().column);
1094 #else
1095  e.prepend("YamlConfig: Invalid TCP/UDP port number (not an unsigned int)");
1096 #endif
1097  throw;
1098  }
1099  if (p <= 0 || p >= 65535) {
1100  throw Exception("YamlConfig: Invalid TCP/UDP port number "
1101  "(%u out of allowed range)",
1102  p);
1103  }
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",
1109  node.Mark().line,
1110  node.Mark().column,
1111  scalar.c_str());
1112 #else
1113  throw Exception("YamlConfig: %s is not a valid URL", scalar.c_str());
1114 #endif
1115  }
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",
1121  node.Mark().line,
1122  node.Mark().column,
1123  scalar.c_str());
1124 #else
1125  throw Exception("YamlConfig: %s is not a valid frame ID", scalar.c_str());
1126 #endif
1127  }
1128  }
1129  }
1130 
1131 private:
1132  std::string name_;
1133  Type::value type_;
1134  std::string scalar_value_;
1135  std::map<std::string, std::shared_ptr<YamlConfigurationNode>> children_;
1136  std::vector<std::string> list_values_;
1137  bool is_default_;
1138 };
1139 
1140 /// @endcond
1141 
1142 } // end namespace fawkes
1143 
1144 #endif
fawkes::str_split
static std::vector< std::string > str_split(const std::string &s, char delim='/')
Split string by delimiter.
Definition: string_split.h:41
fawkes::StringConversions::to_string
static std::string to_string(unsigned int i)
Convert unsigned int value to a string.
Definition: string_conversions.cpp:73
fawkes::get_list
static std::vector< T > get_list(std::shared_ptr< YamlConfigurationNode > root, const char *path)
Retrieve value casted to given type T.
Definition: memory.cpp:119
fawkes
Fawkes library namespace.
fawkes::is_type
static bool is_type(std::shared_ptr< YamlConfigurationNode > root, const char *path)
Check if value is of given type T.
Definition: memory.cpp:195
fawkes::str_split_to_queue
static std::queue< std::string > str_split_to_queue(const std::string &s, char delim='/')
Split string by delimiter.
Definition: string_split.h:202
fawkes::Exception
Base class for exceptions in Fawkes.
Definition: exception.h:36