Merge pull request #28 from JamesTheMaker/main

Added many new patch features
This commit is contained in:
Kae 2024-03-09 06:25:55 +11:00 committed by GitHub
commit 35dc974a8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 258 additions and 24 deletions

View File

@ -86,10 +86,21 @@ Assets::Assets(Settings settings, StringList assetSources) {
m_assetSourcePaths.add(sourcePath, source); m_assetSourcePaths.add(sourcePath, source);
for (auto const& filename : source->assetPaths()) { for (auto const& filename : source->assetPaths()) {
if (filename.contains(AssetsPatchSuffix, String::CaseInsensitive)) {
if (filename.endsWith(AssetsPatchSuffix, String::CaseInsensitive)) { if (filename.endsWith(AssetsPatchSuffix, String::CaseInsensitive)) {
auto targetPatchFile = filename.substr(0, filename.size() - strlen(AssetsPatchSuffix)); auto targetPatchFile = filename.substr(0, filename.size() - strlen(AssetsPatchSuffix));
if (auto p = m_files.ptr(targetPatchFile)) if (auto p = m_files.ptr(targetPatchFile))
p->patchSources.append({filename, source}); p->patchSources.append({filename, source});
} else {
for (int i = 0; i < 10; i++) {
if (filename.endsWith(AssetsPatchSuffix + toString(i), String::CaseInsensitive)) {
auto targetPatchFile = filename.substr(0, filename.size() - strlen(AssetsPatchSuffix) + 1);
if (auto p = m_files.ptr(targetPatchFile))
p->patchSources.append({filename, source});
break;
}
}
}
} }
auto& descriptor = m_files[filename]; auto& descriptor = m_files[filename];
descriptor.sourceName = filename; descriptor.sourceName = filename;

View File

@ -1035,4 +1035,34 @@ Json jsonMerge(Json const& base, Json const& merger) {
} }
} }
bool jsonCompare(Json const& base, Json const& compare) {
if (base == compare) {
return true;
} else {
if (base.type() == Json::Type::Object && compare.type() == Json::Type::Object) {
for (auto const& c : compare.toObject()) {
if (!base.contains(c.first) || !jsonCompare(base.get(c.first), c.second))
return false;
}
return true;
}
if (base.type() == Json::Type::Array && compare.type() == Json::Type::Array) {
for (auto const& c : compare.toArray()) {
bool similar = false;
for (auto const& b : base.toArray()) {
if (jsonCompare(c, b)) {
similar = true;
break;
}
}
if (!similar)
return false;
}
return true;
}
return false;
}
}
} }

View File

@ -355,6 +355,22 @@ Json jsonMergeQueryDef(String const& key, Json def, Json const& first, T const&.
return def; return def;
} }
// Compares the two given json values and returns a boolean, by the following
// rules (applied in order): If both values are identical, return true. If both
// values are not equal, check if they are objects. If they are objects,
// iterate through every pair in the comparing object and check if the key is
// in the base object. If the key is in the base object, then jsonCompare is
// called recursively on both values. If the base object does not contain the
// key, or the recursion fails, return false. Otherwise, return true. If they
// are not objects, check if they are arrays. If they are arrays, iterate
// through every value in the comparing object and then recursively call
// jsonCompare on every value in the base object until a match is found. If a
// match is found, break and move on to the next value in the comparing array.
// If a match is found for every value in the comparing array, return true.
// Otherwise, return false. If both values are not identical, and are not
// objects or arrays, return false.
bool jsonCompare(Json const& base, Json const& compare);
} }
template <> struct fmt::formatter<Star::Json> : ostream_formatter {}; template <> struct fmt::formatter<Star::Json> : ostream_formatter {};

View File

@ -25,6 +25,7 @@ namespace JsonPatching {
{"replace", std::bind(applyReplaceOperation, _1, _2)}, {"replace", std::bind(applyReplaceOperation, _1, _2)},
{"move", std::bind(applyMoveOperation, _1, _2)}, {"move", std::bind(applyMoveOperation, _1, _2)},
{"copy", std::bind(applyCopyOperation, _1, _2)}, {"copy", std::bind(applyCopyOperation, _1, _2)},
{"merge", std::bind(applyMergeOperation, _1, _2)},
}; };
Json applyOperation(Json const& base, Json const& op) { Json applyOperation(Json const& base, Json const& op) {
@ -40,12 +41,36 @@ namespace JsonPatching {
Json applyTestOperation(Json const& base, Json const& op) { Json applyTestOperation(Json const& base, Json const& op) {
auto path = op.getString("path"); auto path = op.getString("path");
auto value = op.opt("value");
auto inverseTest = op.getBool("inverse", false); auto inverseTest = op.getBool("inverse", false);
auto pointer = JsonPath::Pointer(path); auto pointer = JsonPath::Pointer(path);
try { try {
if (op.contains("search")) {
Json searchArray = pointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
bool found = false;
for(auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
}
if (found) {
if (inverseTest)
throw JsonPatchTestFail(strf("Test operation failure, expected {} to be missing.", searchValue));
return base;
} else {
if (!inverseTest)
throw JsonPatchTestFail(strf("Test operation failure, could not find {}.", searchValue));
return base;
}
} else {
throw JsonPatchException(strf("Search operation failure, value at '{}' is not an array.", path));
}
} else {
auto value = op.opt("value");
auto testValue = pointer.get(base); auto testValue = pointer.get(base);
if (!value) { if (!value) {
if (inverseTest) if (inverseTest)
@ -58,6 +83,7 @@ namespace JsonPatching {
} }
throw JsonPatchTestFail(strf("Test operation failure, expected {} found {}.", value, testValue)); throw JsonPatchTestFail(strf("Test operation failure, expected {} found {}.", value, testValue));
}
} catch (JsonPath::TraversalException& e) { } catch (JsonPath::TraversalException& e) {
if (inverseTest) if (inverseTest)
return base; return base;
@ -66,32 +92,180 @@ namespace JsonPatching {
} }
Json applyRemoveOperation(Json const& base, Json const& op) { Json applyRemoveOperation(Json const& base, Json const& op) {
if (op.contains("search")) {
String path = op.getString("path");
auto pointer = JsonPath::Pointer(path);
Json searchArray = pointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
size_t index = 0;
bool found = false;
for (auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
index++;
}
if (found)
searchArray = searchArray.eraseIndex(index);
return pointer.add(pointer.remove(base), searchArray);
} else {
throw JsonPatchException(strf("Search operation failure, value at {} is not an array.", path));
}
} else {
return JsonPath::Pointer(op.getString("path")).remove(base); return JsonPath::Pointer(op.getString("path")).remove(base);
} }
}
Json applyAddOperation(Json const& base, Json const& op) { Json applyAddOperation(Json const& base, Json const& op) {
if (op.contains("search")) {
Json value = op.get("value");
String path = op.getString("path");
auto pointer = JsonPath::Pointer(path);
Json searchArray = pointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
bool found = false;
for (auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
}
if (found)
searchArray = searchArray.append(value);
return pointer.add(pointer.remove(base), searchArray);
} else {
throw JsonPatchException(strf("Search operation failure, value at {} is not an array.", path));
}
} else {
return JsonPath::Pointer(op.getString("path")).add(base, op.get("value")); return JsonPath::Pointer(op.getString("path")).add(base, op.get("value"));
} }
}
Json applyReplaceOperation(Json const& base, Json const& op) { Json applyReplaceOperation(Json const& base, Json const& op) {
String path = op.getString("path");
auto pointer = JsonPath::Pointer(op.getString("path")); auto pointer = JsonPath::Pointer(op.getString("path"));
if (op.contains("search")) {
Json value = op.get("value");
Json searchArray = pointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
size_t index = 0;
bool found = false;
for (auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
index++;
}
if (found)
searchArray = searchArray.set(index, value);
return pointer.add(pointer.remove(base), searchArray);
} else {
throw JsonPatchException(strf("Search operation failure, value at {} is not an array.", path));
}
} else {
return pointer.add(pointer.remove(base), op.get("value")); return pointer.add(pointer.remove(base), op.get("value"));
} }
}
Json applyMoveOperation(Json const& base, Json const& op) { Json applyMoveOperation(Json const& base, Json const& op) {
String path = op.getString("path");
auto toPointer = JsonPath::Pointer(path);
auto fromPointer = JsonPath::Pointer(op.getString("from")); auto fromPointer = JsonPath::Pointer(op.getString("from"));
auto toPointer = JsonPath::Pointer(op.getString("path"));
if (op.contains("search")) {
Json value = op.get("value");
Json searchArray = fromPointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
size_t index = 0;
bool found = false;
for (auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
index++;
}
if (found) {
toPointer.add(toPointer.remove(base), searchArray.get(index));
searchArray = searchArray.eraseIndex(index);
fromPointer.add(fromPointer.remove(base), searchArray);
}
return toPointer.get(base);
} else {
throw JsonPatchException(strf("Search operation failure, value at {} is not an array.", path));
}
} else {
Json value = fromPointer.get(base); Json value = fromPointer.get(base);
return toPointer.add(fromPointer.remove(base), value); return toPointer.add(fromPointer.remove(base), value);
} }
}
Json applyCopyOperation(Json const& base, Json const& op) { Json applyCopyOperation(Json const& base, Json const& op) {
String path = op.getString("path");
auto toPointer = JsonPath::Pointer(path);
auto fromPointer = JsonPath::Pointer(op.getString("from")); auto fromPointer = JsonPath::Pointer(op.getString("from"));
auto toPointer = JsonPath::Pointer(op.getString("path"));
if (op.contains("search")) {
Json value = op.get("value");
Json searchArray = fromPointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
size_t index = 0;
bool found = false;
for (auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
index++;
}
if (found)
toPointer.add(base, searchArray.get(index));
return toPointer.get(base);
} else {
throw JsonPatchException(strf("Search operation failure, value at {} is not an array.", path));
}
} else {
Json value = fromPointer.get(base);
return toPointer.add(base, fromPointer.get(base)); return toPointer.add(base, fromPointer.get(base));
} }
} }
Json applyMergeOperation(Json const& base, Json const& op) {
String path = op.getString("path");
auto pointer = JsonPath::Pointer(op.getString("path"));
if (op.contains("search")) {
Json value = op.get("value");
Json searchArray = pointer.get(base);
Json searchValue = op.get("search");
if (searchArray.type() == Json::Type::Array) {
size_t index = 0;
bool found = false;
for (auto& v : searchArray.toArray()) {
if (jsonCompare(v, searchValue)) {
found = true;
break;
}
index++;
}
if (found)
searchArray = searchArray.set(index, jsonMerge(searchArray.get(index), op.get("value")));
return pointer.add(pointer.remove(base), searchArray);
} else {
throw JsonPatchException(strf("Search operation failure, value at {} is not an array.", path));
}
} else {
return pointer.add(pointer.remove(base), jsonMerge(pointer.get(base), op.get("value")));
}
}
}
} }

View File

@ -33,6 +33,9 @@ namespace JsonPatching {
// Copies "from" to "path" // Copies "from" to "path"
Json applyCopyOperation(Json const& base, Json const& op); Json applyCopyOperation(Json const& base, Json const& op);
// Merges "value" at "path"
Json applyMergeOperation(Json const& base, Json const& op);
} }
} }