aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor C. Richberger <Taywee@gmx.com>2016-05-05 13:03:25 -0600
committerTaylor C. Richberger <Taywee@gmx.com>2016-05-05 13:03:25 -0600
commit8aefb080e01b3b3d9da50e366cae60d1e6165ff3 (patch)
tree2640dd5136cb6a674f3b0a7014edc095887c0c5f
parentMerge remote-tracking branch 'origin/1-create-flag-terminator-logic' (diff)
downloadargs.hxx-8aefb080e01b3b3d9da50e366cae60d1e6165ff3.tar.xz
push readme
-rw-r--r--README.md331
-rw-r--r--args.hxx21
-rw-r--r--test.cxx96
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.
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 <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>
diff --git a/test.cxx b/test.cxx
index 9ce23d3..5945af6 100644
--- a/test.cxx
+++ b/test.cxx
@@ -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;
}