-
Notifications
You must be signed in to change notification settings - Fork 8
Feature/control append #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
93a434f
bd26de4
3ce8d91
41aad1c
81b4ab4
148aa1f
d5d5d56
d7e27d4
454bb79
6593d7e
1ddd703
ccf1302
1ed84ac
612782a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#include <iostream> | ||
|
||
#include <config_utilities/parsing/commandline.h> | ||
|
||
const std::string help_msg = | ||
R"""(Usage: composite-config [--config-utilities-yaml YAML_TOKEN ...]... [--config-utilities-file FILEPATH[@NAMESPACE]]... | ||
|
||
Merges the input YAML values from left to right and outputs the resulting composite YAML to stdout. | ||
Invalid YAML or missing files get dropped during compositing. | ||
|
||
Options: | ||
-h/--help: Show this message. | ||
--config-utilities-yaml: Takes an arbitrary set of tokens that form a valid YAML string. | ||
Spaces are not required to be escaped, so `--config-utilities foo: value` | ||
is parsed the same as `--config-utilities 'foo: value'`. Can be specified | ||
multiple times. | ||
--config-utilities-file: Takes a filepath to YAML to load and composite. The YAML can optionally | ||
be namespaced by `@NAMESPACE` where 'FILE@a/b' maps to | ||
'{a: {b: FILE_CONTENTS}}'. Can be specified multiple times. | ||
|
||
Example: | ||
> echo "{a: 42, bar: hello}" > /tmp/test_in.yaml | ||
> composite-configs --config-utilities-yaml "{foo: {bar: value, b: -1.0}}" --config-utilities-file /tmp/test_in.yaml@foo | ||
{foo: {bar: hello, b: -1.0, a: 42}} | ||
|
||
See https://github.com/MIT-SPARK/config_utilities/blob/main/docs/Parsing.md#parse-from-the-command-line | ||
for more information. | ||
)"""; | ||
|
||
int main(int argc, char* argv[]) { | ||
config::internal::ParserInfo info; | ||
auto result = config::internal::loadFromArguments(argc, argv, false, &info); | ||
if (info.help_present) { | ||
std::cerr << help_msg << std::endl; | ||
return 1; | ||
} | ||
|
||
switch (result.Type()) { | ||
case YAML::NodeType::Null: | ||
case YAML::NodeType::Undefined: | ||
default: | ||
break; | ||
case YAML::NodeType::Scalar: | ||
case YAML::NodeType::Sequence: | ||
case YAML::NodeType::Map: | ||
std::cout << result << std::endl; | ||
break; | ||
} | ||
|
||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,12 +42,30 @@ | |
|
||
namespace config::internal { | ||
|
||
enum class MergeMode { | ||
//! @brief Combine the two trees, recursing into matching sequence entries | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be good to also specify how conflicts are resolved here, I assume There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I can document the behavior better, I just forget if I changed anything with how keys are handled for maps or not (so I might stick it somewhere else) |
||
UPDATE, | ||
//! @brief Combine the two trees, appending right sequences into the left | ||
APPEND, | ||
//! @brief Combine the two trees, replacing left sequences with the right | ||
REPLACE | ||
}; | ||
|
||
/** | ||
* @brief Merges node b into a, overwriting values previously defined in a if they can not be | ||
* merged. Modifies node a, whereas b is const. Sequences can optionally be appended together at the same level of the | ||
* YAML tree. | ||
* @brief Merges node b into a with conflicting keys handled by choice of mode | ||
* | ||
* Recurses through the YAML "tree" of b, adding all non-conflicting nodes to a. Conflicting nodes (i.e. map keys or | ||
* shared indices in sequences that already exist in a) are handled according to the mode selection. For `REPLACE`, any | ||
* conflicting node stops the recursion, and the conflicting node is replaced by the value in b. For 'APPEND', any | ||
* conflicting sequence node will stop the recursion and cause the entire contents of the node in b to be append to the | ||
* node in a. For 'UPDATE', any conflicting map or sequence node recursively calls `mergeYamlNodes` with the children of | ||
* the conflicting nodes as the new roots. | ||
* | ||
* @param a Node to merge into ("left" node and will be changed). | ||
* @param b Node to merge from ("right" node and remains constant). | ||
* @param mode Mode to use when merging | ||
*/ | ||
void mergeYamlNodes(YAML::Node& a, const YAML::Node& b, bool extend_sequences = false); | ||
void mergeYamlNodes(YAML::Node& a, const YAML::Node& b, MergeMode mode = MergeMode::UPDATE); | ||
|
||
/** | ||
* @brief Get a pointer to the final node of the specified namespace if it exists, where each map in the yaml is | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/** ----------------------------------------------------------------------------- | ||
* 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 <yaml-cpp/yaml.h> | ||
|
||
namespace config { | ||
|
||
struct TagProcessor { | ||
virtual ~TagProcessor() = default; | ||
|
||
/** | ||
* @brief Attempt to replace node with corresponding tag | ||
* @param[in] node Node to perform substitution on | ||
*/ | ||
virtual void processNode(YAML::Node node) const = 0; | ||
}; | ||
|
||
class RegisteredTags { | ||
public: | ||
template <typename T> | ||
struct Registration { | ||
explicit Registration(const std::string& tag); | ||
}; | ||
|
||
~RegisteredTags() = default; | ||
|
||
static const TagProcessor* getEntry(const std::string& tag); | ||
|
||
private: | ||
template <typename T> | ||
friend struct Registration; | ||
|
||
static RegisteredTags& instance(); | ||
|
||
static void addEntry(const std::string& tag, std::unique_ptr<TagProcessor>&& proc); | ||
|
||
RegisteredTags(); | ||
static std::unique_ptr<RegisteredTags> s_instance_; | ||
std::map<std::string, std::unique_ptr<TagProcessor>> entries_; | ||
}; | ||
|
||
template <typename T> | ||
RegisteredTags::Registration<T>::Registration(const std::string& tag) { | ||
RegisteredTags::addEntry(tag, std::make_unique<T>()); | ||
} | ||
|
||
/** | ||
* @brief Attempts to replace the node `!env VARNAME` with the value of VARNAME from the environment | ||
*/ | ||
struct EnvTag : public TagProcessor { | ||
void processNode(YAML::Node node) const override; | ||
}; | ||
|
||
/** | ||
* @brief Iterate through the node, resolving tags | ||
* @param[in] node Node to resolve tags for | ||
*/ | ||
void resolveTags(YAML::Node node); | ||
|
||
} // namespace config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha I remember having the discussion whether we want a tool like that and we opted against it at the time, but seems reasonable to have in config_utilities 🙂