diff options
| author | Taylor C. Richberger <taywee@gmx.com> | 2016-05-05 15:04:46 -0400 | 
|---|---|---|
| committer | Taylor C. Richberger <taywee@gmx.com> | 2016-05-05 15:04:46 -0400 | 
| commit | 99a66cf4c4a931a066196e07c69e3d8bf813bc5d (patch) | |
| tree | 2640dd5136cb6a674f3b0a7014edc095887c0c5f | |
| parent | Merge remote-tracking branch 'origin/1-create-flag-terminator-logic' (diff) | |
| parent | push readme (diff) | |
| download | args.hxx-99a66cf4c4a931a066196e07c69e3d8bf813bc5d.tar.xz | |
Merge branch '4-write-out-readme' into 'master'
push readme
Closes #4
See merge request !2
| -rw-r--r-- | README.md | 331 | ||||
| -rw-r--r-- | args.hxx | 21 | ||||
| -rw-r--r-- | test.cxx | 96 | 
3 files changed, 373 insertions, 75 deletions
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 <iostream> + +#include <args.hxx> + +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 <iostream> +#include <tuple> + +#include <args.hxx> + +std::istream& operator>>(std::istream& is, std::tuple<int, int>& 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<double, double> &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<std::tuple<int, int>> ints(parser, "INTS", "This takes a pair of integers."); +    args::PosArg<std::tuple<double, double>, 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. @@ -1,5 +1,22 @@ -/* Copyright © 2016 Taylor C. Richberger <taywee@gmx.com> - * This code is released under the license described in the LICENSE file +/* Copyright (c) 2016 Taylor C. Richberger <taywee@gmx.com> + *  + * 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 <algorithm> @@ -3,39 +3,30 @@   */  #include <iostream> -#include <list> -#include <clocale> +#include <tuple>  #include <args.hxx> +std::istream& operator>>(std::istream& is, std::tuple<int, int>& 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<double, double> &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<std::string> bararg(parser, "bar", "This is a bar flag", args::Matcher({'b', 'B'}, {"Bar", "bar"})); -    args::ArgFlag<double> 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<double> list(parser, "flaglist", "This is a list flag", args::Matcher({'l'}, {"list"})); -    args::PosArg<std::string> pos(parser, "Position", "This is a position arg"); -    args::PosArgList<double> poslist(parser, "Position list", "This is a position list arg"); -    args::PosArg<double> pos2(parser, "Position", "This is a position arg which has a long enough description to probably necessitate wrapping"); -    args::PosArg<double> pos3(parser, "Position", "This is a position arg"); -    args::PosArg<double> pos4(parser, "Pösitiön", "This is a position arg"); -    args::PosArg<double> pos5(parser, "Position", "This is a position arg"); -    args::PosArg<double> pos6(parser, "Position", "This is a position arg"); -    args::PosArgList<double> poslist1(parser, "Position list", "This is a position list arg"); -    args::PosArgList<double> poslist2(parser, "Position list", "This is a position list arg"); -    args::PosArgList<double> poslist3(parser, "Position list", "This is a position list arg"); -    args::PosArg<double> pos7(parser, "Position", "This is a position arg"); -    args::PosArg<double> pos8(parser, "Position", "This is a position arg"); -    args::PosArgList<double> poslist4(parser, "Position list", "This is a position list arg"); -    args::PosArg<double> 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<std::tuple<int, int>> ints(parser, "INTS", "This takes a pair of integers."); +    args::PosArg<std::tuple<double, double>, 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;  }  | 
