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,6 +748,14 @@ Spaces before and after the name and argument are ignored. Multiple arguments ar | ||
| 748 | To print a configuration file from the passed | 748 | To print a configuration file from the passed |
| 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. | 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 | ### Inheriting defaults | 759 | ### Inheriting defaults |
| 752 | 760 | ||
| 753 | 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. | 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,6 +78,16 @@ sub.subcommand = true | ||
| 78 | 78 | ||
| 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. | 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 | ## Writing out a configure file | 91 | ## Writing out a configure file |
| 82 | 92 | ||
| 83 | 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 | 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,29 +2053,31 @@ class App { | ||
| 2053 | void _process_config_file() { | 2053 | void _process_config_file() { |
| 2054 | if(config_ptr_ != nullptr) { | 2054 | if(config_ptr_ != nullptr) { |
| 2055 | bool config_required = config_ptr_->get_required(); | 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 | if(config_required) { | 2059 | if(config_required) { |
| 2060 | throw FileError::Missing("no specified config file"); | 2060 | throw FileError::Missing("no specified config file"); |
| 2061 | } | 2061 | } |
| 2062 | return; | 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,6 +591,89 @@ TEST_F(TApp, IniNotRequiredNotDefault) { | ||
| 591 | EXPECT_EQ(app.get_config_ptr()->as<std::string>(), tmpini2.c_str()); | 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 | TEST_F(TApp, IniRequiredNotFound) { | 677 | TEST_F(TApp, IniRequiredNotFound) { |
| 595 | 678 | ||
| 596 | std::string noini = "TestIniNotExist.ini"; | 679 | std::string noini = "TestIniNotExist.ini"; |
tests/TransformTest.cpp
| @@ -7,6 +7,7 @@ | @@ -7,6 +7,7 @@ | ||
| 7 | #include "app_helper.hpp" | 7 | #include "app_helper.hpp" |
| 8 | 8 | ||
| 9 | #include <array> | 9 | #include <array> |
| 10 | +#include <chrono> | ||
| 10 | #include <cstdint> | 11 | #include <cstdint> |
| 11 | #include <unordered_map> | 12 | #include <unordered_map> |
| 12 | 13 | ||
| @@ -850,6 +851,22 @@ TEST_F(TApp, AsSizeValue1000_1024) { | @@ -850,6 +851,22 @@ TEST_F(TApp, AsSizeValue1000_1024) { | ||
| 850 | EXPECT_EQ(value, ki_value); | 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 | TEST_F(TApp, AsSizeValue1024) { | 870 | TEST_F(TApp, AsSizeValue1024) { |
| 854 | std::uint64_t value{0}; | 871 | std::uint64_t value{0}; |
| 855 | app.add_option("-s", value)->transform(CLI::AsSizeValue(false)); | 872 | app.add_option("-s", value)->transform(CLI::AsSizeValue(false)); |