mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 20:23:55 +00:00
Just use Formatter
Removing OptionFormatter Rename files Rename to just Formatter Remove OptionFormatMode (just needs a bool) Renaming option functions on formatter
This commit is contained in:
parent
89975e51e7
commit
af2ed66d6e
@ -1,8 +1,8 @@
|
||||
### Version 1.6: Formatters
|
||||
### Version 1.6: Formatting
|
||||
|
||||
Added a new formatting system. You can now set the formatter on Apps and Options.
|
||||
Added a new formatting system. You can now set the formatter on Apps.
|
||||
|
||||
* Added `AppFormatter` and `OptionFormatter`, and `formatter` slot
|
||||
* Added `CLI::Formatter` and `formatter` slot for apps, inherited.
|
||||
* Added `help_all` support (not added by default)
|
||||
* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering
|
||||
* Added `get_groups()` to get groups
|
||||
|
@ -193,7 +193,6 @@ The add commands return a pointer to an internally stored `Option`. If you set t
|
||||
* `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
|
||||
* `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
|
||||
* `->configurable(false)`: Disable this option from being in an ini configuration file.
|
||||
* `->formatter(fmt)`: Set the formatter, with signature `std::string(const Option*, OptionFormatMode)`. See Formatting for more details.
|
||||
|
||||
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
|
||||
|
||||
@ -306,15 +305,13 @@ The default settings for options are inherited to subcommands, as well.
|
||||
|
||||
## Formatting
|
||||
|
||||
The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App` or `Option`.
|
||||
CLI11 comes with a default App formatter functional, `AppFormatter`, and a default `OptionFormatter` as well. They are both customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
|
||||
that too; you just need to implement the correct signature. The first argument is a const pointer to the App or Option in question. The App formatter will get a `std::string` usage name as the second option, and both end with with a mode. Both formatters return a `std::string`.
|
||||
The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature.
|
||||
CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
|
||||
that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`.
|
||||
|
||||
The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the situation the help was called in. `Sub` is optional, but the default formatter uses it to make sure expanded subcommands are called with
|
||||
their own formatter since you can't access anything but the call operator once a formatter has been set.
|
||||
|
||||
The `OptionFormatMode` also has three values, depending on the calling situation: `Positional`, `Optional`, and `Usage`. The default formatter uses these to print out the option in each of these situations.
|
||||
|
||||
## Subclassing
|
||||
|
||||
The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can also set a help-all flag with `.set_help_all_flag(name, help_string)`; this will expand the subcommands (one level only). You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse
|
||||
|
@ -1,17 +1,15 @@
|
||||
#include <CLI/CLI.hpp>
|
||||
|
||||
class MyFormatter : public CLI::OptionFormatter {
|
||||
class MyFormatter : public CLI::Formatter {
|
||||
public:
|
||||
std::string make_opts(const CLI::Option *) const override { return " OPTION"; }
|
||||
std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; }
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
CLI::App app;
|
||||
app.set_help_all_flag("--help-all", "Show all help");
|
||||
|
||||
app.option_defaults()->formatter(MyFormatter());
|
||||
|
||||
CLI::AppFormatter fmt;
|
||||
MyFormatter fmt;
|
||||
fmt.column_width(15);
|
||||
app.formatter(fmt);
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/TypeTools.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
#include "CLI/FormatterFwd.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
@ -107,7 +107,7 @@ class App {
|
||||
Option *help_all_ptr_{nullptr};
|
||||
|
||||
/// This is the formatter for help printing. Default provided. INHERITABLE
|
||||
std::function<std::string(const App *, std::string, AppFormatMode)> formatter_{AppFormatter()};
|
||||
std::function<std::string(const App *, std::string, AppFormatMode)> formatter_{Formatter()};
|
||||
|
||||
/// The error message printing function INHERITABLE
|
||||
std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple;
|
||||
|
@ -1,185 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "CLI/App.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
inline std::string
|
||||
AppFormatter::make_group(std::string group, std::vector<const Option *> opts, OptionFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
out << "\n" << group << ":\n";
|
||||
for(const Option *opt : opts) {
|
||||
out << opt->help(mode);
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_groups(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
std::vector<const Option *> positionals =
|
||||
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
|
||||
|
||||
if(!positionals.empty())
|
||||
out << make_group(get_label("Positionals"), positionals, OptionFormatMode::Positional);
|
||||
|
||||
// Options
|
||||
for(const std::string &group : groups) {
|
||||
std::vector<const Option *> grouped_items =
|
||||
app->get_options([&group](const Option *opt) { return opt->nonpositional() && opt->get_group() == group; });
|
||||
|
||||
if(mode == AppFormatMode::Sub) {
|
||||
grouped_items.erase(std::remove_if(grouped_items.begin(),
|
||||
grouped_items.end(),
|
||||
[app](const Option *opt) {
|
||||
return app->get_help_ptr() == opt || app->get_help_all_ptr() == opt;
|
||||
}),
|
||||
grouped_items.end());
|
||||
}
|
||||
|
||||
if(!group.empty() && !grouped_items.empty()) {
|
||||
out << make_group(group, grouped_items, OptionFormatMode::Optional);
|
||||
if(group != groups.back())
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_description(const App *app) const {
|
||||
std::string desc = app->get_description();
|
||||
|
||||
if(!desc.empty())
|
||||
return desc + "\n";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_usage(const App *app, std::string name) const {
|
||||
std::stringstream out;
|
||||
|
||||
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
|
||||
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
|
||||
// Print an Options badge if any options exist
|
||||
std::vector<const Option *> non_pos_options =
|
||||
app->get_options([](const Option *opt) { return opt->nonpositional(); });
|
||||
if(!non_pos_options.empty())
|
||||
out << " [" << get_label("OPTIONS") << "]";
|
||||
|
||||
// Positionals need to be listed here
|
||||
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
|
||||
|
||||
// Print out positionals if any are left
|
||||
if(!positionals.empty()) {
|
||||
// Convert to help names
|
||||
std::vector<std::string> positional_names(positionals.size());
|
||||
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [](const Option *opt) {
|
||||
return opt->help(OptionFormatMode::Usage);
|
||||
});
|
||||
|
||||
out << " " << detail::join(positional_names, " ");
|
||||
}
|
||||
|
||||
// Add a marker if subcommands are expected or optional
|
||||
if(!app->get_subcommands({}).empty()) {
|
||||
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
|
||||
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
|
||||
: "SUBCOMMANDS")
|
||||
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
|
||||
}
|
||||
|
||||
out << std::endl;
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_footer(const App *app) const {
|
||||
std::string footer = app->get_footer();
|
||||
if(!footer.empty())
|
||||
return footer + "\n";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::operator()(const App *app, std::string name, AppFormatMode mode) const {
|
||||
|
||||
std::stringstream out;
|
||||
if(mode == AppFormatMode::Normal) {
|
||||
out << make_description(app);
|
||||
out << make_usage(app, name);
|
||||
out << make_groups(app, mode);
|
||||
out << make_subcommands(app, mode);
|
||||
out << make_footer(app);
|
||||
} else if(mode == AppFormatMode::Sub) {
|
||||
out << make_expanded(app);
|
||||
} else if(mode == AppFormatMode::All) {
|
||||
out << make_description(app);
|
||||
out << make_usage(app, name);
|
||||
out << make_groups(app, mode);
|
||||
out << make_subcommands(app, mode);
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_subcommands(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
|
||||
std::vector<const App *> subcommands = app->get_subcommands({});
|
||||
|
||||
// Make a list in definition order of the groups seen
|
||||
std::vector<std::string> subcmd_groups_seen;
|
||||
for(const App *com : subcommands) {
|
||||
std::string group_key = com->get_group();
|
||||
if(!group_key.empty() &&
|
||||
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
|
||||
return detail::to_lower(a) == detail::to_lower(group_key);
|
||||
}) == subcmd_groups_seen.end())
|
||||
subcmd_groups_seen.push_back(group_key);
|
||||
}
|
||||
|
||||
// For each group, filter out and print subcommands
|
||||
for(const std::string &group : subcmd_groups_seen) {
|
||||
out << "\n" << group << ":\n";
|
||||
if(mode == AppFormatMode::All)
|
||||
out << "\n";
|
||||
std::vector<const App *> subcommands_group = app->get_subcommands(
|
||||
[&group](const App *app) { return detail::to_lower(app->get_group()) == detail::to_lower(group); });
|
||||
for(const App *new_com : subcommands_group) {
|
||||
if(mode != AppFormatMode::All) {
|
||||
out << make_subcommand(new_com);
|
||||
} else {
|
||||
out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
|
||||
if(new_com != subcommands_group.back())
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_subcommand(const App *sub) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string AppFormatter::make_expanded(const App *sub) const {
|
||||
std::stringstream out;
|
||||
out << sub->get_name() << "\n " << sub->get_description();
|
||||
out << make_groups(sub, AppFormatMode::Sub);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace CLI
|
@ -24,12 +24,10 @@
|
||||
|
||||
#include "CLI/Validators.hpp"
|
||||
|
||||
#include "CLI/Formatter.hpp"
|
||||
#include "CLI/FormatterFwd.hpp"
|
||||
|
||||
#include "CLI/Option.hpp"
|
||||
|
||||
#include "CLI/OptionFormatter.hpp"
|
||||
|
||||
#include "CLI/App.hpp"
|
||||
|
||||
#include "CLI/AppFormatter.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
|
@ -4,187 +4,234 @@
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/App.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
class Option;
|
||||
class App;
|
||||
|
||||
/// This enum signifies what situation the option is beig printed in.
|
||||
///
|
||||
/// This is passed in as part of the built in formatter in App; it is
|
||||
/// possible that a custom App formatter could avoid using it, however.
|
||||
enum class OptionFormatMode {
|
||||
Usage, //< In the program usage line
|
||||
Positional, //< In the positionals
|
||||
Optional //< In the normal optionals
|
||||
};
|
||||
|
||||
/// This enum signifies the type of help requested
|
||||
///
|
||||
/// This is passed in by App; all user classes must accept this as
|
||||
/// the second argument.
|
||||
|
||||
enum class AppFormatMode {
|
||||
Normal, //< The normal, detailed help
|
||||
All, //< A fully expanded help
|
||||
Sub, //< Used when printed as part of expanded subcommand
|
||||
};
|
||||
|
||||
/// This is an example formatter (and also the default formatter)
|
||||
/// For option help.
|
||||
class OptionFormatter {
|
||||
protected:
|
||||
/// @name Options
|
||||
///@{
|
||||
|
||||
/// @brief The required help printout labels (user changeable)
|
||||
/// Values are REQUIRED, NEEDS, EXCLUDES
|
||||
std::map<std::string, std::string> labels_{{"REQUIRED", "(REQUIRED)"}};
|
||||
|
||||
/// The width of the first column
|
||||
size_t column_width_{30};
|
||||
|
||||
///@}
|
||||
/// @name Basic
|
||||
///@{
|
||||
|
||||
public:
|
||||
OptionFormatter() = default;
|
||||
OptionFormatter(const OptionFormatter &) = default;
|
||||
OptionFormatter(OptionFormatter &&) = default;
|
||||
|
||||
///@}
|
||||
/// @name Setters
|
||||
///@{
|
||||
|
||||
/// Set the "REQUIRED" label
|
||||
void label(std::string key, std::string val) { labels_[key] = val; }
|
||||
|
||||
/// Set the column width
|
||||
void column_width(size_t val) { column_width_ = val; }
|
||||
|
||||
///@}
|
||||
/// @name Getters
|
||||
///@{
|
||||
|
||||
/// Get the current value of a name (REQUIRED, etc.)
|
||||
std::string get_label(std::string key) const {
|
||||
if(labels_.find(key) == labels_.end())
|
||||
return key;
|
||||
else
|
||||
return labels_.at(key);
|
||||
inline std::string
|
||||
Formatter::make_group(std::string group, std::vector<const Option *> opts, bool is_positional) const {
|
||||
std::stringstream out;
|
||||
out << "\n" << group << ":\n";
|
||||
for(const Option *opt : opts) {
|
||||
out << make_option(opt, is_positional);
|
||||
}
|
||||
|
||||
/// Get the current column width
|
||||
size_t get_column_width() const { return column_width_; }
|
||||
return out.str();
|
||||
}
|
||||
|
||||
///@}
|
||||
/// @name Overridables
|
||||
///@{
|
||||
inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
std::vector<const Option *> positionals =
|
||||
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
|
||||
|
||||
/// @brief This is the name part of an option, Default: left column
|
||||
virtual std::string make_name(const Option *, OptionFormatMode) const;
|
||||
if(!positionals.empty())
|
||||
out << make_group(get_label("Positionals"), positionals, true);
|
||||
|
||||
/// @brief This is the options part of the name, Default: combined into left column
|
||||
virtual std::string make_opts(const Option *) const;
|
||||
// Options
|
||||
for(const std::string &group : groups) {
|
||||
std::vector<const Option *> grouped_items =
|
||||
app->get_options([&group](const Option *opt) { return opt->nonpositional() && opt->get_group() == group; });
|
||||
|
||||
/// @brief This is the description. Default: Right column, on new line if left column too large
|
||||
virtual std::string make_desc(const Option *) const;
|
||||
if(mode == AppFormatMode::Sub) {
|
||||
grouped_items.erase(std::remove_if(grouped_items.begin(),
|
||||
grouped_items.end(),
|
||||
[app](const Option *opt) {
|
||||
return app->get_help_ptr() == opt || app->get_help_all_ptr() == opt;
|
||||
}),
|
||||
grouped_items.end());
|
||||
}
|
||||
|
||||
/// @brief This is used to print the name on the USAGE line (by App formatter)
|
||||
virtual std::string make_usage(const Option *opt) const;
|
||||
|
||||
/// @brief This is the standard help combiner that does the "default" thing.
|
||||
virtual std::string operator()(const Option *opt, OptionFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
if(mode == OptionFormatMode::Usage)
|
||||
out << make_usage(opt);
|
||||
else
|
||||
detail::format_help(out, make_name(opt, mode) + make_opts(opt), make_desc(opt), column_width_);
|
||||
return out.str();
|
||||
if(!group.empty() && !grouped_items.empty()) {
|
||||
out << make_group(group, grouped_items, false);
|
||||
if(group != groups.back())
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///@}
|
||||
};
|
||||
return out.str();
|
||||
}
|
||||
|
||||
class AppFormatter {
|
||||
/// @name Options
|
||||
///@{
|
||||
inline std::string Formatter::make_description(const App *app) const {
|
||||
std::string desc = app->get_description();
|
||||
|
||||
/// The width of the first column
|
||||
size_t column_width_{30};
|
||||
if(!desc.empty())
|
||||
return desc + "\n";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @brief The required help printout labels (user changeable)
|
||||
/// Values are Needs, Excludes, etc.
|
||||
std::map<std::string, std::string> labels_;
|
||||
inline std::string Formatter::make_usage(const App *app, std::string name) const {
|
||||
std::stringstream out;
|
||||
|
||||
///@}
|
||||
/// @name Basic
|
||||
///@{
|
||||
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
|
||||
|
||||
public:
|
||||
AppFormatter() = default;
|
||||
AppFormatter(const AppFormatter &) = default;
|
||||
AppFormatter(AppFormatter &&) = default;
|
||||
///@}
|
||||
/// @name Setters
|
||||
///@{
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
|
||||
/// Set the "REQUIRED" label
|
||||
void label(std::string key, std::string val) { labels_[key] = val; }
|
||||
// Print an Options badge if any options exist
|
||||
std::vector<const Option *> non_pos_options =
|
||||
app->get_options([](const Option *opt) { return opt->nonpositional(); });
|
||||
if(!non_pos_options.empty())
|
||||
out << " [" << get_label("OPTIONS") << "]";
|
||||
|
||||
/// Set the column width
|
||||
void column_width(size_t val) { column_width_ = val; }
|
||||
// Positionals need to be listed here
|
||||
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
|
||||
|
||||
///@}
|
||||
/// @name Getters
|
||||
///@{
|
||||
// Print out positionals if any are left
|
||||
if(!positionals.empty()) {
|
||||
// Convert to help names
|
||||
std::vector<std::string> positional_names(positionals.size());
|
||||
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
|
||||
return make_option_usage(opt);
|
||||
});
|
||||
|
||||
/// Get the current value of a name (REQUIRED, etc.)
|
||||
std::string get_label(std::string key) const {
|
||||
if(labels_.find(key) == labels_.end())
|
||||
return key;
|
||||
else
|
||||
return labels_.at(key);
|
||||
out << " " << detail::join(positional_names, " ");
|
||||
}
|
||||
|
||||
/// Get the current column width
|
||||
size_t get_column_width() const { return column_width_; }
|
||||
// Add a marker if subcommands are expected or optional
|
||||
if(!app->get_subcommands({}).empty()) {
|
||||
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
|
||||
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
|
||||
: "SUBCOMMANDS")
|
||||
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
|
||||
}
|
||||
|
||||
/// @name Overridables
|
||||
///@{
|
||||
out << std::endl;
|
||||
|
||||
/// This prints out a group of options
|
||||
virtual std::string make_group(std::string group, std::vector<const Option *> opts, OptionFormatMode mode) const;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// This prints out all the groups of options
|
||||
virtual std::string make_groups(const App *app, AppFormatMode mode) const;
|
||||
inline std::string Formatter::make_footer(const App *app) const {
|
||||
std::string footer = app->get_footer();
|
||||
if(!footer.empty())
|
||||
return footer + "\n";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// This prints out all the subcommands
|
||||
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
|
||||
inline std::string Formatter::operator()(const App *app, std::string name, AppFormatMode mode) const {
|
||||
|
||||
/// This prints out a subcommand
|
||||
virtual std::string make_subcommand(const App *sub) const;
|
||||
std::stringstream out;
|
||||
if(mode == AppFormatMode::Normal) {
|
||||
out << make_description(app);
|
||||
out << make_usage(app, name);
|
||||
out << make_groups(app, mode);
|
||||
out << make_subcommands(app, mode);
|
||||
out << make_footer(app);
|
||||
} else if(mode == AppFormatMode::Sub) {
|
||||
out << make_expanded(app);
|
||||
} else if(mode == AppFormatMode::All) {
|
||||
out << make_description(app);
|
||||
out << make_usage(app, name);
|
||||
out << make_groups(app, mode);
|
||||
out << make_subcommands(app, mode);
|
||||
}
|
||||
|
||||
/// This prints out a subcommand in help-all
|
||||
virtual std::string make_expanded(const App *sub) const;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// This prints out all the groups of options
|
||||
virtual std::string make_footer(const App *app) const;
|
||||
inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
|
||||
/// This displays the description line
|
||||
virtual std::string make_description(const App *app) const;
|
||||
std::vector<const App *> subcommands = app->get_subcommands({});
|
||||
|
||||
/// This displays the usage line
|
||||
virtual std::string make_usage(const App *app, std::string name) const;
|
||||
// Make a list in definition order of the groups seen
|
||||
std::vector<std::string> subcmd_groups_seen;
|
||||
for(const App *com : subcommands) {
|
||||
std::string group_key = com->get_group();
|
||||
if(!group_key.empty() &&
|
||||
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
|
||||
return detail::to_lower(a) == detail::to_lower(group_key);
|
||||
}) == subcmd_groups_seen.end())
|
||||
subcmd_groups_seen.push_back(group_key);
|
||||
}
|
||||
|
||||
/// This puts everything together
|
||||
virtual std::string operator()(const App *, std::string, AppFormatMode) const;
|
||||
///@}
|
||||
};
|
||||
// For each group, filter out and print subcommands
|
||||
for(const std::string &group : subcmd_groups_seen) {
|
||||
out << "\n" << group << ":\n";
|
||||
if(mode == AppFormatMode::All)
|
||||
out << "\n";
|
||||
std::vector<const App *> subcommands_group = app->get_subcommands(
|
||||
[&group](const App *app) { return detail::to_lower(app->get_group()) == detail::to_lower(group); });
|
||||
for(const App *new_com : subcommands_group) {
|
||||
if(mode != AppFormatMode::All) {
|
||||
out << make_subcommand(new_com);
|
||||
} else {
|
||||
out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
|
||||
if(new_com != subcommands_group.back())
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_subcommand(const App *sub) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_expanded(const App *sub) const {
|
||||
std::stringstream out;
|
||||
out << sub->get_name() << "\n " << sub->get_description();
|
||||
out << make_groups(sub, AppFormatMode::Sub);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
|
||||
if(is_positional)
|
||||
return opt->get_name(true, false);
|
||||
else
|
||||
return opt->get_name(false, true);
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_opts(const Option *opt) const {
|
||||
std::stringstream out;
|
||||
|
||||
if(opt->get_type_size() != 0) {
|
||||
if(!opt->get_typeval().empty())
|
||||
out << " " << get_label(opt->get_typeval());
|
||||
if(!opt->get_defaultval().empty())
|
||||
out << "=" << opt->get_defaultval();
|
||||
if(opt->get_expected() > 1)
|
||||
out << " x " << opt->get_expected();
|
||||
if(opt->get_expected() == -1)
|
||||
out << " ...";
|
||||
if(opt->get_required())
|
||||
out << " " << get_label("REQUIRED");
|
||||
}
|
||||
if(!opt->get_envname().empty())
|
||||
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
|
||||
if(!opt->get_needs().empty()) {
|
||||
out << " " << get_label("Needs") << ":";
|
||||
for(const Option *op : opt->get_needs())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
if(!opt->get_excludes().empty()) {
|
||||
out << " " << get_label("Excludes") << ":";
|
||||
for(const Option *op : opt->get_excludes())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
|
||||
|
||||
inline std::string Formatter::make_option_usage(const Option *opt) const {
|
||||
// Note that these are positionals usages
|
||||
std::stringstream out;
|
||||
out << make_option_name(opt, true);
|
||||
|
||||
if(opt->get_expected() > 1)
|
||||
out << "(" << std::to_string(opt->get_expected()) << "x)";
|
||||
else if(opt->get_expected() < 0)
|
||||
out << "...";
|
||||
|
||||
return opt->get_required() ? out.str() : "[" + out.str() + "]";
|
||||
}
|
||||
|
||||
} // namespace CLI
|
||||
|
127
include/CLI/FormatterFwd.hpp
Normal file
127
include/CLI/FormatterFwd.hpp
Normal file
@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "CLI/StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
class Option;
|
||||
class App;
|
||||
|
||||
/// This enum signifies the type of help requested
|
||||
///
|
||||
/// This is passed in by App; all user classes must accept this as
|
||||
/// the second argument.
|
||||
|
||||
enum class AppFormatMode {
|
||||
Normal, //< The normal, detailed help
|
||||
All, //< A fully expanded help
|
||||
Sub, //< Used when printed as part of expanded subcommand
|
||||
};
|
||||
|
||||
class Formatter {
|
||||
/// @name Options
|
||||
///@{
|
||||
|
||||
/// The width of the first column
|
||||
size_t column_width_{30};
|
||||
|
||||
/// @brief The required help printout labels (user changeable)
|
||||
/// Values are Needs, Excludes, etc.
|
||||
std::map<std::string, std::string> labels_;
|
||||
|
||||
///@}
|
||||
/// @name Basic
|
||||
///@{
|
||||
|
||||
public:
|
||||
Formatter() = default;
|
||||
Formatter(const Formatter &) = default;
|
||||
Formatter(Formatter &&) = default;
|
||||
///@}
|
||||
/// @name Setters
|
||||
///@{
|
||||
|
||||
/// Set the "REQUIRED" label
|
||||
void label(std::string key, std::string val) { labels_[key] = val; }
|
||||
|
||||
/// Set the column width
|
||||
void column_width(size_t val) { column_width_ = val; }
|
||||
|
||||
///@}
|
||||
/// @name Getters
|
||||
///@{
|
||||
|
||||
/// Get the current value of a name (REQUIRED, etc.)
|
||||
std::string get_label(std::string key) const {
|
||||
if(labels_.find(key) == labels_.end())
|
||||
return key;
|
||||
else
|
||||
return labels_.at(key);
|
||||
}
|
||||
|
||||
/// Get the current column width
|
||||
size_t get_column_width() const { return column_width_; }
|
||||
|
||||
/// @name Overridables
|
||||
///@{
|
||||
|
||||
/// This prints out a group of options
|
||||
virtual std::string make_group(std::string group, std::vector<const Option *> opts, bool is_positional) const;
|
||||
|
||||
/// This prints out all the groups of options
|
||||
virtual std::string make_groups(const App *app, AppFormatMode mode) const;
|
||||
|
||||
/// This prints out all the subcommands
|
||||
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
|
||||
|
||||
/// This prints out a subcommand
|
||||
virtual std::string make_subcommand(const App *sub) const;
|
||||
|
||||
/// This prints out a subcommand in help-all
|
||||
virtual std::string make_expanded(const App *sub) const;
|
||||
|
||||
/// This prints out all the groups of options
|
||||
virtual std::string make_footer(const App *app) const;
|
||||
|
||||
/// This displays the description line
|
||||
virtual std::string make_description(const App *app) const;
|
||||
|
||||
/// This displays the usage line
|
||||
virtual std::string make_usage(const App *app, std::string name) const;
|
||||
|
||||
/// This puts everything together
|
||||
virtual std::string operator()(const App *, std::string, AppFormatMode) const;
|
||||
|
||||
///@}
|
||||
/// @name Options
|
||||
///@{
|
||||
|
||||
virtual std::string make_option(const Option *opt, bool is_positional) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(
|
||||
out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// @brief This is the name part of an option, Default: left column
|
||||
virtual std::string make_option_name(const Option *, bool) const;
|
||||
|
||||
/// @brief This is the options part of the name, Default: combined into left column
|
||||
virtual std::string make_option_opts(const Option *) const;
|
||||
|
||||
/// @brief This is the description. Default: Right column, on new line if left column too large
|
||||
virtual std::string make_option_desc(const Option *) const;
|
||||
|
||||
/// @brief This is used to print the name on the USAGE line
|
||||
virtual std::string make_option_usage(const Option *opt) const;
|
||||
|
||||
///@}
|
||||
};
|
||||
|
||||
} // namespace CLI
|
@ -16,7 +16,6 @@
|
||||
#include "CLI/Macros.hpp"
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
@ -49,17 +48,12 @@ template <typename CRTP> class OptionBase {
|
||||
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
|
||||
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
|
||||
|
||||
/// @brief A formatter to print out help
|
||||
/// Used by the default App help formatter.
|
||||
std::function<std::string(const Option *, OptionFormatMode)> formatter_{OptionFormatter()};
|
||||
|
||||
template <typename T> void copy_to(T *other) const {
|
||||
other->group(group_);
|
||||
other->required(required_);
|
||||
other->ignore_case(ignore_case_);
|
||||
other->configurable(configurable_);
|
||||
other->multi_option_policy(multi_option_policy_);
|
||||
other->formatter(formatter_);
|
||||
}
|
||||
|
||||
public:
|
||||
@ -81,12 +75,6 @@ template <typename CRTP> class OptionBase {
|
||||
/// Support Plumbum term
|
||||
CRTP *mandatory(bool value = true) { return required(value); }
|
||||
|
||||
/// Set a formatter for this option
|
||||
CRTP *formatter(std::function<std::string(const Option *, OptionFormatMode)> value) {
|
||||
formatter_ = value;
|
||||
return static_cast<CRTP *>(this);
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
/// Get the group of this option
|
||||
@ -491,17 +479,6 @@ class Option : public OptionBase<Option> {
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Call this with a OptionFormatMode to run the currently configured help formatter.
|
||||
///
|
||||
/// Changed in Version 1.6:
|
||||
///
|
||||
/// * `help_positinoal` MOVED TO `help_usage` (name not included) or Usage mode
|
||||
/// * `help_name` CHANGED to `help_name` with different true/false flags
|
||||
/// * `pname` with type info MOVED to `help_name`
|
||||
/// * `help_aftername()` MOVED to `help_opts()`
|
||||
/// * Instead of `opt->help_mode()` use `opt->help(mode)`
|
||||
std::string help(OptionFormatMode mode) const { return formatter_(this, mode); }
|
||||
|
||||
///@}
|
||||
/// @name Parser tools
|
||||
///@{
|
||||
|
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "CLI/Option.hpp"
|
||||
#include "CLI/Formatter.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
inline std::string OptionFormatter::make_name(const Option *opt, OptionFormatMode mode) const {
|
||||
if(mode == OptionFormatMode::Optional)
|
||||
return opt->get_name(false, true);
|
||||
else
|
||||
return opt->get_name(true, false);
|
||||
}
|
||||
|
||||
inline std::string OptionFormatter::make_opts(const Option *opt) const {
|
||||
std::stringstream out;
|
||||
|
||||
if(opt->get_type_size() != 0) {
|
||||
if(!opt->get_typeval().empty())
|
||||
out << " " << get_label(opt->get_typeval());
|
||||
if(!opt->get_defaultval().empty())
|
||||
out << "=" << opt->get_defaultval();
|
||||
if(opt->get_expected() > 1)
|
||||
out << " x " << opt->get_expected();
|
||||
if(opt->get_expected() == -1)
|
||||
out << " ...";
|
||||
if(opt->get_required())
|
||||
out << " " << get_label("REQUIRED");
|
||||
}
|
||||
if(!opt->get_envname().empty())
|
||||
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
|
||||
if(!opt->get_needs().empty()) {
|
||||
out << " " << get_label("Needs") << ":";
|
||||
for(const Option *op : opt->get_needs())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
if(!opt->get_excludes().empty()) {
|
||||
out << " " << get_label("Excludes") << ":";
|
||||
for(const Option *op : opt->get_excludes())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string OptionFormatter::make_desc(const Option *opt) const { return opt->get_description(); }
|
||||
|
||||
inline std::string OptionFormatter::make_usage(const Option *opt) const {
|
||||
// Note that these are positionals usages
|
||||
std::stringstream out;
|
||||
out << make_name(opt, OptionFormatMode::Usage);
|
||||
|
||||
if(opt->get_expected() > 1)
|
||||
out << "(" << std::to_string(opt->get_expected()) << "x)";
|
||||
else if(opt->get_expected() < 0)
|
||||
out << "...";
|
||||
|
||||
return opt->get_required() ? out.str() : "[" + out.str() + "]";
|
||||
}
|
||||
|
||||
} // namespace CLI
|
@ -25,10 +25,10 @@ TEST(Formatter, Nothing) {
|
||||
TEST(Formatter, OptCustomize) {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
CLI::OptionFormatter optfmt;
|
||||
CLI::Formatter optfmt;
|
||||
optfmt.column_width(25);
|
||||
optfmt.label("REQUIRED", "(MUST HAVE)");
|
||||
app.option_defaults()->formatter(optfmt);
|
||||
app.formatter(optfmt);
|
||||
|
||||
int v;
|
||||
app.add_option("--opt", v, "Something")->required();
|
||||
@ -40,7 +40,7 @@ TEST(Formatter, OptCustomize) {
|
||||
"My prog\n"
|
||||
"Usage: [OPTIONS]\n\n"
|
||||
"Options:\n"
|
||||
" -h,--help Print this help message and exit\n"
|
||||
" -h,--help Print this help message and exit\n"
|
||||
" --opt INT (MUST HAVE) Something\n");
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ TEST(Formatter, AptCustomize) {
|
||||
CLI::App app{"My prog"};
|
||||
app.add_subcommand("subcom1", "This");
|
||||
|
||||
CLI::AppFormatter appfmt;
|
||||
CLI::Formatter appfmt;
|
||||
appfmt.column_width(20);
|
||||
appfmt.label("Usage", "Run");
|
||||
app.formatter(appfmt);
|
||||
@ -60,7 +60,7 @@ TEST(Formatter, AptCustomize) {
|
||||
"My prog\n"
|
||||
"Run: [OPTIONS] [SUBCOMMAND]\n\n"
|
||||
"Options:\n"
|
||||
" -h,--help Print this help message and exit\n\n"
|
||||
" -h,--help Print this help message and exit\n\n"
|
||||
"Subcommands:\n"
|
||||
" subcom1 This\n"
|
||||
" subcom2 This\n");
|
||||
|
@ -570,7 +570,7 @@ TEST(THelp, RequiredPrintout) {
|
||||
int x;
|
||||
app.add_option("-a,--alpha", x)->required();
|
||||
|
||||
EXPECT_THAT(app.help(), HasSubstr("(REQUIRED)"));
|
||||
EXPECT_THAT(app.help(), HasSubstr(" REQUIRED"));
|
||||
}
|
||||
|
||||
TEST(THelp, GroupOrder) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user