From e45b0df6dfaee8d87ee6ec2f13d99711f52ee273 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 25 Jun 2018 12:02:49 +0200 Subject: [PATCH 1/3] Moving to_flag to base class --- include/CLI/ConfigFwd.hpp | 40 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp index 691a0f164..cdce642c5 100644 --- a/include/CLI/ConfigFwd.hpp +++ b/include/CLI/ConfigFwd.hpp @@ -70,27 +70,7 @@ class Config { virtual std::vector from_config(std::istream &) const = 0; /// Convert a flag to a bool - virtual std::vector to_flag(const ConfigItem &) const = 0; - - /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure - std::vector from_file(const std::string &name) { - std::ifstream input{name}; - if(!input.good()) - throw FileError::Missing(name); - - return from_config(input); - } - - /// virtual destructor - virtual ~Config() = default; -}; - -/// This converter works with INI files -class ConfigINI : public Config { - public: - std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; - - std::vector to_flag(const ConfigItem &item) const override { + virtual std::vector to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { std::string val = item.inputs.at(0); val = detail::to_lower(val); @@ -112,6 +92,24 @@ class ConfigINI : public Config { } } + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + std::vector from_config(std::istream &input) const override { std::string line; std::string section = "default"; From 75657722f3fe6a13156d06234844e968a1c688bb Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 25 Jun 2018 12:03:47 +0200 Subject: [PATCH 2/3] Adding example with json config --- .ci/make_and_test.sh | 5 +- .gitmodules | 3 ++ .travis.yml | 6 +-- examples/CMakeLists.txt | 24 +++++++++ examples/json.cpp | 113 ++++++++++++++++++++++++++++++++++++++++ extern/json | 1 + 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 examples/json.cpp create mode 160000 extern/json diff --git a/.ci/make_and_test.sh b/.ci/make_and_test.sh index 2ba81aeef..e05ae1409 100755 --- a/.ci/make_and_test.sh +++ b/.ci/make_and_test.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash echo -en "travis_fold:start:script.build\\r" echo "Building..." +STD=$1 +shift set -evx + mkdir -p build cd build -cmake .. -DCLI11_CXX_STD=$1 -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache +cmake .. -DCLI11_CXX_STD=$STD -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache $@ cmake --build . -- -j2 set +evx diff --git a/.gitmodules b/.gitmodules index 2b5117d2c..e9ec356f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "extern/sanitizers"] path = extern/sanitizers url = ../../arsenm/sanitizers-cmake +[submodule "extern/json"] + path = extern/json + url = ../../nlohmann/json.git diff --git a/.travis.yml b/.travis.yml index 1f31e4705..d55169f6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,9 +76,9 @@ matrix: - ". .ci/build_lcov.sh" - ".ci/run_codecov.sh" script: - - .ci/make_and_test.sh 11 - - .ci/make_and_test.sh 14 - - .ci/make_and_test.sh 17 + - .ci/make_and_test.sh 11 -DCLI11_EXAMPLE_JSON=ON + - .ci/make_and_test.sh 14 -DCLI11_EXAMPLE_JSON=ON + - .ci/make_and_test.sh 17 -DCLI11_EXAMPLE_JSON=ON # GCC 4.7 and Conan - compiler: gcc diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c4618ca47..2a286a535 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,6 +14,30 @@ function(add_cli_exe T) endif() endfunction() +option(CLI11_EXAMPLE_JSON OFF) +if(CLI11_EXAMPLE_JSON) + if(CMAKE_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) + message(WARNING "The json example requires GCC 4.9+ (requirement on json library)") + endif() + add_cli_exe(json json.cpp) + target_include_directories(json PUBLIC SYSTEM ../extern/json/single_include) + + add_test(NAME json_config_out COMMAND json --item 2) + set_property(TEST json_config_out PROPERTY PASS_REGULAR_EXPRESSION + "{" + "\"item\": \"2\"" + "\"simple\": false" + "}") + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/input.json" [=[{"item":3,"simple":false}]=]) + add_test(NAME json_config_in COMMAND json --config "${CMAKE_CURRENT_BINARY_DIR}/input.json") + set_property(TEST json_config_in PROPERTY PASS_REGULAR_EXPRESSION + "{" + "\"item\": \"3\"" + "\"simple\": false" + "}") +endif() + add_cli_exe(simple simple.cpp) add_test(NAME simple_basic COMMAND simple) add_test(NAME simple_all COMMAND simple -f filename.txt -c 12 --flag --flag -d 1.2) diff --git a/examples/json.cpp b/examples/json.cpp new file mode 100644 index 000000000..bce5ecdab --- /dev/null +++ b/examples/json.cpp @@ -0,0 +1,113 @@ +#include +#include + +// This example is only built on GCC 7 on Travis due to mismatch in stdlib +// for clang (CLI11 is forgiving about mismatches, json.hpp is not) + +using nlohmann::json; + +class ConfigJSON : public CLI::Config { + public: + std::string to_config(const CLI::App *app, bool default_also, bool, std::string) const override { + + json j; + + for(const CLI::Option *opt : app->get_options({})) { + + // Only process option with a long-name and configurable + if(!opt->get_lnames().empty() && opt->get_configurable()) { + std::string name = opt->get_lnames()[0]; + + // Non-flags + if(opt->get_type_size() != 0) { + + // If the option was found on command line + if(opt->count() == 1) + j[name] = opt->results().at(0); + else if(opt->count() > 1) + j[name] = opt->results(); + + // If the option has a default and is requested by optional argument + else if(default_also && !opt->get_defaultval().empty()) + j[name] = opt->get_defaultval(); + + // Flag, one passed + } else if(opt->count() == 1) { + j[name] = true; + + // Flag, multiple passed + } else if(opt->count() > 1) { + j[name] = opt->count(); + + // Flag, not present + } else if(opt->count() == 0 && default_also) { + j[name] = false; + } + } + } + + for(const CLI::App *subcom : app->get_subcommands({})) + j[subcom->get_name()] = json(to_config(subcom, default_also, false, "")); + + return j.dump(4); + } + + std::vector from_config(std::istream &input) const override { + json j; + input >> j; + return _from_config(j); + } + + std::vector + _from_config(json j, std::string name = "", std::vector prefix = {}) const { + std::vector results; + + if(j.is_object()) { + for(json::iterator item = j.begin(); item != j.end(); ++item) { + auto copy_prefix = prefix; + if(!name.empty()) + copy_prefix.push_back(name); + auto sub_results = _from_config(*item, item.key(), copy_prefix); + results.insert(results.end(), sub_results.begin(), sub_results.end()); + } + } else if(!name.empty()) { + results.emplace_back(); + CLI::ConfigItem &res = results.back(); + res.name = name; + res.parents = prefix; + if(j.is_boolean()) { + res.inputs = {j.get() ? "true" : "false"}; + } else if(j.is_number()) { + std::stringstream ss; + ss << j.get(); + res.inputs = {ss.str()}; + } else if(j.is_string()) { + res.inputs = {j.get()}; + } else if(j.is_array()) { + for(std::string ival : j) + res.inputs.push_back(ival); + } else { + throw CLI::ConversionError("Failed to convert " + name); + } + } else { + throw CLI::ConversionError("You must make all top level values objects in json!"); + } + + return results; + } +}; + +int main(int argc, char **argv) { + CLI::App app; + app.config_formatter(std::make_shared()); + + int item; + + app.add_flag("--simple"); + app.add_option("--item", item); + app.set_config("--config"); + + CLI11_PARSE(app, argc, argv); + + std::cout << app.config_to_str(true, true) << std::endl; +} diff --git a/extern/json b/extern/json new file mode 160000 index 000000000..d2dd27dc3 --- /dev/null +++ b/extern/json @@ -0,0 +1 @@ +Subproject commit d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe From 95d6cbc83af32b4004032bab99fe01c9ea84bcda Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 25 Jun 2018 17:39:12 +0200 Subject: [PATCH 3/3] Adding recent changes to changelog --- CHANGELOG.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bba8c04c..1bb6b139b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ New for Config file reading and writing [#121]: * Has `config_formatter()` and `get_config_formatter()` * Dropped prefix argument from `config_to_str` * Added `ConfigItem` +* Added an example of a custom config format using [json](https://github.com/nlohmann/json) [#138] Validators are now much more powerful [#118], all built in validators upgraded to the new form: @@ -36,9 +37,9 @@ Validators are now much more powerful [#118], all built in validators upgraded t Other changes: -* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val` -* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. -* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name` +* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. [#136] +* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val` [#136] +* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name` [#136] * Added `->each()` to make adding custom callbacks easier [#126] * Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering * Added `get_groups()` to get groups @@ -53,6 +54,7 @@ Other changes: * Better CMake policy handling [#110] * Includes are properly sorted [#120] * Help flags now use new `short_circuit` property to simplify parsing [#121] +* Const added to argv [#126] [#109]: https://github.com/CLIUtils/CLI11/pull/109 [#110]: https://github.com/CLIUtils/CLI11/pull/110 @@ -65,6 +67,8 @@ Other changes: [#120]: https://github.com/CLIUtils/CLI11/pull/120 [#121]: https://github.com/CLIUtils/CLI11/pull/121 [#126]: https://github.com/CLIUtils/CLI11/pull/126 +[#127]: https://github.com/CLIUtils/CLI11/pull/127 +[#138]: https://github.com/CLIUtils/CLI11/pull/138 ### Version 1.5.4: Optionals This version fixes the optional search in the single file version; some macros were not yet defined when it did the search. You can define the `CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search.