Introduce cmArgumentParser
This commit is contained in:
parent
8c28e63cb4
commit
4359fe133b
@ -143,6 +143,8 @@ set(SRCS
|
||||
cmAffinity.cxx
|
||||
cmAffinity.h
|
||||
cmArchiveWrite.cxx
|
||||
cmArgumentParser.cxx
|
||||
cmArgumentParser.h
|
||||
cmBase32.cxx
|
||||
cmCacheManager.cxx
|
||||
cmCacheManager.h
|
||||
|
93
Source/cmArgumentParser.cxx
Normal file
93
Source/cmArgumentParser.cxx
Normal 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
143
Source/cmArgumentParser.h
Normal 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
|
@ -5,6 +5,7 @@ include_directories(
|
||||
)
|
||||
|
||||
set(CMakeLib_TESTS
|
||||
testArgumentParser.cxx
|
||||
testGeneratedFileStream.cxx
|
||||
testRST.cxx
|
||||
testRange.cxx
|
||||
|
148
Tests/CMakeLib/testArgumentParser.cxx
Normal file
148
Tests/CMakeLib/testArgumentParser.cxx
Normal 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;
|
||||
}
|
@ -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 ] },
|
||||
|
Loading…
Reference in New Issue
Block a user