mirror of
https://github.com/Kitware/CMake.git
synced 2025-12-30 10:20:56 -06:00
cmJSONHelpers.h: Add FilteredObject helper
Iterate over the object's members and call a filter callable to decide what to do with the current key/value. A filter returns one of the `FilterResult` values. A container type is an associative or a sequence container of pairs (key, value). Refactor `MapFilter()` and `Map()` to use `FilteredObject()`. Moreover, for C++ >= 17 implementation is more optimized depending on the given filter object type and capable of detecting and properly calling the filter callable using 1 or 3 arguments, up to totally eliminate any checking (even dummy) in the generated code. Supported container types, used to append key/value items, aren't limited to `std::map` only and can be any associative container or a sequenced one with pairs of key/value as elements.
This commit is contained in:
@@ -7,8 +7,10 @@
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -60,9 +62,28 @@ ErrorGenerator INVALID_NAMED_OBJECT_KEY(
|
||||
ObjectError errorType, const Json::Value::Members& extraFields);
|
||||
}
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
namespace details {
|
||||
// A meta-function to check if a given callable type
|
||||
// can be called with the only string ref arg.
|
||||
template <typename F, typename Enable = void>
|
||||
struct is_bool_filter
|
||||
{
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct is_bool_filter<F,
|
||||
std::enable_if_t<std::is_same_v<
|
||||
std::invoke_result_t<F, const std::string&>, bool>>>
|
||||
{
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
struct cmJSONHelperBuilder
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
class Object
|
||||
{
|
||||
@@ -323,13 +344,28 @@ struct cmJSONHelperBuilder
|
||||
[](const T&) { return true; });
|
||||
}
|
||||
|
||||
template <typename T, typename F, typename Filter>
|
||||
static cmJSONHelper<std::map<std::string, T>> MapFilter(
|
||||
enum class FilterResult
|
||||
{
|
||||
Continue, ///< A filter has accepted a given key (and value)
|
||||
Skip, ///< A filter has rejected a given key (or value)
|
||||
Error ///< A filter has found and reported an error
|
||||
};
|
||||
|
||||
/// Iterate over the object's members and call a filter callable to
|
||||
/// decide what to do with the current key/value.
|
||||
/// A filter returns one of the `FilterResult` values.
|
||||
/// A container type is an associative or a sequence
|
||||
/// container of pairs (key, value).
|
||||
template <typename Container, typename F, typename Filter>
|
||||
static cmJSONHelper<Container> FilteredObject(
|
||||
const JsonErrors::ErrorGenerator& error, F func, Filter filter)
|
||||
{
|
||||
return [error, func, filter](std::map<std::string, T>& out,
|
||||
const Json::Value* value,
|
||||
return [error, func, filter](Container& out, const Json::Value* value,
|
||||
cmJSONState* state) -> bool {
|
||||
// NOTE Some compile-time code path don't use `filter` at all.
|
||||
// So, suppress "unused lambda capture" warning is needed.
|
||||
static_cast<void>(filter);
|
||||
|
||||
if (!value) {
|
||||
out.clear();
|
||||
return true;
|
||||
@@ -339,30 +375,94 @@ struct cmJSONHelperBuilder
|
||||
return false;
|
||||
}
|
||||
out.clear();
|
||||
auto outIt = std::inserter(out, out.end());
|
||||
bool success = true;
|
||||
for (auto const& key : value->getMemberNames()) {
|
||||
state->push_stack(key, &(*value)[key]);
|
||||
if (!filter(key)) {
|
||||
state->pop_stack();
|
||||
continue;
|
||||
#if __cplusplus >= 201703L
|
||||
if constexpr (std::is_same_v<Filter, std::true_type>) {
|
||||
// Filtering functionality isn't needed at all...
|
||||
} else if constexpr (details::is_bool_filter<Filter>::value) {
|
||||
// A given `Filter` is `bool(const std::string&)` callable.
|
||||
if (!filter(key)) {
|
||||
state->pop_stack();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
// A full-featured `Filter` has been given
|
||||
auto res = filter(key, &(*value)[key], state);
|
||||
if (res == FilterResult::Skip) {
|
||||
state->pop_stack();
|
||||
continue;
|
||||
}
|
||||
if (res == FilterResult::Error) {
|
||||
state->pop_stack();
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
#if __cplusplus >= 201703L
|
||||
}
|
||||
T t;
|
||||
if (!func(t, &(*value)[key], state)) {
|
||||
success = false;
|
||||
}
|
||||
out.emplace(key, std::move(t));
|
||||
#endif
|
||||
typename Container::value_type::second_type t;
|
||||
// ATTENTION Call the function first (for it's side-effects),
|
||||
// then accumulate the result!
|
||||
success = func(t, &(*value)[key], state) && success;
|
||||
outIt = typename Container::value_type{ key, std::move(t) };
|
||||
state->pop_stack();
|
||||
}
|
||||
return success;
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename F, typename Filter>
|
||||
static cmJSONHelper<std::map<std::string, T>> MapFilter(
|
||||
const JsonErrors::ErrorGenerator& error, F func, Filter filter)
|
||||
{
|
||||
// clang-format off
|
||||
return FilteredObject<std::map<std::string, T>>(
|
||||
error, func,
|
||||
#if __cplusplus >= 201703L
|
||||
// In C++ 17 a filter callable can be passed as is.
|
||||
// Depending on its type `FilteredObject()` will call
|
||||
// it with a key only (backward compatible behavior)
|
||||
// or with 3 args supported by the full-featured
|
||||
// filtering feature.
|
||||
filter
|
||||
#else
|
||||
// For C++14 and below, to keep backward compatibility
|
||||
// with CMake Presets code, `MapFilter()` can accept only
|
||||
// `bool(const std::string&)` callables.
|
||||
[filter](const std::string &key, const Json::Value * /*value*/,
|
||||
cmJSONState * /*state*/) -> FilterResult {
|
||||
// Simple adaptor to translate `bool` to `FilterResult`
|
||||
return filter(key) ? FilterResult::Continue : FilterResult::Skip;
|
||||
}
|
||||
#endif
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
static cmJSONHelper<std::map<std::string, T>> Map(
|
||||
const JsonErrors::ErrorGenerator& error, F func)
|
||||
{
|
||||
return MapFilter<T, F>(error, func,
|
||||
[](const std::string&) { return true; });
|
||||
// clang-format off
|
||||
return FilteredObject<std::map<std::string, T>>(
|
||||
error, func,
|
||||
#if __cplusplus >= 201703L
|
||||
// With C++ 17 and above, pass a marker type, that no
|
||||
// filtering is needed at all.
|
||||
std::true_type()
|
||||
#else
|
||||
// In C++ 14 and below, pass an always-true dummy functor.
|
||||
[](const std::string& /*key*/, const Json::Value* /*value*/,
|
||||
cmJSONState* /*state*/) -> FilterResult {
|
||||
return FilterResult::Continue;
|
||||
}
|
||||
#endif
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/optional>
|
||||
@@ -36,6 +39,8 @@ ErrorGenerator ErrorGeneratorBuilder(std::string errorMessage)
|
||||
ErrorGenerator InvalidArray = ErrorGeneratorBuilder("Invalid Array");
|
||||
ErrorGenerator MissingRequired = ErrorGeneratorBuilder("Missing Required");
|
||||
ErrorGenerator InvalidMap = ErrorGeneratorBuilder("Invalid Map");
|
||||
ErrorGenerator FaultyObjectProperty =
|
||||
ErrorGeneratorBuilder("Faulty Object Property");
|
||||
ErrorGenerator InvalidObject(JsonErrors::ObjectError /*errorType*/,
|
||||
const Json::Value::Members& extraFields)
|
||||
{
|
||||
@@ -460,12 +465,72 @@ bool testRequired()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JSONHelperBuilder::FilterResult ObjectPropsFilter(const std::string& key,
|
||||
const Json::Value* value,
|
||||
cmJSONState* state)
|
||||
{
|
||||
if (key == "ignore") {
|
||||
return JSONHelperBuilder::FilterResult::Skip;
|
||||
}
|
||||
if (value->isString() && value->asString() == "ignore") {
|
||||
return JSONHelperBuilder::FilterResult::Skip;
|
||||
}
|
||||
if (key == "zerror") {
|
||||
ErrorMessages::FaultyObjectProperty(value, state);
|
||||
return JSONHelperBuilder::FilterResult::Error;
|
||||
}
|
||||
return JSONHelperBuilder::FilterResult::Continue;
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
bool testFilteredObject()
|
||||
{
|
||||
auto const FilteredObjectHelper =
|
||||
JSONHelperBuilder::FilteredObject<Container>(
|
||||
ErrorMessages::InvalidMap, StringHelper, ObjectPropsFilter);
|
||||
|
||||
Json::Value v(Json::objectValue);
|
||||
cmJSONState state;
|
||||
v["field1"] = "Hello";
|
||||
v["field2"] = "world!";
|
||||
v["ignore"] = "any";
|
||||
v["any"] = "ignore";
|
||||
v["zerror"] = "error";
|
||||
|
||||
Container m{ { "key", "default" } };
|
||||
Container expected{ { "field1", "Hello" }, { "field2", "world!" } };
|
||||
|
||||
ASSERT_TRUE(!FilteredObjectHelper(m, &v, &state));
|
||||
ASSERT_TRUE(m == expected);
|
||||
|
||||
auto error = state.GetErrorMessage();
|
||||
ASSERT_TRUE(error == "\nFaulty Object Property");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int testJSONHelpers(int /*unused*/, char* /*unused*/[])
|
||||
{
|
||||
return runTests({ testInt, testUInt, testBool, testString, testObject,
|
||||
testObjectInherited, testObjectNoExtra, testObjectOptional,
|
||||
testVector, testVectorFilter, testMap, testMapFilter,
|
||||
testOptional, testRequired });
|
||||
return runTests({
|
||||
testInt,
|
||||
testUInt,
|
||||
testBool,
|
||||
testString,
|
||||
testObject,
|
||||
testObjectInherited,
|
||||
testObjectNoExtra,
|
||||
testObjectOptional,
|
||||
testVector,
|
||||
testVectorFilter,
|
||||
testMap,
|
||||
testMapFilter,
|
||||
testOptional,
|
||||
testRequired,
|
||||
testFilteredObject<std::map<std::string, std::string>>,
|
||||
testFilteredObject<std::unordered_map<std::string, std::string>>,
|
||||
testFilteredObject<std::vector<std::pair<std::string, std::string>>>,
|
||||
testFilteredObject<std::list<std::pair<std::string, std::string>>>,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user