// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // // SPDX-License-Identifier: BSD-3-Clause #pragma once // IWYU pragma: private, include "CLI/CLI.hpp" // This include is only needed for IDEs to discover symbols #include "../App.hpp" #include "../Argv.hpp" #include "../Encoding.hpp" // [CLI11:public_includes:set] #include #include #include #include #include #include // [CLI11:public_includes:end] namespace CLI { // [CLI11:app_inl_hpp:verbatim] CLI11_INLINE App::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_; usage_ = parent_->usage_; footer_ = parent_->footer_; formatter_ = parent_->formatter_; config_formatter_ = parent_->config_formatter_; require_subcommand_max_ = parent_->require_subcommand_max_; } } CLI11_NODISCARD CLI11_INLINE char **App::ensure_utf8(char **argv) { #ifdef _WIN32 (void)argv; normalized_argv_ = detail::compute_win32_argv(); if(!normalized_argv_view_.empty()) { normalized_argv_view_.clear(); } normalized_argv_view_.reserve(normalized_argv_.size()); for(auto &arg : normalized_argv_) { // using const_cast is well-defined, string is known to not be const. normalized_argv_view_.push_back(const_cast(arg.data())); } return normalized_argv_view_.data(); #else return argv; #endif } CLI11_INLINE App *App::name(std::string app_name) { if(parent_ != nullptr) { std::string 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; } CLI11_INLINE 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; } CLI11_INLINE App *App::immediate_callback(bool immediate) { 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; } CLI11_INLINE App *App::ignore_case(bool value) { 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; } CLI11_INLINE App *App::ignore_underscore(bool value) { 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; } CLI11_INLINE Option *App::add_option(std::string option_name, callback_t option_callback, std::string option_description, bool defaulted, std::function func) { Option myopt{option_name, option_description, option_callback, this, allow_non_standard_options_}; if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) == std::end(options_)) { if(myopt.lnames_.empty() && myopt.snames_.empty()) { // if the option is positional only there is additional potential for ambiguities in config files and needs // to be checked std::string test_name = "--" + myopt.get_single_name(); if(test_name.size() == 3) { test_name.erase(0, 1); } auto *op = get_option_no_throw(test_name); if(op != nullptr && op->get_configurable()) { throw(OptionAlreadyAdded("added option positional name matches existing option: " + test_name)); } } else if(parent_ != nullptr) { for(auto &ln : myopt.lnames_) { auto *op = parent_->get_option_no_throw(ln); if(op != nullptr && op->get_configurable()) { throw(OptionAlreadyAdded("added option matches existing positional option: " + ln)); } } for(auto &sn : myopt.snames_) { auto *op = parent_->get_option_no_throw(sn); if(op != nullptr && op->get_configurable()) { throw(OptionAlreadyAdded("added option matches existing positional option: " + sn)); } } } if(allow_non_standard_options_ && !myopt.snames_.empty()) { for(auto &sname : myopt.snames_) { if(sname.length() > 1) { std::string test_name; test_name.push_back('-'); test_name.push_back(sname.front()); auto *op = get_option_no_throw(test_name); if(op != nullptr) { throw(OptionAlreadyAdded("added option interferes with existing short option: " + sname)); } } } for(auto &opt : options_) { for(const auto &osn : opt->snames_) { if(osn.size() > 1) { std::string test_name; test_name.push_back(osn.front()); if(myopt.check_sname(test_name)) { throw(OptionAlreadyAdded("added option interferes with existing non standard option: " + osn)); } } } } } options_.emplace_back(); Option_p &option = options_.back(); option.reset(new Option(option_name, option_description, option_callback, this, allow_non_standard_options_)); // 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 } CLI11_INLINE Option *App::set_help_flag(std::string flag_name, const std::string &help_description) { // take flag_description by const reference otherwise add_flag tries to assign to help_description if(help_ptr_ != nullptr) { remove_option(help_ptr_); help_ptr_ = nullptr; } // Empty name will simply remove the help flag if(!flag_name.empty()) { help_ptr_ = add_flag(flag_name, help_description); help_ptr_->configurable(false); } return help_ptr_; } CLI11_INLINE Option *App::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_; } CLI11_INLINE Option * App::set_version_flag(std::string flag_name, const std::string &versionString, const std::string &version_help) { // 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; } // 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_; } CLI11_INLINE Option * App::set_version_flag(std::string flag_name, std::function vfunc, const std::string &version_help) { 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_; } CLI11_INLINE Option *App::_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; } CLI11_INLINE Option *App::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) { using CLI::detail::lexical_cast; bool trigger{false}; auto result = lexical_cast(res[0], trigger); if(result && trigger) { function(); } return result; }; return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); } CLI11_INLINE Option * App::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) { using CLI::detail::lexical_cast; std::int64_t flag_count{0}; 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); } CLI11_INLINE Option *App::set_config(std::string option_name, std::string default_filename, const std::string &help_message, bool config_required) { // 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_->force_callback_ = true; } config_ptr_->configurable(false); // set the option to take the last value and reverse given by default config_ptr_->multi_option_policy(MultiOptionPolicy::Reverse); } return config_ptr_; } CLI11_INLINE bool App::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; } CLI11_INLINE App *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 '-' and control characters"); } 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 control characters"); } } } CLI::App_p subcom = std::shared_ptr(new App(std::move(subcommand_description), subcommand_name, this)); return add_subcommand(std::move(subcom)); } CLI11_INLINE App *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(); } CLI11_INLINE bool App::remove_subcommand(App *subcom) { // Make sure no links exist for(App_p &sub : subcommands_) { sub->remove_excludes(subcom); sub->remove_needs(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; } CLI11_INLINE App *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()); } CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(std::string subcom) const { auto *subc = _find_subcommand(subcom, false, false); if(subc == nullptr) throw OptionNotFound(subcom); return subc; } CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand_no_throw(std::string subcom) const noexcept { return _find_subcommand(subcom, false, false); } CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(int index) const { if(index >= 0) { auto uindex = static_cast(index); if(uindex < subcommands_.size()) return subcommands_[uindex].get(); } throw OptionNotFound(std::to_string(index)); } CLI11_INLINE CLI::App_p App::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()); } CLI11_NODISCARD CLI11_INLINE CLI::App_p App::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 CLI11_INLINE CLI::App_p App::get_subcommand_ptr(int index) const { if(index >= 0) { auto uindex = static_cast(index); if(uindex < subcommands_.size()) return subcommands_[uindex]; } throw OptionNotFound(std::to_string(index)); } CLI11_NODISCARD CLI11_INLINE CLI::App *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 CLI11_INLINE std::size_t App::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_INLINE void App::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(); } } CLI11_INLINE void App::parse(int argc, const char *const *argv) { parse_char_t(argc, argv); } CLI11_INLINE void App::parse(int argc, const wchar_t *const *argv) { parse_char_t(argc, argv); } namespace detail { // Do nothing or perform narrowing CLI11_INLINE const char *maybe_narrow(const char *str) { return str; } CLI11_INLINE std::string maybe_narrow(const wchar_t *str) { return narrow(str); } } // namespace detail template CLI11_INLINE void App::parse_char_t(int argc, const CharT *const *argv) { // If the name is not set, read from command line if(name_.empty() || has_automatic_name_) { has_automatic_name_ = true; name_ = detail::maybe_narrow(argv[0]); } std::vector args; args.reserve(static_cast(argc) - 1U); for(auto i = static_cast(argc) - 1U; i > 0U; --i) args.emplace_back(detail::maybe_narrow(argv[i])); parse(std::move(args)); } CLI11_INLINE void App::parse(std::string commandline, bool program_name_included) { 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()); try { detail::remove_quotes(args); } catch(const std::invalid_argument &arg) { throw CLI::ParseError(arg.what(), CLI::ExitCodes::InvalidError); } std::reverse(args.begin(), args.end()); parse(std::move(args)); } CLI11_INLINE void App::parse(std::wstring commandline, bool program_name_included) { parse(narrow(commandline), program_name_included); } CLI11_INLINE void App::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(); } CLI11_INLINE void App::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(); } CLI11_INLINE void App::parse_from_stream(std::istream &input) { if(parsed_ == 0) { _validate(); _configure(); // set the parent as nullptr as this object should be the top now } _parse_stream(input); run_callback(); } CLI11_INLINE int App::exit(const Error &e, std::ostream &out, std::ostream &err) 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() << '\n'; 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(); } CLI11_INLINE std::vector App::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; } CLI11_INLINE std::vector App::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; } CLI11_INLINE bool App::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; } CLI11_INLINE bool App::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; } CLI11_INLINE bool App::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; } CLI11_INLINE bool App::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; } CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMode mode) 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.back()->help(prev, mode); } return formatter_->make_help(this, prev, mode); } CLI11_NODISCARD CLI11_INLINE std::string App::version() const { std::string val; if(version_ptr_ != nullptr) { // copy the results for reuse later results_t 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_INLINE std::vector App::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)); } for(const auto &subcp : subcommands_) { // also check down into nameless subcommands const App *subc = subcp.get(); if(subc->get_name().empty() && !subc->get_group().empty() && subc->get_group().front() == '+') { std::vector subcopts = subc->get_options(filter); options.insert(options.end(), subcopts.begin(), subcopts.end()); } } return options; } CLI11_INLINE std::vector