diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e8d4b4..8b02105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,9 @@ target_include_directories(argparse INTERFACE $ $) +if(ARGPARSE_BUILD_SAMPLES) + add_subdirectory(samples) +endif() if(ARGPARSE_BUILD_TESTS) add_subdirectory(test) diff --git a/README.md b/README.md index 40eb9d9..2a173d9 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ catch (const std::runtime_error& err) { } if (program["--verbose"] == true) { - std::cout << "Verbosity enabled" << std::endl; + std::cout << "Verbosity enabled" << std::endl; } ``` @@ -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 -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 ."); @@ -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] [] +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 as the commit message. foo@bar:/home/dev/$ ./git submodule --help -Usage: git submodule [options] [] +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. diff --git a/clang_format.bash b/clang_format.bash index fa26930..518de18 100755 --- a/clang_format.bash +++ b/clang_format.bash @@ -1 +1 @@ -clang-format -i include/argparse/*.hpp test/*.cpp +clang-format -i include/argparse/*.hpp test/*.cpp samples/*.cpp diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index ba27b43..5e826c5 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -36,6 +36,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -329,6 +330,21 @@ template struct parse_number { } }; +template +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 Argument &default_value(T &&value) { m_default_value_repr = details::repr(value); m_default_value = std::forward(value); @@ -573,29 +594,90 @@ 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(name_stream, " ")); - stream << name_stream.str() << "\t" << argument.m_help; - if (argument.m_default_value.has_value()) { - if (!argument.m_help.empty()) { - 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; + + // print nargs spec + if (!argument.m_help.empty()) { + stream << " "; + } + stream << argument.m_num_args_range; + + if (argument.m_default_value.has_value() && + argument.m_num_args_range != NArgsRange{0, 0}) { stream << "[default: " << argument.m_default_value_repr << "]"; } else if (argument.m_is_required) { - if (!argument.m_help.empty()) { - stream << " "; - } stream << "[required]"; } stream << "\n"; @@ -647,6 +729,29 @@ private: std::size_t get_min() const { return m_min; } std::size_t get_max() const { return m_max; } + + // Print help message + friend auto operator<<(std::ostream &stream, const NArgsRange &range) + -> std::ostream & { + if (range.m_min == range.m_max) { + if (range.m_min != 0 && range.m_min != 1) { + stream << "[nargs: " << range.m_min << "] "; + } + } else { + if (range.m_max == std::numeric_limits::max()) { + stream << "[nargs: " << range.m_min << " or more] "; + } else { + stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; + } + } + return stream; + } + + 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 +1018,7 @@ private: std::vector 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; @@ -1094,8 +1200,8 @@ public: * @throws std::runtime_error in case of any invalid argument */ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - void parse_known_args(int argc, const char *const argv[]) { - parse_known_args({argv, argv + argc}); + auto parse_known_args(int argc, const char *const argv[]) { + return parse_known_args({argv, argv + argc}); } /* Getter for options with default values. @@ -1171,17 +1277,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() ? "" : " ") - << " []"; - } - 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 +1311,10 @@ public: : "\n") << "Subcommands:\n"; for (const auto &[command, subparser] : parser.m_subparser_map) { - stream.width(static_cast(longest_arg_length)); - stream << command << "\t" << subparser->get().m_description << "\n"; + stream << std::setw(2) << " "; + stream << std::setw(static_cast(longest_arg_length - 2)) + << command; + stream << " " << subparser->get().m_description << "\n"; } } @@ -1232,6 +1333,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 diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt new file mode 100644 index 0000000..b146b2d --- /dev/null +++ b/samples/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.6) +project(argparse_samples) + +if(MSVC) + # Force to always compile with W4 + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() +elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + # Update if necessary + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic -Wsign-conversion -Wshadow -Wconversion") +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Disable deprecation for windows +if (WIN32) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) +endif() + +function(add_sample NAME) + ADD_EXECUTABLE(ARGPARSE_SAMPLE_${NAME} ${NAME}.cpp) + INCLUDE_DIRECTORIES("../include" ".") + TARGET_LINK_LIBRARIES(ARGPARSE_SAMPLE_${NAME} PRIVATE argparse::argparse) + set_target_properties(ARGPARSE_SAMPLE_${NAME} PROPERTIES OUTPUT_NAME ${NAME}) +endfunction() + +add_sample(positional_argument) +add_sample(optional_flag_argument) +add_sample(required_optional_argument) +add_sample(is_used) +add_sample(joining_repeated_optional_arguments) +add_sample(repeating_argument_to_increase_value) +add_sample(negative_numbers) +add_sample(description_epilog_metavar) +add_sample(list_of_arguments) +add_sample(compound_arguments) +add_sample(gathering_remaining_arguments) +add_sample(subcommands) +add_sample(parse_known_args) +add_sample(custom_prefix_characters) +add_sample(custom_assignment_characters) \ No newline at end of file diff --git a/samples/compound_arguments.cpp b/samples/compound_arguments.cpp new file mode 100644 index 0000000..c44d02d --- /dev/null +++ b/samples/compound_arguments.cpp @@ -0,0 +1,36 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + program.add_argument("-a").default_value(false).implicit_value(true); + + program.add_argument("-b").default_value(false).implicit_value(true); + + program.add_argument("-c") + .nargs(2) + .default_value(std::vector{0.0f, 0.0f}) + .scan<'g', float>(); + + try { + program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + auto a = program.get("-a"); // true + auto b = program.get("-b"); // true + auto c = program.get>("-c"); // {1.95, 2.47} + + std::cout << "a: " << std::boolalpha << a << "\n"; + std::cout << "b: " << b << "\n"; + if (!c.empty()) { + std::cout << "c: "; + for (auto &v : c) { + std::cout << v << " "; + } + std::cout << std::endl; + } +} \ No newline at end of file diff --git a/samples/custom_assignment_characters.cpp b/samples/custom_assignment_characters.cpp new file mode 100644 index 0000000..7d9ab39 --- /dev/null +++ b/samples/custom_assignment_characters.cpp @@ -0,0 +1,27 @@ +#include +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + program.set_prefix_chars("-+/"); + program.set_assign_chars("=:"); + + program.add_argument("--foo"); + program.add_argument("/B"); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + if (program.is_used("--foo")) { + std::cout << "--foo : " << program.get("--foo") << "\n"; + } + + if (program.is_used("/B")) { + std::cout << "/B : " << program.get("/B") << "\n"; + } +} \ No newline at end of file diff --git a/samples/custom_prefix_characters.cpp b/samples/custom_prefix_characters.cpp new file mode 100644 index 0000000..cdce04d --- /dev/null +++ b/samples/custom_prefix_characters.cpp @@ -0,0 +1,31 @@ +#include +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + program.set_prefix_chars("-+/"); + + program.add_argument("+f"); + program.add_argument("--bar"); + program.add_argument("/foo"); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + if (program.is_used("+f")) { + std::cout << "+f : " << program.get("+f") << "\n"; + } + + if (program.is_used("--bar")) { + std::cout << "--bar : " << program.get("--bar") << "\n"; + } + + if (program.is_used("/foo")) { + std::cout << "/foo : " << program.get("/foo") << "\n"; + } +} \ No newline at end of file diff --git a/samples/description_epilog_metavar.cpp b/samples/description_epilog_metavar.cpp new file mode 100644 index 0000000..ef7f11e --- /dev/null +++ b/samples/description_epilog_metavar.cpp @@ -0,0 +1,17 @@ +#include + +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.add_description("Forward a thing to the next member."); + program.add_epilog("Possible things include betingalw, chiz, and res."); + + program.parse_args(argc, argv); + + std::cout << program << std::endl; +} \ No newline at end of file diff --git a/samples/gathering_remaining_arguments.cpp b/samples/gathering_remaining_arguments.cpp new file mode 100644 index 0000000..e3fb84e --- /dev/null +++ b/samples/gathering_remaining_arguments.cpp @@ -0,0 +1,24 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("compiler"); + + program.add_argument("files").remaining(); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + try { + auto files = program.get>("files"); + std::cout << files.size() << " files provided" << std::endl; + for (auto &file : files) + std::cout << file << std::endl; + } catch (std::logic_error &e) { + std::cout << "No files provided" << std::endl; + } +} \ No newline at end of file diff --git a/samples/is_used.cpp b/samples/is_used.cpp new file mode 100644 index 0000000..a2f0a14 --- /dev/null +++ b/samples/is_used.cpp @@ -0,0 +1,26 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + program.add_argument("--color") + .default_value(std::string{ + "orange"}) // might otherwise be type const char* leading to an error + // when trying program.get + .help("specify the cat's fur color"); + + try { + program.parse_args(argc, argv); // Example: ./main --color orange + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + auto color = program.get("--color"); // "orange" + auto explicit_color = + program.is_used("--color"); // true, user provided orange + std::cout << "Color: " << color << "\n"; + std::cout << "Argument was explicitly provided by user? " << std::boolalpha + << explicit_color << "\n"; +} diff --git a/samples/joining_repeated_optional_arguments.cpp b/samples/joining_repeated_optional_arguments.cpp new file mode 100644 index 0000000..ab819c8 --- /dev/null +++ b/samples/joining_repeated_optional_arguments.cpp @@ -0,0 +1,28 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + program.add_argument("--color") + .default_value>({"orange"}) + .append() + .help("specify the cat's fur color"); + + try { + program.parse_args( + argc, argv); // Example: ./main --color red --color green --color blue + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + auto colors = program.get>( + "--color"); // {"red", "green", "blue"} + + std::cout << "Colors: "; + for (const auto &c : colors) { + std::cout << c << " "; + } + std::cout << "\n"; +} diff --git a/samples/list_of_arguments.cpp b/samples/list_of_arguments.cpp new file mode 100644 index 0000000..ef4d421 --- /dev/null +++ b/samples/list_of_arguments.cpp @@ -0,0 +1,30 @@ +#include + +int main(int argc, char *argv[]) { + + argparse::ArgumentParser program("main"); + + program.add_argument("--input_files") + .help("The list of input files") + .nargs(2); + + try { + program.parse_args( + argc, argv); // Example: ./main --input_files config.yml System.xml + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + auto files = program.get>( + "--input_files"); // {"config.yml", "System.xml"} + + if (!files.empty()) { + std::cout << "Files: "; + for (auto &file : files) { + std::cout << file << " "; + } + std::cout << std::endl; + } +} \ No newline at end of file diff --git a/samples/negative_numbers.cpp b/samples/negative_numbers.cpp new file mode 100644 index 0000000..bb1c888 --- /dev/null +++ b/samples/negative_numbers.cpp @@ -0,0 +1,32 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + program.add_argument("integer").help("Input number").scan<'i', int>(); + + program.add_argument("floats") + .help("Vector of floats") + .nargs(4) + .scan<'g', float>(); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + if (program.is_used("integer")) { + std::cout << "Integer : " << program.get("integer") << "\n"; + } + + if (program.is_used("floats")) { + std::cout << "Floats : "; + for (const auto &f : program.get>("floats")) { + std::cout << f << " "; + } + std::cout << std::endl; + } +} diff --git a/samples/optional_flag_argument.cpp b/samples/optional_flag_argument.cpp new file mode 100644 index 0000000..fff720a --- /dev/null +++ b/samples/optional_flag_argument.cpp @@ -0,0 +1,22 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + program.add_argument("--verbose") + .help("increase output verbosity") + .default_value(false) + .implicit_value(true); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + if (program["--verbose"] == true) { + std::cout << "Verbosity enabled" << std::endl; + } +} diff --git a/samples/parse_known_args.cpp b/samples/parse_known_args.cpp new file mode 100644 index 0000000..17b5f4f --- /dev/null +++ b/samples/parse_known_args.cpp @@ -0,0 +1,26 @@ +#include +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + program.add_argument("--foo").implicit_value(true).default_value(false); + program.add_argument("bar"); + + auto unknown_args = program.parse_known_args(argc, argv); + + if (program.is_used("--foo")) { + std::cout << "--foo : " << program.get("--foo") << "\n"; + } + + if (program.is_used("bar")) { + std::cout << "bar : " << program.get("bar") << "\n"; + } + + if (!unknown_args.empty()) { + std::cout << "Unknown args : "; + for (const auto &u : unknown_args) { + std::cout << u << " "; + } + std::cout << std::endl; + } +} \ No newline at end of file diff --git a/samples/positional_argument.cpp b/samples/positional_argument.cpp new file mode 100644 index 0000000..646ddc3 --- /dev/null +++ b/samples/positional_argument.cpp @@ -0,0 +1,28 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("main"); + + program.add_argument("square") + .help("display the square of a given number") + .scan<'i', int>(); + + program.add_argument("--verbose").default_value(false).implicit_value(true); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + int input = program.get("square"); + + if (program["--verbose"] == true) { + std::cout << "The square of " << input << " is " << (input * input) + << std::endl; + } else { + std::cout << (input * input) << std::endl; + } +} diff --git a/samples/repeating_argument_to_increase_value.cpp b/samples/repeating_argument_to_increase_value.cpp new file mode 100644 index 0000000..e0de41e --- /dev/null +++ b/samples/repeating_argument_to_increase_value.cpp @@ -0,0 +1,17 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + int verbosity = 0; + program.add_argument("-V", "--verbose") + .action([&](const auto &) { ++verbosity; }) + .append() + .default_value(false) + .implicit_value(true) + .nargs(0); + + program.parse_args(argc, argv); // Example: ./main -VVVV + + std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4 +} diff --git a/samples/required_optional_argument.cpp b/samples/required_optional_argument.cpp new file mode 100644 index 0000000..1fe2dcc --- /dev/null +++ b/samples/required_optional_argument.cpp @@ -0,0 +1,19 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("test"); + + program.add_argument("-o", "--output") + .required() + .help("specify the output file."); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + std::cout << "Output written to " << program.get("-o") << "\n"; +} diff --git a/samples/subcommands.cpp b/samples/subcommands.cpp new file mode 100644 index 0000000..f88f05c --- /dev/null +++ b/samples/subcommands.cpp @@ -0,0 +1,65 @@ +#include + +int main(int argc, char *argv[]) { + argparse::ArgumentParser program("git"); + + // 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) + .implicit_value(true); + + commit_command.add_argument("-m", "--message") + .help("Use the given as the commit message."); + + // 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 ."); + + catfile_command.add_argument("-p").help( + "Pretty-print the contents of based on its type."); + + // 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); + submodule_update_command.add_argument("--recursive") + .default_value(false) + .implicit_value(true); + submodule_command.add_subparser(submodule_update_command); + + program.add_subparser(add_command); + program.add_subparser(commit_command); + program.add_subparser(catfile_command); + program.add_subparser(submodule_command); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + // Use arguments +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b228feb..c7a5657 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.6) -project(argparse) +project(argparse_tests) if(MSVC) # Force to always compile with W4 @@ -54,10 +54,10 @@ file(GLOB ARGPARSE_TEST_SOURCES set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) -ADD_EXECUTABLE(ARGPARSE ${ARGPARSE_TEST_SOURCES}) +ADD_EXECUTABLE(ARGPARSE_TESTS ${ARGPARSE_TEST_SOURCES}) INCLUDE_DIRECTORIES("../include" ".") -set_target_properties(ARGPARSE PROPERTIES OUTPUT_NAME tests) -set_property(TARGET ARGPARSE PROPERTY CXX_STANDARD 17) +set_target_properties(ARGPARSE_TESTS PROPERTIES OUTPUT_NAME tests) +set_property(TARGET ARGPARSE_TESTS PROPERTY CXX_STANDARD 17) # Set ${PROJECT_NAME} as the startup project -set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE_TESTS)