mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-04 15:14:39 +00:00
Added support for metavar and improved help/usage based on #187
This commit is contained in:
parent
5a83edd3c4
commit
3b9df0b1e7
86
README.md
86
README.md
@ -338,15 +338,15 @@ The square of 4 is 16
|
||||
|
||||
```
|
||||
foo@bar:/home/dev/$ ./main --help
|
||||
Usage: main [options] square
|
||||
Usage: main [-h] [--verbose] square
|
||||
|
||||
Positional arguments:
|
||||
square display the square of a given number
|
||||
square display the square of a given number
|
||||
|
||||
Optional arguments:
|
||||
-h --help shows help message and exits [default: false]
|
||||
-v --version prints version information and exits [default: false]
|
||||
--verbose [default: false]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
--verbose
|
||||
```
|
||||
|
||||
You may also get the help message in string via `program.help().str()`.
|
||||
@ -357,30 +357,36 @@ You may also get the help message in string via `program.help().str()`.
|
||||
information. `ArgumentParser::add_epilog` will add text after all other help output.
|
||||
|
||||
```cpp
|
||||
argparse::ArgumentParser program("main");
|
||||
#include <argparse/argparse.hpp>
|
||||
|
||||
program.add_argument("thing")
|
||||
.help("Thing to use.");
|
||||
program.add_description("Forward a thing to the next member.");
|
||||
program.add_epilog("Possible things include betingalw, chiz, and res.");
|
||||
int main(int argc, char *argv[]) {
|
||||
argparse::ArgumentParser program("main");
|
||||
program.add_argument("thing").help("Thing to use.").metavar("THING");
|
||||
program.add_argument("--member").help("The alias for the member to pass to.").metavar("ALIAS");
|
||||
program.add_argument("--verbose").default_value(false).implicit_value(true);
|
||||
|
||||
program.parse_args(argc, argv);
|
||||
program.add_description("Forward a thing to the next member.");
|
||||
program.add_epilog("Possible things include betingalw, chiz, and res.");
|
||||
|
||||
std::cout << program << std::endl;
|
||||
program.parse_args(argc, argv);
|
||||
|
||||
std::cout << program << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
foo@bar:/home/dev/$ ./main --help
|
||||
Usage: main thing
|
||||
Usage: main [-h] [--member ALIAS] [--verbose] THING
|
||||
|
||||
Forward a thing to the next member.
|
||||
|
||||
Positional arguments:
|
||||
thing Thing to use.
|
||||
THING Thing to use.
|
||||
|
||||
Optional arguments:
|
||||
-h --help shows help message and exits [default: false]
|
||||
-v --version prints version information and exits [default: false]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
--member ALIAS The alias for the member to pass to.
|
||||
--verbose
|
||||
|
||||
Possible things include betingalw, chiz, and res.
|
||||
```
|
||||
@ -710,12 +716,14 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// git add subparser
|
||||
argparse::ArgumentParser add_command("add");
|
||||
add_command.add_description("Add file contents to the index");
|
||||
add_command.add_argument("files")
|
||||
.help("Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.")
|
||||
.remaining();
|
||||
|
||||
// git commit subparser
|
||||
argparse::ArgumentParser commit_command("commit");
|
||||
commit_command.add_description("Record changes to the repository");
|
||||
commit_command.add_argument("-a", "--all")
|
||||
.help("Tell the command to automatically stage files that have been modified and deleted.")
|
||||
.default_value(false)
|
||||
@ -726,6 +734,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// git cat-file subparser
|
||||
argparse::ArgumentParser catfile_command("cat-file");
|
||||
catfile_command.add_description("Provide content or type and size information for repository objects");
|
||||
catfile_command.add_argument("-t")
|
||||
.help("Instead of the content, show the object type identified by <object>.");
|
||||
|
||||
@ -734,7 +743,9 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// git submodule subparser
|
||||
argparse::ArgumentParser submodule_command("submodule");
|
||||
submodule_command.add_description("Initialize, update or inspect submodules");
|
||||
argparse::ArgumentParser submodule_update_command("update");
|
||||
submodule_update_command.add_description("Update the registered submodules to match what the superproject expects");
|
||||
submodule_update_command.add_argument("--init")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
@ -763,41 +774,52 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
```console
|
||||
foo@bar:/home/dev/$ ./git --help
|
||||
Usage: git [options] <command> [<args>]
|
||||
Usage: git [-h] {add,cat-file,commit,submodule}
|
||||
|
||||
Optional arguments:
|
||||
-h --help shows help message and exits [default: false]
|
||||
-v --version prints version information and exits [default: false]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
|
||||
Subcommands:
|
||||
add Add file contents to the index
|
||||
cat-file Provide content or type and size information for repository objects
|
||||
commit Record changes to the repository
|
||||
submodule Initialize, update or inspect submodules
|
||||
add Add file contents to the index
|
||||
cat-file Provide content or type and size information for repository objects
|
||||
commit Record changes to the repository
|
||||
submodule Initialize, update or inspect submodules
|
||||
|
||||
foo@bar:/home/dev/$ ./git add --help
|
||||
Usage: git add [options] files
|
||||
Usage: add [-h] files
|
||||
|
||||
Add file contents to the index
|
||||
|
||||
Positional arguments:
|
||||
files Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.
|
||||
files Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.
|
||||
|
||||
Optional arguments:
|
||||
-h --help shows help message and exits [default: false]
|
||||
-v --version prints version information and exits [default: false]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
|
||||
foo@bar:/home/dev/$ ./git commit --help
|
||||
Usage: commit [-h] [--all] [--message VAR]
|
||||
|
||||
Record changes to the repository
|
||||
|
||||
Optional arguments:
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
-a, --all Tell the command to automatically stage files that have been modified and deleted.
|
||||
-m, --message Use the given <msg> as the commit message.
|
||||
|
||||
foo@bar:/home/dev/$ ./git submodule --help
|
||||
Usage: git submodule [options] <command> [<args>]
|
||||
Usage: submodule [-h] {update}
|
||||
|
||||
Initialize, update or inspect submodules
|
||||
|
||||
Optional arguments:
|
||||
-h --help shows help message and exits [default: false]
|
||||
-v --version prints version information and exits [default: false]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
|
||||
Subcommands:
|
||||
update Update the registered submodules to match what the superproject expects
|
||||
update Update the registered submodules to match what the superproject expects
|
||||
```
|
||||
|
||||
When a help message is requested from a subparser, only the help for that particular parser will be printed. The help message will not include parent parser or sibling parser messages.
|
||||
|
@ -36,6 +36,7 @@ SOFTWARE.
|
||||
#include <charconv>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
@ -329,6 +330,21 @@ template <class T> struct parse_number<T, chars_format::fixed> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename StrIt>
|
||||
std::string join(StrIt first, StrIt last, const std::string &separator) {
|
||||
if (first == last) {
|
||||
return "";
|
||||
}
|
||||
std::stringstream value;
|
||||
value << *first;
|
||||
++first;
|
||||
while (first != last) {
|
||||
value << separator << *first;
|
||||
++first;
|
||||
}
|
||||
return value.str();
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
enum class nargs_pattern { optional, any, at_least_one };
|
||||
@ -379,6 +395,11 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
Argument &metavar(std::string metavar) {
|
||||
m_metavar = std::move(metavar);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T> Argument &default_value(T &&value) {
|
||||
m_default_value_repr = details::repr(value);
|
||||
m_default_value = std::forward<T>(value);
|
||||
@ -573,21 +594,82 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_inline_usage() const {
|
||||
std::stringstream usage;
|
||||
// Find the longest variant to show in the usage string
|
||||
std::string longest_name = m_names[0];
|
||||
for (const auto &s : m_names) {
|
||||
if (s.size() > longest_name.size()) {
|
||||
longest_name = s;
|
||||
}
|
||||
}
|
||||
if (!m_is_required) {
|
||||
usage << "[";
|
||||
}
|
||||
usage << longest_name;
|
||||
const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
|
||||
if (m_num_args_range.get_max() > 0) {
|
||||
usage << " " << metavar;
|
||||
if (m_num_args_range.get_max() > 1) {
|
||||
usage << "...";
|
||||
}
|
||||
}
|
||||
if (!m_is_required) {
|
||||
usage << "]";
|
||||
}
|
||||
return usage.str();
|
||||
}
|
||||
|
||||
std::size_t get_arguments_length() const {
|
||||
return std::accumulate(std::begin(m_names), std::end(m_names),
|
||||
std::size_t(0), [](const auto &sum, const auto &s) {
|
||||
return sum + s.size() +
|
||||
1; // +1 for space between names
|
||||
});
|
||||
|
||||
std::size_t names_size = std::accumulate(
|
||||
std::begin(m_names), std::end(m_names), std::size_t(0),
|
||||
[](const auto &sum, const auto &s) { return sum + s.size(); });
|
||||
|
||||
if (is_positional(m_names.front(), m_prefix_chars)) {
|
||||
// A set metavar means this replaces the names
|
||||
if (!m_metavar.empty()) {
|
||||
// Indent and metavar
|
||||
return 2 + m_metavar.size();
|
||||
}
|
||||
|
||||
// Indent and space-separated
|
||||
return 2 + names_size + (m_names.size() - 1);
|
||||
}
|
||||
// Is an option - include both names _and_ metavar
|
||||
// size = text + (", " between names)
|
||||
std::size_t size = names_size + 2 * (m_names.size() - 1);
|
||||
if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) {
|
||||
size += m_metavar.size() + 1;
|
||||
}
|
||||
return size + 2; // indent
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream,
|
||||
const Argument &argument) {
|
||||
std::stringstream name_stream;
|
||||
std::copy(std::begin(argument.m_names), std::end(argument.m_names),
|
||||
std::ostream_iterator<std::string>(name_stream, " "));
|
||||
name_stream << " "; // indent
|
||||
if (argument.is_positional(argument.m_names.front(),
|
||||
argument.m_prefix_chars)) {
|
||||
if (!argument.m_metavar.empty()) {
|
||||
name_stream << argument.m_metavar;
|
||||
} else {
|
||||
name_stream << details::join(argument.m_names.begin(),
|
||||
argument.m_names.end(), " ");
|
||||
}
|
||||
} else {
|
||||
name_stream << details::join(argument.m_names.begin(),
|
||||
argument.m_names.end(), ", ");
|
||||
// If we have a metavar, and one narg - print the metavar
|
||||
if (!argument.m_metavar.empty() &&
|
||||
argument.m_num_args_range == NArgsRange{1, 1}) {
|
||||
name_stream << " " << argument.m_metavar;
|
||||
}
|
||||
}
|
||||
stream << name_stream.str() << "\t" << argument.m_help;
|
||||
if (argument.m_default_value.has_value()) {
|
||||
|
||||
if (argument.m_default_value.has_value() &&
|
||||
argument.m_num_args_range != NArgsRange{0, 0}) {
|
||||
if (!argument.m_help.empty()) {
|
||||
stream << " ";
|
||||
}
|
||||
@ -647,6 +729,12 @@ private:
|
||||
std::size_t get_min() const { return m_min; }
|
||||
|
||||
std::size_t get_max() const { return m_max; }
|
||||
|
||||
bool operator==(const NArgsRange &rhs) const {
|
||||
return rhs.m_min == m_min && rhs.m_max == m_max;
|
||||
}
|
||||
|
||||
bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
|
||||
};
|
||||
|
||||
void throw_nargs_range_validation_error() const {
|
||||
@ -913,6 +1001,7 @@ private:
|
||||
std::vector<std::string> m_names;
|
||||
std::string_view m_used_name;
|
||||
std::string m_help;
|
||||
std::string m_metavar;
|
||||
std::any m_default_value;
|
||||
std::string m_default_value_repr;
|
||||
std::any m_implicit_value;
|
||||
@ -1171,17 +1260,10 @@ public:
|
||||
friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
|
||||
-> std::ostream & {
|
||||
stream.setf(std::ios_base::left);
|
||||
stream << "Usage: " << parser.m_parser_path << " [options] ";
|
||||
for (const auto &argument : parser.m_positional_arguments) {
|
||||
stream << argument.m_names.front() << " ";
|
||||
}
|
||||
|
||||
if (!parser.m_subparser_map.empty()) {
|
||||
stream << (parser.m_positional_arguments.empty() ? "" : " ")
|
||||
<< "<command> [<args>]";
|
||||
}
|
||||
std::size_t longest_arg_length = parser.get_length_of_longest_argument();
|
||||
stream << "\n\n";
|
||||
auto longest_arg_length = parser.get_length_of_longest_argument();
|
||||
|
||||
stream << parser.usage() << "\n\n";
|
||||
|
||||
if (!parser.m_description.empty()) {
|
||||
stream << parser.m_description << "\n\n";
|
||||
@ -1212,8 +1294,10 @@ public:
|
||||
: "\n")
|
||||
<< "Subcommands:\n";
|
||||
for (const auto &[command, subparser] : parser.m_subparser_map) {
|
||||
stream.width(static_cast<std::streamsize>(longest_arg_length));
|
||||
stream << command << "\t" << subparser->get().m_description << "\n";
|
||||
stream << std::setw(2) << " ";
|
||||
stream << std::setw(static_cast<int>(longest_arg_length - 2))
|
||||
<< command;
|
||||
stream << " " << subparser->get().m_description << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1232,6 +1316,48 @@ public:
|
||||
return out;
|
||||
}
|
||||
|
||||
// Format usage part of help only
|
||||
auto usage() const -> std::string {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "Usage: " << this->m_program_name;
|
||||
|
||||
// Add any options inline here
|
||||
for (const auto &argument : this->m_optional_arguments) {
|
||||
if (argument.m_names[0] == "-v") {
|
||||
continue;
|
||||
} else if (argument.m_names[0] == "-h") {
|
||||
stream << " [-h]";
|
||||
} else {
|
||||
stream << " " << argument.get_inline_usage();
|
||||
}
|
||||
}
|
||||
// Put positional arguments after the optionals
|
||||
for (const auto &argument : this->m_positional_arguments) {
|
||||
if (!argument.m_metavar.empty()) {
|
||||
stream << " " << argument.m_metavar;
|
||||
} else {
|
||||
stream << " " << argument.m_names.front();
|
||||
}
|
||||
}
|
||||
// Put subcommands after positional arguments
|
||||
if (!m_subparser_map.empty()) {
|
||||
stream << " {";
|
||||
std::size_t i{0};
|
||||
for (const auto &[command, unused] : m_subparser_map) {
|
||||
if (i == 0) {
|
||||
stream << command;
|
||||
} else {
|
||||
stream << "," << command;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
stream << "}";
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
// Printing the one and only help message
|
||||
// I've stuck with a simple message format, nothing fancy.
|
||||
[[deprecated("Use cout << program; instead. See also help().")]] std::string
|
||||
|
Loading…
Reference in New Issue
Block a user