Commit 6aa58d58285a6c655f21b57c2f8be13dc1f0983c
Committed by
GitHub
1 parent
8ce1594e
Add an ability to deal handle multiple config files (#494)
Showing
5 changed files
with
136 additions
and
16 deletions
README.md
| ... | ... | @@ -748,6 +748,14 @@ Spaces before and after the name and argument are ignored. Multiple arguments ar |
| 748 | 748 | To print a configuration file from the passed |
| 749 | 749 | arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details. |
| 750 | 750 | |
| 751 | +If it is desired that multiple configuration be allowed. Use | |
| 752 | + | |
| 753 | +```cpp | |
| 754 | +app.set_config("--config")->expected(1, X); | |
| 755 | +``` | |
| 756 | + | |
| 757 | +Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments. | |
| 758 | + | |
| 751 | 759 | ### Inheriting defaults |
| 752 | 760 | |
| 753 | 761 | Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`,`immediate_callback` and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. | ... | ... |
book/chapters/config.md
| ... | ... | @@ -78,6 +78,16 @@ sub.subcommand = true |
| 78 | 78 | |
| 79 | 79 | The main differences are in vector notation and comment character. Note: CLI11 is not a full TOML parser as it just reads values as strings. It is possible (but not recommended) to mix notation. |
| 80 | 80 | |
| 81 | +## Multiple configuration files | |
| 82 | + | |
| 83 | +If it is desired that multiple configuration be allowed. Use | |
| 84 | + | |
| 85 | +```cpp | |
| 86 | +app.set_config("--config")->expected(1, X); | |
| 87 | +``` | |
| 88 | + | |
| 89 | +Where X is some positive integer and will allow up to `X` configuration files to be specified by separate `--config` arguments. | |
| 90 | + | |
| 81 | 91 | ## Writing out a configure file |
| 82 | 92 | |
| 83 | 93 | To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include option descriptions and the App description | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -2053,29 +2053,31 @@ class App { |
| 2053 | 2053 | void _process_config_file() { |
| 2054 | 2054 | if(config_ptr_ != nullptr) { |
| 2055 | 2055 | bool config_required = config_ptr_->get_required(); |
| 2056 | - bool file_given = config_ptr_->count() > 0; | |
| 2057 | - auto config_file = config_ptr_->as<std::string>(); | |
| 2058 | - if(config_file.empty()) { | |
| 2056 | + auto file_given = config_ptr_->count() > 0; | |
| 2057 | + auto config_files = config_ptr_->as<std::vector<std::string>>(); | |
| 2058 | + if(config_files.empty() || config_files.front().empty()) { | |
| 2059 | 2059 | if(config_required) { |
| 2060 | 2060 | throw FileError::Missing("no specified config file"); |
| 2061 | 2061 | } |
| 2062 | 2062 | return; |
| 2063 | 2063 | } |
| 2064 | - | |
| 2065 | - auto path_result = detail::check_path(config_file.c_str()); | |
| 2066 | - if(path_result == detail::path_type::file) { | |
| 2067 | - try { | |
| 2068 | - std::vector<ConfigItem> values = config_formatter_->from_file(config_file); | |
| 2069 | - _parse_config(values); | |
| 2070 | - if(!file_given) { | |
| 2071 | - config_ptr_->add_result(config_file); | |
| 2064 | + for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) { | |
| 2065 | + const auto &config_file = *rit; | |
| 2066 | + auto path_result = detail::check_path(config_file.c_str()); | |
| 2067 | + if(path_result == detail::path_type::file) { | |
| 2068 | + try { | |
| 2069 | + std::vector<ConfigItem> values = config_formatter_->from_file(config_file); | |
| 2070 | + _parse_config(values); | |
| 2071 | + if(!file_given) { | |
| 2072 | + config_ptr_->add_result(config_file); | |
| 2073 | + } | |
| 2074 | + } catch(const FileError &) { | |
| 2075 | + if(config_required || file_given) | |
| 2076 | + throw; | |
| 2072 | 2077 | } |
| 2073 | - } catch(const FileError &) { | |
| 2074 | - if(config_required || file_given) | |
| 2075 | - throw; | |
| 2078 | + } else if(config_required || file_given) { | |
| 2079 | + throw FileError::Missing(config_file); | |
| 2076 | 2080 | } |
| 2077 | - } else if(config_required || file_given) { | |
| 2078 | - throw FileError::Missing(config_file); | |
| 2079 | 2081 | } |
| 2080 | 2082 | } |
| 2081 | 2083 | } | ... | ... |
tests/ConfigFileTest.cpp
| ... | ... | @@ -591,6 +591,89 @@ TEST_F(TApp, IniNotRequiredNotDefault) { |
| 591 | 591 | EXPECT_EQ(app.get_config_ptr()->as<std::string>(), tmpini2.c_str()); |
| 592 | 592 | } |
| 593 | 593 | |
| 594 | +TEST_F(TApp, MultiConfig) { | |
| 595 | + | |
| 596 | + TempFile tmpini{"TestIniTmp.ini"}; | |
| 597 | + TempFile tmpini2{"TestIniTmp2.ini"}; | |
| 598 | + | |
| 599 | + app.set_config("--config")->expected(1, 3); | |
| 600 | + | |
| 601 | + { | |
| 602 | + std::ofstream out{tmpini}; | |
| 603 | + out << "[default]" << std::endl; | |
| 604 | + out << "two=99" << std::endl; | |
| 605 | + out << "three=3" << std::endl; | |
| 606 | + } | |
| 607 | + | |
| 608 | + { | |
| 609 | + std::ofstream out{tmpini2}; | |
| 610 | + out << "[default]" << std::endl; | |
| 611 | + out << "one=55" << std::endl; | |
| 612 | + out << "three=4" << std::endl; | |
| 613 | + } | |
| 614 | + | |
| 615 | + int one{0}, two{0}, three{0}; | |
| 616 | + app.add_option("--one", one); | |
| 617 | + app.add_option("--two", two); | |
| 618 | + app.add_option("--three", three); | |
| 619 | + | |
| 620 | + args = {"--config", tmpini2, "--config", tmpini}; | |
| 621 | + run(); | |
| 622 | + | |
| 623 | + EXPECT_EQ(99, two); | |
| 624 | + EXPECT_EQ(3, three); | |
| 625 | + EXPECT_EQ(55, one); | |
| 626 | + | |
| 627 | + args = {"--config", tmpini, "--config", tmpini2}; | |
| 628 | + run(); | |
| 629 | + | |
| 630 | + EXPECT_EQ(99, two); | |
| 631 | + EXPECT_EQ(4, three); | |
| 632 | + EXPECT_EQ(55, one); | |
| 633 | +} | |
| 634 | + | |
| 635 | +TEST_F(TApp, MultiConfig_single) { | |
| 636 | + | |
| 637 | + TempFile tmpini{"TestIniTmp.ini"}; | |
| 638 | + TempFile tmpini2{"TestIniTmp2.ini"}; | |
| 639 | + | |
| 640 | + app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); | |
| 641 | + | |
| 642 | + { | |
| 643 | + std::ofstream out{tmpini}; | |
| 644 | + out << "[default]" << std::endl; | |
| 645 | + out << "two=99" << std::endl; | |
| 646 | + out << "three=3" << std::endl; | |
| 647 | + } | |
| 648 | + | |
| 649 | + { | |
| 650 | + std::ofstream out{tmpini2}; | |
| 651 | + out << "[default]" << std::endl; | |
| 652 | + out << "one=55" << std::endl; | |
| 653 | + out << "three=4" << std::endl; | |
| 654 | + } | |
| 655 | + | |
| 656 | + int one{0}, two{0}, three{0}; | |
| 657 | + app.add_option("--one", one); | |
| 658 | + app.add_option("--two", two); | |
| 659 | + app.add_option("--three", three); | |
| 660 | + | |
| 661 | + args = {"--config", tmpini2, "--config", tmpini}; | |
| 662 | + run(); | |
| 663 | + | |
| 664 | + EXPECT_EQ(99, two); | |
| 665 | + EXPECT_EQ(3, three); | |
| 666 | + EXPECT_EQ(0, one); | |
| 667 | + | |
| 668 | + two = 0; | |
| 669 | + args = {"--config", tmpini, "--config", tmpini2}; | |
| 670 | + run(); | |
| 671 | + | |
| 672 | + EXPECT_EQ(0, two); | |
| 673 | + EXPECT_EQ(4, three); | |
| 674 | + EXPECT_EQ(55, one); | |
| 675 | +} | |
| 676 | + | |
| 594 | 677 | TEST_F(TApp, IniRequiredNotFound) { |
| 595 | 678 | |
| 596 | 679 | std::string noini = "TestIniNotExist.ini"; | ... | ... |
tests/TransformTest.cpp
| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | #include "app_helper.hpp" |
| 8 | 8 | |
| 9 | 9 | #include <array> |
| 10 | +#include <chrono> | |
| 10 | 11 | #include <cstdint> |
| 11 | 12 | #include <unordered_map> |
| 12 | 13 | |
| ... | ... | @@ -850,6 +851,22 @@ TEST_F(TApp, AsSizeValue1000_1024) { |
| 850 | 851 | EXPECT_EQ(value, ki_value); |
| 851 | 852 | } |
| 852 | 853 | |
| 854 | +TEST_F(TApp, duration_test) { | |
| 855 | + std::chrono::seconds duration{1}; | |
| 856 | + | |
| 857 | + app.option_defaults()->ignore_case(); | |
| 858 | + app.add_option_function<std::size_t>( | |
| 859 | + "--duration", | |
| 860 | + [&](size_t a_value) { duration = std::chrono::seconds{a_value}; }, | |
| 861 | + "valid units: sec, min, h, day.") | |
| 862 | + ->capture_default_str() | |
| 863 | + ->transform(CLI::AsNumberWithUnit( | |
| 864 | + std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}})); | |
| 865 | + EXPECT_NO_THROW(app.parse(std::vector<std::string>{"1 day", "--duration"})); | |
| 866 | + | |
| 867 | + EXPECT_EQ(duration, std::chrono::seconds(86400)); | |
| 868 | +} | |
| 869 | + | |
| 853 | 870 | TEST_F(TApp, AsSizeValue1024) { |
| 854 | 871 | std::uint64_t value{0}; |
| 855 | 872 | app.add_option("-s", value)->transform(CLI::AsSizeValue(false)); | ... | ... |