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> operator[](const std::string &p)
428  {
429  std::map<std::string, std::shared_ptr<YamlConfigurationNode>>::iterator i;
430  if ((i = children_.find(p)) != children_.end()) {
431  return i->second;
432  } else {
433  return NULL;
434  }
435  }
436 
437  std::shared_ptr<YamlConfigurationNode>
438  operator+=(const std::shared_ptr<YamlConfigurationNode> n)
439  {
440  if (!n)
441  return shared_from_this();
442 
443  std::shared_ptr<YamlConfigurationNode> add_to = shared_from_this();
444 
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;
450  }
451  add_to = children_[n->name()];
452  }
453 
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());
458  }
459  add_to->set_scalar(n->get_scalar());
460  } else if (add_to->is_list()) {
461  if (!n->is_list()) {
462  throw Exception("YamlConfig: cannot overwrite list value %s with non-list",
463  add_to->name().c_str());
464  }
465  if (is_type<unsigned int>()) {
466  try {
467  int v = get_int();
468  if (v >= 0) {
469  add_to->set_list(n->get_list<unsigned int>());
470  } else {
471  add_to->set_list(n->get_list<int>());
472  }
473  } catch (Exception &e) {
474  // can happen if value > MAX_INT
475  add_to->set_list(n->get_list<unsigned int>());
476  }
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>());
485  } else {
486  std::vector<std::string> empty;
487  add_to->set_list(empty);
488  }
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());
493  }
494  add_to->children_.clear();
495  for (auto i = n->begin(); i != n->end(); ++i) {
496  *add_to += i->second;
497  }
498  } else {
499  for (auto i = n->begin(); i != n->end(); ++i) {
500  *add_to += i->second;
501  }
502  }
503 
504  return shared_from_this();
505  }
506 
507  bool
508  operator==(const YamlConfigurationNode &n) const
509  {
510  return (name_ == n.name_) && (type_ == n.type_) && (scalar_value_ == n.scalar_value_);
511  }
512 
513  bool
514  operator!=(const YamlConfigurationNode &n) const
515  {
516  return (name_ != n.name_) || (type_ != n.type_) || (scalar_value_ != n.scalar_value_);
517  }
518 
519  /** Check for differences in two trees.
520  * This returns a list of all changes that have occured in b opposed
521  * to b. This means keys which have been added or changed in b compared to
522  * a. It also includes keys which have been removed from a, i.e. which exist
523  * in b but not in a.
524  * @param a root node of first tree
525  * @param b root node of second tree
526  * @return list of paths to leaf nodes that changed
527  */
528  static std::list<std::string>
529  diff(const std::shared_ptr<YamlConfigurationNode> a,
530  const std::shared_ptr<YamlConfigurationNode> b)
531  {
532  std::list<std::string> rv;
533 
534  std::map<std::string, std::shared_ptr<YamlConfigurationNode>> na, nb;
535  a->enum_leafs(na);
536  b->enum_leafs(nb);
537 
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()) {
541  // this is a new key in a
542  // printf("A %s NOT in B\n", i->first.c_str());
543  rv.push_back(i->first);
544  } else if (*i->second != *nb[i->first]) {
545  // different values/types
546  // printf("A %s modified\n", i->first.c_str());
547  rv.push_back(i->first);
548  }
549  }
550 
551  for (i = nb.begin(); i != nb.end(); ++i) {
552  if (na.find(i->first) == na.end()) {
553  // this is a new key in b
554  // printf("B %s NOT in A\n", i->first.c_str());
555  rv.push_back(i->first);
556  }
557  }
558 
559  return rv;
560  }
561 
562  /** Retrieve value casted to given type T.
563  * @param path path to query
564  * @return value casted as desired
565  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
566  * a different type.
567  */
568  template <typename T>
569  T
570  get_value() const
571  {
572  if (type_ == Type::SEQUENCE) {
573  throw Exception("YamlConfiguration: value of %s is a list", name_.c_str());
574  }
575  T rv;
576  if (yaml_utils::convert(scalar_value_, rv)) {
577  return rv;
578  } else {
579  // might want to have custom exception here later
580  throw Exception("YamlConfig: value or type error on %s", name_.c_str());
581  }
582  }
583 
584  /** Get the list elements as string.
585  * The first element determines the type of the list.
586  * @return value string as list, i.e. as space-separated list of items
587  */
588  std::string
589  get_list_as_string() const
590  {
591  if (type_ != Type::SEQUENCE) {
592  throw fawkes::Exception("YamlConfiguration: value of %s is not a list", name_.c_str());
593  }
594  if (list_values_.empty())
595  return "";
596 
597  std::string rv = "";
598  bool is_string = (determine_scalar_type() == Type::STRING);
599  if (is_string) {
600  rv = " \"" + list_values_[0] + "\"";
601  for (size_t i = 1; i < list_values_.size(); ++i) {
602  rv += " \"" + list_values_[i] + "\"";
603  }
604  } else {
605  rv = list_values_[0];
606  for (size_t i = 1; i < list_values_.size(); ++i) {
607  rv += " " + list_values_[i];
608  }
609  }
610 
611  return rv;
612  }
613 
614  /** Retrieve value casted to given type T.
615  * @return value casted as desired
616  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
617  * a different type.
618  */
619  template <typename T>
620  std::vector<T>
621  get_list() const
622  {
623  if (type_ != Type::SEQUENCE) {
624  throw Exception("YamlConfiguration: value of %s is not a list", name_.c_str());
625  }
626  std::vector<T> rv;
627  const typename std::vector<T>::size_type N = list_values_.size();
628  rv.resize(N);
629  for (typename std::vector<T>::size_type i = 0; i < N; ++i) {
630  T t;
631  if (!yaml_utils::convert(list_values_[i], t)) {
632  // might want to have custom exception here later
633  throw Exception("YamlConfig: value or type error on %s[%zi]", name_.c_str(), i);
634  }
635  rv[i] = t;
636  }
637  return rv;
638  }
639 
640  /** Retrieve list size.
641  * @return size of list
642  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
643  * a different type.
644  */
645  size_t
646  get_list_size() const
647  {
648  if (type_ != Type::SEQUENCE) {
649  throw Exception("YamlConfiguration: value of %s is not a list", name_.c_str());
650  }
651  return list_values_.size();
652  }
653 
654  /** Set value of given type T.
655  * @param path path to query
656  * @return value casted as desired
657  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
658  * a different type.
659  */
660  template <typename T>
661  void
662  set_value(const char *path, T t)
663  {
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);
667  }
668  n->set_scalar(StringConversions::to_string(t));
669  }
670 
671  /** Set list of given type T.
672  * @param path path to query
673  * @return value casted as desired
674  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
675  * a different type.
676  */
677  template <typename T>
678  void
679  set_list(const char *path, std::vector<T> &t)
680  {
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);
684  }
685  std::vector<std::string> v;
686  typename std::vector<T>::size_type N = t.size();
687  v.resize(N);
688  for (typename std::vector<T>::size_type i = 0; i < N; ++i) {
689  v[i] = StringConversions::to_string(t[i]);
690  }
691  n->set_scalar_list(v);
692  }
693 
694  /** Set value of given type T.
695  * @param path path to query
696  * @return value casted as desired
697  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
698  * a different type.
699  */
700  template <typename T>
701  void
702  set_value(T t)
703  {
704  if (has_children()) {
705  throw Exception("YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
706  }
707  set_scalar(StringConversions::to_string(t));
708  }
709 
710  /** Set list of values of given type T.
711  * @param path path to query
712  * @return value casted as desired
713  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
714  * a different type.
715  */
716  template <typename T>
717  void
718  set_list(const std::vector<T> &t)
719  {
720  if (has_children()) {
721  throw Exception("YamlConfig: cannot set value on non-leaf path node %s", name_.c_str());
722  }
723  std::vector<std::string> v;
724  typename std::vector<T>::size_type N = t.size();
725  v.resize(N);
726  for (typename std::vector<T>::size_type i = 0; i < N; ++i) {
727  v[i] = StringConversions::to_string(t[i]);
728  }
729  set_scalar_list(v);
730  }
731 
732  /** Check if value is of given type T.
733  * @param path path to query
734  * @return value casted as desired
735  * @throw YAML::ScalarInvalid thrown if value does not exist or is of
736  * a different type.
737  */
738  template <typename T>
739  bool
740  is_type() const
741  {
742  T rv;
743  if (type_ == Type::SEQUENCE) {
744  if (!list_values_.empty()) {
745  T rv;
746  return (yaml_utils::convert(list_values_[0], rv));
747  } else {
748  return true;
749  }
750  } else {
751  return (yaml_utils::convert(scalar_value_, rv));
752  }
753  }
754 
755  Type::value
756  get_type() const
757  {
758  return type_;
759  }
760 
761  bool
762  is_scalar() const
763  {
764  switch (type_) {
765  case Type::UINT32:
766  case Type::INT32:
767  case Type::FLOAT:
768  case Type::BOOL:
769  case Type::STRING: return true;
770  default: return false;
771  }
772  }
773 
774  bool
775  is_list() const
776  {
777  return (type_ == Type::SEQUENCE);
778  }
779 
780  float
781  get_float() const
782  {
783  return get_value<float>();
784  }
785 
786  unsigned int
787  get_uint() const
788  {
789  return get_value<unsigned int>();
790  }
791 
792  int
793  get_int() const
794  {
795  return get_value<int>();
796  }
797 
798  bool
799  get_bool() const
800  {
801  return get_value<bool>();
802  }
803 
804  std::string
805  get_string() const
806  {
807  return get_value<std::string>();
808  }
809 
810  void
811  set_scalar(const std::string &s)
812  {
813  scalar_value_ = s;
814  type_ = determine_scalar_type();
815  }
816 
817  void
818  set_scalar_list(const std::vector<std::string> &s)
819  {
820  list_values_ = s;
821  type_ = Type::SEQUENCE;
822  }
823 
824  const std::string &
825  get_scalar() const
826  {
827  return scalar_value_;
828  }
829 
830  void
831  set_sequence(const YAML::Node &n)
832  {
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)",
836  n.Mark().line,
837  n.Mark().column);
838 #else
839  throw Exception("Cannot initialize list from non-sequence");
840 #endif
841  }
842  type_ = Type::SEQUENCE;
843  list_values_.resize(n.size());
844  if (n.size() > 0) {
845  if (n.begin()->Type() == YAML::NodeType::Scalar) {
846  unsigned int i = 0;
847  for (YAML::const_iterator it = n.begin(); it != n.end(); ++it) {
848  list_values_[i++] = it->as<std::string>();
849  }
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));
855  }
856  } else {
857 #ifdef HAVE_YAMLCPP_NODE_MARK
858  throw Exception("Sequence neither of type scalar nor map (line %i, column %i)",
859  n.Mark().line,
860  n.Mark().column);
861 #else
862  throw Exception("Sequence neither of type scalar nor map");
863 #endif
864  }
865  }
866  }
867 
868  void
869  set_map(const YAML::Node &node)
870  {
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) {
875  // we need to split and find the proper insertion node
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]];
879  if (!n) {
880  n = std::make_shared<YamlConfigurationNode>(pel[i]);
881  in->add_child(pel[i], n);
882  }
883  in = n;
884  }
885 
886  key = pel.back();
887  }
888 
889  if (children_.find(key) != children_.end()) {
890  // we are updating a value
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
894  throw Exception(
895  "YamlConfig (line %d, column %d): overwriting value with incompatible type",
896  node.Mark().line,
897  node.Mark().column);
898 #else
899  throw Exception("YamlConfig: overwriting value with incompatible type");
900 #endif
901  }
902  in->add_child(key, new_value);
903  } else {
904  in->add_child(key, YamlConfigurationNode::create(it->second, key));
905  }
906  }
907  }
908 
909  bool
910  has_children() const
911  {
912  return !children_.empty();
913  }
914 
915  bool
916  is_default() const
917  {
918  return is_default_;
919  }
920 
921  void
922  set_default(const char *path, bool is_default)
923  {
924  std::shared_ptr<YamlConfigurationNode> n = find(path);
925  n->set_default(is_default);
926  }
927 
928  void
929  set_default(bool is_default)
930  {
931  is_default_ = is_default;
932  }
933 
934  void
935  enum_leafs(std::map<std::string, std::shared_ptr<YamlConfigurationNode>> &nodes,
936  std::string prefix = "") const
937  {
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);
943  } else {
944  nodes[path] = c->second;
945  }
946  }
947  }
948 
949  void
950  print(std::string indent = "")
951  {
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 + " ");
958  }
959  } else {
960  std::cout << indent << scalar_value_ << " (" << Type::to_string(get_type()) << ")"
961  << std::endl;
962  }
963  }
964 
965 private:
966  void
967  emit(YAML::Emitter &ye)
968  {
969  if (!children_.empty()) {
970  ye << YAML::BeginMap;
971 
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()) {
975  // recurse
976  ye << YAML::Key << c->first << YAML::Value;
977  c->second->emit(ye);
978  } else {
979  ye << YAML::Key << c->first << YAML::Value << c->second->get_scalar();
980  }
981  }
982 
983  ye << YAML::EndMap;
984  }
985  }
986 
987 public:
988  void
989  emit(std::string &filename)
990  {
991  if (access(filename.c_str(), W_OK) != 0) {
992  if (errno != ENOENT) {
993  throw Exception(errno, "YamlConfig: cannot write host file");
994  }
995  }
996 
997  std::ofstream fout(filename.c_str());
998  YAML::Emitter ye;
999  emit(ye);
1000  fout << ye.c_str();
1001  }
1002 
1003  const std::string &
1004  name() const
1005  {
1006  return name_;
1007  }
1008 
1009 private:
1010  void
1011  set_name(const std::string &name)
1012  {
1013  name_ = name;
1014  }
1015 
1016  void
1017  set_type(Type::value type)
1018  {
1019  type_ = type;
1020  }
1021 
1022  Type::value
1023  determine_scalar_type() const
1024  {
1025  if (is_type<unsigned int>()) {
1026  try {
1027  int v = get_int();
1028  if (v >= 0) {
1029  return Type::UINT32;
1030  } else {
1031  return Type::INT32;
1032  }
1033  } catch (Exception &e) {
1034  // can happen if value > MAX_INT
1035  return Type::UINT32;
1036  }
1037  } else if (is_type<int>()) {
1038  return Type::INT32;
1039  } else if (is_type<float>()) {
1040  return Type::FLOAT;
1041  } else if (is_type<bool>()) {
1042  return Type::BOOL;
1043  } else if (is_type<std::string>()) {
1044  return Type::STRING;
1045  } else {
1046  return Type::UNKNOWN;
1047  }
1048  }
1049 
1050  void
1051  verify_scalar(const YAML::Node &node) const
1052  {
1053  if (node.Tag() == "tag:fawkesrobotics.org,cfg/ipv4"
1054  || node.Tag() == "tag:fawkesrobotics.org,cfg/ipv6") {
1055  std::string addr_s;
1056  try {
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)",
1061  node.Mark().line,
1062  node.Mark().column);
1063 #else
1064  e.prepend("YamlConfig: Invalid IPv4 or IPv6 address (not a string)");
1065 #endif
1066  throw;
1067  }
1068 
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());
1073  }
1074  }
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());
1079  }
1080  }
1081 
1082  } else if (node.Tag() == "tag:fawkesrobotics.org,cfg/tcp-port"
1083  || node.Tag() == "tag:fawkesrobotics.org,cfg/udp-port") {
1084  unsigned int p = 0;
1085  try {
1086  p = get_uint();
1087  } catch (Exception &e) {
1088 #ifdef HAVE_YAMLCPP_NODE_MARK
1089  e.prepend(
1090  "YamlConfig (line %d, column %d): Invalid TCP/UDP port number (not an unsigned int)",
1091  node.Mark().line,
1092  node.Mark().column);
1093 #else
1094  e.prepend("YamlConfig: Invalid TCP/UDP port number (not an unsigned int)");
1095 #endif
1096  throw;
1097  }
1098  if (p <= 0 || p >= 65535) {
1099  throw Exception("YamlConfig: Invalid TCP/UDP port number "
1100  "(%u out of allowed range)",
1101  p);
1102  }
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",
1108  node.Mark().line,
1109  node.Mark().column,
1110  scalar.c_str());
1111 #else
1112  throw Exception("YamlConfig: %s is not a valid URL", scalar.c_str());
1113 #endif
1114  }
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",
1120  node.Mark().line,
1121  node.Mark().column,
1122  scalar.c_str());
1123 #else
1124  throw Exception("YamlConfig: %s is not a valid frame ID", scalar.c_str());
1125 #endif
1126  }
1127  }
1128  }
1129 
1130 private:
1131  std::string name_;
1132  Type::value type_;
1133  std::string scalar_value_;
1134  std::map<std::string, std::shared_ptr<YamlConfigurationNode>> children_;
1135  std::vector<std::string> list_values_;
1136  bool is_default_;
1137 };
1138 
1139 /// @endcond
1140 
1141 } // end namespace fawkes
1142 
1143 #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:47
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:125
fawkes
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:201
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:208
fawkes::Exception
Definition: exception.h:41