From b658670abd3c61c3f14454279796f8d8985ec613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20T=C3=B6pfer?= Date: Fri, 24 Nov 2017 12:16:47 +0100 Subject: [PATCH] Docopt (#58) * switching docopt.cpp to upstream version 0.6.2 - contains Boost fix for borken C++11 std::regex on GCC 4.8.x * success exit code for `--help` --- .gitmodules | 3 + CMakeLists.txt | 1 + external/docopt.cpp | 1 + src/CMakeLists.txt | 14 +- src/docopt/.gitignore | 18 - src/docopt/LICENSE-Boost-1.0 | 23 - src/docopt/LICENSE-MIT | 23 - src/docopt/README.rst | 442 -------------- src/docopt/docopt.cpp | 1081 ---------------------------------- src/docopt/docopt.h | 66 --- src/docopt/docopt_private.h | 305 ---------- src/docopt/docopt_util.h | 100 ---- src/docopt/docopt_value.h | 310 ---------- src/haploclique.cpp | 9 +- src/version.h.in | 1 + test/CMakeLists.txt | 4 +- 16 files changed, 26 insertions(+), 2375 deletions(-) create mode 160000 external/docopt.cpp delete mode 100644 src/docopt/.gitignore delete mode 100644 src/docopt/LICENSE-Boost-1.0 delete mode 100644 src/docopt/LICENSE-MIT delete mode 100644 src/docopt/README.rst delete mode 100644 src/docopt/docopt.cpp delete mode 100644 src/docopt/docopt.h delete mode 100644 src/docopt/docopt_private.h delete mode 100644 src/docopt/docopt_util.h delete mode 100644 src/docopt/docopt_value.h create mode 100644 src/version.h.in diff --git a/.gitmodules b/.gitmodules index a639f66..421b754 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ path = external/googletest url = https://github.com/google/googletest.git branch = master +[submodule "external/docopt.cpp"] + path = external/docopt.cpp + url = https://github.com/docopt/docopt.cpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 49b63a3..c5cee6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,5 +38,6 @@ include(hc-compilerflags) include(hc-dependencies) add_subdirectory(${HC_EXTERNALDIR}/BamTools external/bamtools) +add_subdirectory(${HC_EXTERNALDIR}/docopt.cpp external/docopt.cpp) add_subdirectory(src) add_subdirectory(test) diff --git a/external/docopt.cpp b/external/docopt.cpp new file mode 160000 index 0000000..1811022 --- /dev/null +++ b/external/docopt.cpp @@ -0,0 +1 @@ +Subproject commit 18110222dc9cb57ec880ce24fbbd7291b2d1046e diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f66f6a6..49f7a8e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,14 +27,16 @@ target_include_directories(hc PUBLIC ${HC_SRCDIR} ${Boost_INCLUDE_DIR} ${HC_EXTERNALDIR}/BamTools/src + ${HC_EXTERNALDIR}/docopt.cpp + ${PROJECT_BINARY_DIR} ) target_link_libraries(hc ${Boost_LIBRARIES} ) if (HC_STATIC) - target_link_libraries(hc BamTools-static) + target_link_libraries(hc BamTools-static docopt_s) else() - target_link_libraries(hc BamTools) + target_link_libraries(hc BamTools docopt_s) endif() set_target_properties(hc PROPERTIES COMPILE_FLAGS ${HC_COMPILE_FLAGS}) @@ -42,6 +44,12 @@ if (HC_LINK_FLAGS) set_target_properties(hc PROPERTIES LINK_FLAGS ${HC_LINK_FLAGS}) endif() +# Version +configure_file ( + "${HC_SRCDIR}/version.h.in" + "${PROJECT_BINARY_DIR}/version.h" +) + # Haploclique executable add_executable (haploclique haploclique.cpp) set_target_properties(haploclique PROPERTIES COMPILE_FLAGS ${HC_COMPILE_FLAGS}) @@ -55,4 +63,4 @@ set_target_properties(haploclique PROPERTIES target_link_libraries(haploclique hc) -install(TARGETS haploclique RUNTIME DESTINATION bin) \ No newline at end of file +install(TARGETS haploclique RUNTIME DESTINATION bin) diff --git a/src/docopt/.gitignore b/src/docopt/.gitignore deleted file mode 100644 index f1efbe4..0000000 --- a/src/docopt/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -# Compiled Object files -*.slo -*.lo -*.o - -# Compiled Dynamic libraries -*.so -*.dylib - -# Compiled Static libraries -*.lai -*.la -*.a - -# Compiled examples, as per docs -example -naval_fate -run_testcase diff --git a/src/docopt/LICENSE-Boost-1.0 b/src/docopt/LICENSE-Boost-1.0 deleted file mode 100644 index 36b7cd9..0000000 --- a/src/docopt/LICENSE-Boost-1.0 +++ /dev/null @@ -1,23 +0,0 @@ -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -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, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/src/docopt/LICENSE-MIT b/src/docopt/LICENSE-MIT deleted file mode 100644 index 58ff1bc..0000000 --- a/src/docopt/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2012 Vladimir Keleshev, - -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. diff --git a/src/docopt/README.rst b/src/docopt/README.rst deleted file mode 100644 index 5895f47..0000000 --- a/src/docopt/README.rst +++ /dev/null @@ -1,442 +0,0 @@ -``docopt.cpp``: A C++11 Port -============================ -doctopt creates *beautiful* command-line interfaces ---------------------------------------------------- - -Isn't it awesome how ``getopt`` (and ``boost::program_options`` for you fancy -folk!) generate help messages based on your code?! These timeless functions -have been around for decades and have proven we don't need anything better, right? - -*Hell no!* You know what's awesome? It's when the option parser *is* -generated based on the beautiful help message that you write yourself! -This way you don't need to write this stupid repeatable parser-code, -and instead can write only the help message--*the way you want it*. - -**docopt** helps you create most beautiful command-line interfaces -*easily*: - -.. code:: c++ - - #include "docopt.h" - - #include - - static const char USAGE[] = - R"(Naval Fate. - - Usage: - naval_fate ship new ... - naval_fate ship move [--speed=] - naval_fate ship shoot - naval_fate mine (set|remove) [--moored | --drifting] - naval_fate (-h | --help) - naval_fate --version - - Options: - -h --help Show this screen. - --version Show version. - --speed= Speed in knots [default: 10]. - --moored Moored (anchored) mine. - --drifting Drifting mine. - )"; - - int main(int argc, const char** argv) - { - std::map args - = docopt::docopt(USAGE, - { argv + 1, argv + argc }, - true, // show help if requested - "Naval Fate 2.0"); // version string - - for(auto const& arg : args) { - std::cout << arg.first << arg.second << std::endl; - } - - return 0; - } - -Beat that! The option parser is generated based on the docstring above -that is passed to ``docopt::docopt`` function. ``docopt`` parses the usage -pattern (``"Usage: ..."``) and option descriptions (lines starting -with dash "``-``") and ensures that the program invocation matches the -usage pattern; it parses options, arguments and commands based on -that. The basic idea is that *a good help message has all necessary -information in it to make a parser*. - -C++11 port details ---------------------------------------------------- - -This is a port of the ``docopt.py`` module (https://github.com/docopt/docopt), -and we have tried to maintain full feature parity (and code structure) as the -original. - -This port is written in C++11 and also requires a good C++11 standard library -(in particular, one with ``regex`` support). The following compilers are known -to work with docopt: - -- Clang 3.3 and later -- GCC 4.9 -- Visual C++ 2015 RC - -Note that GCC-4.8 will not work due to its missing the ``regex`` module. - -This port is licensed under the MIT license, just like the original module. -However, we are also dual-licensing this code under the Boost License, version 1.0, -as this is a popular C++ license. The licenses are similar and you are free to -use this code under the terms of either license. - -The differences from the Python port are: - -* the addition of a ``docopt_parse`` function, which does not terminate - the program on error -* a ``docopt::value`` type to hold the various value types that can be parsed. - We considered using boost::variant, but it seems better to have no external - dependencies (beyond a good STL). -* because C++ is statically-typed and Python is not, we had to make some - changes to the interfaces of the internal parse tree types. -* because ``std::regex`` does not have an equivalent to Python's regex.split, - some of the regex's had to be restructured and additional loops used. - -API ---------------------------------------------------- - -.. code:: c++ - - docopt::docopt(doc, argv, help /* =true */, version /* ="" */, options_first /* =false */) - -``docopt`` takes 2 required and 3 optional arguments: - -- ``doc`` is a string that contains a **help message** that will be parsed to - create the option parser. The simple rules of how to write such a - help message are given in next sections. Here is a quick example of - such a string (note that this example uses the "raw string literal" feature - that was added to C++11): - -.. code:: c++ - - R"(Usage: my_program [-hso FILE] [--quiet | --verbose] [INPUT ...] - - -h --help show this - -s --sorted sorted output - -o FILE specify output file [default: ./test.txt] - --quiet print less text - --verbose print more text - )" - -- ``argv`` is a vector of strings representing the args passed. Although - main usually takes a ``(int argc, const char** argv)`` pair, you can - pass the value ``{argv+1, argv+argc}`` to generate the vector automatically. - (Note we skip the argv[0] argument!) Alternatively you can supply a list of - strings like ``{ "--verbose", "-o", "hai.txt" }``. - -- ``help``, by default ``true``, specifies whether the parser should - automatically print the help message (supplied as ``doc``) and - terminate, in case ``-h`` or ``--help`` option is encountered - (options should exist in usage pattern, more on that below). If you - want to handle ``-h`` or ``--help`` options manually (as other - options), set ``help=false``. - -- ``version``, by default empty, is an optional argument that - specifies the version of your program. If supplied, then, (assuming - ``--version`` option is mentioned in usage pattern) when parser - encounters the ``--version`` option, it will print the supplied - version and terminate. ``version`` could be any printable object, - but most likely a string, e.g. ``"2.1.0rc1"``. - - Note, when ``docopt`` is set to automatically handle ``-h``, - ``--help`` and ``--version`` options, you still need to mention - them in usage pattern for this to work (also so your users to - know about them!) - -- ``options_first``, by default ``false``. If set to ``true`` will - disallow mixing options and positional argument. I.e. after first - positional argument, all arguments will be interpreted as positional - even if the look like options. This can be used for strict - compatibility with POSIX, or if you want to dispatch your arguments - to other programs. - -The **return** value is a ``map`` with options, -arguments and commands as keys, spelled exactly like in your help message. -Long versions of options are given priority. For example, if you invoke the -top example as:: - - naval_fate ship Guardian move 100 150 --speed=15 - -the return dictionary will be: - -.. code:: python - - {"--drifting": false, "mine": false, - "--help": false, "move": true, - "--moored": false, "new": false, - "--speed": "15", "remove": false, - "--version": false, "set": false, - "": ["Guardian"], "ship": true, - "": "100", "shoot": false, - "": "150"} - -If any parsing error (in either the usage, or due to incorrect user inputs) is -encountered, the program will exit with exit code -1. - -Note that there is another function that does not exit on error, and instead will -propogate an exception that you can catch and process as you like. See the docopt.h file -for information on the exceptions and usage: - -.. code:: c++ - - docopt::docopt_parse(doc, argv, help /* =true */, version /* =true */, options_first /* =false) - - -Help message format ---------------------------------------------------- - -Help message consists of 2 parts: - -- Usage pattern, e.g.:: - - Usage: my_program [-hso FILE] [--quiet | --verbose] [INPUT ...] - -- Option descriptions, e.g.:: - - -h --help show this - -s --sorted sorted output - -o FILE specify output file [default: ./test.txt] - --quiet print less text - --verbose print more text - -Their format is described below; other text is ignored. - -Usage pattern format ----------------------------------------------------------------------- - -**Usage pattern** is a substring of ``doc`` that starts with -``usage:`` (case *insensitive*) and ends with a *visibly* empty line. -Minimum example: - -.. code:: python - - """Usage: my_program - - """ - -The first word after ``usage:`` is interpreted as your program's name. -You can specify your program's name several times to signify several -exclusive patterns: - -.. code:: python - - """Usage: my_program FILE - my_program COUNT FILE - - """ - -Each pattern can consist of the following elements: - -- ****, **ARGUMENTS**. Arguments are specified as either - upper-case words, e.g. ``my_program CONTENT-PATH`` or words - surrounded by angular brackets: ``my_program ``. -- **--options**. Options are words started with dash (``-``), e.g. - ``--output``, ``-o``. You can "stack" several of one-letter - options, e.g. ``-oiv`` which will be the same as ``-o -i -v``. The - options can have arguments, e.g. ``--input=FILE`` or ``-i FILE`` or - even ``-iFILE``. However it is important that you specify option - descriptions if you want your option to have an argument, a default - value, or specify synonymous short/long versions of the option (see - next section on option descriptions). -- **commands** are words that do *not* follow the described above - conventions of ``--options`` or ```` or ``ARGUMENTS``, - plus two special commands: dash "``-``" and double dash "``--``" - (see below). - -Use the following constructs to specify patterns: - -- **[ ]** (brackets) **optional** elements. e.g.: ``my_program - [-hvqo FILE]`` -- **( )** (parens) **required** elements. All elements that are *not* - put in **[ ]** are also required, e.g.: ``my_program - --path= ...`` is the same as ``my_program - (--path= ...)``. (Note, "required options" might be not - a good idea for your users). -- **|** (pipe) **mutually exclusive** elements. Group them using **( - )** if one of the mutually exclusive elements is required: - ``my_program (--clockwise | --counter-clockwise) TIME``. Group - them using **[ ]** if none of the mutually-exclusive elements are - required: ``my_program [--left | --right]``. -- **...** (ellipsis) **one or more** elements. To specify that - arbitrary number of repeating elements could be accepted, use - ellipsis (``...``), e.g. ``my_program FILE ...`` means one or - more ``FILE``-s are accepted. If you want to accept zero or more - elements, use brackets, e.g.: ``my_program [FILE ...]``. Ellipsis - works as a unary operator on the expression to the left. -- **[options]** (case sensitive) shortcut for any options. You can - use it if you want to specify that the usage pattern could be - provided with any options defined below in the option-descriptions - and do not want to enumerate them all in usage-pattern. -- "``[--]``". Double dash "``--``" is used by convention to separate - positional arguments that can be mistaken for options. In order to - support this convention add "``[--]``" to your usage patterns. -- "``[-]``". Single dash "``-``" is used by convention to signify that - ``stdin`` is used instead of a file. To support this add "``[-]``" - to your usage patterns. "``-``" acts as a normal command. - -If your pattern allows to match argument-less option (a flag) several -times:: - - Usage: my_program [-v | -vv | -vvv] - -then number of occurrences of the option will be counted. I.e. -``args['-v']`` will be ``2`` if program was invoked as ``my_program --vv``. Same works for commands. - -If your usage patterns allows to match same-named option with argument -or positional argument several times, the matched arguments will be -collected into a list:: - - Usage: my_program --path=... - -I.e. invoked with ``my_program file1 file2 --path=./here ---path=./there`` the returned dict will contain ``args[''] == -['file1', 'file2']`` and ``args['--path'] == ['./here', './there']``. - - -Option descriptions format ----------------------------------------------------------------------- - -**Option descriptions** consist of a list of options that you put -below your usage patterns. - -It is necessary to list option descriptions in order to specify: - -- synonymous short and long options, -- if an option has an argument, -- if option's argument has a default value. - -The rules are as follows: - -- Every line in ``doc`` that starts with ``-`` or ``--`` (not counting - spaces) is treated as an option description, e.g.:: - - Options: - --verbose # GOOD - -o FILE # GOOD - Other: --bad # BAD, line does not start with dash "-" - -- To specify that option has an argument, put a word describing that - argument after space (or equals "``=``" sign) as shown below. Follow - either or UPPER-CASE convention for options' - arguments. You can use comma if you want to separate options. In - the example below, both lines are valid, however you are recommended - to stick to a single style.:: - - -o FILE --output=FILE # without comma, with "=" sign - -i , --input # with comma, without "=" sing - -- Use two spaces to separate options with their informal description:: - - --verbose More text. # BAD, will be treated as if verbose option had - # an argument "More", so use 2 spaces instead - -q Quit. # GOOD - -o FILE Output file. # GOOD - --stdout Use stdout. # GOOD, 2 spaces - -- If you want to set a default value for an option with an argument, - put it into the option-description, in form ``[default: - ]``:: - - --coefficient=K The K coefficient [default: 2.95] - --output=FILE Output file [default: test.txt] - --directory=DIR Some directory [default: ./] - -- If the option is not repeatable, the value inside ``[default: ...]`` - will be interpreted as string. If it *is* repeatable, it will be - splited into a list on whitespace:: - - Usage: my_program [--repeatable= --repeatable=] - [--another-repeatable=]... - [--not-repeatable=] - - # will be ['./here', './there'] - --repeatable= [default: ./here ./there] - - # will be ['./here'] - --another-repeatable= [default: ./here] - - # will be './here ./there', because it is not repeatable - --not-repeatable= [default: ./here ./there] - -Examples ----------------------------------------------------------------------- - -We have an extensive list of `examples -`_ which cover -every aspect of functionality of **docopt**. Try them out, read the -source if in doubt. - -There are also very intersting applications and ideas at that page. -Check out the sister project for more information! - -Subparsers, multi-level help and *huge* applications (like git) ----------------------------------------------------------------------- - -If you want to split your usage-pattern into several, implement -multi-level help (with separate help-screen for each subcommand), -want to interface with existing scripts that don't use **docopt**, or -you're building the next "git", you will need the new ``options_first`` -parameter (described in API section above). To get you started quickly -we implemented a subset of git command-line interface as an example: -`examples/git -`_ - -Compiling the example / Running the tests ----------------------------------------------------------------------- -The original Python module includes some language-agnostic unit tests, -and these can be run with this port as well. - -The tests are a Python driver that uses the testcases.docopt file to then invoke -a C++ test case runner (run_testcase.cpp):: - - $ clang++ --std=c++11 --stdlib=libc++ docopt.cpp run_testcase.cpp -o run_testcase - $ python run_tests.py - PASS (175) - -You can also compile the example shown at the start (included as example.cpp):: - - $ clang++ --std=c++11 --stdlib=libc++ -I . docopt.cpp examples/naval_fate.cpp -o naval_fate - $ ./naval_fate --help - [ ... ] - $ ./naval_fate ship Guardian move 100 150 --speed=15 - --drifting: false - --help: false - --moored: false - --speed: "15" - --version: false - : ["Guardian"] - : "100" - : "150" - mine: false - move: true - new: false - remove: false - set: false - ship: true - shoot: false - -Development ---------------------------------------------------- - -Comments and suggestions are *very* welcome! If you find issues, please -file them and help improve our code! - -Please note, however, that we have tried to stay true to the original -Python code. If you have any major patches, structural changes, or new features, -we might want to first negotiate these changes into the Python code first. -However, bring it up! Let's hear it! - -Changelog ---------------------------------------------------- - -**docopt** follows `semantic versioning `_. The -first release with stable API will be 1.0.0 (soon). - -- 0.6.1 The initial C++ port of docopt.py - diff --git a/src/docopt/docopt.cpp b/src/docopt/docopt.cpp deleted file mode 100644 index 7ce58e0..0000000 --- a/src/docopt/docopt.cpp +++ /dev/null @@ -1,1081 +0,0 @@ -// -// docopt.cpp -// docopt -// -// Created by Jared Grubb on 2013-11-03. -// Copyright (c) 2013 Jared Grubb. All rights reserved. -// - -#include "docopt.h" -#include "docopt_util.h" -#include "docopt_private.h" - -#include "docopt_value.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace docopt; - -DocoptExitHelp::DocoptExitHelp() -: std::runtime_error("Docopt --help argument encountered") -{} - -DocoptExitVersion::DocoptExitVersion() -: std::runtime_error("Docopt --version argument encountered") -{} - -const char* value::kindAsString(Kind kind) -{ - switch (kind) { - case Kind::Empty: return "empty"; - case Kind::Bool: return "bool"; - case Kind::Long: return "long"; - case Kind::String: return "string"; - case Kind::StringList: return "string-list"; - } - return "unknown"; -} - -void value::throwIfNotKind(Kind expected) const -{ - if (kind == expected) - return; - - std::string error = "Illegal cast to "; - error += kindAsString(expected); - error += "; type is actually "; - error += kindAsString(kind); - throw std::runtime_error(std::move(error)); -} - -std::ostream& docopt::operator<<(std::ostream& os, value const& val) -{ - if (val.isBool()) { - bool b = val.asBool(); - std::cout << (b ? "true" : "false"); - } else if (val.isLong()) { - long v = val.asLong(); - std::cout << v; - } else if (val.isString()) { - std::string const& str = val.asString(); - std::cout << '"' << str << '"'; - } else if (val.isStringList()) { - auto const& list = val.asStringList(); - std::cout << "["; - bool first = true; - for(auto const& el : list) { - if (first) { - first = false; - } else { - std::cout << ", "; - } - std::cout << '"' << el << '"'; - } - std::cout << "]"; - } else { - std::cout << "null"; - } - return os; -} - -#pragma mark - -#pragma mark Pattern types - -std::vector Pattern::leaves() { - std::vector ret; - collect_leaves(ret); - return ret; -} - -bool Required::match(PatternList& left, std::vector>& collected) const -{ - auto l = left; - auto c = collected; - - for(auto const& pattern : fChildren) { - bool ret = pattern->match(l, c); - if (!ret) { - // leave (left, collected) untouched - return false; - } - } - - left = std::move(l); - collected = std::move(c); - return true; -} - -bool LeafPattern::match(PatternList& left, std::vector>& collected) const -{ - auto match = single_match(left); - if (!match.second) { - return false; - } - - left.erase(left.begin()+match.first); - - auto same_name = std::find_if(collected.begin(), collected.end(), [&](std::shared_ptr const& p) { - return p->name()==name(); - }); - if (getValue().isLong()) { - long val = 1; - if (same_name == collected.end()) { - collected.push_back(match.second); - match.second->setValue(value{val}); - } else if ((**same_name).getValue().isLong()) { - val += (**same_name).getValue().asLong(); - (**same_name).setValue(value{val}); - } else { - (**same_name).setValue(value{val}); - } - } else if (getValue().isStringList()) { - std::vector val; - if (match.second->getValue().isString()) { - val.push_back(match.second->getValue().asString()); - } else if (match.second->getValue().isStringList()) { - val = match.second->getValue().asStringList(); - } else { - /// cant be!? - } - - if (same_name == collected.end()) { - collected.push_back(match.second); - match.second->setValue(value{val}); - } else if ((**same_name).getValue().isStringList()) { - std::vector const& list = (**same_name).getValue().asStringList(); - val.insert(val.begin(), list.begin(), list.end()); - (**same_name).setValue(value{val}); - } else { - (**same_name).setValue(value{val}); - } - } else { - collected.push_back(match.second); - } - return true; -} - -Option Option::parse(std::string const& option_description) -{ - std::string shortOption, longOption; - int argcount = 0; - value val { false }; - - auto double_space = option_description.find(" "); - auto options_end = option_description.end(); - if (double_space != std::string::npos) { - options_end = option_description.begin() + double_space; - } - - static const std::regex pattern {"(--|-)?(.*?)([,= ]|$)"}; - for(std::sregex_iterator i {option_description.begin(), options_end, pattern, std::regex_constants::match_not_null}, - e{}; - i != e; - ++i) - { - std::smatch const& match = *i; - if (match[1].matched) { // [1] is optional. - if (match[1].length()==1) { - shortOption = "-" + match[2].str(); - } else { - longOption = "--" + match[2].str(); - } - } else if (match[2].length() > 0) { // [2] always matches. - std::string m = match[2]; - argcount = 1; - } else { - // delimeter - } - - if (match[3].length() == 0) { // [3] always matches. - // Hit end of string. For some reason 'match_not_null' will let us match empty - // at the end, and then we'll spin in an infinite loop. So, if we hit an empty - // match, we know we must be at the end. - break; - } - } - - if (argcount) { - std::smatch match; - if (std::regex_search(options_end, option_description.end(), - match, - std::regex{"\\[default: (.*)\\]", std::regex::icase})) - { - val = match[1].str(); - } - } - - return {std::move(shortOption), - std::move(longOption), - argcount, - std::move(val)}; -} - -bool OneOrMore::match(PatternList& left, std::vector>& collected) const -{ - assert(fChildren.size() == 1); - - auto l = left; - auto c = collected; - - bool matched = true; - size_t times = 0; - - decltype(l) l_; - bool firstLoop = true; - - while (matched) { - // could it be that something didn't match but changed l or c? - matched = fChildren[0]->match(l, c); - - if (matched) - ++times; - - if (firstLoop) { - firstLoop = false; - } else if (l == l_) { - break; - } - - l_ = l; - } - - if (times == 0) { - return false; - } - - left = std::move(l); - collected = std::move(c); - return true; -} - -bool Either::match(PatternList& left, std::vector>& collected) const -{ - using Outcome = std::pair>>; - - std::vector outcomes; - - for(auto const& pattern : fChildren) { - // need a copy so we apply the same one for every iteration - auto l = left; - auto c = collected; - bool matched = pattern->match(l, c); - if (matched) { - outcomes.emplace_back(std::move(l), std::move(c)); - } - } - - auto min = std::min_element(outcomes.begin(), outcomes.end(), [](Outcome const& o1, Outcome const& o2) { - return o1.first.size() < o2.first.size(); - }); - - if (min == outcomes.end()) { - // (left, collected) unchanged - return false; - } - - std::tie(left, collected) = std::move(*min); - return true; -} - -std::pair> Argument::single_match(PatternList const& left) const -{ - std::pair> ret {}; - - for(size_t i = 0, size = left.size(); i < size; ++i) - { - auto arg = dynamic_cast(left[i].get()); - if (arg) { - ret.first = i; - ret.second = std::make_shared(name(), arg->getValue()); - break; - } - } - - return ret; -} - -std::pair> Command::single_match(PatternList const& left) const -{ - std::pair> ret {}; - - for(size_t i = 0, size = left.size(); i < size; ++i) - { - auto arg = dynamic_cast(left[i].get()); - if (arg) { - if (name() == arg->getValue()) { - ret.first = i; - ret.second = std::make_shared(name(), value{true}); - } - break; - } - } - - return ret; -} - -std::pair> Option::single_match(PatternList const& left) const -{ - std::pair> ret {}; - - for(size_t i = 0, size = left.size(); i < size; ++i) - { - auto leaf = std::dynamic_pointer_cast(left[i]); - if (leaf && name() == leaf->name()) { - ret.first = i; - ret.second = leaf; - break; - } - } - - return ret; -} - -#pragma mark - -#pragma mark Parsing stuff - -std::vector transform(PatternList pattern); - -void BranchPattern::fix_repeating_arguments() -{ - std::vector either = transform(children()); - for(auto const& group : either) { - // use multiset to help identify duplicate entries - std::unordered_multiset, PatternHasher> group_set {group.begin(), group.end()}; - for(auto const& e : group_set) { - if (group_set.count(e) == 1) - continue; - - LeafPattern* leaf = dynamic_cast(e.get()); - if (!leaf) continue; - - bool ensureList = false; - bool ensureInt = false; - - if (dynamic_cast(leaf)) { - ensureInt = true; - } else if (dynamic_cast(leaf)) { - ensureList = true; - } else if (Option* o = dynamic_cast(leaf)) { - if (o->argCount()) { - ensureList = true; - } else { - ensureInt = true; - } - } - - if (ensureList) { - std::vector newValue; - if (leaf->getValue().isString()) { - newValue = split(leaf->getValue().asString()); - } - if (!leaf->getValue().isStringList()) { - leaf->setValue(value{newValue}); - } - } else if (ensureInt) { - leaf->setValue(value{0}); - } - } - } -} - -std::vector transform(PatternList pattern) -{ - std::vector result; - - std::vector groups; - groups.emplace_back(std::move(pattern)); - - while(!groups.empty()) { - // pop off the first element - auto children = std::move(groups[0]); - groups.erase(groups.begin()); - - // find the first branch node in the list - auto child_iter = std::find_if(children.begin(), children.end(), [](std::shared_ptr const& p) { - return dynamic_cast(p.get()); - }); - - // no branch nodes left : expansion is complete for this grouping - if (child_iter == children.end()) { - result.emplace_back(std::move(children)); - continue; - } - - // pop the child from the list - auto child = std::move(*child_iter); - children.erase(child_iter); - - // expand the branch in the appropriate way - if (Either* either = dynamic_cast(child.get())) { - // "[e] + children" for each child 'e' in Either - for(auto const& eitherChild : either->children()) { - PatternList group = { eitherChild }; - group.insert(group.end(), children.begin(), children.end()); - - groups.emplace_back(std::move(group)); - } - } else if (OneOrMore* oneOrMore = dynamic_cast(child.get())) { - // child.children * 2 + children - auto const& subchildren = oneOrMore->children(); - PatternList group = subchildren; - group.insert(group.end(), subchildren.begin(), subchildren.end()); - group.insert(group.end(), children.begin(), children.end()); - - groups.emplace_back(std::move(group)); - } else { // Required, Optional, OptionsShortcut - BranchPattern* branch = dynamic_cast(child.get()); - - // child.children + children - PatternList group = branch->children(); - group.insert(group.end(), children.begin(), children.end()); - - groups.emplace_back(std::move(group)); - } - } - - return result; -} - -class Tokens { -public: - Tokens(std::vector tokens, bool isParsingArgv = true) - : fTokens(std::move(tokens)), - fIsParsingArgv(isParsingArgv) - {} - - explicit operator bool() const { - return fIndex < fTokens.size(); - } - - static Tokens from_pattern(std::string const& source) { - static const std::regex re_separators { - "(?:\\s*)" // any spaces (non-matching subgroup) - "(" - "[\\[\\]\\(\\)\\|]" // one character of brackets or parens or pipe character - "|" - "\\.\\.\\." // elipsis - ")" }; - - static const std::regex re_strings { - "(?:\\s*)" // any spaces (non-matching subgroup) - "(" - "\\S*<.*?>" // strings, but make sure to keep "< >" strings together - "|" - "\\S+" // string without <> - ")" }; - - // We do two stages of regex matching. The '[]()' and '...' are strong delimeters - // and need to be split out anywhere they occur (even at the end of a token). We - // first split on those, and then parse the stuff between them to find the string - // tokens. This is a little harder than the python version, since they have regex.split - // and we dont have anything like that. - - std::vector tokens; - std::for_each(std::sregex_iterator{ source.begin(), source.end(), re_separators }, - std::sregex_iterator{}, - [&](std::smatch const& match) - { - // handle anything before the separator (this is the "stuff" between the delimeters) - if (match.prefix().matched) { - std::for_each(std::sregex_iterator{match.prefix().first, match.prefix().second, re_strings}, - std::sregex_iterator{}, - [&](std::smatch const& m) - { - tokens.push_back(m[1].str()); - }); - } - - // handle the delimter token itself - if (match[1].matched) { - tokens.push_back(match[1].str()); - } - }); - - return Tokens(tokens, false); - } - - std::string const& current() const { - if (*this) - return fTokens[fIndex]; - - static std::string const empty; - return empty; - } - - std::string the_rest() const { - if (!*this) - return {}; - return join(fTokens.begin()+fIndex, - fTokens.end(), - " "); - } - - std::string pop() { - return std::move(fTokens.at(fIndex++)); - } - - bool isParsingArgv() const { return fIsParsingArgv; } - - struct OptionError : std::runtime_error { using runtime_error::runtime_error; }; - -private: - std::vector fTokens; - size_t fIndex = 0; - bool fIsParsingArgv; -}; - -// Get all instances of 'T' from the pattern -template -std::vector flat_filter(Pattern& pattern) { - std::vector flattened = pattern.flat([](Pattern const* p) -> bool { - return dynamic_cast(p); - }); - - // now, we're guaranteed to have T*'s, so just use static_cast - std::vector ret; - std::transform(flattened.begin(), flattened.end(), std::back_inserter(ret), [](Pattern* p) { - return static_cast(p); - }); - return ret; -} - -std::vector parse_section(std::string const& name, std::string const& source) { - // ECMAScript regex only has "?=" for a non-matching lookahead. In order to make sure we always have - // a newline to anchor our matching, we have to avoid matching the final newline of each grouping. - // Therefore, our regex is adjusted from the docopt Python one to use ?= to match the newlines before - // the following lines, rather than after. - std::regex const re_section_pattern { - "(?:^|\\n)" // anchored at a linebreak (or start of string) - "(" - "[^\\n]*" + name + "[^\\n]*(?=\\n?)" // a line that contains the name - "(?:\\n[ \\t].*?(?=\\n|$))*" // followed by any number of lines that are indented - ")", - std::regex::icase - }; - - std::vector ret; - std::for_each(std::sregex_iterator(source.begin(), source.end(), re_section_pattern), - std::sregex_iterator(), - [&](std::smatch const& match) - { - ret.push_back(trim(match[1].str())); - }); - - return ret; -} - -bool is_argument_spec(std::string const& token) { - if (token.empty()) - return false; - - if (token[0]=='<' && token[token.size()-1]=='>') - return true; - - if (std::all_of(token.begin(), token.end(), &::isupper)) - return true; - - return false; -} - -template -std::vector longOptions(I iter, I end) { - std::vector ret; - std::transform(iter, end, - std::back_inserter(ret), - [](typename I::reference opt) { return opt->longOption(); }); - return ret; -} - -PatternList parse_long(Tokens& tokens, std::vector