Version: SMASH-3.1
configuration.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2019,2022
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #include "smash/configuration.h"
11 
12 #include <cstdio>
13 #include <filesystem>
14 #include <string>
15 #include <vector>
16 
17 #include "yaml-cpp/yaml.h"
18 
20 #include "smash/input_keys.h"
21 #include "smash/inputfunctions.h"
22 #include "smash/logging.h"
23 #include "smash/stringfunctions.h"
24 
25 namespace smash {
26 static constexpr int LConfiguration = LogArea::Configuration::id;
27 
28 // internal helper functions
29 namespace {
38 void descend_one_existing_level(std::optional<YAML::Node> &node,
39  std::string_view key) {
40  if (node) {
41  for (const auto &section : node.value()) {
42  /* Two remarks:
43  1) The Node::operator[] creates an undefined node in the YAML tree if
44  the node corresponding to the passed key does not exist and hence
45  in this function, which descend one level which is expected to
46  exist, we need to use it only if we are sure the node exists.
47  2) Node::reset does what you might expect Node::operator= to do. But
48  operator= assigns a value to the node and so
49  node = node[key]
50  would lead to a further modification of the data structure and
51  this function would not be simply traversal. Note that node and
52  root_node_ point to the same memory and modification to the first
53  would affect the second, too. */
54  if (section.first.Scalar() == key) {
55  node.value().reset(node.value()[key]);
56  return;
57  }
58  }
59  node = std::nullopt;
60  }
61 }
62 
69 YAML::Node remove_empty_maps(YAML::Node root) {
70  if (root.IsMap()) {
71  std::vector<std::string> to_remove(root.size());
72  for (auto n : root) {
73  remove_empty_maps(n.second);
74  // If the node is an empty sequence, we do NOT remove it!
75  if (n.second.IsMap() && n.second.size() == 0) {
76  to_remove.emplace_back(n.first.Scalar());
77  }
78  }
79  for (const auto &key : to_remove) {
80  root.remove(key);
81  }
82  }
83  return root;
84 }
85 
93 YAML::Node operator|=(YAML::Node a, const YAML::Node &b) {
94  if (b.IsMap()) {
95  for (auto n0 : b) {
96  a[n0.first.Scalar()] |= n0.second;
97  }
98  } else {
99  a = b;
100  }
101  return a;
102 }
103 
110 std::string join_quoted(std::initializer_list<const char *> keys) {
111  return std::accumulate(keys.begin(), keys.end(), std::string{"{"},
112  [](const std::string &ss, const std::string &s) {
113  return ss + ((ss.size() == 1) ? "\"" : ", \"") + s +
114  "\"";
115  }) +
116  "}";
117 }
118 
119 } // unnamed namespace
120 
121 // Default constructor
122 Configuration::Configuration(const std::filesystem::path &path)
123  : Configuration(path, "config.yaml") {}
124 
125 // Constructor checking for validity of input
126 Configuration::Configuration(const std::filesystem::path &path,
127  const std::filesystem::path &filename) {
128  const auto file_path = path / filename;
129  if (!std::filesystem::exists(file_path)) {
130  throw FileDoesNotExist("The configuration file was expected at '" +
131  file_path.native() +
132  "', but the file does not exist.");
133  }
134  if (has_crlf_line_ending(read_all(std::ifstream((file_path))))) {
135  throw std::runtime_error(
136  "The configuration file has CR LF line endings. Please use LF "
137  "line endings.");
138  }
139  try {
140  root_node_ = YAML::LoadFile(file_path.native());
141  } catch (YAML::ParserException &e) {
142  if (e.msg == "illegal map value" || e.msg == "end of map not found") {
143  const auto line = std::to_string(e.mark.line + 1);
144  throw ParseError("YAML parse error at\n" + file_path.native() + ':' +
145  line + ": " + e.msg +
146  " (check that the indentation of map keys matches)");
147  }
148  throw;
149  }
150 }
151 
153  : root_node_(std::move(other.root_node_)),
154  uncaught_exceptions_(std::move(other.uncaught_exceptions_)) {
155  other.root_node_.reset();
156  other.uncaught_exceptions_ = 0;
157 }
158 
160  // YAML does not offer != operator between nodes
161  if (!(root_node_ == other.root_node_)) {
162  root_node_ = std::move(other.root_node_);
163  uncaught_exceptions_ = std::move(other.uncaught_exceptions_);
164  other.root_node_.reset();
165  other.uncaught_exceptions_ = 0;
166  }
167  return *this;
168 }
169 
170 Configuration::~Configuration() noexcept(false) {
171  // Make sure that stack unwinding is not taking place befor throwing
172  if (std::uncaught_exceptions() == uncaught_exceptions_) {
173  // In this scenario is fine to throw
174  if (root_node_.size() != 0) {
175  throw std::logic_error(
176  "Configuration object destroyed with unused keys:\n" + to_string());
177  }
178  }
179  /* If this destructor is called during stack unwinding, it is irrelevant
180  that the Configuration has not be completely parsed. */
181 }
182 
183 void Configuration::merge_yaml(const std::string &yaml) {
184  try {
185  root_node_ |= YAML::Load(yaml);
186  } catch (YAML::ParserException &e) {
187  if (e.msg == "illegal map value" || e.msg == "end of map not found") {
188  const auto line = std::to_string(e.mark.line + 1);
189  throw ParseError("YAML parse error in:\n" + yaml + "\nat line " + line +
190  ": " + e.msg +
191  " (check that the indentation of map keys matches)");
192  }
193  throw;
194  }
195 }
196 
197 std::vector<std::string> Configuration::list_upmost_nodes() {
198  std::vector<std::string> r;
199  r.reserve(root_node_.size());
200  for (auto i : root_node_) {
201  r.emplace_back(i.first.Scalar());
202  }
203  return r;
204 }
205 
207  std::initializer_list<const char *> keys) {
208  assert(keys.size() > 0);
209  /* Here we want to descend the YAML tree but not all the way to the last key,
210  because we need the node associated to the previous to last key in order to
211  remove the taken key. */
212  auto last_key_it = keys.end() - 1;
213  auto previous_to_last_node = find_existing_node({keys.begin(), last_key_it});
214  auto to_be_returned{previous_to_last_node};
215  descend_one_existing_level(to_be_returned, *last_key_it);
216  if (!previous_to_last_node || !to_be_returned) {
217  throw std::invalid_argument(
218  "Attempt to take value of a not existing key: " + join_quoted(keys));
219  }
220  previous_to_last_node.value().remove(*last_key_it);
222  return {to_be_returned.value(), *last_key_it};
223 }
224 
226  std::initializer_list<const char *> keys) const {
227  auto found_node = find_existing_node(keys);
228  if (found_node) {
229  return {found_node.value(), keys.begin()[keys.size() - 1]};
230  } else {
231  throw std::invalid_argument(
232  "Attempt to read value of a not existing key: " + join_quoted(keys));
233  }
234 }
235 
237  const std::string &key, std::initializer_list<const char *> section) {
238  auto found_node = find_existing_node(section);
239  if (found_node) {
240  std::vector<std::string> to_remove{};
241  bool key_exists = false;
242  for (auto i : found_node.value()) {
243  if (i.first.Scalar() != key) {
244  to_remove.push_back(i.first.Scalar());
245  } else {
246  key_exists = true;
247  }
248  }
249  if (!key_exists) {
250  std::string section_string{" section "};
251  if (section.size() > 0) {
252  section_string += join_quoted(section) + " ";
253  } else {
254  section_string = " top-level" + section_string;
255  }
256  throw std::invalid_argument("Attempt to remove all keys in" +
257  section_string +
258  "except not existing one: \"" + key + "\"");
259  } else {
260  for (auto i : to_remove) {
261  found_node.value().remove(i);
262  }
263  }
264  } else {
265  throw std::invalid_argument(
266  "Attempt to remove entries in not existing section: " +
267  join_quoted(section));
268  }
269 }
270 
272  std::initializer_list<const char *> keys,
273  Configuration::GetEmpty empty_if_not_existing) {
274  // Same logic as in take method
275  assert(keys.size() > 0);
276  auto last_key_it = keys.end() - 1;
277  auto previous_to_section_node =
278  find_existing_node({keys.begin(), last_key_it});
279  auto sub_conf_root_node{previous_to_section_node};
280  descend_one_existing_level(sub_conf_root_node, *last_key_it);
281  if (!previous_to_section_node || !sub_conf_root_node) {
282  if (empty_if_not_existing == Configuration::GetEmpty::Yes)
283  return Configuration(YAML::Node{});
284  else
285  throw std::runtime_error("Attempt to extract not existing section " +
286  join_quoted(keys));
287  }
288  /* Here sub_conf_root_node cannot be a nullopt, since if it was the function
289  would have returned before and it cannot be that previous_to_section_node
290  is nullopt and sub_conf_root_node is not */
291  else if (sub_conf_root_node->IsNull() || // NOLINT[whitespace/newline]
292  (sub_conf_root_node->IsMap() && sub_conf_root_node->size() == 0)) {
293  // Here we put together the cases of a key without value or with
294  // an empty map {} as value (no need at the moment to distinguish)
295  throw std::runtime_error("Attempt to extract empty section " +
296  join_quoted(keys));
297  } else if (sub_conf_root_node->IsMap() && sub_conf_root_node->size() != 0) {
298  Configuration sub_config{*sub_conf_root_node};
299  previous_to_section_node->remove(*last_key_it);
301  return sub_config;
302  } else { // sequence or scalar or any future new YAML type
303  throw std::runtime_error("Tried to extract configuration section at " +
304  join_quoted(keys) +
305  " to get a key value. Use take instead!");
306  }
307 }
308 
310  std::initializer_list<const char *> keys) const {
311  const auto found_node = find_existing_node(keys);
312  return found_node.has_value();
313 }
314 
315 bool Configuration::has_value(std::initializer_list<const char *> keys) const {
316  const auto found_node = find_existing_node(keys);
317  return found_node.has_value() && !(found_node.value().IsNull());
318 }
319 
320 std::string Configuration::to_string() const {
321  std::stringstream s;
322  s << root_node_;
323  return s.str();
324 }
325 
326 std::optional<YAML::Node> Configuration::find_existing_node(
327  std::vector<const char *> keys) const {
328  /* Here we do not assert(keys.size()>0) and allow to pass in an empty vector,
329  in which case the passed in YAML:Node is simply returned. This might happen
330  e.g. in the take or extract_sub_configuration methods if called with a
331  label of a key at top level of the configuration file. */
332  std::optional<YAML::Node> node{root_node_};
333  for (const auto &key : keys) {
334  descend_one_existing_level(node, key);
335  }
336  return node;
337 }
338 
340  std::vector<const char *> keys) const {
341  assert(keys.size() > 0);
342  YAML::Node node{root_node_};
343  for (const auto &key : keys) {
344  // See comments in descend_one_existing_level function
345  node.reset(node[key]);
346  }
347  return node;
348 }
349 
350 // internal helper functions
351 namespace {
370 void fill_list_of_labels_per_key_in_yaml_tree(const YAML::Node &root_node,
371  std::vector<KeyLabels> &list,
372  KeyLabels &new_list_entry) {
373  // Here sub_node is an iterator value, i.e. a key/value pair of nodes,
374  // not a single YAML node (that's how YAML library works)
375  for (const auto &sub_node : root_node) {
376  new_list_entry.push_back(sub_node.first.as<std::string>());
377  if (sub_node.second.IsMap())
378  fill_list_of_labels_per_key_in_yaml_tree(sub_node.second, list,
379  new_list_entry);
380  else
381  list.push_back(new_list_entry);
382  new_list_entry.pop_back();
383  }
384 }
385 
399 auto get_list_of_labels_per_key_in_yaml_tree(const YAML::Node &root_node) {
400  std::vector<KeyLabels> list{};
401  KeyLabels aux{};
402  fill_list_of_labels_per_key_in_yaml_tree(root_node, list, aux);
403  return list;
404 }
405 
411 template <typename T>
412 struct IsStdMap {
418  static constexpr bool value = false;
419 };
420 
428 template <typename MapKey, typename MapValue>
429 struct IsStdMap<std::map<MapKey, MapValue>> {
434  static constexpr bool value = true;
435 };
436 
444  std::vector<KeyLabels> labels_of_keys_taken_as_map{};
445  for (const auto &keys_variant : smash::InputKeys::list) {
446  std::visit(
447  [&labels_of_keys_taken_as_map](auto &&var) {
448  /*
449  * The following if checks if the SMASH input key has a map as value
450  * and it deserves some explanation about the type extraction:
451  *
452  * - arg -> object of type: std::cref(const Key<T>)
453  * - arg.get() -> object of type: const Key<T>&
454  * - decltype(arg.get()) -> type: const Key<T>&
455  * - std::decay_t<decltype(arg.get())>::type -> type: Key<T>
456  * - std::decay_t<decltype(arg.get())>::type::value -> type: T
457  */
458  if constexpr (IsStdMap<typename std::decay_t<
459  decltype(var.get())>::type>::value)
460  labels_of_keys_taken_as_map.push_back(var.get().labels());
461  },
462  keys_variant);
463  }
464  return labels_of_keys_taken_as_map;
465 }
466 
480  std::vector<KeyLabels> &list_of_input_key_labels) {
481  const std::vector<KeyLabels> labels_of_keys_taken_as_map =
483  for (const auto &labels : labels_of_keys_taken_as_map) {
484  std::for_each(list_of_input_key_labels.begin(),
485  list_of_input_key_labels.end(),
486  [&labels](KeyLabels &labels_of_input_key) {
487  if (std::equal(labels.begin(), labels.end(),
488  labels_of_input_key.begin(),
489  labels_of_input_key.begin() + labels.size()))
490  labels_of_input_key = labels;
491  });
492  }
493  // The identical keys in list are now next to each other and we do
494  // not need/want to sort the list before calling std::unique.
495  list_of_input_key_labels.erase(std::unique(list_of_input_key_labels.begin(),
496  list_of_input_key_labels.end()),
497  list_of_input_key_labels.end());
498 }
499 
516  auto key_ref_var_it = std::find_if(
518  [&labels](auto key) {
519  return std::visit(
520  [&labels](auto &&arg) { return arg.get().has_same_labels(labels); },
521  key);
522  });
523  if (key_ref_var_it == smash::InputKeys::list.end()) {
524  logg[LConfiguration].error("Key ", smash::quote(smash::join(labels, ": ")),
525  " is not a valid SMASH input key.");
527  }
528 
529  smash::InputKeys::key_references_variant found_variant = *key_ref_var_it;
530  const auto key_labels =
531  std::visit([](auto &&var) { return static_cast<std::string>(var.get()); },
532  found_variant);
533 
534  if (std::visit([](auto &&var) { return !var.get().is_allowed(); },
535  found_variant)) {
536  const auto v_removal = std::visit(
537  [](auto &&var) { return var.get().removed_in(); }, found_variant);
538  logg[LConfiguration].error("Key ", key_labels,
539  " has been removed in version ", v_removal,
540  " and it is not valid anymore.");
542  }
543  if (std::visit([](auto &&var) { return var.get().is_deprecated(); },
544  found_variant)) {
545  const auto v_deprecation = std::visit(
546  [](auto &&var) { return var.get().deprecated_in(); }, found_variant);
547  logg[LConfiguration].warn(
548  "Key ", key_labels, " has been deprecated in version ", v_deprecation);
550  } else {
551  logg[LConfiguration].debug("Key ", key_labels, " is valid!");
553  }
554 }
555 
568  Configuration::Is new_value) {
569  switch (result_so_far) {
571  break;
573  if (new_value != Configuration::Is::Valid) {
574  result_so_far = new_value;
575  }
576  break;
578  result_so_far = new_value;
579  break;
580  }
581 }
582 
583 } // namespace
584 
585 Configuration::Is Configuration::validate(bool full_validation) const {
588  Is validation_result{Is::Valid};
589  for (const auto &key_labels : list) {
590  Is key_state = validate_key(key_labels);
591  if (full_validation) {
592  accumulate_validation(validation_result, key_state);
593  } else {
594  if (key_state != Is::Valid)
595  return key_state;
596  }
597  }
598  return validation_result;
599 }
600 
601 } // namespace smash
Return type of Configuration::take that automatically determines the target type.
Interface to the SMASH configuration files.
Is
Return type of Configuration::validate which conveys more information that simply a two-state boolean...
bool has_value(std::initializer_list< const char * > keys) const
Return whether there is a non-empty value behind the requested keys.
void merge_yaml(const std::string &yaml)
Merge the configuration in yaml into the existing tree.
std::string to_string() const
Return a string of the current YAML tree.
bool has_value_including_empty(std::initializer_list< const char * > keys) const
Return whether there is a (maybe empty) value behind the requested keys.
Configuration(const std::filesystem::path &path)
Read config.yaml from the specified path.
void remove_all_entries_in_section_but_one(const std::string &key, std::initializer_list< const char * > section={})
Remove all entries in the given section except for key.
YAML::Node find_node_creating_it_if_not_existing(std::vector< const char * > keys) const
Descend in and if needed modify the YAML tree from the given node using the provided keys.
YAML::Node root_node_
The general_config.yaml contents - fully parsed.
Configuration extract_sub_configuration(std::initializer_list< const char * > keys, Configuration::GetEmpty empty_if_not_existing=Configuration::GetEmpty::No)
Create a new configuration from a then-removed section of the present object.
int uncaught_exceptions_
Counter to be able to optionally throw in destructor.
Value take(std::initializer_list< const char * > keys)
The default interface for SMASH to read configuration values.
Value read(std::initializer_list< const char * > keys) const
Additional interface for SMASH to read configuration values without removing them.
Is validate(bool full_validation=true) const
Validate content of configuration in terms of YAML keys.
~Configuration() noexcept(false)
Destroy the object, optionally throwing if not all keys were taken.
GetEmpty
Flag to tune method(s) behavior such that it is descriptive from the caller side.
std::optional< YAML::Node > find_existing_node(std::vector< const char * > keys) const
Descend in the YAML tree from the given node using the provided keys.
std::vector< std::string > list_upmost_nodes()
Lists all YAML::Nodes from the configuration setup.
Configuration & operator=(const Configuration &)=delete
Prevent Configuration objects from being copy-assigned.
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
auto collect_input_keys_taken_as_maps()
Extract from the InputKeys database the labels of keys that have a std::map as type.
void adjust_list_of_labels_dealing_with_keys_taken_as_maps(std::vector< KeyLabels > &list_of_input_key_labels)
Remove last labels of keys that are taken as maps in SMASH and remove duplicates from the resulting l...
void fill_list_of_labels_per_key_in_yaml_tree(const YAML::Node &root_node, std::vector< KeyLabels > &list, KeyLabels &new_list_entry)
Implementation of the algorithm to translate a YAML tree into lists of labels, each identifying a key...
YAML::Node operator|=(YAML::Node a, const YAML::Node &b)
Merge two YAML::Nodes.
void accumulate_validation(Configuration::Is &result_so_far, Configuration::Is new_value)
Utility function to accumulate validation results of keys.
Configuration::Is validate_key(const KeyLabels &labels)
Given some YAML labels (assumed to be in order from the top section), it is checked whether any valid...
void descend_one_existing_level(std::optional< YAML::Node > &node, std::string_view key)
Reset the passed in node to that one at the provided key, which is expected to exist in the node.
std::string join_quoted(std::initializer_list< const char * > keys)
Build a string with a list of keys as specified in the code.
YAML::Node remove_empty_maps(YAML::Node root)
Remove all empty maps of a YAML::Node.
auto get_list_of_labels_per_key_in_yaml_tree(const YAML::Node &root_node)
Create a list of lists of key labels present in the passed YAML node considered to be the root one of...
constexpr int n
Neutron.
Definition: action.h:24
std::vector< std::string > KeyLabels
Descriptive alias for storing key labels, i.e.
Definition: input_keys.h:39
UnaryFunction for_each(Container &&c, UnaryFunction &&f)
Convenience wrapper for std::for_each that operates on a complete container.
Definition: algorithms.h:96
std::string quote(const std::string &s)
Add quotes around string.
static constexpr int LConfiguration
std::string join(const std::vector< std::string > &v, const std::string &delim)
Join strings using delimiter.
bool has_crlf_line_ending(const std::string in)
Check if a line in the string ends with \r\n.
std::string read_all(std::istream &&input)
Utility function to read a complete input stream (e.g.
Thrown if the file does not exist.
Thrown for YAML parse errors.
std::variant< std::reference_wrapper< const Key< bool > >, std::reference_wrapper< const Key< int > >, std::reference_wrapper< const Key< double > >, std::reference_wrapper< const Key< std::string > >, std::reference_wrapper< const Key< std::array< int, 3 > >>, std::reference_wrapper< const Key< std::array< double, 2 > >>, std::reference_wrapper< const Key< std::array< double, 3 > >>, std::reference_wrapper< const Key< std::pair< double, double > >>, std::reference_wrapper< const Key< std::vector< double > >>, std::reference_wrapper< const Key< std::vector< std::string > >>, std::reference_wrapper< const Key< std::set< ThermodynamicQuantity > >>, std::reference_wrapper< const Key< std::map< PdgCode, int > >>, std::reference_wrapper< const Key< std::map< std::string, std::string > >>, std::reference_wrapper< const Key< einhard::LogLevel > >, std::reference_wrapper< const Key< BoxInitialCondition > >, std::reference_wrapper< const Key< CalculationFrame > >, std::reference_wrapper< const Key< CollisionCriterion > >, std::reference_wrapper< const Key< DensityType > >, std::reference_wrapper< const Key< DerivativesMode > >, std::reference_wrapper< const Key< ExpansionMode > >, std::reference_wrapper< const Key< FermiMotion > >, std::reference_wrapper< const Key< FieldDerivativesMode > >, std::reference_wrapper< const Key< MultiParticleReactionsBitSet > >, std::reference_wrapper< const Key< NNbarTreatment > >, std::reference_wrapper< const Key< OutputOnlyFinal > >, std::reference_wrapper< const Key< PdgCode > >, std::reference_wrapper< const Key< PseudoResonance > >, std::reference_wrapper< const Key< ReactionsBitSet > >, std::reference_wrapper< const Key< RestFrameDensityDerivativesMode > >, std::reference_wrapper< const Key< Sampling > >, std::reference_wrapper< const Key< SmearingMode > >, std::reference_wrapper< const Key< SphereInitialCondition > >, std::reference_wrapper< const Key< ThermalizationAlgorithm > >, std::reference_wrapper< const Key< TimeStepMode > >, std::reference_wrapper< const Key< TotalCrossSectionStrategy > >> key_references_variant
Alias for the type to be used in the list of keys.
Definition: input_keys.h:5241
static const std::vector< key_references_variant > list
List of references to all existing SMASH keys.
Definition: input_keys.h:5244
A utility type to be specialized to check if a type is a std::map .