diff --git a/.ci/azure-build.yml b/.ci/azure-build.yml index fbe674c7d..ab0bf069b 100644 --- a/.ci/azure-build.yml +++ b/.ci/azure-build.yml @@ -1,4 +1,8 @@ steps: + # Needed on GCC 4.8 docker image for some reason + - script: mkdir build + displalyName: "Make build directory" + - task: CMake@1 inputs: cmakeArgs: diff --git a/.clang-tidy b/.clang-tidy index 47a367de4..82450d1b5 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -6,6 +6,7 @@ # modernize-avoid-c-arrays trips up in TEMPLATE_TEST_CASE catch macro # modernize-return-braced-init-list triggers on lambdas ? # modernize-make-unique requires C++14 +# readability-avoid-const-params-in-decls Affected by the pre-compile split Checks: | *bugprone*, @@ -39,7 +40,6 @@ Checks: | *performance*, -performance-unnecessary-value-param, -performance-inefficient-string-concatenation, - readability-avoid-const-params-in-decls, readability-const-return-type, readability-container-size-empty, readability-delete-null-pointer, diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1fe376224..a0b9e6c81 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,7 @@ jobs: strategy: matrix: std: ["11", "14", "17", "20"] + precompile: ["ON", "OFF"] steps: - uses: actions/checkout@v3 @@ -33,6 +34,7 @@ jobs: -DCMAKE_CXX_STANDARD=${{matrix.std}} \ -DCLI11_SINGLE_FILE_TESTS=OFF \ -DCLI11_EXAMPLES=OFF \ + -DCLI11_PRECOMPILED=${{matrix.precompile}} \ -DCMAKE_BUILD_TYPE=Coverage - name: Build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6ee0c28c..0d6d0c011 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,23 +45,23 @@ repos: - id: markdownlint args: ["--style=scripts/mdlint_style.rb"] - - repo: local - hooks: - - id: remarklint - name: remarklint - language: node - entry: remark - types: [markdown] - args: ["--frail", "--quiet"] - additional_dependencies: - [ - remark, - remark-lint, - remark-cli, - remark-preset-lint-recommended, - remark-lint-list-item-indent, - remark-lint-no-undefined-references, - ] + # - repo: local + # hooks: + # - id: remarklint + # name: remarklint + # language: node + # entry: remark + # types: [markdown] + # args: ["--frail", "--quiet"] + # additional_dependencies: + # [ + # remark, + # remark-lint, + # remark-cli, + # remark-preset-lint-recommended, + # remark-lint-list-item-indent, + # remark-lint-no-undefined-references, + # ] - repo: local hooks: diff --git a/CLI11.hpp.in b/CLI11.hpp.in index 64e1ae299..83f228ebf 100644 --- a/CLI11.hpp.in +++ b/CLI11.hpp.in @@ -46,24 +46,38 @@ namespace {namespace} {{ {string_tools_hpp} +{string_tools_inl_hpp} + {error_hpp} {type_tools_hpp} {split_hpp} +{split_inl_hpp} + {config_fwd_hpp} {validators_hpp} +{validators_inl_hpp} + {formatter_fwd_hpp} {option_hpp} +{option_inl_hpp} + {app_hpp} +{app_inl_hpp} + {config_hpp} +{config_inl_hpp} + {formatter_hpp} +{formatter_inl_hpp} + }} // namespace {namespace} diff --git a/CMakeLists.txt b/CMakeLists.txt index c67031ce4..7786d9cdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ endif() option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)") option(CLI11_SINGLE_FILE "Generate a single header file") +option(CLI11_PRECOMPILED "Generate a precompiled static library instead of a header-only" OFF) cmake_dependent_option(CLI11_SANITIZERS "Download the sanitizers CMake config" OFF "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) @@ -105,6 +106,11 @@ cmake_dependent_option( CLI11_CUDA_TESTS "Build the tests with NVCC to check for warnings there - requires CMake 3.9+" OFF "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) +if(CLI11_PRECOMPILED AND CLI11_SINGLE_FILE) + # Sanity check + message(FATAL_ERROR "CLI11_PRECOMPILE and CLI11_SINGLE_FILE are mutually exclusive") +endif() + if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT DEFINED CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 11) endif() @@ -160,13 +166,35 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.13) target_link_options(CLI11_warnings INTERFACE $<$:-stdlib=libc++>) endif() +# To see in IDE, headers must be listed for target +set(MAYBE_CONFIGURE_DEPENDS "") +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT CMAKE_VERSION VERSION_LESS 3.12) + list(INSERT MAYBE_CONFIGURE_DEPENDS 0 CONFIGURE_DEPENDS) +endif() + +file(GLOB CLI11_headers ${MAYBE_CONFIGURE_DEPENDS} "${PROJECT_SOURCE_DIR}/include/CLI/*.hpp") +file(GLOB CLI11_impl_headers ${MAYBE_CONFIGURE_DEPENDS} + "${PROJECT_SOURCE_DIR}/include/CLI/impl/*.hpp") + +if(CLI11_PRECOMPILED) + # Create static lib + file(GLOB CLI11_precompile_sources "${PROJECT_SOURCE_DIR}/src/*.cpp") + add_library(CLI11 STATIC ${CLI11_headers} ${CLI11_impl_headers} ${CLI11_precompile_sources}) + target_compile_definitions(CLI11 PUBLIC -DCLI11_COMPILE) + + set(PUBLIC_OR_INTERFACE PUBLIC) +else() + add_library(CLI11 INTERFACE) + set(PUBLIC_OR_INTERFACE INTERFACE) +endif() + # Allow IDE's to group targets into folders -add_library(CLI11 INTERFACE) add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls # Duplicated because CMake adds the current source dir if you don't. -target_include_directories(CLI11 INTERFACE $ - $) +target_include_directories( + CLI11 ${PUBLIC_OR_INTERFACE} $ + $) if(CMAKE_VERSION VERSION_LESS 3.8) # This might not be a complete list @@ -184,14 +212,6 @@ else() target_compile_features(CLI11 INTERFACE cxx_std_11) endif() -# To see in IDE, headers must be listed for target -set(header-patterns "${PROJECT_SOURCE_DIR}/include/CLI/*") -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT CMAKE_VERSION VERSION_LESS 3.12) - list(INSERT header-patterns 0 CONFIGURE_DEPENDS) -endif() - -file(GLOB CLI11_headers ${header-patterns}) - # Allow tests to be run on CUDA if(CLI11_CUDA_TESTS) enable_language(CUDA) @@ -202,7 +222,10 @@ endif() # This folder should be installed if(CLI11_INSTALL) - install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + install(FILES ${CLI11_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI") + if(NOT CLI11_COMPILE) + install(FILES ${CLI11_impl_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI/impl") + endif() # Make an export target install(TARGETS CLI11 EXPORT CLI11Targets) @@ -257,9 +280,10 @@ if(CLI11_SINGLE_FILE) OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" COMMAND Python::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" - ${CLI11_headers} --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" --output - "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" --version "${CLI11_VERSION}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers}) + ${CLI11_headers} ${CLI11_impl_headers} --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" + --output "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" --version "${CLI11_VERSION}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers} + ${CLI11_impl_headers}) add_custom_target(CLI11-generate-single-file ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") set_property(TARGET CLI11-generate-single-file PROPERTY FOLDER "Scripts") diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fd891ba55..7540a2ea8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,6 +16,7 @@ variables: cli11.std: 14 cli11.build_type: Debug cli11.options: -DCLI11_EXAMPLES_JSON=ON + cli11.precompile: OFF CMAKE_BUILD_PARALLEL_LEVEL: 4 jobs: @@ -33,15 +34,26 @@ jobs: matrix: Linux14: vmImage: "ubuntu-latest" + Linux14PC: + vmImage: "ubuntu-latest" + cli11.precompile: ON macOS17: vmImage: "macOS-latest" cli11.std: 17 macOS11: vmImage: "macOS-latest" cli11.std: 11 + macOS11PC: + vmImage: "macOS-latest" + cli11.std: 11 + cli11.precompile: ON Windows17: vmImage: "windows-2019" cli11.std: 17 + Windows17PC: + vmImage: "windows-2019" + cli11.std: 17 + cli11.precompile: ON Windows11: vmImage: "windows-2019" cli11.std: 11 diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 7c8b7667b..d29aa6a89 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -282,39 +282,7 @@ class App { ///@} /// Special private constructor for subcommand - App(std::string app_description, std::string app_name, App *parent) - : name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) { - // Inherit if not from a nullptr - if(parent_ != nullptr) { - if(parent_->help_ptr_ != nullptr) - set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description()); - if(parent_->help_all_ptr_ != nullptr) - set_help_all_flag(parent_->help_all_ptr_->get_name(false, true), - parent_->help_all_ptr_->get_description()); - - /// OptionDefaults - option_defaults_ = parent_->option_defaults_; - - // INHERITABLE - failure_message_ = parent_->failure_message_; - allow_extras_ = parent_->allow_extras_; - allow_config_extras_ = parent_->allow_config_extras_; - prefix_command_ = parent_->prefix_command_; - immediate_callback_ = parent_->immediate_callback_; - ignore_case_ = parent_->ignore_case_; - ignore_underscore_ = parent_->ignore_underscore_; - fallthrough_ = parent_->fallthrough_; - validate_positionals_ = parent_->validate_positionals_; - validate_optional_arguments_ = parent_->validate_optional_arguments_; - configurable_ = parent_->configurable_; - allow_windows_style_options_ = parent_->allow_windows_style_options_; - group_ = parent_->group_; - footer_ = parent_->footer_; - formatter_ = parent_->formatter_; - config_formatter_ = parent_->config_formatter_; - require_subcommand_max_ = parent_->require_subcommand_max_; - } - } + App(std::string app_description, std::string app_name, App *parent); public: /// @name Basic @@ -369,41 +337,10 @@ class App { } /// Set a name for the app (empty will use parser to set the name) - App *name(std::string app_name = "") { - - if(parent_ != nullptr) { - auto oname = name_; - name_ = app_name; - const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent()); - if(!res.empty()) { - name_ = oname; - throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names")); - } - } else { - name_ = app_name; - } - has_automatic_name_ = false; - return this; - } + App *name(std::string app_name = ""); /// Set an alias for the app - App *alias(std::string app_name) { - if(app_name.empty() || !detail::valid_alias_name_string(app_name)) { - throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters"); - } - if(parent_ != nullptr) { - aliases_.push_back(app_name); - const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent()); - if(!res.empty()) { - aliases_.pop_back(); - throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name)); - } - } else { - aliases_.push_back(app_name); - } - - return this; - } + App *alias(std::string app_name); /// Remove the error when extras are left over on the command line. App *allow_extras(bool allow = true) { @@ -452,17 +389,7 @@ class App { } /// Set the subcommand callback to be executed immediately on subcommand completion - App *immediate_callback(bool immediate = true) { - immediate_callback_ = immediate; - if(immediate_callback_) { - if(final_callback_ && !(parse_complete_callback_)) { - std::swap(final_callback_, parse_complete_callback_); - } - } else if(!(final_callback_) && parse_complete_callback_) { - std::swap(final_callback_, parse_complete_callback_); - } - return this; - } + App *immediate_callback(bool immediate = true); /// Set the subcommand to validate positional arguments before assigning App *validate_positionals(bool validate = true) { @@ -500,19 +427,7 @@ class App { } /// Ignore case. Subcommands inherit value. - App *ignore_case(bool value = true) { - if(value && !ignore_case_) { - ignore_case_ = true; - auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this; - const auto &match = _compare_subcommand_names(*this, *p); - if(!match.empty()) { - ignore_case_ = false; // we are throwing so need to be exception invariant - throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match); - } - } - ignore_case_ = value; - return this; - } + App *ignore_case(bool value = true); /// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit /// value. @@ -534,19 +449,7 @@ class App { } /// Ignore underscore. Subcommands inherit value. - App *ignore_underscore(bool value = true) { - if(value && !ignore_underscore_) { - ignore_underscore_ = true; - auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this; - const auto &match = _compare_subcommand_names(*this, *p); - if(!match.empty()) { - ignore_underscore_ = false; - throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match); - } - } - ignore_underscore_ = value; - return this; - } + App *ignore_underscore(bool value = true); /// Set the help formatter App *formatter(std::shared_ptr fmt) { @@ -594,42 +497,7 @@ class App { callback_t option_callback, std::string option_description = "", bool defaulted = false, - std::function func = {}) { - Option myopt{option_name, option_description, option_callback, this}; - - if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { - return *v == myopt; - }) == std::end(options_)) { - options_.emplace_back(); - Option_p &option = options_.back(); - option.reset(new Option(option_name, option_description, option_callback, this)); - - // Set the default string capture function - option->default_function(func); - - // For compatibility with CLI11 1.7 and before, capture the default string here - if(defaulted) - option->capture_default_str(); - - // Transfer defaults to the new option - option_defaults_.copy_to(option.get()); - - // Don't bother to capture if we already did - if(!defaulted && option->get_always_capture_default()) - option->capture_default_str(); - - return option.get(); - } - // we know something matches now find what it is so we can produce more error information - for(auto &opt : options_) { - const auto &matchname = opt->matching_name(myopt); - if(!matchname.empty()) { - throw(OptionAlreadyAdded("added option matched existing option name: " + matchname)); - } - } - // this line should not be reached the above loop should trigger the throw - throw(OptionAlreadyAdded("added option matched existing option name")); // LCOV_EXCL_LINE - } + std::function func = {}); /// Add option for assigning to a variable template configurable(false); - } - - return help_ptr_; - } + Option *set_help_flag(std::string flag_name = "", const std::string &help_description = ""); /// Set a help all flag, replaced the existing one if present - Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") { - // take flag_description by const reference otherwise add_flag tries to assign to flag_description - if(help_all_ptr_ != nullptr) { - remove_option(help_all_ptr_); - help_all_ptr_ = nullptr; - } - - // Empty name will simply remove the help all flag - if(!help_name.empty()) { - help_all_ptr_ = add_flag(help_name, help_description); - help_all_ptr_->configurable(false); - } - - return help_all_ptr_; - } + Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = ""); /// Set a version flag and version display string, replace the existing one if present Option *set_version_flag(std::string flag_name = "", const std::string &versionString = "", - const std::string &version_help = "Display program version information and exit") { - // take flag_description by const reference otherwise add_flag tries to assign to version_description - if(version_ptr_ != nullptr) { - remove_option(version_ptr_); - version_ptr_ = nullptr; - } + const std::string &version_help = "Display program version information and exit"); - // Empty name will simply remove the version flag - if(!flag_name.empty()) { - version_ptr_ = add_flag_callback( - flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help); - version_ptr_->configurable(false); - } - - return version_ptr_; - } /// Generate the version string through a callback function Option *set_version_flag(std::string flag_name, std::function vfunc, - const std::string &version_help = "Display program version information and exit") { - if(version_ptr_ != nullptr) { - remove_option(version_ptr_); - version_ptr_ = nullptr; - } - - // Empty name will simply remove the version flag - if(!flag_name.empty()) { - version_ptr_ = add_flag_callback( - flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help); - version_ptr_->configurable(false); - } - - return version_ptr_; - } + const std::string &version_help = "Display program version information and exit"); private: /// Internal function for adding a flag - Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) { - Option *opt = nullptr; - if(detail::has_default_flag_values(flag_name)) { - // check for default values and if it has them - auto flag_defaults = detail::get_default_flag_values(flag_name); - detail::remove_default_flag_values(flag_name); - opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); - for(const auto &fname : flag_defaults) - opt->fnames_.push_back(fname.first); - opt->default_flag_values_ = std::move(flag_defaults); - } else { - opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); - } - // flags cannot have positional values - if(opt->get_positional()) { - auto pos_name = opt->get_name(true); - remove_option(opt); - throw IncorrectConstruction::PositionalFlag(pos_name); - } - opt->multi_option_policy(MultiOptionPolicy::TakeLast); - opt->expected(0); - opt->required(false); - return opt; - } + Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description); public: /// Add a flag with no description or variable assignment @@ -863,33 +652,12 @@ class App { /// Add option for callback that is triggered with a true flag and takes no arguments Option *add_flag_callback(std::string flag_name, std::function function, ///< A function to call, void(void) - std::string flag_description = "") { - - CLI::callback_t fun = [function](const CLI::results_t &res) { - bool trigger{false}; - auto result = CLI::detail::lexical_cast(res[0], trigger); - if(result && trigger) { - function(); - } - return result; - }; - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); - } + std::string flag_description = ""); /// Add option for callback with an integer value Option *add_flag_function(std::string flag_name, std::function function, ///< A function to call, void(int) - std::string flag_description = "") { - - CLI::callback_t fun = [function](const CLI::results_t &res) { - std::int64_t flag_count{0}; - CLI::detail::lexical_cast(res[0], flag_count); - function(flag_count); - return true; - }; - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)) - ->multi_option_policy(MultiOptionPolicy::Sum); - } + std::string flag_description = ""); #ifdef CLI11_CPP14 /// Add option for callback (C++14 or better only) @@ -904,50 +672,10 @@ class App { Option *set_config(std::string option_name = "", std::string default_filename = "", const std::string &help_message = "Read an ini file", - bool config_required = false) { - - // Remove existing config if present - if(config_ptr_ != nullptr) { - remove_option(config_ptr_); - config_ptr_ = nullptr; // need to remove the config_ptr completely - } - - // Only add config if option passed - if(!option_name.empty()) { - config_ptr_ = add_option(option_name, help_message); - if(config_required) { - config_ptr_->required(); - } - if(!default_filename.empty()) { - config_ptr_->default_str(std::move(default_filename)); - } - config_ptr_->configurable(false); - } - - return config_ptr_; - } + bool config_required = false); /// Removes an option from the App. Takes an option pointer. Returns true if found and removed. - bool remove_option(Option *opt) { - // Make sure no links exist - for(Option_p &op : options_) { - op->remove_needs(opt); - op->remove_excludes(opt); - } - - if(help_ptr_ == opt) - help_ptr_ = nullptr; - if(help_all_ptr_ == opt) - help_all_ptr_ = nullptr; - - auto iterator = - std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; }); - if(iterator != std::end(options_)) { - options_.erase(iterator); - return true; - } - return false; - } + bool remove_option(Option *opt); /// creates an option group as part of the given app template @@ -968,119 +696,35 @@ class App { ///@{ /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag - App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") { - if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) { - if(!detail::valid_first_char(subcommand_name[0])) { - throw IncorrectConstruction( - "Subcommand name starts with invalid character, '!' and '-' are not allowed"); - } - for(auto c : subcommand_name) { - if(!detail::valid_later_char(c)) { - throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c + - "'), all characters are allowed except" - "'=',':','{','}', and ' '"); - } - } - } - CLI::App_p subcom = std::shared_ptr(new App(std::move(subcommand_description), subcommand_name, this)); - return add_subcommand(std::move(subcom)); - } + App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = ""); /// Add a previously created app as a subcommand - App *add_subcommand(CLI::App_p subcom) { - if(!subcom) - throw IncorrectConstruction("passed App is not valid"); - auto *ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this; - const auto &mstrg = _compare_subcommand_names(*subcom, *ckapp); - if(!mstrg.empty()) { - throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg)); - } - subcom->parent_ = this; - subcommands_.push_back(std::move(subcom)); - return subcommands_.back().get(); - } + App *add_subcommand(CLI::App_p subcom); /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed. - bool remove_subcommand(App *subcom) { - // Make sure no links exist - for(App_p &sub : subcommands_) { - sub->remove_excludes(subcom); - sub->remove_needs(subcom); - } + bool remove_subcommand(App *subcom); - auto iterator = std::find_if( - std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; }); - if(iterator != std::end(subcommands_)) { - subcommands_.erase(iterator); - return true; - } - return false; - } /// Check to see if a subcommand is part of this command (doesn't have to be in command line) /// returns the first subcommand if passed a nullptr - App *get_subcommand(const App *subcom) const { - if(subcom == nullptr) - throw OptionNotFound("nullptr passed"); - for(const App_p &subcomptr : subcommands_) - if(subcomptr.get() == subcom) - return subcomptr.get(); - throw OptionNotFound(subcom->get_name()); - } + App *get_subcommand(const App *subcom) const; /// Check to see if a subcommand is part of this command (text version) - CLI11_NODISCARD App *get_subcommand(std::string subcom) const { - auto *subc = _find_subcommand(subcom, false, false); - if(subc == nullptr) - throw OptionNotFound(subcom); - return subc; - } + CLI11_NODISCARD App *get_subcommand(std::string subcom) const; + /// Get a pointer to subcommand by index - CLI11_NODISCARD App *get_subcommand(int index = 0) const { - if(index >= 0) { - auto uindex = static_cast(index); - if(uindex < subcommands_.size()) - return subcommands_[uindex].get(); - } - throw OptionNotFound(std::to_string(index)); - } + CLI11_NODISCARD App *get_subcommand(int index = 0) const; /// Check to see if a subcommand is part of this command and get a shared_ptr to it - CLI::App_p get_subcommand_ptr(App *subcom) const { - if(subcom == nullptr) - throw OptionNotFound("nullptr passed"); - for(const App_p &subcomptr : subcommands_) - if(subcomptr.get() == subcom) - return subcomptr; - throw OptionNotFound(subcom->get_name()); - } + CLI::App_p get_subcommand_ptr(App *subcom) const; /// Check to see if a subcommand is part of this command (text version) - CLI11_NODISCARD CLI::App_p get_subcommand_ptr(std::string subcom) const { - for(const App_p &subcomptr : subcommands_) - if(subcomptr->check_name(subcom)) - return subcomptr; - throw OptionNotFound(subcom); - } + CLI11_NODISCARD CLI::App_p get_subcommand_ptr(std::string subcom) const; /// Get an owning pointer to subcommand by index - CLI11_NODISCARD CLI::App_p get_subcommand_ptr(int index = 0) const { - if(index >= 0) { - auto uindex = static_cast(index); - if(uindex < subcommands_.size()) - return subcommands_[uindex]; - } - throw OptionNotFound(std::to_string(index)); - } + CLI11_NODISCARD CLI::App_p get_subcommand_ptr(int index = 0) const; /// Check to see if an option group is part of this App - CLI11_NODISCARD App *get_option_group(std::string group_name) const { - for(const App_p &app : subcommands_) { - if(app->name_.empty() && app->group_ == group_name) { - return app.get(); - } - } - throw OptionNotFound(group_name); - } + CLI11_NODISCARD App *get_option_group(std::string group_name) const; /// No argument version of count counts the number of times this subcommand was /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless @@ -1089,19 +733,7 @@ class App { /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were /// treated as extras. - CLI11_NODISCARD std::size_t count_all() const { - std::size_t cnt{0}; - for(const auto &opt : options_) { - cnt += opt->count(); - } - for(const auto &sub : subcommands_) { - cnt += sub->count_all(); - } - if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called - cnt += parsed_; - } - return cnt; - } + CLI11_NODISCARD std::size_t count_all() const; /// Changes the group membership App *group(std::string group_name) { @@ -1192,153 +824,34 @@ class App { ///@{ // /// Reset the parsed data - void clear() { - - parsed_ = 0; - pre_parse_called_ = false; - - missing_.clear(); - parsed_subcommands_.clear(); - for(const Option_p &opt : options_) { - opt->clear(); - } - for(const App_p &subc : subcommands_) { - subc->clear(); - } - } + void clear(); /// Parses the command line - throws errors. /// This must be called after the options are in but before the rest of the program. - void parse(int argc, const char *const *argv) { - // If the name is not set, read from command line - if(name_.empty() || has_automatic_name_) { - has_automatic_name_ = true; - name_ = argv[0]; - } - - std::vector args; - args.reserve(static_cast(argc) - 1U); - for(auto i = static_cast(argc) - 1U; i > 0U; --i) - args.emplace_back(argv[i]); - parse(std::move(args)); - } + void parse(int argc, const char *const *argv); /// Parse a single string as if it contained command line arguments. /// This function splits the string into arguments then calls parse(std::vector &) /// the function takes an optional boolean argument specifying if the programName is included in the string to /// process - void parse(std::string commandline, bool program_name_included = false) { - - if(program_name_included) { - auto nstr = detail::split_program_name(commandline); - if((name_.empty()) || (has_automatic_name_)) { - has_automatic_name_ = true; - name_ = nstr.first; - } - commandline = std::move(nstr.second); - } else { - detail::trim(commandline); - } - // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations - if(!commandline.empty()) { - commandline = detail::find_and_modify(commandline, "=", detail::escape_detect); - if(allow_windows_style_options_) - commandline = detail::find_and_modify(commandline, ":", detail::escape_detect); - } - - auto args = detail::split_up(std::move(commandline)); - // remove all empty strings - args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); - std::reverse(args.begin(), args.end()); - - parse(std::move(args)); - } + void parse(std::string commandline, bool program_name_included = false); /// The real work is done here. Expects a reversed vector. /// Changes the vector to the remaining options. - void parse(std::vector &args) { - // Clear if parsed - if(parsed_ > 0) - clear(); - - // parsed_ is incremented in commands/subcommands, - // but placed here to make sure this is cleared when - // running parse after an error is thrown, even by _validate or _configure. - parsed_ = 1; - _validate(); - _configure(); - // set the parent as nullptr as this object should be the top now - parent_ = nullptr; - parsed_ = 0; - - _parse(args); - run_callback(); - } + void parse(std::vector &args); /// The real work is done here. Expects a reversed vector. - void parse(std::vector &&args) { - // Clear if parsed - if(parsed_ > 0) - clear(); - - // parsed_ is incremented in commands/subcommands, - // but placed here to make sure this is cleared when - // running parse after an error is thrown, even by _validate or _configure. - parsed_ = 1; - _validate(); - _configure(); - // set the parent as nullptr as this object should be the top now - parent_ = nullptr; - parsed_ = 0; - - _parse(std::move(args)); - run_callback(); - } - - void parse_from_stream(std::istream &input) { - if(parsed_ == 0) { - _validate(); - _configure(); - // set the parent as nullptr as this object should be the top now - } + void parse(std::vector &&args); + + void parse_from_stream(std::istream &input); - _parse_stream(input); - run_callback(); - } /// Provide a function to print a help message. The function gets access to the App pointer and error. void failure_message(std::function function) { failure_message_ = function; } /// Print a nice error message and return the exit code - int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const { - - /// Avoid printing anything if this is a CLI::RuntimeError - if(e.get_name() == "RuntimeError") - return e.get_exit_code(); - - if(e.get_name() == "CallForHelp") { - out << help(); - return e.get_exit_code(); - } - - if(e.get_name() == "CallForAllHelp") { - out << help("", AppFormatMode::All); - return e.get_exit_code(); - } - - if(e.get_name() == "CallForVersion") { - out << e.what() << std::endl; - return e.get_exit_code(); - } - - if(e.get_exit_code() != static_cast(ExitCodes::Success)) { - if(failure_message_) - err << failure_message_(this, e) << std::flush; - } - - return e.get_exit_code(); - } + int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const; ///@} /// @name Post parsing @@ -1353,38 +866,11 @@ class App { /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all /// subcommands (const) - std::vector get_subcommands(const std::function &filter) const { - std::vector subcomms(subcommands_.size()); - std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { - return v.get(); - }); - - if(filter) { - subcomms.erase(std::remove_if(std::begin(subcomms), - std::end(subcomms), - [&filter](const App *app) { return !filter(app); }), - std::end(subcomms)); - } - - return subcomms; - } + std::vector get_subcommands(const std::function &filter) const; /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all /// subcommands - std::vector get_subcommands(const std::function &filter) { - std::vector subcomms(subcommands_.size()); - std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { - return v.get(); - }); - - if(filter) { - subcomms.erase( - std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }), - std::end(subcomms)); - } - - return subcomms; - } + std::vector get_subcommands(const std::function &filter); /// Check to see if given subcommand was selected bool got_subcommand(const App *subcom) const { @@ -1442,47 +928,16 @@ class App { } /// Removes an option from the excludes list of this subcommand - bool remove_excludes(Option *opt) { - auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt); - if(iterator == std::end(exclude_options_)) { - return false; - } - exclude_options_.erase(iterator); - return true; - } + bool remove_excludes(Option *opt); /// Removes a subcommand from the excludes list of this subcommand - bool remove_excludes(App *app) { - auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app); - if(iterator == std::end(exclude_subcommands_)) { - return false; - } - auto *other_app = *iterator; - exclude_subcommands_.erase(iterator); - other_app->remove_excludes(this); - return true; - } + bool remove_excludes(App *app); /// Removes an option from the needs list of this subcommand - bool remove_needs(Option *opt) { - auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt); - if(iterator == std::end(need_options_)) { - return false; - } - need_options_.erase(iterator); - return true; - } + bool remove_needs(Option *opt); /// Removes a subcommand from the needs list of this subcommand - bool remove_needs(App *app) { - auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app); - if(iterator == std::end(need_subcommands_)) { - return false; - } - need_subcommands_.erase(iterator); - return true; - } - + bool remove_needs(App *app); ///@} /// @name Help ///@{ @@ -1505,37 +960,10 @@ class App { /// Makes a help message, using the currently configured formatter /// Will only do one subcommand at a time - CLI11_NODISCARD std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const { - if(prev.empty()) - prev = get_name(); - else - prev += " " + get_name(); - - // Delegate to subcommand if needed - auto selected_subcommands = get_subcommands(); - if(!selected_subcommands.empty()) { - return selected_subcommands.at(0)->help(prev, mode); - } - return formatter_->make_help(this, prev, mode); - } + CLI11_NODISCARD std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const; /// Displays a version string - CLI11_NODISCARD std::string version() const { - std::string val; - if(version_ptr_ != nullptr) { - auto rv = version_ptr_->results(); - version_ptr_->clear(); - version_ptr_->add_result("true"); - try { - version_ptr_->run_callback(); - } catch(const CLI::CallForVersion &cfv) { - val = cfv.what(); - } - version_ptr_->clear(); - version_ptr_->add_result(rv); - } - return val; - } + CLI11_NODISCARD std::string version() const; ///@} /// @name Getters ///@{ @@ -1566,75 +994,16 @@ class App { } /// Get the list of options (user facing function, so returns raw pointers), has optional filter function - std::vector get_options(const std::function filter = {}) const { - std::vector options(options_.size()); - std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { - return val.get(); - }); - - if(filter) { - options.erase(std::remove_if(std::begin(options), - std::end(options), - [&filter](const Option *opt) { return !filter(opt); }), - std::end(options)); - } - - return options; - } + std::vector get_options(const std::function filter = {}) const; /// Non-const version of the above - std::vector