Skip to content

Commit

Permalink
fixed handling of empty args
Browse files Browse the repository at this point in the history
  • Loading branch information
muellan committed Sep 20, 2018
1 parent dfda4ac commit 6271962
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 27 deletions.
32 changes: 14 additions & 18 deletions include/clipp.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*****************************************************************************
* ___ _ _ ___ ___
* | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++
* | |_ | |_ | | | _/ _/ version 1.2.0
* | |_ | |_ | | | _/ _/ version 1.2.1
* |___||___||_| |_| |_| https://github.com/muellan/clipp
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Expand Down Expand Up @@ -114,12 +114,12 @@ class subrange {

/** @brief returns true, if query string is a prefix of the subject string */
constexpr bool prefix() const noexcept {
return at_ == 0 && length_ > 0;
return at_ == 0;
}

/** @brief returns true, if query is a substring of the query string */
constexpr explicit operator bool () const noexcept {
return at_ != arg_string::npos && length_ > 0;
return at_ != arg_string::npos;
}

private:
Expand Down Expand Up @@ -605,8 +605,7 @@ class map_arg_to
map_arg_to(T& target) noexcept : t_{std::addressof(target)} {}

void operator () (const char* s) const {
if(t_ && s && (std::strlen(s) > 0))
*t_ = detail::make<T>::from(s);
if(t_ && s) *t_ = detail::make<T>::from(s);
}

private:
Expand Down Expand Up @@ -914,6 +913,7 @@ inline subrange
substring_match(const std::basic_string<C,T,A>& subject,
const std::basic_string<C,T,A>& query)
{
if(subject.empty() && query.empty()) return subrange(0,0);
if(subject.empty() || query.empty()) return subrange{};
auto i = subject.find(query);
if(i == std::basic_string<C,T,A>::npos) return subrange{};
Expand Down Expand Up @@ -2014,12 +2014,13 @@ class parameter :
subrange
match(const arg_string& arg) const
{
if(arg.empty()) return subrange{};

if(flags_.empty()) {
return matcher_(arg);
}
else {
//empty flags are not allowed
if(arg.empty()) return subrange{};

if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) {
return subrange{0,arg.size()};
}
Expand Down Expand Up @@ -4337,7 +4338,7 @@ class match_t {
const arg_string& str() const noexcept { return str_; }
const scoped_dfs_traverser& pos() const noexcept { return pos_; }

explicit operator bool() const noexcept { return !str_.empty(); }
explicit operator bool() const noexcept { return bool(pos_); }

private:
arg_string str_;
Expand All @@ -4357,8 +4358,6 @@ match_t
full_match(scoped_dfs_traverser pos, const arg_string& arg,
const ParamSelector& select)
{
if(arg.empty()) return match_t{};

while(pos) {
if(pos->is_param()) {
const auto& param = pos->as_param();
Expand Down Expand Up @@ -4388,8 +4387,6 @@ match_t
prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
const ParamSelector& select)
{
if(arg.empty()) return match_t{};

while(pos) {
if(pos->is_param()) {
const auto& param = pos->as_param();
Expand Down Expand Up @@ -4424,8 +4421,6 @@ match_t
partial_match(scoped_dfs_traverser pos, const arg_string& arg,
const ParamSelector& select)
{
if(arg.empty()) return match_t{};

while(pos) {
if(pos->is_param()) {
const auto& param = pos->as_param();
Expand Down Expand Up @@ -4581,7 +4576,7 @@ class parser
++eaten_;
++index_;

if(!valid() || arg.empty()) return false;
if(!valid()) return false;

if(!blocked_ && try_match(arg)) return true;

Expand Down Expand Up @@ -4856,7 +4851,7 @@ class parser
void add_match(const match_t& match)
{
const auto& pos = match.pos();
if(!pos || !pos->is_param() || match.str().empty()) return;
if(!pos || !pos->is_param()) return;

pos_.next_after_match(pos);

Expand Down Expand Up @@ -5256,7 +5251,7 @@ parse(std::initializer_list<const char*> arglist, const group& cli)
arg_list args;
args.reserve(arglist.size());
for(auto a : arglist) {
if(std::strlen(a) > 0) args.push_back(a);
args.push_back(a);
}

return parse(std::move(args), cli);
Expand Down Expand Up @@ -6924,7 +6919,8 @@ void print(OStream& os, const parsing_result& result)
template<class OStream>
void print(OStream& os, const parameter& p)
{
if(p.blocking()) os << '!';
if(p.greedy()) os << '!';
if(p.blocking()) os << '~';
if(!p.required()) os << '[';
os << doc_label(p);
if(p.repeatable()) os << "...";
Expand Down
100 changes: 100 additions & 0 deletions test/empty_args.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*****************************************************************************
*
* CLIPP - command line interfaces for modern C++
*
* released under MIT license
*
* (c) 2017-2018 André Müller; [email protected]
*
*****************************************************************************/

#include "testing.h"


//-------------------------------------------------------------------
struct active {
active() = default;
explicit
active(bool o_, std::string ov_ = "_", std::string pv_ = "_"):
o{o_}, ov{std::move(ov_)}, pv{std::move(pv_)}
{}

bool o = false;
std::string ov = "_";
std::string pv = "_";

friend bool operator == (const active& x, const active& y) noexcept {
return (x.o == y.o && x.ov == y.ov && x.pv == y.pv);
}
};


//-------------------------------------------------------------------
void test_empty(int lineNo,
const std::initializer_list<const char*> args,
const active& matches)
{
using namespace clipp;

active m;
auto cli = (
option("-o", "--opt").set(m.o) & value(match::any, "O", m.ov),
value("P", m.pv)
);

run_wrapped_variants({ __FILE__, lineNo }, args, cli,
[&]{ m = active{}; },
[&]{ return m == matches; });

}


//-------------------------------------------------------------------
void test_nonempty(int lineNo,
const std::initializer_list<const char*> args,
const active& matches)
{
using namespace clipp;

active m;
auto cli = (
option("-o", "--opt").set(m.o) & value(match::nonempty, "O", m.ov),
value("P", m.pv)
);

run_wrapped_variants({ __FILE__, lineNo }, args, cli,
[&]{ m = active{}; },
[&]{ return m == matches; });

}


//-------------------------------------------------------------------
int main()
{
try {
test_empty(__LINE__, {}, active{});
test_empty(__LINE__, {""}, active{});
test_empty(__LINE__, {"-o"}, active{true});
test_empty(__LINE__, {"-o", ""}, active{true, ""});
test_empty(__LINE__, {"-o", "X"}, active{true, "X"});
test_empty(__LINE__, {"X"}, active{false, "_", "X"});
test_empty(__LINE__, {"-o", "", "X"}, active{true, "", "X"});


test_nonempty(__LINE__, {}, active{});
test_nonempty(__LINE__, {""}, active{});
test_nonempty(__LINE__, {"-o"}, active{true});
test_nonempty(__LINE__, {"-o", ""}, active{true});
test_nonempty(__LINE__, {"-o", "X"}, active{true, "X"});
test_nonempty(__LINE__, {"X"}, active{false, "_", "X"});

//ambiguous -> cannot map to value "P", expects value "O" first
test_nonempty(__LINE__, {"-o", "", "X"}, active{true});

}
catch(std::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
}
23 changes: 14 additions & 9 deletions test/testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,21 @@ wrapped_variants(const CLI& cli)
,
/* 3*/ group{group{cli}}
,
/* 4*/ group{option("?????"), cli}
/* 4*/ group{option("?a?"), cli}
,
/* 5*/ group{cli, option("?????")}
/* 5*/ group{cli, option("?a?")}
,
/* 6*/ group{option("!!!!!"), cli, option("?????")}
/* 6*/ group{option("?b?"), cli, option("?a?")}
,
/* 7*/ group{group{option("?????")}, cli}
/* 7*/ group{group{option("?a?")}, cli}
,
/* 8*/ group{cli, group{option("?????")}}
/* 8*/ group{cli, group{option("?a?")}}
,
/* 9*/ group{option("?????"), group{cli}}
/* 9*/ group{option("?a?"), group{cli}}
,
/*10*/ group{group{cli}, option("?????")}
/*10*/ group{group{cli}, option("?a?")}
,
/*11*/ group{option("!!!!!"), group{cli}, option("?????")}
/*11*/ group{option("?b?"), group{cli}, option("?a?")}
};
}

Expand Down Expand Up @@ -99,6 +99,9 @@ void run_wrapped_variants(
// << " with variant " << variant << '\n'
// << "==============================================\n";
// clipp::debug::print(cout, wrappedCli);
// cout << "args = { ";
// for(const auto& a : args) cout << '"' << a << "\" ";
// cout << "}\n";
// auto res = clipp::parse(args, wrappedCli);
// cout << "----------------------------------------------\n";
// clipp::debug::print(cout, res);
Expand Down Expand Up @@ -133,8 +136,10 @@ void run_test(
// cout << "==============================================\n"
// << " in file " << info.file << " in line " << info.line << '\n'
// << "==============================================\n";

// clipp::debug::print(cout, cli);
// cout << "args = { ";
// for(const auto& a : args) cout << '"' << a << "\" ";
// cout << "}\n";
// auto res = clipp::parse(args, cli);
// cout << "----------------------------------------------\n";
// clipp::debug::print(cout, res);
Expand Down

0 comments on commit 6271962

Please sign in to comment.