Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
gershnik committed Mar 14, 2022
2 parents ea63726 + 8aa3b33 commit 66e1c3b
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 63 deletions.
21 changes: 5 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Fully-featured, powerful and simple to use C++ command line argument parser.

[![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/)
[![Standard](https://img.shields.io/badge/C%2B%2B-20-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization)
[![License](https://img.shields.io/badge/license-BSD-brightgreen.svg)](https://opensource.org/licenses/BSD-3-Clause)
[![Tests](https://github.com/gershnik/argum/actions/workflows/test.yml/badge.svg)](https://github.com/gershnik/argum/actions/workflows/test.yml)

<!-- TOC depthfrom:2 -->

- [Features and goals](#features-and-goals)
Expand Down Expand Up @@ -212,19 +217,3 @@ Having multiple options arguments is a very bad idea. Consider this. Normally wi
This is simply due to the fact that, currently, not all compilers have standard libraries have ranges yet. Once ranges become widely available this library will integrate them.


















6 changes: 3 additions & 3 deletions inc/argum/char-constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ namespace Argum {
return !CharConstants<Char>::isSpace(c);
});
str.erase(str.begin(), firstNotSpace);
auto lastNotSpace = std::find_if(str.begin(), str.end(), [](const auto c) {
return CharConstants<Char>::isSpace(c);
});
auto lastNotSpace = std::find_if(str.rbegin(), str.rend(), [](const auto c) {
return !CharConstants<Char>::isSpace(c);
}).base();
str.erase(lastNotSpace, str.end());
return str;
}
Expand Down
11 changes: 11 additions & 0 deletions inc/argum/command-line.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ namespace Argum {
BasicResponseFileReader(std::initializer_list<StringType> prefixes): m_prefixes(prefixes) {
}

//throws ResponseFileReader::Exception on I/O failures
auto expand(int argc, CharType ** argv) -> std::vector<StringType> {
return expand(makeArgSpan(argc, argv));
}

//throws ResponseFileReader::Exception on I/O failures
template<class Splitter>
auto expand(int argc, CharType ** argv, Splitter && splitter) -> std::vector<StringType> {
return expand(makeArgSpan(argc, argv), std::forward<Splitter>(splitter));
}

//throws ResponseFileReader::Exception on I/O failures
template<ArgRange<CharType> Args>
auto expand(const Args & args) -> std::vector<StringType> {
Expand Down
16 changes: 16 additions & 0 deletions inc/argum/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@ namespace Argum {
return ret;
}

template<class It1, class It2, class Joiner, class Projection>
auto join(It1 first, It2 last, Joiner joiner, Projection proj) -> decltype(proj(*first) + joiner) {

decltype(proj(*first) + joiner) ret;

if (first == last)
return ret;

ret += proj(*first);
for(++first; first != last; ++first) {
ret += joiner;
ret += proj(*first);
};
return ret;
}

template<class Char>
auto matchPrefix(std::basic_string_view<Char> value, std::basic_string_view<Char> prefix) -> bool {

Expand Down
64 changes: 47 additions & 17 deletions inc/argum/help-formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@

namespace Argum {

template<class Char> class BasicOption;
template<class Char> class BasicPositional;
template<class Char> class BasicParser;

ARGUM_MOD_EXPORTED
template<class Parser>
class HelpFormatter {
template<class Char>
class BasicHelpFormatter {
public:
using CharType = typename Parser::CharType;
using CharType = Char;
using StringViewType = std::basic_string_view<CharType>;
using StringType = std::basic_string<CharType>;
using Option = BasicOption<CharType>;
using Positional = BasicPositional<CharType>;

struct Layout {
unsigned width = 80;
Expand All @@ -38,8 +44,16 @@ namespace Argum {
using Messages = Argum::Messages<CharType>;

public:
HelpFormatter(const Parser & parser, StringViewType progName, Layout layout = defaultLayout):
m_parser(parser),
BasicHelpFormatter(const BasicParser<Char> & parser, StringViewType progName, Layout layout = defaultLayout):
BasicHelpFormatter(progName, parser.options(), parser.positionals(), layout) {
}

BasicHelpFormatter(StringViewType progName,
const std::vector<Option> & options,
const std::vector<Positional> & positionals,
Layout layout = defaultLayout):
m_options(options),
m_positionals(positionals),
m_progName(progName),
m_layout(layout) {

Expand All @@ -50,7 +64,12 @@ namespace Argum {
}

auto formatUsage() const -> StringType {
return wordWrap(StringType(Messages::usageStart()).append(this->formatSyntax()), m_layout.width);
constexpr auto space = CharConstants::space;
return wordWrap(StringType(Messages::usageStart()).
append(this->m_progName).
append({space}).
append(this->formatSyntax()),
m_layout.width);
}

auto formatHelp() const -> StringType {
Expand All @@ -63,15 +82,15 @@ namespace Argum {
if (helpContent.maxNameLen > m_layout.helpNameMaxWidth)
helpContent.maxNameLen = m_layout.helpNameMaxWidth;

if (!this->m_parser.positionals().empty()) {
if (!this->m_positionals.empty()) {
ret.append(wordWrap(Messages::positionalHeader(), m_layout.width));
for(auto & [name, desc]: helpContent.positionalItems) {
ret.append({endl}).append(this->formatItemHelp(name, desc, helpContent.maxNameLen));
}
ret.append(2, endl);
}

if (!this->m_parser.options().empty()) {
if (!this->m_options.empty()) {
ret.append(wordWrap(Messages::optionsHeader(), m_layout.width));
for(auto & [name, desc]: helpContent.optionItems) {
ret.append({endl}).append(this->formatItemHelp(name, desc, helpContent.maxNameLen));
Expand All @@ -85,15 +104,23 @@ namespace Argum {

constexpr auto space = CharConstants::space;

StringType ret = this->m_progName;
auto getSyntax = [](auto & obj) {
return obj.formatSyntax();
};

for(auto & opt: this->m_parser.options())
ret.append({space}).append(opt.formatSyntax());
StringType options = join(this->m_options.begin(), this->m_options.end(), space, getSyntax);
StringType positionals = join(this->m_positionals.begin(), this->m_positionals.end(), space, getSyntax);

for (auto & pos: this->m_parser.positionals())
ret.append({space}).append(pos.formatSyntax());
if (!options.empty()) {
if (!positionals.empty()) {
options += space;
options += positionals;
return options;
}
return options;
}

return ret;
return positionals;
}


Expand All @@ -106,13 +133,13 @@ namespace Argum {

HelpContent ret;

std::for_each(this->m_parser.positionals().begin(), this->m_parser.positionals().end(), [&](auto & pos){
std::for_each(this->m_positionals.begin(), this->m_positionals.end(), [&](auto & pos){
auto name = pos.formatHelpName();
if (name.length() > ret.maxNameLen)
ret.maxNameLen = unsigned(name.length());
ret.positionalItems.emplace_back(std::move(name), pos.formatHelpDescription());
});
std::for_each(this->m_parser.options().begin(), this->m_parser.options().end(), [&](auto & opt){
std::for_each(this->m_options.begin(), this->m_options.end(), [&](auto & opt){
auto name = opt.formatHelpName();
if (name.length() > ret.maxNameLen)
ret.maxNameLen = unsigned(name.length());
Expand Down Expand Up @@ -145,11 +172,14 @@ namespace Argum {
return ret;
}
private:
const Parser & m_parser;
const std::vector<Option> & m_options;
const std::vector<Positional> & m_positionals;
StringType m_progName;
Layout m_layout;
};

ARGUM_DECLARE_FRIENDLY_NAMES(HelpFormatter)

}


Expand Down
14 changes: 10 additions & 4 deletions inc/argum/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,11 @@ namespace Argum {
private:
using CharConstants = Argum::CharConstants<CharType>;
using Messages = Argum::Messages<CharType>;
using Tokenizer = BasicTokenizer<Char>;
using ValidationData = ParsingValidationData<Char>;
using Tokenizer = BasicTokenizer<CharType>;
using ValidationData = BasicValidationData<CharType>;
using HelpFormatter = BasicHelpFormatter<CharType>;

using ValidatorFunction = std::function<bool (const ParsingValidationData<CharType> &)>;
using ValidatorFunction = std::function<bool (const ValidationData &)>;

public:
struct UnrecognizedOption : public ParsingException {
Expand Down Expand Up @@ -386,6 +387,11 @@ namespace Argum {
}

auto add(Positional positional) {
//for expected number of positionals this is faster than maintaining a map
for(auto & existing: this->m_positionals) {
if (existing.m_name == positional.m_name)
ARGUM_INVALID_ARGUMENT("duplicate positional name");
}
this->m_positionals.emplace_back(std::move(positional));
++m_updateCount;
}
Expand Down Expand Up @@ -723,7 +729,7 @@ namespace Argum {
int m_positionalIndex = -1;
std::vector<unsigned> m_positionalSizes;

ParsingValidationData<CharType> m_validationData;
ValidationData m_validationData;
};

private:
Expand Down
16 changes: 9 additions & 7 deletions inc/argum/validators.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Argum {

ARGUM_MOD_EXPORTED
template<class Char>
class ParsingValidationData {
class BasicValidationData {
public:
auto optionCount(std::basic_string_view<Char> name) const -> unsigned {
auto it = this->m_optionCounts.find(name);
Expand All @@ -46,8 +46,10 @@ namespace Argum {
FlatMap<std::basic_string<Char>, unsigned> m_positionalCounts;
};

ARGUM_DECLARE_FRIENDLY_NAMES(ValidationData)

template<class T, class Char>
concept ParserValidator = std::is_invocable_r_v<bool, T, const ParsingValidationData<Char>>;
concept ParserValidator = std::is_invocable_r_v<bool, T, const BasicValidationData<Char>>;

template<class T, class Char>
concept DescribableParserValidator = ParserValidator<T, Char> &&
Expand Down Expand Up @@ -80,7 +82,7 @@ namespace Argum {

template<class Char>
requires(ParserValidator<Impl, Char>)
auto operator()(const ParsingValidationData<Char> & data) const -> bool {
auto operator()(const BasicValidationData<Char> & data) const -> bool {
return !this->m_impl(data);
}

Expand Down Expand Up @@ -130,7 +132,7 @@ namespace Argum {

template<class Char>
requires(CompatibleParserValidators<Char, Args...>)
auto operator()(const ParsingValidationData<Char> & data) const -> bool {
auto operator()(const BasicValidationData<Char> & data) const -> bool {
if constexpr (Comb == ValidatorCombination::And) {
return std::apply([&data](const Args & ...args) -> bool { return (args(data) && ...); }, this->m_items);
} else if constexpr (Comb == ValidatorCombination::Or) {
Expand Down Expand Up @@ -168,7 +170,7 @@ namespace Argum {

template<size_t N, class Char>
requires(CompatibleParserValidators<Char, Args...>)
auto evalOneOrNone(const ParsingValidationData<Char> & data, unsigned & counter) const {
auto evalOneOrNone(const BasicValidationData<Char> & data, unsigned & counter) const {

constexpr auto idx = sizeof...(Args) - N;

Expand All @@ -186,7 +188,7 @@ namespace Argum {

template<size_t N, class Char>
requires(CompatibleParserValidators<Char, Args...>)
auto evalAllOrNone(const ParsingValidationData<Char> & data, unsigned & counter) const {
auto evalAllOrNone(const BasicValidationData<Char> & data, unsigned & counter) const {

constexpr auto idx = sizeof...(Args) - N;

Expand Down Expand Up @@ -365,7 +367,7 @@ namespace Argum {
public:
ItemOccurs(std::basic_string_view<Char> name, unsigned count) : m_name(name), m_count(count) {}

auto operator()(const ParsingValidationData<Char> & data) const -> bool {
auto operator()(const BasicValidationData<Char> & data) const -> bool {
if constexpr (IsOption)
return Comp()(data.optionCount(this->m_name), this->m_count);
else
Expand Down
6 changes: 6 additions & 0 deletions test/test-command-line.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@ TEST_CASE( "Wide response file" , "[command-line]") {
});
CHECK(expanded == vector<wstring>{L"first", L"foo", L"bar", L"hello", L"world", L"baz", L"last"});
}

TEST_CASE( "Trim in place" , "[command-line]") {

std::string str = "help help";
CHECK(trimInPlace(str) == "help help");
}
7 changes: 6 additions & 1 deletion test/test-parser-positionals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ TEST_CASE( "Positional boundary Cases" , "[parser]") {
{
Parser parser;
parser.add(POSITIONAL("p"));
parser.add(POSITIONAL("p")); //duplicate names are fine though stupid
CHECK_THROWS_AS(parser.add(POSITIONAL("p")), invalid_argument);
}

{
Parser parser;
parser.add(POSITIONAL("p"));
CHECK_THROWS_AS(parser.add(POSITIONAL("p").occurs(Quantifier(6,0))), invalid_argument);
}

Expand Down
Loading

0 comments on commit 66e1c3b

Please sign in to comment.