//
// docopt.cpp
// docopt
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the MIT and/or Boost licenses at your convenience.
// See the LICENSE-MIT and LICENSE-Boost-1.0 files for details.
//
// 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
using namespace docopt;
DOCOPT_INLINE
std::ostream& docopt::operatorsetValue(value{true});
}
ret.push_back(o);
} else {
auto o = std::make_shared(*similar[0]);
value val;
if (o->argCount()) {
if (i == token.end()) {
// consume the next token
auto const& ttoken = tokens.current();
if (ttoken.empty() || ttoken=="--") {
std::string error = shortOpt + " requires an argument";
throw Tokens::OptionError(std::move(error));
}
val = tokens.pop();
} else {
// consume all the rest
val = std::string{i, token.end()};
i = token.end();
}
}
if (tokens.isParsingArgv()) {
o->setValue(val ? std::move(val) : value{true});
}
ret.push_back(o);
}
}
return ret;
}
static PatternList parse_expr(Tokens& tokens, std::vector& options);
static PatternList parse_atom(Tokens& tokens, std::vector& options)
{
// atom ::= '(' expr ')' | '[' expr ']' | 'options'
// | long | shorts | argument | command ;
std::string const& token = tokens.current();
PatternList ret;
if (token == "[") {
tokens.pop();
auto expr = parse_expr(tokens, options);
auto trailing = tokens.pop();
if (trailing != "]") {
throw DocoptLanguageError("Mismatched '['");
}
ret.emplace_back(std::make_shared(std::move(expr)));
} else if (token=="(") {
tokens.pop();
auto expr = parse_expr(tokens, options);
auto trailing = tokens.pop();
if (trailing != ")") {
throw DocoptLanguageError("Mismatched '('");
}
ret.emplace_back(std::make_shared(std::move(expr)));
} else if (token == "options") {
tokens.pop();
ret.emplace_back(std::make_shared());
} else if (starts_with(token, "--") && token != "--") {
ret = parse_long(tokens, options);
} else if (starts_with(token, "-") && token != "-" && token != "--") {
ret = parse_short(tokens, options);
} else if (is_argument_spec(token)) {
ret.emplace_back(std::make_shared(tokens.pop()));
} else {
ret.emplace_back(std::make_shared(tokens.pop()));
}
return ret;
}
static PatternList parse_seq(Tokens& tokens, std::vector& options)
{
// seq ::= ( atom [ '...' ] )* ;"""
PatternList ret;
while (tokens) {
auto const& token = tokens.current();
if (token=="]" || token==")" || token=="|")
break;
auto atom = parse_atom(tokens, options);
if (tokens.current() == "...") {
ret.emplace_back(std::make_shared(std::move(atom)));
tokens.pop();
} else {
std::move(atom.begin(), atom.end(), std::back_inserter(ret));
}
}
return ret;
}
static std::shared_ptr maybe_collapse_to_required(PatternList&& seq)
{
if (seq.size()==1) {
return std::move(seq[0]);
}
return std::make_shared(std::move(seq));
}
static std::shared_ptr maybe_collapse_to_either(PatternList&& seq)
{
if (seq.size()==1) {
return std::move(seq[0]);
}
return std::make_shared(std::move(seq));
}
PatternList parse_expr(Tokens& tokens, std::vector& options)
{
// expr ::= seq ( '|' seq )* ;
auto seq = parse_seq(tokens, options);
if (tokens.current() != "|")
return seq;
PatternList ret;
ret.emplace_back(maybe_collapse_to_required(std::move(seq)));
while (tokens.current() == "|") {
tokens.pop();
seq = parse_seq(tokens, options);
ret.emplace_back(maybe_collapse_to_required(std::move(seq)));
}
return { maybe_collapse_to_either(std::move(ret)) };
}
static Required parse_pattern(std::string const& source, std::vector& options)
{
auto tokens = Tokens::from_pattern(source);
auto result = parse_expr(tokens, options);
if (tokens)
throw DocoptLanguageError("Unexpected ending: '" + tokens.the_rest() + "'");
assert(result.size() == 1 && "top level is always one big");
return Required{ std::move(result) };
}
static std::string formal_usage(std::string const& section) {
std::string ret = "(";
auto i = section.find(':')+1; // skip past "usage:"
auto parts = split(section, i);
for(size_t ii = 1; ii < parts.size(); ++ii) {
if (parts[ii] == parts[0]) {
ret += " ) | (";
} else {
ret.push_back(' ');
ret += parts[ii];
}
}
ret += " )";
return ret;
}
static PatternList parse_argv(Tokens tokens, std::vector& options, bool options_first)
{
// Parse command-line argument vector.
//
// If options_first:
// argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ;
// else:
// argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ;
PatternList ret;
while (tokens) {
auto const& token = tokens.current();
if (token=="--") {
// option list is done; convert all the rest to arguments
while (tokens) {
ret.emplace_back(std::make_shared("", tokens.pop()));
}
} else if (starts_with(token, "--")) {
auto&& parsed = parse_long(tokens, options);
std::move(parsed.begin(), parsed.end(), std::back_inserter(ret));
} else if (token[0]=='-' && token != "-") {
auto&& parsed = parse_short(tokens, options);
std::move(parsed.begin(), parsed.end(), std::back_inserter(ret));
} else if (options_first) {
// option list is done; convert all the rest to arguments
while (tokens) {
ret.emplace_back(std::make_shared("", tokens.pop()));
}
} else {
ret.emplace_back(std::make_shared("", tokens.pop()));
}
}
return ret;
}
std::vector parse_defaults(std::string const& doc) {
// This pattern is a delimiter by which we split the options.
// The delimiter is a new line followed by a whitespace(s) followed by one or two hyphens.
static std::regex const re_delimiter{
"(?:^|\\n)[ \\t]*" // a new line with leading whitespace
"(?=-{1,2})" // [split happens here] (positive lookahead) ... and followed by one or two hyphes
};
std::vector defaults;
for (auto s : parse_section("options:", doc)) {
s.erase(s.begin(), s.begin() + static_cast(s.find(':')) + 1); // get rid of "options:"
for (const auto& opt : regex_split(s, re_delimiter)) {
if (starts_with(opt, "-")) {
defaults.emplace_back(Option::parse(opt));
}
}
}
return defaults;
}
static bool isOptionSet(PatternList const& options, std::string const& opt1, std::string const& opt2 = "") {
return std::any_of(options.begin(), options.end(), [&](std::shared_ptr const& opt) -> bool {
auto const& name = opt->name();
if (name==opt1 || (!opt2.empty() && name==opt2)) {
return opt->hasValue();
}
return false;
});
}
static void extras(bool help, bool version, PatternList const& options) {
if (help && isOptionSet(options, "-h", "--help")) {
throw DocoptExitHelp();
}
if (version && isOptionSet(options, "--version")) {
throw DocoptExitVersion();
}
}
// Parse the doc string and generate the Pattern tree
static std::pair create_pattern_tree(std::string const& doc)
{
auto usage_sections = parse_section("usage:", doc);
if (usage_sections.empty()) {
throw DocoptLanguageError("'usage:' (case-insensitive) not found.");
}
if (usage_sections.size() > 1) {
throw DocoptLanguageError("More than one 'usage:' (case-insensitive).");
}
std::vector options = parse_defaults(doc);
Required pattern = parse_pattern(formal_usage(usage_sections[0]), options);
std::vector pattern_options = flat_filter(pattern);
using UniqueOptions = std::unordered_set;
UniqueOptions const uniq_pattern_options { pattern_options.begin(), pattern_options.end() };
// Fix up any "[options]" shortcuts with the actual option tree
for(auto& options_shortcut : flat_filter(pattern)) {
std::vector doc_options = parse_defaults(doc);
// set(doc_options) - set(pattern_options)
UniqueOptions uniq_doc_options;
for(auto const& opt : doc_options) {
if (uniq_pattern_options.count(&opt))
continue;
uniq_doc_options.insert(&opt);
}
// turn into shared_ptr's and set as children
PatternList children;
std::transform(uniq_doc_options.begin(), uniq_doc_options.end(),
std::back_inserter(children), [](Option const* opt) {
return std::make_shared(*opt);
});
options_shortcut->setChildren(std::move(children));
}
return { std::move(pattern), std::move(options) };
}
DOCOPT_INLINE
std::map
docopt::docopt_parse(std::string const& doc,
std::vector const& argv,
bool help,
bool version,
bool options_first)
{
Required pattern;
std::vector options;
try {
std::tie(pattern, options) = create_pattern_tree(doc);
} catch (Tokens::OptionError const& error) {
throw DocoptLanguageError(error.what());
}
PatternList argv_patterns;
try {
argv_patterns = parse_argv(Tokens(argv), options, options_first);
} catch (Tokens::OptionError const& error) {
throw DocoptArgumentError(error.what());
}
extras(help, version, argv_patterns);
std::vector collected;
bool matched = pattern.fix().match(argv_patterns, collected);
if (matched && argv_patterns.empty()) {
std::map ret;
// (a.name, a.value) for a in (pattern.flat() + collected)
for (auto* p : pattern.leaves()) {
ret[p->name()] = p->getValue();
}
for (auto const& p : collected) {
ret[p->name()] = p->getValue();
}
return ret;
}
if (matched) {
std::string leftover = join(argv.begin(), argv.end(), ", ");
throw DocoptArgumentError("Unexpected argument: " + leftover);
}
throw DocoptArgumentError("Arguments did not match expected patterns"); // BLEH. Bad error.
}
DOCOPT_INLINE
std::map
docopt::docopt(std::string const& doc,
std::vector const& argv,
bool help,
std::string const& version,
bool options_first) noexcept
{
try {
return docopt_parse(doc, argv, help, !version.empty(), options_first);
} catch (DocoptExitHelp const&) {
std::cout