mirror of
https://github.com/Kitware/CMake.git
synced 2026-05-05 13:50:10 -05:00
Merge topic 'json-helpers'
b2f3f831e2Refactor: Use JSON helpers in CTest resource spec3f3a30e1e0JSON: Add helpers Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !5197
This commit is contained in:
@@ -336,6 +336,7 @@ set(SRCS
|
||||
cmInstallTargetGenerator.cxx
|
||||
cmInstallDirectoryGenerator.h
|
||||
cmInstallDirectoryGenerator.cxx
|
||||
cmJSONHelpers.h
|
||||
cmLDConfigLDConfigTool.cxx
|
||||
cmLDConfigLDConfigTool.h
|
||||
cmLDConfigTool.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;
|
||||
TopVersion version;
|
||||
ReadFileResult result;
|
||||
if ((result = RootVersionHelper(version, &root)) !=
|
||||
ReadFileResult::READ_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user