32 static constexpr std::string_view value =
"value";
37struct MetavarName<std::
string> {
38 static constexpr std::string_view value =
"string";
43struct MetavarName<bool> {
44 static constexpr std::string_view value =
"bool";
49struct MetavarName<float> {
50 static constexpr std::string_view value =
"float";
55struct MetavarName<double> {
56 static constexpr std::string_view value =
"double";
61struct MetavarName<std::filesystem::path> {
62 static constexpr std::string_view value =
"path";
66template <std::
signed_
integral T>
67struct MetavarName<T> {
68 static constexpr std::string_view value =
"int";
72template <std::
unsigned_
integral T>
73struct MetavarName<T> {
74 static constexpr std::string_view value =
"uint";
89 using type = std::remove_cvref_t<T>;
90 static constexpr bool variadic =
false;
95struct UnwrapStorage<std::
optional<T>> {
96 using type = std::remove_cvref_t<T>;
97 static constexpr bool variadic =
false;
102struct UnwrapStorage<std::vector<T>> {
103 using type = std::remove_cvref_t<T>;
104 static constexpr bool variadic =
true;
109using unwrap_storage_t =
typename UnwrapStorage<std::remove_cvref_t<T>>::type;
123auto metavar_for() -> std::string {
124 using Storage = std::remove_cvref_t<T>;
125 using Base = unwrap_storage_t<Storage>;
126 std::string out =
"<";
127 out += MetavarName<Base>::value;
129 if constexpr (UnwrapStorage<Storage>::variadic) {
130 out.insert(out.size() - 1,
"...");
147auto command_usage_suffix(T&
value) -> std::string {
149 bool has_options =
false;
150 bool has_positionals =
false;
151 bool has_commands =
false;
154 [&](
auto&... fields) ->
auto {
157 using F = std::remove_cvref_t<
decltype(fields)>;
158 if constexpr (std::derived_from<F, OptionTag>) {
159 if (!fields.hidden()) has_options =
true;
160 }
else if constexpr (std::derived_from<F, PositionalTag>) {
161 if (!fields.hidden()) has_positionals =
true;
162 }
else if constexpr (std::derived_from<F, CommandTag>) {
163 if (!fields.hidden()) has_commands =
true;
173 if (has_positionals) {
196auto field_usage_label() -> std::string {
197 using F = std::remove_cvref_t<T>;
198 if constexpr (std::derived_from<F, OptionTag>) {
200 if constexpr (F::short_name !=
'\0') {
202 out += F::short_name;
206 out += F::name.view();
207 if constexpr (!(F::nargs.min == 0 && F::nargs.max == 0)) {
209 out += metavar_for<typename F::value_type>();
212 }
else if constexpr (std::derived_from<F, PositionalTag>) {
213 return metavar_for<typename F::value_type>();
214 }
else if constexpr (std::derived_from<F, CommandTag>) {
215 return std::string(F::command_name());
217 return std::string{};
223auto styled_field_usage_label(
const AnsiStyle& style) -> std::string {
224 using F = std::remove_cvref_t<T>;
225 const auto reset = style.reset();
226 if constexpr (std::derived_from<F, OptionTag>) {
228 if constexpr (F::short_name !=
'\0') {
229 out += style.option();
231 out += F::short_name;
235 out += style.option();
237 out += F::name.view();
239 if constexpr (!(F::nargs.min == 0 && F::nargs.max == 0)) {
241 out += style.metavar();
242 out += metavar_for<typename F::value_type>();
246 }
else if constexpr (std::derived_from<F, PositionalTag>) {
247 std::string out = std::string(style.metavar());
248 out += metavar_for<typename F::value_type>();
251 }
else if constexpr (std::derived_from<F, CommandTag>) {
252 std::string out = std::string(style.command());
253 out += F::command_name();
257 return std::string{};
270inline auto split_lines(std::string_view text) -> std::vector<std::string> {
271 std::vector<std::string> lines;
272 std::size_t start = 0;
273 while (start <= text.size()) {
274 const auto end = text.find(
'\n', start);
275 if (end == std::string_view::npos) {
276 lines.emplace_back(text.substr(start));
279 lines.emplace_back(text.substr(start, end - start));
283 lines.emplace_back();
299inline auto append_wrapped_description(std::string& out,
300 std::string_view description,
301 std::size_t indent) ->
void {
302 if (description.empty()) {
307 auto lines = split_lines(description);
308 out += lines.front();
310 for (std::size_t i = 1; i < lines.size(); ++i) {
311 out.append(indent,
' ');
336template <
class T,
class Pred,
class Render>
337auto append_section(std::string& out, std::string_view title,
338 std::string_view heading_color, std::string_view reset_color,
339 T&
value, Pred pred, Render render) ->
void {
340 std::vector<std::string> rows;
341 std::size_t width = 0;
344 [&](
auto&... fields) ->
auto {
347 using F = std::remove_cvref_t<
decltype(fields)>;
348 if constexpr (pred.template operator()<F>()) {
349 const auto label = field_usage_label<F>();
350 width = std::max(width, label.size());
351 rows.push_back(render(fields, label));
363 out += heading_color;
367 for (
auto& row : rows) {
388template <
class Field>
389consteval auto relation_field_name() -> std::string_view {
390 using F = std::remove_cvref_t<Field>;
391 if constexpr (std::derived_from<F, OptionTag> ||
392 std::derived_from<F, CommandTag>) {
393 return F::name.view();
399inline auto relation_name_list(NameList<> names) -> std::string {
401 for (std::size_t i = 0; i < names.size; ++i) {
402 if (i != 0) out +=
", ";
404 out += names.values[i];
410constexpr auto relation_group(std::string_view name) -> NameList<> {
412 if constexpr (detail::HasRelations<T>) {
414 [&](
auto... rels) ->
auto {
417 using R = std::remove_cvref_t<
decltype(rels)>;
418 if constexpr (std::same_as<R, GroupRelation>) {
419 if (rels.name == name) result = rels.members;
430constexpr auto relation_operand_contains(std::string_view operand,
431 std::string_view field_name) ->
bool {
432 if (operand == field_name)
return true;
433 const auto group = relation_group<T>(operand);
434 for (
auto member :
group) {
435 if (member == field_name)
return true;
441auto relation_operand_label(std::string_view operand) -> std::string {
442 if constexpr (detail::HasRelations<T>) {
443 if (!relation_group<T>(operand).empty()) {
444 return std::string(operand);
447 return "--" + std::string(operand);
450template <
class T,
class Field>
451auto relation_help_metadata() -> std::vector<std::string> {
452 std::vector<std::string> metadata;
453 constexpr auto field_name = relation_field_name<Field>();
454 if constexpr (field_name.empty()) {
456 }
else if constexpr (detail::HasRelations<T>) {
458 [&](
auto... rels) ->
auto {
461 using R = std::remove_cvref_t<
decltype(rels)>;
462 if constexpr (std::same_as<R, GroupRelation>) {
464 }
else if constexpr (std::same_as<R, ConflictsRelation>) {
465 if (relation_operand_contains<T>(rels.left, field_name)) {
466 metadata.emplace_back(
"conflicts: " +
467 relation_operand_label<T>(rels.right));
469 if (relation_operand_contains<T>(rels.right, field_name)) {
470 metadata.emplace_back(
"conflicts: " +
471 relation_operand_label<T>(rels.left));
473 }
else if constexpr (std::same_as<R, DependsOnRelation>) {
474 if (relation_operand_contains<T>(rels.source, field_name)) {
475 metadata.emplace_back(
476 "requires: " + relation_operand_label<T>(rels.target));
478 if (relation_operand_contains<T>(rels.target, field_name)) {
479 metadata.emplace_back(
481 relation_operand_label<T>(rels.source));
494template <
class T,
class Field>
495auto help_metadata(Field& field) -> std::vector<std::string> {
496 std::vector<std::string> metadata;
497 if constexpr (std::derived_from<std::remove_cvref_t<Field>, OptionTag> ||
498 std::derived_from<std::remove_cvref_t<Field>, PositionalTag>) {
500 metadata.emplace_back(
"required");
502 if (!field.env().empty()) {
503 metadata.emplace_back(
"env: " + std::string(field.env()));
505 if (
const auto& default_value = field.default_value()) {
506 using Value = std::remove_cvref_t<
decltype(*default_value)>;
507 if constexpr (
requires(std::ostream& os,
const Value&
value) {
510 std::ostringstream out;
511 out << *default_value;
512 metadata.emplace_back(
"default: " + out.str());
516 if (!field.deprecated().empty()) {
517 metadata.emplace_back(
"deprecated: " + std::string(field.deprecated()));
519 auto relation_metadata = relation_help_metadata<T, Field>();
520 metadata.insert(metadata.end(),
521 std::make_move_iterator(relation_metadata.begin()),
522 std::make_move_iterator(relation_metadata.end()));
526inline auto append_help_metadata(std::string& out,
527 const std::vector<std::string>& metadata,
528 const AnsiStyle& style) ->
void {
529 if (metadata.empty()) {
532 out += style.metadata();
534 for (std::size_t i = 0; i < metadata.size(); ++i) {
541 out += style.reset();
544inline auto append_row_description(std::string& out,
545 std::string_view description,
546 const std::vector<std::string>& metadata,
547 const AnsiStyle& style, std::size_t indent)
549 auto lines = split_lines(description);
550 out += lines.front();
551 append_help_metadata(out, metadata, style);
553 for (std::size_t i = 1; i < lines.size(); ++i) {
554 out.append(indent,
' ');
561auto append_group_requirements(std::string& row, std::string_view group_name)
563 if constexpr (detail::HasRelations<T>) {
565 [&](
auto... rels) ->
auto {
568 using R = std::remove_cvref_t<
decltype(rels)>;
569 if constexpr (std::same_as<R, DependsOnRelation>) {
570 if (rels.source == group_name) {
571 row +=
" requires: ";
572 row += relation_operand_label<T>(rels.target);
584auto relation_option_group_row(
const GroupRelation&
group,
585 const AnsiStyle& style) -> std::string {
586 std::string row =
" ";
587 row += style.group();
589 row += style.reset();
591 row += relation_name_list(
group.members);
592 row +=
" must be used together\n";
593 append_group_requirements<T>(row,
group.name);
598auto relation_option_group_rows(
const AnsiStyle& style)
599 -> std::vector<std::string> {
600 std::vector<std::string> rows;
601 if constexpr (detail::HasRelations<T>) {
603 [&](
auto... rels) ->
auto {
606 using R = std::remove_cvref_t<
decltype(rels)>;
607 if constexpr (std::same_as<R, GroupRelation>) {
608 rows.push_back(relation_option_group_row<T>(rels, style));
618template <
class T,
class Field>
619auto render_help_row(Field& field, std::string_view label,
620 std::string_view styled_label, std::size_t width,
621 const AnsiStyle& style) -> std::string {
622 std::string out =
" ";
624 const auto metadata = help_metadata<T>(field);
625 if (!field.help_text().empty() || !metadata.empty()) {
626 out.append(width - label.size() + 2,
' ');
627 append_row_description(out, field.help_text(), metadata, style, width + 4);
646auto find_description(T&
value) -> std::string_view {
647 std::string_view description;
649 [&](
auto&... fields) ->
auto {
652 using F = std::remove_cvref_t<
decltype(fields)>;
653 if constexpr (std::derived_from<F, DescriptionTag>) {
654 if (description.empty()) {
655 description = fields;
688auto format_help_impl(T&
value, std::string_view program_name,
690 std::string_view command_path,
693 const auto style = resolve_color(color_mode, palette);
694 const std::string heading = std::string(style.heading());
695 const std::string reset = std::string(style.reset());
697 std::string full_program = std::string(program_name);
698 if (!command_path.empty()) {
700 full_program += command_path;
703 std::string out = std::string(style.usage());
708 out += command_usage_suffix(
value);
711 if (
const auto description = find_description(
value); !description.empty()) {
717 std::vector<std::pair<std::string, std::string>> option_rows;
718 std::vector<std::pair<std::string, std::string>> positional_rows;
719 std::vector<std::pair<std::string, std::string>> command_rows;
720 std::size_t option_width = 0;
721 std::size_t positional_width = 0;
722 std::size_t command_width = 0;
725 [&](
auto&... fields) ->
auto {
728 using F = std::remove_cvref_t<
decltype(fields)>;
729 if constexpr (std::derived_from<F, OptionTag>) {
730 if (fields.hidden())
return;
731 auto label = field_usage_label<F>();
732 option_width = std::max(option_width, label.size());
733 option_rows.emplace_back(std::move(label), std::string{});
734 }
else if constexpr (std::derived_from<F, PositionalTag>) {
735 if (fields.hidden())
return;
736 auto label = field_usage_label<F>();
737 positional_width = std::max(positional_width, label.size());
738 positional_rows.emplace_back(std::move(label), std::string{});
739 }
else if constexpr (std::derived_from<F, CommandTag>) {
740 if (fields.hidden())
return;
741 auto label = field_usage_label<F>();
742 command_width = std::max(command_width, label.size());
743 command_rows.emplace_back(std::move(label), std::string{});
751 [&](std::string_view title,
752 const std::vector<std::pair<std::string, std::string>>& rows) ->
auto {
761 for (
const auto& [label, row] : rows) {
768 positional_rows.clear();
769 command_rows.clear();
771 [&](
auto&... fields) ->
auto {
774 using F = std::remove_cvref_t<
decltype(fields)>;
775 if constexpr (std::derived_from<F, OptionTag>) {
776 if (fields.hidden())
return;
777 auto label = field_usage_label<F>();
778 auto styled_label = styled_field_usage_label<F>(style);
779 option_rows.emplace_back(
780 label, render_help_row<T>(fields, label, styled_label,
781 option_width, style));
782 }
else if constexpr (std::derived_from<F, PositionalTag>) {
783 if (fields.hidden())
return;
784 auto label = field_usage_label<F>();
785 auto styled_label = styled_field_usage_label<F>(style);
786 positional_rows.emplace_back(
787 label, render_help_row<T>(fields, label, styled_label,
788 positional_width, style));
789 }
else if constexpr (std::derived_from<F, CommandTag>) {
790 if (fields.hidden())
return;
791 auto label = field_usage_label<F>();
792 auto styled_label = styled_field_usage_label<F>(style);
793 command_rows.emplace_back(
794 label, render_help_row<T>(fields, label, styled_label,
795 command_width, style));
802 append_rows(
"Options:", option_rows);
803 append_rows(
"Positional arguments:", positional_rows);
804 append_rows(
"Commands:", command_rows);
806 const auto option_group_rows = relation_option_group_rows<T>(style);
807 if (!option_group_rows.empty()) {
810 out +=
"Option groups:";
813 for (
const auto& row : option_group_rows) {
818 if (recurse && !command_rows.empty()) {
820 [&](
auto&... fields) ->
auto {
823 using F = std::remove_cvref_t<
decltype(fields)>;
824 if constexpr (std::derived_from<F, CommandTag>) {
825 if (fields.hidden())
return;
827 out += format_help_impl<typename F::argument_type>(
828 static_cast<typename F::argument_type&
>(fields),
829 program_name, color_mode,
true, F::command_name(),
858template <ArgumentSpec T>
862 return detail::format_help_impl<T>(
value, program_name, color_mode, recurse,
constexpr HelpPalette default_help_palette
auto format_help(T &value, std::string_view program_name="program", ColorMode color_mode=ColorMode::detect, bool recurse=false, HelpPalette palette=default_help_palette) -> std::string
Generates a complete help string for an argument specification.
constexpr auto optional
Convenience constant for Presence::optional.
constexpr auto group(GroupParams params) -> GroupRelation
constexpr auto as_tuple(T &t)
Converts an aggregate value to a std::tuple of references to its members.
ColorMode
Controls whether ANSI escape codes are emitted by format_help().
ANSI palette used by generated help output.