summaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2020-09-10 14:09:27 (GMT)
committerKitware Robot <kwrobot@kitware.com>2020-09-10 14:09:42 (GMT)
commitdb3efb75686265ec23ad593b59b388c262e7cfc8 (patch)
tree5e4e048a20d99dc4f2c687dd3390ab0b5310233d /Source
parent2eb7ff09c759ce470b8547ac03a661606bf31fc9 (diff)
parentb2f3f831e2b0008e8a517493f4a98f85de1fc868 (diff)
downloadCMake-db3efb75686265ec23ad593b59b388c262e7cfc8.zip
CMake-db3efb75686265ec23ad593b59b388c262e7cfc8.tar.gz
CMake-db3efb75686265ec23ad593b59b388c262e7cfc8.tar.bz2
Merge topic 'json-helpers'
b2f3f831e2 Refactor: Use JSON helpers in CTest resource spec 3f3a30e1e0 JSON: Add helpers Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !5197
Diffstat (limited to 'Source')
-rw-r--r--Source/CMakeLists.txt1
-rw-r--r--Source/CTest/cmCTestResourceSpec.cxx221
-rw-r--r--Source/CTest/cmCTestResourceSpec.h2
-rw-r--r--Source/cmJSONHelpers.h304
4 files changed, 437 insertions, 91 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 310ffeb..1be424a 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -336,6 +336,7 @@ set(SRCS
cmInstallTargetGenerator.cxx
cmInstallDirectoryGenerator.h
cmInstallDirectoryGenerator.cxx
+ cmJSONHelpers.h
cmLDConfigLDConfigTool.cxx
cmLDConfigLDConfigTool.h
cmLDConfigTool.cxx
diff --git a/Source/CTest/cmCTestResourceSpec.cxx b/Source/CTest/cmCTestResourceSpec.cxx
index 21c97de..101dc2c 100644
--- a/Source/CTest/cmCTestResourceSpec.cxx
+++ b/Source/CTest/cmCTestResourceSpec.cxx
@@ -2,19 +2,140 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestResourceSpec.h"
+#include <functional>
#include <map>
#include <string>
#include <utility>
#include <vector>
+#include <cmext/string_view>
+
#include <cm3p/json/reader.h>
#include <cm3p/json/value.h>
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
-static const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
-static const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
+#include "cmJSONHelpers.h"
+
+namespace {
+const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
+const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
+
+struct Version
+{
+ int Major = 1;
+ int Minor = 0;
+};
+
+struct TopVersion
+{
+ struct Version Version;
+};
+
+auto const VersionFieldHelper =
+ cmJSONIntHelper<cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_VERSION);
+
+auto const VersionHelper =
+ cmJSONRequiredHelper<Version, cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::NO_VERSION,
+ cmJSONObjectHelper<Version, cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_VERSION)
+ .Bind("major"_s, &Version::Major, VersionFieldHelper)
+ .Bind("minor"_s, &Version::Minor, VersionFieldHelper));
+
+auto const RootVersionHelper =
+ cmJSONObjectHelper<TopVersion, cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
+ .Bind("version"_s, &TopVersion::Version, VersionHelper, false);
+
+cmCTestResourceSpec::ReadFileResult ResourceIdHelper(std::string& out,
+ const Json::Value* value)
+{
+ auto result = cmJSONStringHelper(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)(out, value);
+ if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
+ return result;
+ }
+ cmsys::RegularExpressionMatch match;
+ if (!IdRegex.find(out.c_str(), match)) {
+ return cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE;
+ }
+ return cmCTestResourceSpec::ReadFileResult::READ_OK;
+}
+
+auto const ResourceHelper =
+ cmJSONObjectHelper<cmCTestResourceSpec::Resource,
+ cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)
+ .Bind("id"_s, &cmCTestResourceSpec::Resource::Id, ResourceIdHelper)
+ .Bind("slots"_s, &cmCTestResourceSpec::Resource::Capacity,
+ cmJSONUIntHelper(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, 1),
+ false);
+
+auto const ResourceListHelper =
+ cmJSONVectorHelper<cmCTestResourceSpec::Resource,
+ cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE,
+ ResourceHelper);
+
+auto const ResourceMapHelper =
+ cmJSONMapFilterHelper<std::vector<cmCTestResourceSpec::Resource>,
+ cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
+ ResourceListHelper, [](const std::string& key) -> bool {
+ cmsys::RegularExpressionMatch match;
+ return IdentifierRegex.find(key.c_str(), match);
+ });
+
+auto const SocketSetHelper = cmJSONVectorHelper<
+ std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, ResourceMapHelper);
+
+cmCTestResourceSpec::ReadFileResult SocketHelper(
+ cmCTestResourceSpec::Socket& out, const Json::Value* value)
+{
+ std::vector<
+ std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>
+ sockets;
+ cmCTestResourceSpec::ReadFileResult result = SocketSetHelper(sockets, value);
+ if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
+ return result;
+ }
+ if (sockets.size() > 1) {
+ return cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC;
+ }
+ if (sockets.empty()) {
+ out.Resources.clear();
+ } else {
+ out.Resources = std::move(sockets[0]);
+ }
+ return cmCTestResourceSpec::ReadFileResult::READ_OK;
+}
+
+auto const LocalRequiredHelper =
+ cmJSONRequiredHelper<cmCTestResourceSpec::Socket,
+ cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, SocketHelper);
+
+auto const RootHelper =
+ cmJSONObjectHelper<cmCTestResourceSpec, cmCTestResourceSpec::ReadFileResult>(
+ cmCTestResourceSpec::ReadFileResult::READ_OK,
+ cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
+ .Bind("local", &cmCTestResourceSpec::LocalSocket, LocalRequiredHelper,
+ false);
+}
cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile(
const std::string& filename)
@@ -30,99 +151,17 @@ cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile(
return ReadFileResult::JSON_PARSE_ERROR;
}
- if (!root.isObject()) {
- return ReadFileResult::INVALID_ROOT;
- }
-
- int majorVersion = 1;
- int minorVersion = 0;
- if (root.isMember("version")) {
- auto const& version = root["version"];
- if (version.isObject()) {
- if (!version.isMember("major") || !version.isMember("minor")) {
- return ReadFileResult::INVALID_VERSION;
- }
- auto const& major = version["major"];
- auto const& minor = version["minor"];
- if (!major.isInt() || !minor.isInt()) {
- return ReadFileResult::INVALID_VERSION;
- }
- majorVersion = major.asInt();
- minorVersion = minor.asInt();
- } else {
- return ReadFileResult::INVALID_VERSION;
- }
- } else {
- return ReadFileResult::NO_VERSION;
+ TopVersion version;
+ ReadFileResult result;
+ if ((result = RootVersionHelper(version, &root)) !=
+ ReadFileResult::READ_OK) {
+ return result;
}
-
- if (majorVersion != 1 || minorVersion != 0) {
+ if (version.Version.Major != 1 || version.Version.Minor != 0) {
return ReadFileResult::UNSUPPORTED_VERSION;
}
- auto const& local = root["local"];
- if (!local.isArray()) {
- return ReadFileResult::INVALID_SOCKET_SPEC;
- }
- if (local.size() > 1) {
- return ReadFileResult::INVALID_SOCKET_SPEC;
- }
-
- if (local.empty()) {
- this->LocalSocket.Resources.clear();
- return ReadFileResult::READ_OK;
- }
-
- auto const& localSocket = local[0];
- if (!localSocket.isObject()) {
- return ReadFileResult::INVALID_SOCKET_SPEC;
- }
- std::map<std::string, std::vector<cmCTestResourceSpec::Resource>> resources;
- cmsys::RegularExpressionMatch match;
- for (auto const& key : localSocket.getMemberNames()) {
- if (IdentifierRegex.find(key.c_str(), match)) {
- auto const& value = localSocket[key];
- auto& r = resources[key];
- if (value.isArray()) {
- for (auto const& item : value) {
- if (item.isObject()) {
- cmCTestResourceSpec::Resource resource;
-
- if (!item.isMember("id")) {
- return ReadFileResult::INVALID_RESOURCE;
- }
- auto const& id = item["id"];
- if (!id.isString()) {
- return ReadFileResult::INVALID_RESOURCE;
- }
- resource.Id = id.asString();
- if (!IdRegex.find(resource.Id.c_str(), match)) {
- return ReadFileResult::INVALID_RESOURCE;
- }
-
- if (item.isMember("slots")) {
- auto const& capacity = item["slots"];
- if (!capacity.isConvertibleTo(Json::uintValue)) {
- return ReadFileResult::INVALID_RESOURCE;
- }
- resource.Capacity = capacity.asUInt();
- } else {
- resource.Capacity = 1;
- }
-
- r.push_back(resource);
- } else {
- return ReadFileResult::INVALID_RESOURCE;
- }
- }
- } else {
- return ReadFileResult::INVALID_RESOURCE_TYPE;
- }
- }
- }
-
- this->LocalSocket.Resources = std::move(resources);
- return ReadFileResult::READ_OK;
+ return RootHelper(*this, &root);
}
const char* cmCTestResourceSpec::ResultToString(ReadFileResult result)
diff --git a/Source/CTest/cmCTestResourceSpec.h b/Source/CTest/cmCTestResourceSpec.h
index 1aa279b..72628a3 100644
--- a/Source/CTest/cmCTestResourceSpec.h
+++ b/Source/CTest/cmCTestResourceSpec.h
@@ -2,6 +2,8 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
+#include "cmConfigure.h" // IWYU pragma: keep
+
#include <map>
#include <string>
#include <vector>
diff --git a/Source/cmJSONHelpers.h b/Source/cmJSONHelpers.h
new file mode 100644
index 0000000..2da2a03
--- /dev/null
+++ b/Source/cmJSONHelpers.h
@@ -0,0 +1,304 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+#include <cm/string_view>
+
+#include <cm3p/json/value.h>
+
+template <typename T, typename E>
+using cmJSONHelper = std::function<E(T& out, const Json::Value* value)>;
+
+template <typename T, typename E>
+class cmJSONObjectHelper
+{
+public:
+ cmJSONObjectHelper(E&& success, E&& fail, bool allowExtra = true);
+
+ template <typename U, typename M, typename F>
+ cmJSONObjectHelper& Bind(const cm::string_view& name, M U::*member, F func,
+ bool required = true);
+ template <typename M, typename F>
+ cmJSONObjectHelper& Bind(const cm::string_view& name, std::nullptr_t, F func,
+ bool required = true);
+
+ E operator()(T& out, const Json::Value* value) const;
+
+private:
+ // Not a true cmJSONHelper, it just happens to match the signature
+ using MemberFunction = std::function<E(T& out, const Json::Value* value)>;
+ struct Member
+ {
+ cm::string_view Name;
+ MemberFunction Function;
+ bool Required;
+ };
+ std::vector<Member> Members;
+ bool AnyRequired = false;
+ E Success;
+ E Fail;
+ bool AllowExtra;
+
+ cmJSONObjectHelper& BindPrivate(const cm::string_view& name,
+ MemberFunction&& func, bool required);
+};
+
+template <typename T, typename E>
+cmJSONObjectHelper<T, E>::cmJSONObjectHelper(E&& success, E&& fail,
+ bool allowExtra)
+ : Success(std::move(success))
+ , Fail(std::move(fail))
+ , AllowExtra(allowExtra)
+{
+}
+
+template <typename T, typename E>
+template <typename U, typename M, typename F>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
+ const cm::string_view& name, M U::*member, F func, bool required)
+{
+ return this->BindPrivate(
+ name,
+ [func, member](T& out, const Json::Value* value) -> E {
+ return func(out.*member, value);
+ },
+ required);
+}
+
+template <typename T, typename E>
+template <typename M, typename F>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
+ const cm::string_view& name, std::nullptr_t, F func, bool required)
+{
+ return this->BindPrivate(name,
+ [func](T& /*out*/, const Json::Value* value) -> E {
+ M dummy;
+ return func(dummy, value);
+ },
+ required);
+}
+
+template <typename T, typename E>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::BindPrivate(
+ const cm::string_view& name, MemberFunction&& func, bool required)
+{
+ Member m;
+ m.Name = name;
+ m.Function = std::move(func);
+ m.Required = required;
+ this->Members.push_back(std::move(m));
+ if (required) {
+ this->AnyRequired = true;
+ }
+ return *this;
+}
+
+template <typename T, typename E>
+E cmJSONObjectHelper<T, E>::operator()(T& out, const Json::Value* value) const
+{
+ if (!value && this->AnyRequired) {
+ return this->Fail;
+ }
+ if (value && !value->isObject()) {
+ return this->Fail;
+ }
+ Json::Value::Members extraFields;
+ if (value) {
+ extraFields = value->getMemberNames();
+ }
+
+ for (auto const& m : this->Members) {
+ std::string name(m.Name.data(), m.Name.size());
+ if (value && value->isMember(name)) {
+ E result = m.Function(out, &(*value)[name]);
+ if (result != this->Success) {
+ return result;
+ }
+ extraFields.erase(
+ std::find(extraFields.begin(), extraFields.end(), name));
+ } else if (!m.Required) {
+ E result = m.Function(out, nullptr);
+ if (result != this->Success) {
+ return result;
+ }
+ } else {
+ return this->Fail;
+ }
+ }
+
+ return this->AllowExtra || extraFields.empty() ? this->Success : this->Fail;
+}
+
+template <typename E>
+cmJSONHelper<std::string, E> cmJSONStringHelper(E success, E fail,
+ const std::string& defval = "")
+{
+ return
+ [success, fail, defval](std::string& out, const Json::Value* value) -> E {
+ if (!value) {
+ out = defval;
+ return success;
+ }
+ if (!value->isString()) {
+ return fail;
+ }
+ out = value->asString();
+ return success;
+ };
+}
+
+template <typename E>
+cmJSONHelper<int, E> cmJSONIntHelper(E success, E fail, int defval = 0)
+{
+ return [success, fail, defval](int& out, const Json::Value* value) -> E {
+ if (!value) {
+ out = defval;
+ return success;
+ }
+ if (!value->isInt()) {
+ return fail;
+ }
+ out = value->asInt();
+ return success;
+ };
+}
+
+template <typename E>
+cmJSONHelper<unsigned int, E> cmJSONUIntHelper(E success, E fail,
+ unsigned int defval = 0)
+{
+ return
+ [success, fail, defval](unsigned int& out, const Json::Value* value) -> E {
+ if (!value) {
+ out = defval;
+ return success;
+ }
+ if (!value->isUInt()) {
+ return fail;
+ }
+ out = value->asUInt();
+ return success;
+ };
+}
+
+template <typename E>
+cmJSONHelper<bool, E> cmJSONBoolHelper(E success, E fail, bool defval = false)
+{
+ return [success, fail, defval](bool& out, const Json::Value* value) -> E {
+ if (!value) {
+ out = defval;
+ return success;
+ }
+ if (!value->isBool()) {
+ return fail;
+ }
+ out = value->asBool();
+ return success;
+ };
+}
+
+template <typename T, typename E, typename F, typename Filter>
+cmJSONHelper<std::vector<T>, E> cmJSONVectorFilterHelper(E success, E fail,
+ F func, Filter filter)
+{
+ return [success, fail, func, filter](std::vector<T>& out,
+ const Json::Value* value) -> E {
+ if (!value) {
+ out.clear();
+ return success;
+ }
+ if (!value->isArray()) {
+ return fail;
+ }
+ out.clear();
+ for (auto const& item : *value) {
+ T t;
+ E result = func(t, &item);
+ if (result != success) {
+ return result;
+ }
+ if (!filter(t)) {
+ continue;
+ }
+ out.push_back(t);
+ }
+ return success;
+ };
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<std::vector<T>, E> cmJSONVectorHelper(E success, E fail, F func)
+{
+ return cmJSONVectorFilterHelper<T, E, F>(success, fail, func,
+ [](const T&) { return true; });
+}
+
+template <typename T, typename E, typename F, typename Filter>
+cmJSONHelper<std::map<std::string, T>, E> cmJSONMapFilterHelper(E success,
+ E fail, F func,
+ Filter filter)
+{
+ return [success, fail, func, filter](std::map<std::string, T>& out,
+ const Json::Value* value) -> E {
+ if (!value) {
+ out.clear();
+ return success;
+ }
+ if (!value->isObject()) {
+ return fail;
+ }
+ out.clear();
+ for (auto const& key : value->getMemberNames()) {
+ if (!filter(key)) {
+ continue;
+ }
+ T t;
+ E result = func(t, &(*value)[key]);
+ if (result != success) {
+ return result;
+ }
+ out[key] = std::move(t);
+ }
+ return success;
+ };
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<std::map<std::string, T>, E> cmJSONMapHelper(E success, E fail,
+ F func)
+{
+ return cmJSONMapFilterHelper<T, E, F>(
+ success, fail, func, [](const std::string&) { return true; });
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<cm::optional<T>, E> cmJSONOptionalHelper(E success, F func)
+{
+ return [success, func](cm::optional<T>& out, const Json::Value* value) -> E {
+ if (!value) {
+ out.reset();
+ return success;
+ }
+ out.emplace();
+ return func(*out, value);
+ };
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<T, E> cmJSONRequiredHelper(E fail, F func)
+{
+ return [fail, func](T& out, const Json::Value* value) -> E {
+ if (!value) {
+ return fail;
+ }
+ return func(out, value);
+ };
+}