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
48namespace 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
58namespace yaml_utils {
59
60namespace detail {
61// we're not gonna mess with the mess that is all the isupper/etc. functions
62inline bool
63IsLower(char ch)
64{
65 return 'a' <= ch && ch <= 'z';
66}
67inline bool
68IsUpper(char ch)
69{
70 return 'A' <= ch && ch <= 'Z';
71}
72inline char
73ToLower(char ch)
74{
75 return IsUpper(ch) ? ch + 'a' - 'A' : ch;
76}
77
78inline std::string
79tolower(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
86template <typename T>
87inline bool
88IsEntirely(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
102inline bool
103IsFlexibleCase(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
117inline bool
118convert(const std::string &input, std::string &output)
119{
120 output = input;
121 return true;
122}
123
124inline bool
125convert(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
158inline bool
159convert(const std::string &input, YAML::_Null &output)
160{
161 return input.empty() || input == "~" || input == "null" || input == "Null" || input == "NULL";
162}
163
164inline bool
165convert(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
186template <typename T>
187inline bool
188convert(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
210static std::regex url_regex{URL_REGEX, std::regex_constants::extended};
211static std::regex frame_regex{FRAME_REGEX, std::regex_constants::extended};
212} // namespace yaml_utils
213
214class YamlConfigurationNode : public std::enable_shared_from_this<YamlConfigurationNode>
215{
216public:
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
966private:
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
988public:
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
1010private:
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
1131private:
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
Base class for exceptions in Fawkes.
Definition: exception.h:36
static std::string to_string(unsigned int i)
Convert unsigned int value to a string.
Fawkes library namespace.
bool operator==(const Uuid &uuid, const Uuid &other) noexcept
Compare two Uuids.
Definition: uuid.cpp:131
bool operator<(const Uuid &uuid, const Uuid &other) noexcept
Compare two Uuids.
Definition: uuid.cpp:120
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
static std::queue< std::string > str_split_to_queue(const std::string &s, char delim='/')
Split string by delimiter.
Definition: string_split.h:202
bool operator!=(const Uuid &uuid, const Uuid &other) noexcept
Compare two Uuids.
Definition: uuid.cpp:142
static bool is_type(std::shared_ptr< YamlConfigurationNode > root, const char *path)
Check if value is of given type T.
Definition: memory.cpp:195
static std::vector< std::string > str_split(const std::string &s, char delim='/')
Split string by delimiter.
Definition: string_split.h:41