Skip to content
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

Config example: json #138

Merged
merged 3 commits into from
Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .ci/make_and_test.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "extern/sanitizers"]
path = extern/sanitizers
url = ../../arsenm/sanitizers-cmake
[submodule "extern/json"]
path = extern/json
url = ../../nlohmann/json.git
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
113 changes: 113 additions & 0 deletions examples/json.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include <CLI/CLI.hpp>
#include <nlohmann/json.hpp>

// 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<CLI::ConfigItem> from_config(std::istream &input) const override {
json j;
input >> j;
return _from_config(j);
}

std::vector<CLI::ConfigItem>
_from_config(json j, std::string name = "", std::vector<std::string> prefix = {}) const {
std::vector<CLI::ConfigItem> 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<bool>() ? "true" : "false"};
} else if(j.is_number()) {
std::stringstream ss;
ss << j.get<double>();
res.inputs = {ss.str()};
} else if(j.is_string()) {
res.inputs = {j.get<std::string>()};
} 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<ConfigJSON>());

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;
}
1 change: 1 addition & 0 deletions extern/json
Submodule json added at d2dd27
40 changes: 19 additions & 21 deletions include/CLI/ConfigFwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,7 @@ class Config {
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;

/// Convert a flag to a bool
virtual std::vector<std::string> to_flag(const ConfigItem &) const = 0;

/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
std::vector<ConfigItem> 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<std::string> to_flag(const ConfigItem &item) const override {
virtual std::vector<std::string> to_flag(const ConfigItem &item) const {
if(item.inputs.size() == 1) {
std::string val = item.inputs.at(0);
val = detail::to_lower(val);
Expand All @@ -112,6 +92,24 @@ class ConfigINI : public Config {
}
}

/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
std::vector<ConfigItem> 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<ConfigItem> from_config(std::istream &input) const override {
std::string line;
std::string section = "default";
Expand Down