Version: SMASH-3.2
smash::Configuration Class Reference

#include <configuration.h>

Interface to the SMASH configuration files.

The configuration is created from a YAML file and then stores a nested map of maps (normally a tree, but YAML allows it to be cyclic - even though we don't want that feature). Since the resource owned by the object is a YAML::Node that handle memory in a similar way as a pointer does, it is forbidden (nor should it be needed) to copy instances of this class, while moving is fine (see special members documentation for more information).

The typical usage of a Configuration is to create it, consume (i.e. take) all its values and let it being destructed. Since this is the contact point with SMASH input file, the class is meant to be strict in its usage, so that it is possible to help the inexpert user, who might being using a wrong input file and/or e.g. specify an unused key hoping in an effect that indeed does not occur. Therefore, it is imposed that all keys must be parsed before an instance gets destroyed. If this is not the case, an exception will be thrown.

For the typical usage in SMASH one needs to read the value once. In that case, use the Configuration::take function, for example:

Key<double> key{{"General", "SIGMA"}, {"1.0"}};
double value = config.take(key);

Note that only Key objects can be taken. This is done on purpose, because all SMASH allowed keys are gathered as static members of the InputKeys class. This allows calls like config.take(InputKeys::key_name) (where of course an existing member should be used).

The opposite operation of take is the Configuration::set_value method, which has a similar syntax, but needs the new value to be assigned, e.g.

config.set_value(key, 3.1415);

If you need to delegate parsing of a section to some object, you can use the Configuration::extract_complete_sub_configuration method, which is taking a full section and returning a new, distinct Configuration instance.

Last but not least, the Configuration::validate method is used by SMASH to check that all given keys are allowed in the present version of the codebase. This is achieved by querying the "database" InputKeys class.

Attention
As the Configuration is implemented, it does not make sense in practice to have constant instances, because their keys could not be taken and their destruction would lead to an exception being thrown. However, it still makes perfectly sense to have constant methods (think e.g. of a const Configuration& being passed to a function).

Definition at line 284 of file configuration.h.

Classes

struct  FileDoesNotExist
 Thrown if the file does not exist. More...
 
struct  IncorrectTypeInAssignment
 Thrown when the types in the config file and C++ don't match. More...
 
struct  isMap
 Utility type trait (general case) for the take and read public methods. More...
 
struct  isMap< std::map< Key, Value > >
 Utility type trait (special case) for the take and read public methods. More...
 
struct  ParseError
 Thrown for YAML parse errors. More...
 
struct  TakeSameKeyTwice
 Thrown if a Key is taken twice. More...
 
class  Value
 Proxy object to be used when taking or reading keys in the configuration. More...
 

Public Types

enum class  GetEmpty { Yes , No }
 Flag to tune method(s) behavior such that it is descriptive from the caller side. More...
 
enum class  Is { Invalid , Deprecated , Valid }
 Return type of Configuration::validate which conveys more information that simply a two-state boolean variable. More...
 

Public Member Functions

 Configuration (const std::filesystem::path &path)
 Read config.yaml from the specified path. More...
 
 Configuration (const std::filesystem::path &path, const std::filesystem::path &filename)
 Read a YAML config file from the specified path. More...
 
 Configuration (const char *yaml, const char sflag)
 Initialize configuration with a YAML formatted string. More...
 
 Configuration (const char *yaml)
 
 Configuration (const Configuration &)=delete
 Prevent Configuration objects from being copied. More...
 
Configurationoperator= (const Configuration &)=delete
 Prevent Configuration objects from being copy-assigned. More...
 
 Configuration (Configuration &&)
 Provide class with move constructor. More...
 
Configurationoperator= (Configuration &&)
 Provide class with move assignment operator. More...
 
 ~Configuration () noexcept(false)
 Destroy the object, optionally throwing if not all keys were taken. More...
 
void merge_yaml (const std::string &yaml)
 Merge the configuration in yaml into the existing tree. More...
 
std::vector< std::string > list_upmost_nodes ()
 Lists all YAML::Nodes from the configuration setup. More...
 
template<typename T >
take (const Key< T > &key)
 The default interface for SMASH to read configuration values. More...
 
template<typename T >
take (const Key< T > &key, T default_value)
 Alternative method to take a key value, specifying the default value. More...
 
template<typename T >
read (const Key< T > &key) const
 Additional interface for SMASH to read configuration values without removing them. More...
 
template<typename T >
read (const Key< T > &key, T default_value)
 Alternative method to read a key value, specifying the default value. More...
 
template<typename T , typename U = remove_cvref_t<T>, typename std::enable_if_t< std::is_convertible_v< T, U >, bool > = true>
void set_value (Key< U > key, T &&value)
 Overwrite the value of the YAML node corresponding to the specified key. More...
 
void remove_all_entries_in_section_but_one (const std::string &key, KeyLabels section={})
 Remove all entries in the given section except for key. More...
 
Configuration extract_sub_configuration (KeyLabels section, Configuration::GetEmpty empty_if_not_existing=Configuration::GetEmpty::No)
 Create a new configuration from a then-removed section of the present object. More...
 
Configuration extract_complete_sub_configuration (KeyLabels section, Configuration::GetEmpty empty_if_not_existing=Configuration::GetEmpty::No)
 Alternative method to extract a sub-configuration, which retains the labels from the top-level in the returned object instead of dropping them. More...
 
void enclose_into_section (KeyLabels section)
 Enclose the configuration into the given section. More...
 
template<typename T >
bool has_key (const Key< T > &key) const
 Return whether the configuration has a (possibly empty) non-map key. More...
 
template<typename T >
bool has_value (const Key< T > &key) const
 Return whether there is a non-empty value behind the requested key (which is supposed not to refer to a section). More...
 
bool has_section (const KeyLabels &labels) const
 Return whether there is a (possibly empty) section with the given labels. More...
 
bool is_empty () const
 
std::string to_string () const
 Return a string of the current YAML tree. More...
 
void clear ()
 Erase the Configuration content. More...
 
Is validate (bool full_validation=true) const
 Validate content of configuration in terms of YAML keys. More...
 

Static Public Attributes

static const char InitializeFromYAMLString = 'S'
 Flag to mark initialization with a YAML formatted string. More...
 

Private Member Functions

 Configuration (const YAML::Node &node)
 Create a sub-object that has its root node at the given node. More...
 
YAML::Node find_node_creating_it_if_not_existing (std::vector< std::string_view > keys) const
 Descend in and if needed modify the YAML tree from the given node using the provided keys. More...
 
std::optional< YAML::Node > find_existing_node (std::vector< std::string_view > keys) const
 Descend in the YAML tree from the given node using the provided keys. More...
 
Value take (std::vector< std::string_view > labels)
 This is the implementation detail to take a key. More...
 
Value read (std::vector< std::string_view > labels) const
 This is the implementation detail to read a key. More...
 
bool did_key_exist_and_was_it_already_taken (const KeyLabels &labels) const
 Find out whether a key has been already taken. More...
 

Private Attributes

YAML::Node root_node_ {YAML::NodeType::Map}
 The general_config.yaml contents - fully parsed. More...
 
int uncaught_exceptions_ {std::uncaught_exceptions()}
 Counter to be able to optionally throw in destructor. More...
 
std::vector< KeyLabelsexisting_keys_already_taken_ {}
 List of taken keys to throw on taking same key twice. More...
 

Member Enumeration Documentation

◆ GetEmpty

Flag to tune method(s) behavior such that it is descriptive from the caller side.

For example, see extract_sub_configuration.

Enumerator
Yes 
No 

Definition at line 324 of file configuration.h.

324 { Yes, No };
@ Yes
Print only final-state particles.
@ No
Print initial, intermediate and final-state particles.

◆ Is

Return type of Configuration::validate which conveys more information that simply a two-state boolean variable.

Enumerator
Invalid 
Deprecated 
Valid 

Definition at line 330 of file configuration.h.

330 { Invalid, Deprecated, Valid };
@ Invalid
Unused, only in the code for internal logic.

Constructor & Destructor Documentation

◆ Configuration() [1/7]

smash::Configuration::Configuration ( const std::filesystem::path &  path)
explicit

Read config.yaml from the specified path.

Parameters
[in]pathThe directory where the SMASH config files are located.

Definition at line 125 of file configuration.cc.

126  : Configuration(path, "config.yaml") {}
Configuration(const std::filesystem::path &path)
Read config.yaml from the specified path.

◆ Configuration() [2/7]

smash::Configuration::Configuration ( const std::filesystem::path &  path,
const std::filesystem::path &  filename 
)
explicit

Read a YAML config file from the specified path.

Parameters
[in]pathThe directory where the SMASH config files are located.
[in]filenameThe filename (without path) of the YAML config file, in case you don't want the default "config.yaml".

Definition at line 129 of file configuration.cc.

130  {
131  const auto file_path = path / filename;
132  if (!std::filesystem::exists(file_path)) {
133  throw FileDoesNotExist("The configuration file was expected at '" +
134  file_path.native() +
135  "', but the file does not exist.");
136  }
137  if (has_crlf_line_ending(read_all(std::ifstream((file_path))))) {
138  throw std::runtime_error(
139  "The configuration file has CR LF line endings. Please use LF "
140  "line endings.");
141  }
142  try {
143  root_node_ = YAML::LoadFile(file_path.native());
144  } catch (YAML::ParserException &e) {
145  if (e.msg == "illegal map value" || e.msg == "end of map not found") {
146  const auto line = std::to_string(e.mark.line + 1);
147  throw ParseError("YAML parse error at\n" + file_path.native() + ':' +
148  line + ": " + e.msg +
149  " (check that the indentation of map keys matches)");
150  }
151  throw;
152  }
153 }
YAML::Node root_node_
The general_config.yaml contents - fully parsed.
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.

◆ Configuration() [3/7]

smash::Configuration::Configuration ( const char *  yaml,
const char  sflag 
)
inlineexplicit

Initialize configuration with a YAML formatted string.

This is useful in 3-rd party application where we may not be able or willing to read in external files.

Parameters
[in]yamlYAML formatted configuration data.
[in]sflagcontrol flag InitializeFromYAMLString.

Definition at line 357 of file configuration.h.

357  {
358  if (sflag == InitializeFromYAMLString) {
359  merge_yaml(yaml);
360  } else {
361  throw std::runtime_error(
362  "Unknown control flag in Configuration constructor"
363  " with a YAML formatted string. Please, use"
364  " Configuration::InitializeFromYAMLString.");
365  }
366  }
void merge_yaml(const std::string &yaml)
Merge the configuration in yaml into the existing tree.
static const char InitializeFromYAMLString
Flag to mark initialization with a YAML formatted string.

◆ Configuration() [4/7]

smash::Configuration::Configuration ( const char *  yaml)
inlineexplicit
Unit tests can use this constructor to get a Configuration object from a built-in string.This function is only available to tests and should never be used/needed in actual SMASH code. The intention is to avoid creating a mock object for Configuration to test other classes of SMASH.

Definition at line 377 of file configuration.h.

377  : root_node_(YAML::Load(yaml)) {
378  if (root_node_.IsNull())
379  root_node_ = YAML::Node{YAML::NodeType::Map};
380  }

◆ Configuration() [5/7]

smash::Configuration::Configuration ( const Configuration )
delete

Prevent Configuration objects from being copied.

Underneath, the resource is a YAML::Node and since this handles memory in a similar way as a pointer would do, copying an object would make several instances point to the same memory and it would make it difficult to use this object correctly. Therefore, copies are not allowed.

◆ Configuration() [6/7]

smash::Configuration::Configuration ( Configuration &&  other)

Provide class with move constructor.

In contrast to copying, moving is fine, since this keeps the owner of the resource unique.

Note
Since the class has the peculiar behavior that all keys must be parsed before it gets destroyed (otherwise an exception is thrown), it is important to manually implement the move operations, in order to ensure that objects that are moved from result cleared and their destruction is not leading to any throw. This is not guaranteed if the special members are defaulted to the compiler generated versions.

Definition at line 155 of file configuration.cc.

156  : root_node_(std::move(other.root_node_)),
157  uncaught_exceptions_(std::move(other.uncaught_exceptions_)),
159  std::move(other.existing_keys_already_taken_)) {
160  other.root_node_.reset();
161  other.uncaught_exceptions_ = 0;
162  other.existing_keys_already_taken_.clear();
163 }
int uncaught_exceptions_
Counter to be able to optionally throw in destructor.
std::vector< KeyLabels > existing_keys_already_taken_
List of taken keys to throw on taking same key twice.

◆ ~Configuration()

smash::Configuration::~Configuration ( )
noexcept

Destroy the object, optionally throwing if not all keys were taken.

This is a way to enforce that the object has to be consumed (i.e. completely parsed) during its lifetime. Since this object might be destructed during stack unwinding, the destructor has to throw only if it is safe to do so and the uncaught_exceptions_ member is used to properly implement this behavior.

Definition at line 179 of file configuration.cc.

179  {
180  // Make sure that stack unwinding is not taking place befor throwing
181  if (std::uncaught_exceptions() == uncaught_exceptions_) {
182  // In this scenario is fine to throw
183  if (root_node_.size() != 0) {
184  throw std::logic_error(
185  "Configuration object destroyed with unused keys:\n" + to_string());
186  }
187  }
188  /* If this destructor is called during stack unwinding, it is irrelevant
189  that the Configuration has not be completely parsed. */
190 }
std::string to_string() const
Return a string of the current YAML tree.

◆ Configuration() [7/7]

smash::Configuration::Configuration ( const YAML::Node &  node)
inlineprivate

Create a sub-object that has its root node at the given node.

Note
This constructor is not explicit because it can be called only from inside Configuration and by making it explicit a return would require the copy constructor.

Definition at line 1488 of file configuration.h.

1489  : root_node_(YAML::Clone(node)) {}

Member Function Documentation

◆ operator=() [1/2]

Configuration& smash::Configuration::operator= ( const Configuration )
delete

Prevent Configuration objects from being copy-assigned.

See copy constructor Configuration(const Configuration &) for more information.

◆ operator=() [2/2]

Configuration & smash::Configuration::operator= ( Configuration &&  other)

Provide class with move assignment operator.

See move constructor Configuration(Configuration &&) for more information.

Definition at line 165 of file configuration.cc.

165  {
166  // YAML does not offer != operator between nodes
167  if (!(root_node_ == other.root_node_)) {
168  root_node_ = std::move(other.root_node_);
169  uncaught_exceptions_ = std::move(other.uncaught_exceptions_);
171  std::move(other.existing_keys_already_taken_);
172  other.root_node_.reset();
173  other.uncaught_exceptions_ = 0;
174  other.existing_keys_already_taken_.clear();
175  }
176  return *this;
177 }

◆ merge_yaml()

void smash::Configuration::merge_yaml ( const std::string &  yaml)

Merge the configuration in yaml into the existing tree.

The function parses the string in yaml into its internal tree representation. Then it merges the nodes from the new tree into the existing tree. The merge resolves conflicts by taking the value from yaml.

Parameters
[in]yamlA string with YAML (or JSON) content that is to be merged.

Definition at line 192 of file configuration.cc.

192  {
193  try {
194  root_node_ |= YAML::Load(yaml);
195  } catch (YAML::ParserException &e) {
196  if (e.msg == "illegal map value" || e.msg == "end of map not found") {
197  const auto line = std::to_string(e.mark.line + 1);
198  throw ParseError("YAML parse error in:\n" + yaml + "\nat line " + line +
199  ": " + e.msg +
200  " (check that the indentation of map keys matches)");
201  }
202  throw;
203  }
204 }

◆ list_upmost_nodes()

std::vector< std::string > smash::Configuration::list_upmost_nodes ( )

Lists all YAML::Nodes from the configuration setup.

Definition at line 206 of file configuration.cc.

206  {
207  std::vector<std::string> r;
208  r.reserve(root_node_.size());
209  for (auto i : root_node_) {
210  r.emplace_back(i.first.Scalar());
211  }
212  return r;
213 }

◆ take() [1/3]

template<typename T >
T smash::Configuration::take ( const Key< T > &  key)
inline

The default interface for SMASH to read configuration values.

The function returns the value at the specified Key and removes its labels from the Configuration object. Therefore, a subsequent call to the take or has_value methods with the same Key throws or returns false respectively. By removing the value, the Configuration object keeps track which keys were never taken.

Attention
If a not existent Key is taken, its default value is returned, if any exists. If a not existing required key is taken, an error will be given.
Note
If taking a key leaves the parent key without a value, then this is in turn removed and so on. From a performance point of view, it might be argued that this is not needed to be checked and done at every take operation and it might be done once for all. However, on one hand it is a natural behaviour to expect and on the other hand this is hardly going to be an application bottle-neck.
Warning
Since take returns the default value when the key is not present in the configuration, it is important to make it throw if an existing key is attempted to be taken twice. Otherwise it would happen that taking any existing key would return the user-defined value the first time and taking it again would return the key default value. This is a misleading behaviour we want to avoid. However, a key can be taken several times if it exists at take time. For example, taking a key, setting its value and taking it again is a valid behaviour.
Parameters
[in]keyThe input key that should be taken. This is usually one of the InputKeys static members, i.e. one of the allowed keys. Of course, any Key would work. For example, given
Group:
  Key: 42
then
Key key<int>{{"Group", "Key"}, {"1.0"}};
string value = config.take(key);
will take the value. This will make the key "Group" also be removed from the configuration, since it remains without any value.
Returns
The value of the taken key if present, its default value otherwise.
Exceptions
TakeSameKeyTwiceif a key was already previously taken.
std::invalid_argumentif a key without a default is taken but it is absent in the configuration.

Definition at line 500 of file configuration.h.

500  {
501  if (has_value(key)) {
502  // The following return statement converts a Value into T
503  return take({key.labels().begin(), key.labels().end()});
504  } else if (has_section(key.labels())) {
505  // In this case, if the Key type is a map, we take it, otherwise fails
506  if constexpr (isMap<typename Key<T>::type>::value) {
507  return take({key.labels().begin(), key.labels().end()});
508  } else {
509  throw std::logic_error(
510  "Key " + std::string{key} + // NOLINT(whitespace/braces)
511  " was taken, but its value is not a map, although there is a "
512  "section in the configuration with its labels.");
513  }
514  } else if (did_key_exist_and_was_it_already_taken(key.labels())) {
515  throw TakeSameKeyTwice("Attempt to take key " +
516  std::string{key} + // NOLINT(whitespace/braces)
517  " twice.");
518  } else {
519  try {
520  return key.default_value();
521  } catch (std::bad_optional_access &) {
522  throw std::invalid_argument(
523  "Key " + std::string{key} + // NOLINT(whitespace/braces)
524  " without default value taken, but missing in configuration.");
525  }
526  }
527  }
bool did_key_exist_and_was_it_already_taken(const KeyLabels &labels) const
Find out whether a key has been already taken.
bool has_value(const Key< T > &key) const
Return whether there is a non-empty value behind the requested key (which is supposed not to refer to...
bool has_section(const KeyLabels &labels) const
Return whether there is a (possibly empty) section with the given labels.
T take(const Key< T > &key)
The default interface for SMASH to read configuration values.
default_type type
Let the clients of this class have access to the key type.
Definition: key.h:168

◆ take() [2/3]

template<typename T >
T smash::Configuration::take ( const Key< T > &  key,
default_value 
)
inline

Alternative method to take a key value, specifying the default value.

See also
take
Template Parameters
TThe type of the key to be taken
Parameters
keyThe key to be taken
default_valueThe default value to be returned if the key is not present in the configuration
Returns
The value of the key
Exceptions
std::logic_errorIf the key has not a default value declared as dependent on external entities.

Definition at line 543 of file configuration.h.

543  {
544  if (!key.has_dependent_default()) {
545  throw std::logic_error(
546  "An input Key without dependent default cannot be taken specifying a "
547  "default value! Either define the key as having a dependent default "
548  "or take it without a default value (which is a Key property).");
549  }
550  if (has_value(key)) {
551  return take(key);
552  }
553  return default_value;
554  }

◆ read() [1/3]

template<typename T >
T smash::Configuration::read ( const Key< T > &  key) const
inline

Additional interface for SMASH to read configuration values without removing them.

The function returns the value of the specified Key but does not remove it from the Configuration object. Semantically, this means the value was not used.

Also this method returns the default value of the key (or an error if none is available), if the key is not present in the configuration.

See also
take
Note
Since reading a key does not remove it from the configuration, it is not necessary to store the key read to avoid taking it multiple times. Actually, doing so is safe and will return the same value.
Parameters
[in]keyThe input key that should be taken.
Returns
The value of the taken key if present, its default value otherwise.
Exceptions
std::invalid_argumentif a key without a default is taken but it is absent in the configuration.

Definition at line 579 of file configuration.h.

579  {
580  if (has_value(key)) {
581  // The following return statement converts a Value into T
582  return read({key.labels().begin(), key.labels().end()});
583  } else if (has_section(key.labels())) {
584  // In this case, if the Key type is a map, we take it, otherwise fails
585  if constexpr (isMap<typename Key<T>::type>::value) {
586  return read({key.labels().begin(), key.labels().end()});
587  } else {
588  throw std::logic_error(
589  "Key " + std::string{key} + // NOLINT(whitespace/braces)
590  " was read, but its value is not a map, although there is a "
591  "section in the configuration with its labels.");
592  }
593  } else {
594  try {
595  return key.default_value();
596  } catch (std::bad_optional_access &) {
597  throw std::invalid_argument(
598  "Key " + std::string{key} + // NOLINT(whitespace/braces)
599  " without default value read, but missing in configuration.");
600  }
601  }
602  }
T read(const Key< T > &key) const
Additional interface for SMASH to read configuration values without removing them.

◆ read() [2/3]

template<typename T >
T smash::Configuration::read ( const Key< T > &  key,
default_value 
)
inline

Alternative method to read a key value, specifying the default value.

See also
read
Template Parameters
TThe type of the key to be read
Parameters
keyThe key to be read
default_valueThe default value to be returned if the key is not present in the configuration
Returns
The value of the key
Exceptions
std::logic_errorIf the key has not a default value declared as dependent on external entities.

Definition at line 618 of file configuration.h.

618  {
619  if (!key.has_dependent_default()) {
620  throw std::logic_error(
621  "An input Key without dependent default cannot be read specifying a "
622  "default value! Either define the key as having a dependent default "
623  "or read it without a default value (which is a Key property).");
624  }
625  if (has_value(key)) {
626  return read(key);
627  }
628  return default_value;
629  }

◆ set_value()

template<typename T , typename U = remove_cvref_t<T>, typename std::enable_if_t< std::is_convertible_v< T, U >, bool > = true>
void smash::Configuration::set_value ( Key< U >  key,
T &&  value 
)
inline

Overwrite the value of the YAML node corresponding to the specified key.

Parameters
[in]keyThe input key that should be changed.
[in]valueAn arbitrary value that yaml-cpp can convert into YAML representation. Any builtin type, strings, maps, and vectors can be used here. Of course, this has to match the Key type passed as first argument.
Template Parameters
TThe type of the value to be assigned to the Key.
UThe type of the key value. This is by default T but it has been allowed to be different from it, as long as it is convertible to T. This enables e.g. to set a key with a string value using a const char* second argument.
Attention
This method creates a new entry in the configuration if the passed key is not yet existing in it.
Note
Removing qualifiers and the reference in the default value of the second template argument is needed because the Key type is a plain type and T might be deduced to a constant and/or reference type.

Definition at line 655 of file configuration.h.

655  {
657  {key.labels().begin(), key.labels().end()});
658  node = std::forward<T>(value);
659  }
YAML::Node find_node_creating_it_if_not_existing(std::vector< std::string_view > keys) const
Descend in and if needed modify the YAML tree from the given node using the provided keys.

◆ remove_all_entries_in_section_but_one()

void smash::Configuration::remove_all_entries_in_section_but_one ( const std::string &  key,
KeyLabels  section = {} 
)

Remove all entries in the given section except for key.

Parameters
[in]keyThe key of the map entry to keep.
[in]sectionYou can pass an arbitrary number of keys inside curly braces, following the nesting structure in the config file, in order to specify the section where to delete entries. Omitting the section is equivalent to specifying {} and the top-level section is understood.

Definition at line 258 of file configuration.cc.

259  {
260  auto found_node = find_existing_node({section.begin(), section.end()});
261  if (found_node) {
262  std::vector<std::string> to_remove{};
263  bool key_exists = false;
264  for (auto i : found_node.value()) {
265  if (i.first.Scalar() != key) {
266  to_remove.push_back(i.first.Scalar());
267  } else {
268  key_exists = true;
269  }
270  }
271  if (!key_exists) {
272  std::string section_string{" section "};
273  if (section.size() > 0) {
274  section_string += join_quoted({section.begin(), section.end()}) + " ";
275  } else {
276  section_string = " top-level" + section_string;
277  }
278  throw std::invalid_argument("Attempt to remove all keys in" +
279  section_string +
280  "except not existing one: \"" + key + "\"");
281  } else {
282  for (auto i : to_remove) {
283  found_node.value().remove(i);
284  }
285  }
286  } else {
287  throw std::invalid_argument(
288  "Attempt to remove entries in not existing section: " +
289  join_quoted({section.begin(), section.end()}));
290  }
291 }
std::optional< YAML::Node > find_existing_node(std::vector< std::string_view > keys) const
Descend in the YAML tree from the given node using the provided keys.
std::string join_quoted(std::vector< std::string_view > keys)
Build a string with a list of keys as specified in the code.

◆ extract_sub_configuration()

Configuration smash::Configuration::extract_sub_configuration ( KeyLabels  section,
Configuration::GetEmpty  empty_if_not_existing = Configuration::GetEmpty::No 
)

Create a new configuration from a then-removed section of the present object.

This method is meant to be used to deal with sections only, i.e. it will throw if used to extract a key value that is not a section (namely a map in YAML language). Use take for that purpose, instead.

Parameters
[in]sectionYou can pass an arbitrary number of keys inside curly braces, following the nesting structure in the config file.
[in]empty_if_not_existingSpecify Configuration::GetEmpty::Yes if you want an empty Configuration in case the requested section does not exist.
Exceptions
std::runtime_errorif the method is used
  • to access a scalar or sequence value;
  • to access a key that has no value or is an empty map;
  • to access a not existing key (unless explicitly allowed).
Returns
A new Configuration containing the chosen section.

Definition at line 293 of file configuration.cc.

294  {
295  // Same logic as in take method
296  assert(section.size() > 0);
297  auto last_key_it = section.end() - 1;
298  auto previous_to_section_node =
299  find_existing_node({section.begin(), last_key_it});
300  auto sub_conf_root_node{previous_to_section_node};
301  descend_one_existing_level(sub_conf_root_node, *last_key_it);
302  if (!previous_to_section_node || !sub_conf_root_node) {
303  if (empty_if_not_existing == Configuration::GetEmpty::Yes)
304  return Configuration(YAML::Node{});
305  else
306  throw std::runtime_error("Attempt to extract not existing section " +
307  join_quoted({section.begin(), section.end()}));
308  }
309  /* Here sub_conf_root_node cannot be a nullopt, since if it was the function
310  would have returned before and it cannot be that previous_to_section_node
311  is nullopt and sub_conf_root_node is not */
312  else if (sub_conf_root_node->IsNull() || // NOLINT[whitespace/newline]
313  (sub_conf_root_node->IsMap() && sub_conf_root_node->size() == 0)) {
314  // Here we put together the cases of a key without value or with
315  // an empty map {} as value (no need at the moment to distinguish)
316  throw std::runtime_error("Attempt to extract empty section " +
317  join_quoted({section.begin(), section.end()}));
318  } else if (sub_conf_root_node->IsMap() && sub_conf_root_node->size() != 0) {
319  Configuration sub_config{*sub_conf_root_node};
320  previous_to_section_node->remove(*last_key_it);
322  return sub_config;
323  } else { // sequence or scalar or any future new YAML type
324  throw std::runtime_error("Tried to extract configuration section at " +
325  join_quoted({section.begin(), section.end()}) +
326  " to get a key value. Use take instead!");
327  }
328 }
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.
YAML::Node remove_empty_maps(YAML::Node root)
Remove all empty maps of a YAML::Node.

◆ extract_complete_sub_configuration()

Configuration smash::Configuration::extract_complete_sub_configuration ( KeyLabels  section,
Configuration::GetEmpty  empty_if_not_existing = Configuration::GetEmpty::No 
)

Alternative method to extract a sub-configuration, which retains the labels from the top-level in the returned object instead of dropping them.

See also
extract_sub_configuration

Definition at line 330 of file configuration.cc.

331  {
332  auto sub_configuration =
333  extract_sub_configuration(section, empty_if_not_existing);
334  sub_configuration.enclose_into_section(section);
335  return sub_configuration;
336 }
Configuration extract_sub_configuration(KeyLabels section, Configuration::GetEmpty empty_if_not_existing=Configuration::GetEmpty::No)
Create a new configuration from a then-removed section of the present object.

◆ enclose_into_section()

void smash::Configuration::enclose_into_section ( KeyLabels  section)

Enclose the configuration into the given section.

Parameters
sectionThe section in which to enclose the configuration.

Definition at line 338 of file configuration.cc.

338  {
339  /* If the configuration is empty, force its root node to be a map. This is not
340  done in the extract_sub_configuration method, which allows the possibility not
341  to have a map node at root in the returned object, but here we
342  know it will be a map. */
343  if (root_node_.size() == 0) {
344  root_node_ = YAML::Node(YAML::NodeType::Map);
345  }
346  /* Create a new configuration from an empty node adding there the nested-map
347  structure. Then add the old root node to it and reset it to the new root one.
348  Refer to the descend_one_existing_level comment to understand the usage of
349  YAML::Node::operator[] and reset methods. */
350  YAML::Node new_root_node{YAML::NodeType::Map};
351  auto last_node = new_root_node;
352  for (const auto &label : section) {
353  last_node[label] = YAML::Node(YAML::NodeType::Map);
354  last_node.reset(last_node[label]);
355  }
356  last_node = root_node_;
357  root_node_.reset(new_root_node);
358 }

◆ has_key()

template<typename T >
bool smash::Configuration::has_key ( const Key< T > &  key) const
inline

Return whether the configuration has a (possibly empty) non-map key.

Although YAML keys can have maps as value, we rather refer to those as sections and we do not consider them as key in the SMASH database sense. A key has then either a scalar or sequence value.

Parameters
[in]keyThe key to be checked for.

Definition at line 724 of file configuration.h.

724  {
725  const auto found_node =
726  find_existing_node({key.labels().begin(), key.labels().end()});
727  return found_node.has_value() && !(found_node.value().IsMap());
728  }

◆ has_value()

template<typename T >
bool smash::Configuration::has_value ( const Key< T > &  key) const
inline

Return whether there is a non-empty value behind the requested key (which is supposed not to refer to a section).

If there is a section with the same labels as the provided key has, this function returns false .

Parameters
[in]keyThe key to be checked for.

Definition at line 738 of file configuration.h.

738  {
739  const auto found_node =
740  find_existing_node({key.labels().begin(), key.labels().end()});
741  return found_node.has_value() && !(found_node.value().IsNull()) &&
742  !(found_node.value().IsMap());
743  }

◆ has_section()

bool smash::Configuration::has_section ( const KeyLabels labels) const
inline

Return whether there is a (possibly empty) section with the given labels.

Parameters
[in]labelsThe labels of the section to be checked for.

Definition at line 750 of file configuration.h.

750  {
751  const auto found_node = find_existing_node({labels.begin(), labels.end()});
752  return found_node.has_value() && found_node.value().IsMap();
753  }

◆ is_empty()

bool smash::Configuration::is_empty ( ) const
inline
Returns
true if the object is empty;
false if at least one key exists.

Definition at line 759 of file configuration.h.

759 { return root_node_.size() == 0; }

◆ to_string()

std::string smash::Configuration::to_string ( ) const

Return a string of the current YAML tree.

Definition at line 360 of file configuration.cc.

360  {
361  std::stringstream s;
362  s << root_node_;
363  return s.str();
364 }

◆ clear()

void smash::Configuration::clear ( )
inline

Erase the Configuration content.

This function is useful e.g. in tests to clean up not taken keys that would trigger an exception being thrown at by the destructor.

Definition at line 772 of file configuration.h.

772 { root_node_.reset(); }

◆ validate()

Configuration::Is smash::Configuration::validate ( bool  full_validation = true) const

Validate content of configuration in terms of YAML keys.

A warning or error message is printed for deprecated or invalid keys, respectively, together with information about SMASH versions, if possible.

Note
Here a full validation is done by default and all keys are checked, although the validation might be shortened by returning false as soon as an invalid key is found. However, a full validation is more user-friendly, since as much information as possible about the input file is provided.
Parameters
[in]full_validationWhether all keys are checked or not.
Returns
Is::Valid if the object contains valid keys only;
Is::Deprecated if the object is valid but has deprecated key(s);
Is::Invalid if the object contains at least one invalid key.

Definition at line 625 of file configuration.cc.

625  {
628  Is validation_result{Is::Valid};
629  for (const auto &key_labels : list) {
630  Is key_state = validate_key(key_labels);
631  if (full_validation) {
632  accumulate_validation(validation_result, key_state);
633  } else {
634  if (key_state != Is::Valid)
635  return key_state;
636  }
637  }
638  return validation_result;
639 }
Is
Return type of Configuration::validate which conveys more information that simply a two-state boolean...
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 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...
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...

◆ find_node_creating_it_if_not_existing()

YAML::Node smash::Configuration::find_node_creating_it_if_not_existing ( std::vector< std::string_view >  keys) const
private

Descend in and if needed modify the YAML tree from the given node using the provided keys.

After this call nodes corresponding to the passed keys are guaranteed to exist in the tree.

Parameters
[in]keysKeys that will be possibly added to the YAML tree.
Returns
Node in the tree reached by using the provided keys.

Definition at line 379 of file configuration.cc.

380  {
381  assert(keys.size() > 0);
382  YAML::Node node{root_node_};
383  for (const auto &key : keys) {
384  // See comments in descend_one_existing_level function
385  node.reset(node[key]);
386  }
387  return node;
388 }

◆ find_existing_node()

std::optional< YAML::Node > smash::Configuration::find_existing_node ( std::vector< std::string_view >  keys) const
private

Descend in the YAML tree from the given node using the provided keys.

This function must not use the YAML::Node subscript operator, which is at the very bottom level creating an undefined node in the YAML tree, hence "wasting" some memory. Note that the fact that this method is marked as const does not forbid to use the access operator on root_node_, because of how the YAML library works. We want the tree to be completely untouched by this method.

Parameters
[in]keysKeys that will be used to descend the YAML tree.
Returns
std::optional<YAML::Node> containing the node in the tree reached by using the provided keys, if it exists;
std::nullopt otherwise.
Note
It has been decided to return an optional value rather than throwing an exception because this method is going to be used in other methods like has_value and putting there a try-catch block would probably cause a performance cost that can be avoided (exceptions on the exceptional path are expensive).

Definition at line 366 of file configuration.cc.

367  {
368  /* Here we do not assert(keys.size()>0) and allow to pass in an empty vector,
369  in which case the passed in YAML:Node is simply returned. This might happen
370  e.g. in the take or extract_sub_configuration methods if called with a
371  label of a key at top level of the configuration file. */
372  std::optional<YAML::Node> node{root_node_};
373  for (const auto &key : keys) {
374  descend_one_existing_level(node, key);
375  }
376  return node;
377 }

◆ take() [3/3]

Configuration::Value smash::Configuration::take ( std::vector< std::string_view >  labels)
private

This is the implementation detail to take a key.

Having a non-templated method for it it allows for defining the method in the source file, which isn't possible for a template.

Parameters
[in]labelsthe labels of the key to be taken
Returns
The Value of the key
Exceptions
std::runtime_errorif the key does not exist

Definition at line 215 of file configuration.cc.

215  {
216  assert(labels.size() > 0);
217  /* Here we want to descend the YAML tree but not all the way to the last key,
218  because we need the node associated to the previous to last key in order to
219  remove the taken key. */
220  auto last_key_it = labels.end() - 1;
221  auto previous_to_last_node =
222  find_existing_node({labels.begin(), last_key_it});
223  auto to_be_returned{previous_to_last_node};
224  descend_one_existing_level(to_be_returned, *last_key_it);
225  if (!previous_to_last_node || !to_be_returned) {
226  throw std::runtime_error(
227  "Private Configuration::take method called with not existing key: " +
228  join_quoted(labels) + ". This should not have happened.");
229  }
230  previous_to_last_node.value().remove(*last_key_it);
232  if (const KeyLabels key_labels{labels.begin(), labels.end()};
234  existing_keys_already_taken_.push_back(key_labels);
235  }
236  /* NOTE: The second argument in the returned statement to construct Value must
237  * point to a string that is outliving the function scope and it would be
238  * wrong to return e.g. something locally declared in the function. This is
239  * because that argument is underneath of type 'const char* const' and, then,
240  * if it was dangling after returning, it would be wrong to access it.
241  */
242  return {to_be_returned.value(), last_key_it->data()};
243 }
std::vector< std::string > KeyLabels
Descriptive alias for storing key labels, i.e.
Definition: key.h:42

◆ read() [3/3]

Configuration::Value smash::Configuration::read ( std::vector< std::string_view >  labels) const
private

This is the implementation detail to read a key.

Having a non-templated method for it it allows for defining the method in the source file, which isn't possible for a template.

Parameters
[in]labelsthe labels of the key to be read
Returns
The Value of the key
Exceptions
std::runtime_errorif the key does not exist

Definition at line 245 of file configuration.cc.

246  {
247  auto found_node = find_existing_node({labels.begin(), labels.end()});
248  if (found_node) {
249  // The same remark about the take return value applies here.
250  return {found_node.value(), labels.back().data()};
251  } else {
252  throw std::runtime_error(
253  "Private Configuration::read method called with not existing key: " +
254  join_quoted(labels) + ". This should not have happened.");
255  }
256 }

◆ did_key_exist_and_was_it_already_taken()

bool smash::Configuration::did_key_exist_and_was_it_already_taken ( const KeyLabels labels) const
inlineprivate

Find out whether a key has been already taken.

Parameters
labelsThe labels of the key to be checked
Returns
true if the key was already taken,
false otherwise.

Definition at line 1574 of file configuration.h.

1574  {
1575  return std::find(existing_keys_already_taken_.begin(),
1577  labels) != existing_keys_already_taken_.end();
1578  }

Member Data Documentation

◆ InitializeFromYAMLString

const char smash::Configuration::InitializeFromYAMLString = 'S'
static

Flag to mark initialization with a YAML formatted string.

Definition at line 318 of file configuration.h.

◆ root_node_

YAML::Node smash::Configuration::root_node_ {YAML::NodeType::Map}
private

The general_config.yaml contents - fully parsed.

Definition at line 1581 of file configuration.h.

◆ uncaught_exceptions_

int smash::Configuration::uncaught_exceptions_ {std::uncaught_exceptions()}
private

Counter to be able to optionally throw in destructor.

Definition at line 1584 of file configuration.h.

◆ existing_keys_already_taken_

std::vector<KeyLabels> smash::Configuration::existing_keys_already_taken_ {}
private

List of taken keys to throw on taking same key twice.

Definition at line 1587 of file configuration.h.


The documentation for this class was generated from the following files: