Skip to content

Commit

Permalink
Add option to update a field (#11)
Browse files Browse the repository at this point in the history
* add option to update a field

* update via node

* wip

* fixes

* cleanup

* more tests

* PR review @Schmluk

* cleanup

* final cleanup
  • Loading branch information
floriantschopp committed Apr 2, 2024
1 parent eda1efd commit 2201be0
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ qtcreator-*
*.ccls*
*.vscode/
*.ipynb_checkpoints/
.cache/
*~

# Emacs
Expand Down
16 changes: 16 additions & 0 deletions config_utilities/include/config_utilities/parsing/ros.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "config_utilities/internal/visitor.h"
#include "config_utilities/internal/yaml_utils.h"
#include "config_utilities/parsing/yaml.h" // NOTE(lschmid): This pulls in more than needed buyt avoids code duplication.
#include "config_utilities/update.h"

namespace config {

Expand Down Expand Up @@ -178,4 +179,19 @@ std::unique_ptr<BaseT> createFromROSWithNamespace(const ros::NodeHandle& nh,
return internal::ObjectWithConfigFactory<BaseT, ConstructorArguments...>::create(internal::rosToYaml(ns_nh), args...);
}

/**
* @brief Update the config with the current parameters in ROS.
* @note This function will update the field and check the validity of the config afterwards. If the config is invalid,
* the field will be reset to its original value.
* @param config The config to update.
* @param nh The ROS nodehandle to update the config from.
* @param name_space Optionally specify a name space to create the config from. Separate names with slashes '/'.
*/
template <typename ConfigT>
bool updateFromRos(ConfigT& config, const ros::NodeHandle& nh, const std::string& name_space = "") {
const ros::NodeHandle ns_nh = ros::NodeHandle(nh, name_space);
const YAML::Node node = internal::rosToYaml(ns_nh);
return updateField(config, node, true, name_space);
}

} // namespace config
14 changes: 14 additions & 0 deletions config_utilities/include/config_utilities/parsing/yaml.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <string>
#include <vector>

#include <yaml-cpp/node/node.h>
#include <yaml-cpp/yaml.h>

#include "config_utilities/factory.h"
Expand Down Expand Up @@ -195,4 +196,17 @@ std::unique_ptr<BaseT> createFromYamlFileWithNamespace(const std::string& file_n
return internal::ObjectWithConfigFactory<BaseT, ConstructorArguments...>::create(node, args...);
}

/**
* @brief Update the config with the values in a YAML node.
* @note This function will update the field and check the validity of the config afterwards. If the config is invalid,
* the field will be reset to its original value.
* @param config The config to update.
* @param node The node containing the field(s) to update.
* @param name_space Optionally specify a name space to create the config from. Separate names with slashes '/'.
*/
template <typename ConfigT>
bool updateFromYaml(ConfigT& config, const YAML::Node& node, const std::string& name_space = "") {
return updateField(config, node, true, name_space);
}

} // namespace config
23 changes: 19 additions & 4 deletions config_utilities/include/config_utilities/types/enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@

namespace config {

/**
* @brief Create a map of enum values to their string names. This is useful for defining the enum names in a single
* location, and then using them for the enum definition and parsing.
* @tparam EnumT The enum type.
* @param enum_names List of all possible enum names in identical order to the enum definition. Use only with sequential
* (=default valued uint type) enums.
* @return Map of enum values to their string names.
*/
template <typename EnumT>
std::map<EnumT, std::string> createEnumMap(const std::vector<std::string>& enum_names) {
std::map<EnumT, std::string> enum_map;
for (size_t i = 0; i < enum_names.size(); ++i) {
enum_map[static_cast<EnumT>(i)] = enum_names[i];
}
return enum_map;
}


/**
* @brief A struct that provides conversion between an ennum type and its string representation. The enum definition can
* provides interfaces for user-side conversion, and is an automatic type converter for config field parsing.
Expand Down Expand Up @@ -235,10 +253,7 @@ void enum_field(EnumT& field, const std::string& field_name, const std::map<Enum
*/
template <typename EnumT>
void enum_field(EnumT& field, const std::string& field_name, const std::vector<std::string>& enum_names) {
std::map<EnumT, std::string> enum_map;
for (size_t i = 0; i < enum_names.size(); ++i) {
enum_map[static_cast<EnumT>(i)] = enum_names[i];
}
const auto enum_map = createEnumMap<EnumT>(enum_names);
enum_field(field, field_name, enum_map);
}

Expand Down
148 changes: 148 additions & 0 deletions config_utilities/include/config_utilities/update.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/** -----------------------------------------------------------------------------
* Copyright (c) 2023 Massachusetts Institute of Technology.
* All Rights Reserved.
*
* AUTHORS: Lukas Schmid <[email protected]>, Nathan Hughes <[email protected]>
* AFFILIATION: MIT-SPARK Lab, Massachusetts Institute of Technology
* YEAR: 2023
* LICENSE: BSD 3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* -------------------------------------------------------------------------- */

#pragma once

#include <string>

#include "config_utilities/internal/logger.h"
#include "config_utilities/internal/visitor.h"
#include "config_utilities/types/enum.h"
#include "config_utilities/validation.h"

namespace config {

/**
* @brief Update fields in a config.
* @note This function will update the field and check the validity of the config afterwards. If the config is invalid,
* the field will be reset to its original value.
* @param node The node containing the fields to update.
*/
template <typename ConfigT>
bool updateFields(ConfigT& config, YAML::Node node, bool print_warnings = true, const std::string& name_space = "") {
const auto backup = internal::Visitor::getValues(config);
const internal::MetaData data =
internal::Visitor::setValues(config, internal::lookupNamespace(node, name_space), print_warnings);
const bool success = !data.hasErrors() && isValid(config);
if (!success) {
internal::Visitor::setValues(config, backup.data, print_warnings);
}

return success;
}

/**
* @brief Update a field in a config.
* @note This function will update the field and check the validity of the config afterwards. If the config is invalid,
* the field will be reset to its original value.
* @param field_name The name of the field to update.
* @param value The new value of the field.
*/
template <typename T, typename ConfigT>
bool updateField(ConfigT& config,
const std::string& field_name,
const T& value,
bool print_warnings = true,
const std::string& name_space = "") {
YAML::Node node;
if constexpr (std::is_enum<T>::value) {
internal::Logger::logWarning("Use updateFieldEnum for enum fields.");
return false;
} else {
node[field_name] = value;
return updateFields<ConfigT>(config, node, print_warnings, name_space);
}
}

/**
* @brief Update an enum field in a config.
* @note This function will update the field and check the validity of the config afterwards. If the config is
* invalid, the field will be reset to its original value.
* @param field_name The name of the field to update.
* @param value The new value of the field.
*/
template <typename ConfigT, typename EnumT>
bool updateFieldEnum(ConfigT& config,
const std::string& field_name,
const EnumT& value,
const std::map<EnumT, std::string>& enum_names = {},
bool print_warnings = true,
const std::string& name_space = "") {
YAML::Node node;
if constexpr (!std::is_enum<EnumT>::value) {
internal::Logger::logWarning("Use updateField for non-enum fields.");
return false;
}

std::string enum_name;
if (enum_names.empty()) {
enum_name = static_cast<int>(value);
}

if (enum_names.find(value) != enum_names.end()) {
enum_name = enum_names.at(value);
} else {
internal::Logger::logWarning("Enum value not found in enum_names.");
return false;
}

return updateField(config, field_name, enum_name, print_warnings, name_space);
}

/**
* @brief Update an enum field in a config.
* @note This function will update the field and check the validity of the config afterwards. If the config is
* invalid, the field will be reset to its original value.
* @param field_name The name of the field to update.
* @param value The new value of the field.
*/
template <typename ConfigT, typename EnumT>
bool updateFieldEnum(ConfigT& config,
const std::string& field_name,
const EnumT& value,
std::vector<std::string> enum_names,
bool print_warnings = true,
const std::string& name_space = "") {
YAML::Node node;
if constexpr (!std::is_enum<EnumT>::value) {
internal::Logger::logWarning("Use updateField for non-enum fields.");
return false;
}

const auto enum_map = createEnumMap<EnumT>(enum_names);
return updateFieldEnum<ConfigT, EnumT>(config, field_name, value, enum_map, print_warnings, name_space);
}

} // namespace config
1 change: 1 addition & 0 deletions config_utilities/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ add_executable(
tests/string_utils.cpp
tests/subconfigs.cpp
tests/traits.cpp
tests/update.cpp
tests/validity_checks.cpp
tests/virtual_config.cpp
tests/yaml_parsing.cpp)
Expand Down
Loading

0 comments on commit 2201be0

Please sign in to comment.