From 8aefb080e01b3b3d9da50e366cae60d1e6165ff3 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Thu, 5 May 2016 13:03:25 -0600 Subject: push readme --- README.md | 331 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ args.hxx | 21 +++- test.cxx | 96 +++++------------- 3 files changed, 373 insertions(+), 75 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..077424e --- /dev/null +++ b/README.md @@ -0,0 +1,331 @@ +# args +A simple, small, flexible, single-header C++11 argument parsing library + +This is designed to somewhat replicate the behavior of Python's argparse, but +in C++, with static type checking, and hopefully a lot faster. + +UTF-8 support is limited at best. No normalization is performed, so non-ascii +characters are very best kept out of flags, and combined glyphs are probably +going to mess up help output if you use it. + +This program is MIT-licensed, so you can use the header as-is with no +restrictions. I'd appreciate attribution in a README, Man page, or something if +you are feeling generous, but all that's required is that you don't remove the +license and my name from the header of the args.hxx file in source +redistributions (ie. don't pretend that you wrote it). I do welcome additions +and updates wherever you feel like contributing code. + +There is no API documentation here. The APIs that are most important are the +ArgumentParser and the constructors of the individual types. The examples +should be, for the most part, enough to use this library, but the API +documentation will come soon enough. + +# What does it do + +It: + +* lets you handle flags, flag+arguments, and positional arguments simply and + elegently, with the full help of static typechecking. +* allows you to use your own types in a pretty simple way. +* lets you use count flags, and lists of all argument-accepting types. +* Allows full validation of groups of required arguments, though output isn't + pretty when something fails group validation. User validation functions are + accepted. Groups are fully nestable. +* Generates pretty help for you, with some good tweakable parameters. +* Lets you customize all prefixes and most separators, allowing you to create + an infinite number of different argument syntaxes +* Lets you parse, by default, any type that has a stream extractor operator for + it. If this doesn't work, you can supply a function and parse the string + yourself if you like. + +# What does it not do + +There are tons of things this library does not do! + +It does not yet: + +* Allow you to use a positional argument list before any other positional + arguments (the last argument list will slurp all subsequent positional + arguments). The logic for allowing this would be a lot more code than I'd + like, and would make static checking much more difficult, requiring us to + sort std::string arguments and pair them to positional arguments before + assigning them, rather than what we currently do, which is assiging them as + we go for better simplicity and speed. + +It will not ever: + +* Allow you to create subparsers like argparse +* Allow one argument flag to take a specific number of arguments + (like `--foo first second`). You can instead split that with a flag list + (`--foo first --foo second`) or a custom type extraction + (`--foo first,second`) +* Allow you to intermix multiple different prefix types (eg. `++foo` and + `--foo` in the same parser), though shortopt and longopt prefixes can be + different. + +# Examples + +All the code examples here will be complete code examples, with some output. + +## Simple example: + +```c++ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); + try + { + parser.ParseCLI(argc, argv); + } + catch (args::Help) + { + std::cout << parser.Help(); + return 0; + } + catch (args::ParseError e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser.Help(); + return 1; + } + return 0; +} +``` + +```shell + % ./test + % ./test -h + ./test {OPTIONS} + + This is a test program. + + OPTIONS: + + -h, --help Display this help menu + + This goes after the options. + % +``` + +## Boolean flags, special group types, different matcher construction: + +```c++ +#include + +#include + +int main(int argc, char **argv) +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::Group group(parser, "This group is all exclusive", args::Group::Validators::Xor); + args::Flag foo(group, "foo", "The foo flag", args::Matcher({'f'}, {"foo"})); + args::Flag bar(group, "bar", "The bar flag", args::Matcher({'b'})); + args::Flag baz(group, "baz", "The baz flag", args::Matcher({"baz"})); + try + { + parser.ParseCLI(argc, argv); + } + catch (args::Help) + { + std::cout << parser.Help(); + return 0; + } + catch (args::ParseError e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser.Help(); + return 1; + } + catch (args::ValidationError e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser.Help(); + return 1; + } + if (foo) { std::cout << "foo" << std::endl; } + if (bar) { std::cout << "bar" << std::endl; } + if (baz) { std::cout << "baz" << std::endl; } + return 0; +} +``` + +```shell + % ./test +Group validation failed somewhere! + ./test {OPTIONS} + + This is a test program. + + OPTIONS: + + -f, --foo The foo flag + -b The bar flag + --baz The baz flag + + This goes after the options. + % ./test -f +foo + % ./test --foo +foo + % ./test --foo -f +foo + % ./test -b +bar + % ./test --baz +baz + % ./test --baz -f +Group validation failed somewhere! + ./test {OPTIONS} + + This is a test program. +... + % ./test --baz -fb +Group validation failed somewhere! + ./test {OPTIONS} +... + % +``` + +## Argument flags, Positional arguments, lists + +```c++ +``` + +```shell +% ./test -h + ./test {OPTIONS} [foo] [numbers...] + + This is a test program. + + OPTIONS: + + -h, --help Display this help menu + -i integer The integer flag + -c characters The character flag + foo The foo position + numbers The numbers position list + "--" can be used to terminate flag options and force all following + arguments to be treated as positional options + + This goes after the options. + % ./test -i 5 +i: 5 + % ./test -i 5.2 +Argument 'integer' received invalid value type '5.2' + ./test {OPTIONS} [foo] [numbers...] + % ./test -c 1 -c 2 -c 3 +c: 1 +c: 2 +c: 3 + % + % ./test 1 2 3 4 5 6 7 8 9 +f: 1 +n: 2 +n: 3 +n: 4 +n: 5 +n: 6 +n: 7 +n: 8 +n: 9 + % ./test 1 2 3 4 5 6 7 8 9 a +Argument 'numbers' received invalid value type 'a' + ./test {OPTIONS} [foo] [numbers...] + + This is a test program. +... +``` + +# Custom type parsers (here we use std::tuple) + +```c++ +#include +#include + +#include + +std::istream& operator>>(std::istream& is, std::tuple& ints) +{ + is >> std::get<0>(ints); + is.get(); + is >> std::get<1>(ints); + return is; +} + +void DoublesReader(const std::string &name, const std::string &value, std::tuple &destination) +{ + size_t commapos = 0; + std::get<0>(destination) = std::stod(value, &commapos); + std::get<1>(destination) = std::stod(std::string(value, commapos + 1)); +} + +int main(int argc, char **argv) +{ + args::ArgumentParser parser("This is a test program."); + args::PosArg> ints(parser, "INTS", "This takes a pair of integers."); + args::PosArg, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); + try + { + parser.ParseCLI(argc, argv); + } + catch (args::Help) + { + std::cout << parser.Help(); + return 0; + } + catch (args::ParseError e) + { + std::cerr << e.what() << std::endl; + std::cerr << parser.Help(); + return 1; + } + if (ints) + { + std::cout << "ints found: " << std::get<0>(ints.value) << " and " << std::get<1>(ints.value) << std::endl; + } + if (doubles) + { + std::cout << "doubles found: " << std::get<0>(doubles.value) << " and " << std::get<1>(doubles.value) << std::endl; + } + return 0; +} +``` + +```shell + % ./test -h +Argument could not be matched: 'h' + ./test [INTS] [DOUBLES] + + This is a test program. + + OPTIONS: + + INTS This takes a pair of integers. + DOUBLES This takes a pair of doubles. + + % ./test 5 +ints found: 5 and 0 + % ./test 5,8 +ints found: 5 and 8 + % ./test 5,8 2.4,8 +ints found: 5 and 8 +doubles found: 2.4 and 8 + % ./test 5,8 2.4, +terminate called after throwing an instance of 'std::invalid_argument' + what(): stod +zsh: abort ./test 5,8 2.4, + % ./test 5,8 2.4 +terminate called after throwing an instance of 'std::out_of_range' + what(): basic_string::basic_string: __pos (which is 4) > this->size() (which is 3) +zsh: abort ./test 5,8 2.4 + % ./test 5,8 2.4-7 +ints found: 5 and 8 +doubles found: 2.4 and 7 + % ./test 5,8 2.4,-7 +ints found: 5 and 8 +doubles found: 2.4 and -7 +``` + +As you can see, with your own types, validation can get a little weird. Make +sure to check and throw a parsing error (or whatever error you want to catch) +if you can't fully deduce your type. The built-in validator will only throw if +there are unextracted characters left in the stream. diff --git a/args.hxx b/args.hxx index ac7854a..59145ef 100644 --- a/args.hxx +++ b/args.hxx @@ -1,5 +1,22 @@ -/* Copyright © 2016 Taylor C. Richberger - * This code is released under the license described in the LICENSE file +/* Copyright (c) 2016 Taylor C. Richberger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ #include diff --git a/test.cxx b/test.cxx index 9ce23d3..5945af6 100644 --- a/test.cxx +++ b/test.cxx @@ -3,39 +3,30 @@ */ #include -#include -#include +#include #include +std::istream& operator>>(std::istream& is, std::tuple& ints) +{ + is >> std::get<0>(ints); + is.get(); + is >> std::get<1>(ints); + return is; +} + +void DoublesReader(const std::string &name, const std::string &value, std::tuple &destination) +{ + size_t commapos = 0; + std::get<0>(destination) = std::stod(value, &commapos); + std::get<1>(destination) = std::stod(std::string(value, commapos + 1)); +} + int main(int argc, char **argv) { - setlocale(LC_ALL, ""); - args::ArgumentParser parser("This is a test program. It has a lot of really cool things about it, and it also has a really really long description line that is going to necessitate some line breaks.", "As you saw above, this previously mentioned program has a lot of long things, and therefore necessitates some line breaking"); - args::Group mutexgroup(parser, "Group test", args::Group::Validators::AllOrNone); - args::Flag aflag(mutexgroup, "a", "This is flag a", args::Matcher({'a'})); - args::Flag bflag(mutexgroup, "b", "This is flag b", args::Matcher({'b'})); - args::Flag cflag(mutexgroup, "c", "This is flag c", args::Matcher({'c'})); - args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); - args::Flag fooflag(parser, "foo", "This is a foo flag", args::Matcher({'f', 'F'}, {"Foo", "foo"})); - args::ArgFlag bararg(parser, "bar", "This is a bar flag", args::Matcher({'b', 'B'}, {"Bar", "bar"})); - args::ArgFlag baz(parser, "baz", "This is a baz flag, and it is long enough that it needs to be wrapped.", args::Matcher({'z', 'Z'}, {"Baz", "baz"})); - args::ArgFlagList list(parser, "flaglist", "This is a list flag", args::Matcher({'l'}, {"list"})); - args::PosArg pos(parser, "Position", "This is a position arg"); - args::PosArgList poslist(parser, "Position list", "This is a position list arg"); - args::PosArg pos2(parser, "Position", "This is a position arg which has a long enough description to probably necessitate wrapping"); - args::PosArg pos3(parser, "Position", "This is a position arg"); - args::PosArg pos4(parser, "Pösitiön", "This is a position arg"); - args::PosArg pos5(parser, "Position", "This is a position arg"); - args::PosArg pos6(parser, "Position", "This is a position arg"); - args::PosArgList poslist1(parser, "Position list", "This is a position list arg"); - args::PosArgList poslist2(parser, "Position list", "This is a position list arg"); - args::PosArgList poslist3(parser, "Position list", "This is a position list arg"); - args::PosArg pos7(parser, "Position", "This is a position arg"); - args::PosArg pos8(parser, "Position", "This is a position arg"); - args::PosArgList poslist4(parser, "Position list", "This is a position list arg"); - args::PosArg pos9(parser, "Position", "This is a position arg"); - args::Counter counter(parser, "counter", "This is a counter flag", args::Matcher({'c'})); + args::ArgumentParser parser("This is a test program."); + args::PosArg> ints(parser, "INTS", "This takes a pair of integers."); + args::PosArg, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); try { parser.ParseCLI(argc, argv); @@ -51,54 +42,13 @@ int main(int argc, char **argv) std::cerr << parser.Help(); return 1; } - if (aflag) - { - std::cout << "A flag found" << std::endl; - } - if (bflag) - { - std::cout << "B flag found" << std::endl; - } - if (cflag) - { - std::cout << "C flag found" << std::endl; - } - if (fooflag) + if (ints) { - std::cout << "Foo flag found" << std::endl; + std::cout << "ints found: " << std::get<0>(ints.value) << " and " << std::get<1>(ints.value) << std::endl; } - if (bararg) + if (doubles) { - std::cout << "Bar arg found: " << bararg.value << std::endl; + std::cout << "doubles found: " << std::get<0>(doubles.value) << " and " << std::get<1>(doubles.value) << std::endl; } - if (baz) - { - std::cout << "Baz arg found: " << baz.value << std::endl; - } - if (counter) - { - std::cout << "counter found: " << counter.count << std::endl; - } - if (list) - { - std::cout << "list found: " << std::endl; - for (const auto &item: list.values) - { - std::cout << "- " << item << std::endl; - } - } - if (pos) - { - std::cout << "pos found: " << pos.value << std::endl; - } - if (poslist) - { - std::cout << "poslist found: " << std::endl; - for (const auto &item: poslist.values) - { - std::cout << "- " << item << std::endl; - } - } - return 0; } -- cgit v1.2.1