summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/cmArgumentParser.cxx93
-rw-r--r--Source/cmArgumentParser.h143
-rw-r--r--Tests/CMakeLib/CMakeLists.txt1
-rw-r--r--Tests/CMakeLib/testArgumentParser.cxx148
-rw-r--r--Utilities/IWYU/mapping.imp1
-rwxr-xr-xbootstrap1
7 files changed, 389 insertions, 0 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 52f6742eb7..a2836d64e7 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -143,6 +143,8 @@ set(SRCS
cmAffinity.cxx
cmAffinity.h
cmArchiveWrite.cxx
+ cmArgumentParser.cxx
+ cmArgumentParser.h
cmBase32.cxx
cmCacheManager.cxx
cmCacheManager.h
diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx
new file mode 100644
index 0000000000..9a9932c569
--- /dev/null
+++ b/Source/cmArgumentParser.cxx
@@ -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
diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h
new file mode 100644
index 0000000000..6cfe946336
--- /dev/null
+++ b/Source/cmArgumentParser.h
@@ -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
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 91f7e2500b..031ab012df 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -5,6 +5,7 @@ include_directories(
)
set(CMakeLib_TESTS
+ testArgumentParser.cxx
testGeneratedFileStream.cxx
testRST.cxx
testRange.cxx
diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx
new file mode 100644
index 0000000000..788fecee65
--- /dev/null
+++ b/Tests/CMakeLib/testArgumentParser.cxx
@@ -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;
+}
diff --git a/Utilities/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp
index 482a08dd82..0393ff199e 100644
--- a/Utilities/IWYU/mapping.imp
+++ b/Utilities/IWYU/mapping.imp
@@ -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 ] },
diff --git a/bootstrap b/bootstrap
index 537b43b32e..addb4f7d63 100755
--- a/bootstrap
+++ b/bootstrap
@@ -260,6 +260,7 @@ CMAKE_CXX_SOURCES="\
cmAddLibraryCommand \
cmAddSubDirectoryCommand \
cmAddTestCommand \
+ cmArgumentParser \
cmBreakCommand \
cmBuildCommand \
cmCMakeMinimumRequired \