Option callback priority v2 (#1226)

Extension allowing all possible priority combinations.
Add a field callback_priority to OptionBase.

---------

Co-authored-by: Philip Top <phlptp@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Volker Christian
2025-10-18 15:52:55 +02:00
committed by GitHub
parent a41ba814b3
commit 0104dceb17
7 changed files with 195 additions and 26 deletions

View File

@@ -1303,12 +1303,12 @@ class App {
void _process_env();
/// Process callbacks. Runs on *all* subcommands.
void _process_callbacks();
void _process_callbacks(CallbackPriority priority);
/// Run help flag processing if any are found.
///
/// The flags allow recursive calls to remember if there was a help flag on a parent.
void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const;
void _process_help_flags(CallbackPriority priority, bool trigger_help = false, bool trigger_all_help = false) const;
/// Verify required options and cross requirements. Subcommands too (only if selected).
void _process_requirements();

View File

@@ -50,6 +50,18 @@ enum class MultiOptionPolicy : char {
Reverse, //!< take only the last Expected number of arguments in reverse order
};
/// @brief enumeration for the callback priority
enum class CallbackPriority : std::uint8_t {
FirstPreHelp = 0,
First = 1,
PreRequirementsCheckPreHelp = 2,
PreRequirementsCheck = 3,
NormalPreHelp = 4,
Normal = 5,
LastPreHelp = 6,
Last = 7
}; // namespace CLI
/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
/// to share parts of the class; an OptionDefaults can copy to an Option.
template <typename CRTP> class OptionBase {
@@ -84,6 +96,9 @@ template <typename CRTP> class OptionBase {
/// Policy for handling multiple arguments beyond the expected Max
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
/// Priority of callback
CallbackPriority callback_priority_{CallbackPriority::Normal};
/// Copy the contents to another similar class (one based on OptionBase)
template <typename T> void copy_to(T *other) const;
@@ -142,6 +157,9 @@ template <typename CRTP> class OptionBase {
/// The status of the multi option policy
CLI11_NODISCARD MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
/// The priority of callback
CLI11_NODISCARD CallbackPriority get_callback_priority() const { return callback_priority_; }
// Shortcuts for multi option policy
/// Set the multi option policy to take last
@@ -201,6 +219,12 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
// Methods here need a different implementation if they are Option vs. OptionDefault
/// Set the callback priority
OptionDefaults *callback_priority(CallbackPriority value = CallbackPriority::Normal) {
callback_priority_ = value;
return this;
}
/// Take the last argument if given multiple times
OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
multi_option_policy_ = value;
@@ -343,7 +367,6 @@ class Option : public OptionBase<Option> {
bool trigger_on_result_{false};
/// flag indicating that the option should force the callback regardless if any results present
bool force_callback_{false};
///@}
/// Making an option by hand is not defined, it must be made by the App class
Option(std::string option_name,
@@ -420,6 +443,13 @@ class Option : public OptionBase<Option> {
/// Get the current value of run_callback_for_default
CLI11_NODISCARD bool get_run_callback_for_default() const { return run_callback_for_default_; }
/// Set the value of callback priority which controls when the callback function should be called relative to other
/// parsing operations the default This is controlled automatically but could be manipulated by the user.
Option *callback_priority(CallbackPriority value = CallbackPriority::Normal) {
callback_priority_ = value;
return this;
}
/// Adds a shared validator
Option *check(Validator_p validator);

View File

@@ -285,7 +285,7 @@ CLI11_INLINE Option *App::set_help_flag(std::string flag_name, const std::string
// Empty name will simply remove the help flag
if(!flag_name.empty()) {
help_ptr_ = add_flag(flag_name, help_description);
help_ptr_->configurable(false);
help_ptr_->configurable(false)->callback_priority(CallbackPriority::First);
}
return help_ptr_;
@@ -301,7 +301,7 @@ CLI11_INLINE Option *App::set_help_all_flag(std::string help_name, const std::st
// 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);
help_all_ptr_->configurable(false)->callback_priority(CallbackPriority::First);
}
return help_all_ptr_;
@@ -319,7 +319,7 @@ App::set_version_flag(std::string flag_name, const std::string &versionString, c
if(!flag_name.empty()) {
version_ptr_ = add_flag_callback(
flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help);
version_ptr_->configurable(false);
version_ptr_->configurable(false)->callback_priority(CallbackPriority::First);
}
return version_ptr_;
@@ -336,7 +336,7 @@ App::set_version_flag(std::string flag_name, std::function<std::string()> vfunc,
if(!flag_name.empty()) {
version_ptr_ =
add_flag_callback(flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help);
version_ptr_->configurable(false);
version_ptr_->configurable(false)->callback_priority(CallbackPriority::First);
}
return version_ptr_;
@@ -1239,43 +1239,48 @@ CLI11_INLINE void App::_process_env() {
}
}
CLI11_INLINE void App::_process_callbacks() {
CLI11_INLINE void App::_process_callbacks(CallbackPriority priority) {
for(App_p &sub : subcommands_) {
// process the priority option_groups first
if(sub->get_name().empty() && sub->parse_complete_callback_) {
if(sub->count_all() > 0) {
sub->_process_callbacks();
sub->run_callback();
sub->_process_callbacks(priority);
if(priority == CallbackPriority::Normal) {
// only run the subcommand callback at normal priority
sub->run_callback();
}
}
}
}
for(const Option_p &opt : options_) {
if((*opt) && !opt->get_callback_run()) {
opt->run_callback();
if(opt->get_callback_priority() == priority) {
if((*opt) && !opt->get_callback_run()) {
opt->run_callback();
}
}
}
for(App_p &sub : subcommands_) {
if(!sub->parse_complete_callback_) {
sub->_process_callbacks();
sub->_process_callbacks(priority);
}
}
}
CLI11_INLINE void App::_process_help_flags(bool trigger_help, bool trigger_all_help) const {
CLI11_INLINE void App::_process_help_flags(CallbackPriority priority, bool trigger_help, bool trigger_all_help) const {
const Option *help_ptr = get_help_ptr();
const Option *help_all_ptr = get_help_all_ptr();
if(help_ptr != nullptr && help_ptr->count() > 0)
if(help_ptr != nullptr && help_ptr->count() > 0 && help_ptr->get_callback_priority() == priority)
trigger_help = true;
if(help_all_ptr != nullptr && help_all_ptr->count() > 0)
if(help_all_ptr != nullptr && help_all_ptr->count() > 0 && help_all_ptr->get_callback_priority() == priority)
trigger_all_help = true;
// If there were parsed subcommands, call those. First subcommand wins if there are multiple ones.
if(!parsed_subcommands_.empty()) {
for(const App *sub : parsed_subcommands_)
sub->_process_help_flags(trigger_help, trigger_all_help);
sub->_process_help_flags(priority, trigger_help, trigger_all_help);
// Only the final subcommand should call for help. All help wins over help.
} else if(trigger_all_help) {
@@ -1416,7 +1421,10 @@ CLI11_INLINE void App::_process_requirements() {
CLI11_INLINE void App::_process() {
// help takes precedence over other potential errors and config and environment shouldn't be processed if help
// throws
_process_help_flags();
_process_callbacks(CallbackPriority::FirstPreHelp);
_process_help_flags(CallbackPriority::First);
_process_callbacks(CallbackPriority::First);
std::exception_ptr config_exception;
try {
// the config file might generate a FileError but that should not be processed until later in the process
@@ -1430,13 +1438,23 @@ CLI11_INLINE void App::_process() {
}
// callbacks and requirements processing can generate exceptions which should take priority
// over the config file error if one exists.
_process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp);
_process_help_flags(CallbackPriority::PreRequirementsCheck);
_process_callbacks(CallbackPriority::PreRequirementsCheck);
_process_requirements();
_process_callbacks();
_process_callbacks(CallbackPriority::NormalPreHelp);
_process_help_flags(CallbackPriority::Normal);
_process_callbacks(CallbackPriority::Normal);
if(config_exception) {
std::rethrow_exception(config_exception);
}
_process_callbacks(CallbackPriority::LastPreHelp);
_process_help_flags(CallbackPriority::Last);
_process_callbacks(CallbackPriority::Last);
}
CLI11_INLINE void App::_process_extras() {
@@ -1496,10 +1514,20 @@ CLI11_INLINE void App::_parse(std::vector<std::string> &args) {
// Convert missing (pairs) to extras (string only) ready for processing in another app
args = remaining_for_passthrough(false);
} else if(parse_complete_callback_) {
_process_callbacks(CallbackPriority::FirstPreHelp);
_process_help_flags(CallbackPriority::First);
_process_callbacks(CallbackPriority::First);
_process_env();
_process_callbacks();
_process_help_flags();
_process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp);
_process_help_flags(CallbackPriority::PreRequirementsCheck);
_process_callbacks(CallbackPriority::PreRequirementsCheck);
_process_requirements();
_process_callbacks(CallbackPriority::NormalPreHelp);
_process_help_flags(CallbackPriority::Normal);
_process_callbacks(CallbackPriority::Normal);
_process_callbacks(CallbackPriority::LastPreHelp);
_process_help_flags(CallbackPriority::Last);
_process_callbacks(CallbackPriority::Last);
run_callback(false, true);
}
}
@@ -1620,8 +1648,15 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
// check for section close
if(item.name == "--") {
if(configurable_ && parse_complete_callback_) {
_process_callbacks();
_process_callbacks(CallbackPriority::FirstPreHelp);
_process_callbacks(CallbackPriority::First);
_process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp);
_process_callbacks(CallbackPriority::PreRequirementsCheck);
_process_requirements();
_process_callbacks(CallbackPriority::NormalPreHelp);
_process_callbacks(CallbackPriority::Normal);
_process_callbacks(CallbackPriority::LastPreHelp);
_process_callbacks(CallbackPriority::Last);
run_callback();
}
return true;
@@ -2081,10 +2116,20 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
_trigger_pre_parse(args.size());
// run the parse complete callback since the subcommand processing is now complete
if(sub->parse_complete_callback_) {
sub->_process_callbacks(CallbackPriority::FirstPreHelp);
sub->_process_help_flags(CallbackPriority::First);
sub->_process_callbacks(CallbackPriority::First);
sub->_process_env();
sub->_process_callbacks();
sub->_process_help_flags();
sub->_process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp);
sub->_process_help_flags(CallbackPriority::PreRequirementsCheck);
sub->_process_callbacks(CallbackPriority::PreRequirementsCheck);
sub->_process_requirements();
sub->_process_callbacks(CallbackPriority::NormalPreHelp);
sub->_process_help_flags(CallbackPriority::Normal);
sub->_process_callbacks(CallbackPriority::Normal);
sub->_process_callbacks(CallbackPriority::LastPreHelp);
sub->_process_help_flags(CallbackPriority::Last);
sub->_process_callbacks(CallbackPriority::Last);
sub->run_callback(false, true);
}
return true;

View File

@@ -32,6 +32,7 @@ template <typename CRTP> template <typename T> void OptionBase<CRTP>::copy_to(T
other->delimiter(delimiter_);
other->always_capture_default(always_capture_default_);
other->multi_option_policy(multi_option_policy_);
other->callback_priority(callback_priority_);
}
CLI11_INLINE Option *Option::expected(int value) {