Introduce cmArgumentParser

This commit is contained in:
Regina Pfeifer 2019-03-23 22:45:41 +01:00 committed by Kyle Edwards
parent 8c28e63cb4
commit 4359fe133b
7 changed files with 389 additions and 0 deletions

View File

@ -143,6 +143,8 @@ set(SRCS
cmAffinity.cxx
cmAffinity.h
cmArchiveWrite.cxx
cmArgumentParser.cxx
cmArgumentParser.h
cmBase32.cxx
cmCacheManager.cxx
cmCacheManager.h

View File

@ -0,0 +1,93 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmArgumentParser.h"
#include <algorithm>
#include <type_traits>
namespace ArgumentParser {
auto ActionMap::Emplace(cm::string_view name, Action action)
-> std::pair<iterator, bool>
{
auto const it =
std::lower_bound(this->begin(), this->end(), name,
[](value_type const& elem, cm::string_view const& k) {
return elem.first < k;
});
return (it != this->end() && it->first == name)
? std::make_pair(it, false)
: std::make_pair(this->emplace(it, name, std::move(action)), true);
}
auto ActionMap::Find(cm::string_view name) const -> const_iterator
{
auto const it =
std::lower_bound(this->begin(), this->end(), name,
[](value_type const& elem, cm::string_view const& k) {
return elem.first < k;
});
return (it != this->end() && it->first == name) ? it : this->end();
}
void Instance::Bind(bool& val)
{
val = true;
this->CurrentString = nullptr;
this->CurrentList = nullptr;
this->ExpectValue = false;
}
void Instance::Bind(std::string& val)
{
this->CurrentString = &val;
this->CurrentList = nullptr;
this->ExpectValue = true;
}
void Instance::Bind(StringList& val)
{
this->CurrentString = nullptr;
this->CurrentList = &val;
this->ExpectValue = true;
}
void Instance::Bind(MultiStringList& val)
{
this->CurrentString = nullptr;
this->CurrentList = (val.emplace_back(), &val.back());
this->ExpectValue = false;
}
void Instance::Consume(cm::string_view arg, void* result,
std::vector<std::string>* unparsedArguments,
std::vector<std::string>* keywordsMissingValue)
{
auto const it = this->Bindings.Find(arg);
if (it != this->Bindings.end()) {
it->second(*this, result);
if (this->ExpectValue && keywordsMissingValue != nullptr) {
keywordsMissingValue->emplace_back(arg);
}
return;
}
if (this->CurrentString != nullptr) {
this->CurrentString->assign(std::string(arg));
this->CurrentString = nullptr;
this->CurrentList = nullptr;
} else if (this->CurrentList != nullptr) {
this->CurrentList->emplace_back(arg);
} else if (unparsedArguments != nullptr) {
unparsedArguments->emplace_back(arg);
}
if (this->ExpectValue) {
if (keywordsMissingValue != nullptr) {
keywordsMissingValue->pop_back();
}
this->ExpectValue = false;
}
}
} // namespace ArgumentParser

143
Source/cmArgumentParser.h Normal file
View File

@ -0,0 +1,143 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmArgumentParser_h
#define cmArgumentParser_h
#include "cmConfigure.h" // IWYU pragma: keep
#include "cm_static_string_view.hxx"
#include "cm_string_view.hxx"
#include <cassert>
#include <functional>
#include <string>
#include <utility>
#include <vector>
namespace ArgumentParser {
using StringList = std::vector<std::string>;
using MultiStringList = std::vector<StringList>;
class Instance;
using Action = std::function<void(Instance&, void*)>;
// using ActionMap = cm::flat_map<cm::string_view, Action>;
class ActionMap : public std::vector<std::pair<cm::string_view, Action>>
{
public:
std::pair<iterator, bool> Emplace(cm::string_view name, Action action);
const_iterator Find(cm::string_view name) const;
};
class Instance
{
public:
Instance(ActionMap const& bindings)
: Bindings(bindings)
{
}
void Bind(bool& val);
void Bind(std::string& val);
void Bind(StringList& val);
void Bind(MultiStringList& val);
void Consume(cm::string_view arg, void* result,
std::vector<std::string>* unparsedArguments,
std::vector<std::string>* keywordsMissingValue);
private:
ActionMap const& Bindings;
std::string* CurrentString = nullptr;
StringList* CurrentList = nullptr;
bool ExpectValue = false;
};
} // namespace ArgumentParser
template <typename Result>
class cmArgumentParser
{
public:
// I *think* this function could be made `constexpr` when the code is
// compiled as C++20. This would allow building a parser at compile time.
template <typename T>
cmArgumentParser& Bind(cm::static_string_view name, T Result::*member)
{
bool const inserted =
this->Bindings
.Emplace(name,
[member](ArgumentParser::Instance& instance, void* result) {
instance.Bind(static_cast<Result*>(result)->*member);
})
.second;
assert(inserted), (void)inserted;
return *this;
}
template <typename Range>
void Parse(Result& result, Range const& args,
std::vector<std::string>* unparsedArguments = nullptr,
std::vector<std::string>* keywordsMissingValue = nullptr) const
{
ArgumentParser::Instance instance(this->Bindings);
for (cm::string_view arg : args) {
instance.Consume(arg, &result, unparsedArguments, keywordsMissingValue);
}
}
template <typename Range>
Result Parse(Range const& args,
std::vector<std::string>* unparsedArguments = nullptr,
std::vector<std::string>* keywordsMissingValue = nullptr) const
{
Result result;
this->Parse(result, args, unparsedArguments, keywordsMissingValue);
return result;
}
private:
ArgumentParser::ActionMap Bindings;
};
template <>
class cmArgumentParser<void>
{
public:
template <typename T>
cmArgumentParser& Bind(cm::static_string_view name, T& ref)
{
bool const inserted = this->Bind(cm::string_view(name), ref);
assert(inserted), (void)inserted;
return *this;
}
template <typename Range>
void Parse(Range const& args,
std::vector<std::string>* unparsedArguments = nullptr,
std::vector<std::string>* keywordsMissingValue = nullptr) const
{
ArgumentParser::Instance instance(this->Bindings);
for (cm::string_view arg : args) {
instance.Consume(arg, nullptr, unparsedArguments, keywordsMissingValue);
}
}
protected:
template <typename T>
bool Bind(cm::string_view name, T& ref)
{
return this->Bindings
.Emplace(name,
[&ref](ArgumentParser::Instance& instance, void*) {
instance.Bind(ref);
})
.second;
}
private:
ArgumentParser::ActionMap Bindings;
};
#endif

View File

@ -5,6 +5,7 @@ include_directories(
)
set(CMakeLib_TESTS
testArgumentParser.cxx
testGeneratedFileStream.cxx
testRST.cxx
testRange.cxx

View File

@ -0,0 +1,148 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmArgumentParser.h"
#include "cm_static_string_view.hxx"
#include "cm_string_view.hxx"
#include <initializer_list>
#include <iostream>
#include <string>
#include <vector>
namespace {
struct Result
{
bool Option1 = false;
bool Option2 = false;
std::string String1;
std::string String2;
std::vector<std::string> List1;
std::vector<std::string> List2;
std::vector<std::string> List3;
std::vector<std::vector<std::string>> Multi1;
std::vector<std::vector<std::string>> Multi2;
std::vector<std::vector<std::string>> Multi3;
};
std::initializer_list<cm::string_view> const args = {
/* clang-format off */
"OPTION_1", // option
"STRING_1", // string arg missing value
"STRING_2", "foo", "bar", // string arg + unparsed value
"LIST_1", // list arg missing values
"LIST_2", "foo", "bar", // list arg with 2 elems
"LIST_3", "bar", // list arg ...
"LIST_3", "foo", // ... with continuation
"MULTI_2", // multi list with 0 lists
"MULTI_3", "foo", "bar", // multi list with first list with two elems
"MULTI_3", "bar", "foo", // multi list with second list with two elems
/* clang-format on */
};
bool verifyResult(Result const& result,
std::vector<std::string> const& unparsedArguments,
std::vector<std::string> const& keywordsMissingValue)
{
static std::vector<std::string> const foobar = { "foo", "bar" };
static std::vector<std::string> const barfoo = { "bar", "foo" };
static std::vector<std::string> const missing = { "STRING_1", "LIST_1" };
#define ASSERT_TRUE(x) \
do { \
if (!(x)) { \
std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
return false; \
} \
} while (false)
ASSERT_TRUE(result.Option1);
ASSERT_TRUE(!result.Option2);
ASSERT_TRUE(result.String1.empty());
ASSERT_TRUE(result.String2 == "foo");
ASSERT_TRUE(result.List1.empty());
ASSERT_TRUE(result.List2 == foobar);
ASSERT_TRUE(result.List3 == barfoo);
ASSERT_TRUE(result.Multi1.empty());
ASSERT_TRUE(result.Multi2.size() == 1);
ASSERT_TRUE(result.Multi2[0].empty());
ASSERT_TRUE(result.Multi3.size() == 2);
ASSERT_TRUE(result.Multi3[0] == foobar);
ASSERT_TRUE(result.Multi3[1] == barfoo);
ASSERT_TRUE(unparsedArguments.size() == 1);
ASSERT_TRUE(unparsedArguments[0] == "bar");
ASSERT_TRUE(keywordsMissingValue == missing);
return true;
}
bool testArgumentParserDynamic()
{
Result result;
std::vector<std::string> unparsedArguments;
std::vector<std::string> keywordsMissingValue;
cmArgumentParser<void>{}
.Bind("OPTION_1"_s, result.Option1)
.Bind("OPTION_2"_s, result.Option2)
.Bind("STRING_1"_s, result.String1)
.Bind("STRING_2"_s, result.String2)
.Bind("LIST_1"_s, result.List1)
.Bind("LIST_2"_s, result.List2)
.Bind("LIST_3"_s, result.List3)
.Bind("MULTI_1"_s, result.Multi1)
.Bind("MULTI_2"_s, result.Multi2)
.Bind("MULTI_3"_s, result.Multi3)
.Parse(args, &unparsedArguments, &keywordsMissingValue);
return verifyResult(result, unparsedArguments, keywordsMissingValue);
}
bool testArgumentParserStatic()
{
static auto const parser = //
cmArgumentParser<Result>{}
.Bind("OPTION_1"_s, &Result::Option1)
.Bind("OPTION_2"_s, &Result::Option2)
.Bind("STRING_1"_s, &Result::String1)
.Bind("STRING_2"_s, &Result::String2)
.Bind("LIST_1"_s, &Result::List1)
.Bind("LIST_2"_s, &Result::List2)
.Bind("LIST_3"_s, &Result::List3)
.Bind("MULTI_1"_s, &Result::Multi1)
.Bind("MULTI_2"_s, &Result::Multi2)
.Bind("MULTI_3"_s, &Result::Multi3);
std::vector<std::string> unparsedArguments;
std::vector<std::string> keywordsMissingValue;
Result const result =
parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
return verifyResult(result, unparsedArguments, keywordsMissingValue);
}
} // namespace
int testArgumentParser(int /*unused*/, char* /*unused*/ [])
{
if (!testArgumentParserDynamic()) {
std::cout << "While executing testArgumentParserDynamic().\n";
return -1;
}
if (!testArgumentParserStatic()) {
std::cout << "While executing testArgumentParserStatic().\n";
return -1;
}
return 0;
}

View File

@ -68,6 +68,7 @@
{ symbol: [ "std::__decay_and_strip<cmGeneratorTarget *&>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::__decay_and_strip<cmFindCommon::PathLabel &>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::__decay_and_strip<cmSearchPath>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::__decay_and_strip<cm::string_view>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::__decay_and_strip<std::basic_string<char> &>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::__decay_and_strip<const std::basic_string<char> &>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::__decay_and_strip<cmFindPackageCommand::PathLabel &>::__type", private, "\"cmConfigure.h\"", public ] },

View File

@ -260,6 +260,7 @@ CMAKE_CXX_SOURCES="\
cmAddLibraryCommand \
cmAddSubDirectoryCommand \
cmAddTestCommand \
cmArgumentParser \
cmBreakCommand \
cmBuildCommand \
cmCMakeMinimumRequired \