string(JSON): Add STRING_ENCODE mode

This commit is contained in:
Kyle Edwards 2025-12-16 10:11:30 -05:00
parent 15973ff247
commit 06e6f1e69f
No known key found for this signature in database
5 changed files with 130 additions and 97 deletions

View File

@ -56,6 +56,8 @@ Synopsis
<member|index> [<member|index> ...] <value>)
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
`EQUAL <JSON-EQUAL_>`__ <json-string1> <json-string2>)
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
`STRING_ENCODE <STRING-ENCODE_>`__ <string>)
Search and Replace
^^^^^^^^^^^^^^^^^^
@ -629,3 +631,11 @@ string is passed as a single argument even if it contains semicolons.
and ``<json-string2>`` should be valid JSON. The ``<out-var>``
will be set to a true value if the JSON objects are considered equal,
or a false value otherwise.
.. signature::
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
STRING_ENCODE <string>)
:target: STRING-ENCODE
Turn a raw string into a JSON string surrounded by quotes. Special characters
will be properly escaped inside the JSON string.

View File

@ -1,4 +1,4 @@
string-json-improvements
------------------------
* The :command:`string(JSON)` command gained a new ``GET_RAW`` mode.
* The :command:`string(JSON)` command gained new ``GET_RAW`` and ``STRING_ENCODE`` modes.

View File

@ -954,113 +954,120 @@ bool HandleJSONCommand(std::vector<std::string> const& arguments,
auto const& mode = args.PopFront("missing mode argument"_s);
if (mode != "GET"_s && mode != "GET_RAW"_s && mode != "TYPE"_s &&
mode != "MEMBER"_s && mode != "LENGTH"_s && mode != "REMOVE"_s &&
mode != "SET"_s && mode != "EQUAL"_s) {
mode != "SET"_s && mode != "EQUAL"_s && mode != "STRING_ENCODE"_s) {
throw json_error(cmStrCat(
"got an invalid mode '"_s, mode,
"', expected one of GET, GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, "
" EQUAL"_s));
" EQUAL, STRING_ENCODE"_s));
}
auto const& jsonstr = args.PopFront("missing json string argument"_s);
Json::Value json = ReadJson(jsonstr);
if (mode == "GET"_s) {
auto const& value = ResolvePath(json, args);
if (value.isObject() || value.isArray()) {
makefile.AddDefinition(*outputVariable, WriteJson(value));
} else if (value.isBool()) {
makefile.AddDefinitionBool(*outputVariable, value.asBool());
} else {
makefile.AddDefinition(*outputVariable, value.asString());
}
} else if (mode == "GET_RAW"_s) {
auto const& value = ResolvePath(json, args);
makefile.AddDefinition(*outputVariable, WriteJson(value));
} else if (mode == "TYPE"_s) {
auto const& value = ResolvePath(json, args);
makefile.AddDefinition(*outputVariable, JsonTypeToString(value.type()));
} else if (mode == "MEMBER"_s) {
auto const& indexStr = args.PopBack("missing member index"_s);
auto const& value = ResolvePath(json, args);
if (!value.isObject()) {
throw json_error(
cmStrCat("MEMBER needs to be called with an element of "
"type OBJECT, got "_s,
JsonTypeToString(value.type())),
args);
}
auto const index = ParseIndex(
indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
auto const memIt = std::next(value.begin(), index);
makefile.AddDefinition(*outputVariable, memIt.name());
} else if (mode == "LENGTH"_s) {
auto const& value = ResolvePath(json, args);
if (!value.isArray() && !value.isObject()) {
throw json_error(cmStrCat("LENGTH needs to be called with an "
"element of type ARRAY or OBJECT, got "_s,
JsonTypeToString(value.type())),
args);
}
cmAlphaNum sizeStr{ value.size() };
makefile.AddDefinition(*outputVariable, sizeStr.View());
} else if (mode == "REMOVE"_s) {
auto const& toRemove =
args.PopBack("missing member or index to remove"_s);
auto& value = ResolvePath(json, args);
if (value.isArray()) {
auto const index = ParseIndex(
toRemove, Args{ args.begin(), args.end() + 1 }, value.size());
Json::Value removed;
value.removeIndex(index, &removed);
} else if (value.isObject()) {
Json::Value removed;
value.removeMember(toRemove, &removed);
} else {
throw json_error(cmStrCat("REMOVE needs to be called with an "
"element of type ARRAY or OBJECT, got "_s,
JsonTypeToString(value.type())),
args);
}
if (mode == "STRING_ENCODE"_s) {
Json::Value json(jsonstr);
makefile.AddDefinition(*outputVariable, WriteJson(json));
} else {
Json::Value json = ReadJson(jsonstr);
} else if (mode == "SET"_s) {
auto const& newValueStr = args.PopBack("missing new value remove"_s);
auto const& toAdd = args.PopBack("missing member name to add"_s);
auto& value = ResolvePath(json, args);
Json::Value newValue = ReadJson(newValueStr);
if (value.isObject()) {
value[toAdd] = newValue;
} else if (value.isArray()) {
auto const index =
ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 });
if (value.isValidIndex(index)) {
value[static_cast<int>(index)] = newValue;
if (mode == "GET"_s) {
auto const& value = ResolvePath(json, args);
if (value.isObject() || value.isArray()) {
makefile.AddDefinition(*outputVariable, WriteJson(value));
} else if (value.isBool()) {
makefile.AddDefinitionBool(*outputVariable, value.asBool());
} else {
value.append(newValue);
makefile.AddDefinition(*outputVariable, value.asString());
}
} else {
throw json_error(cmStrCat("SET needs to be called with an "
"element of type OBJECT or ARRAY, got "_s,
JsonTypeToString(value.type())));
} else if (mode == "GET_RAW"_s) {
auto const& value = ResolvePath(json, args);
makefile.AddDefinition(*outputVariable, WriteJson(value));
} else if (mode == "TYPE"_s) {
auto const& value = ResolvePath(json, args);
makefile.AddDefinition(*outputVariable,
JsonTypeToString(value.type()));
} else if (mode == "MEMBER"_s) {
auto const& indexStr = args.PopBack("missing member index"_s);
auto const& value = ResolvePath(json, args);
if (!value.isObject()) {
throw json_error(
cmStrCat("MEMBER needs to be called with an element of "
"type OBJECT, got "_s,
JsonTypeToString(value.type())),
args);
}
auto const index = ParseIndex(
indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
auto const memIt = std::next(value.begin(), index);
makefile.AddDefinition(*outputVariable, memIt.name());
} else if (mode == "LENGTH"_s) {
auto const& value = ResolvePath(json, args);
if (!value.isArray() && !value.isObject()) {
throw json_error(cmStrCat("LENGTH needs to be called with an "
"element of type ARRAY or OBJECT, got "_s,
JsonTypeToString(value.type())),
args);
}
cmAlphaNum sizeStr{ value.size() };
makefile.AddDefinition(*outputVariable, sizeStr.View());
} else if (mode == "REMOVE"_s) {
auto const& toRemove =
args.PopBack("missing member or index to remove"_s);
auto& value = ResolvePath(json, args);
if (value.isArray()) {
auto const index = ParseIndex(
toRemove, Args{ args.begin(), args.end() + 1 }, value.size());
Json::Value removed;
value.removeIndex(index, &removed);
} else if (value.isObject()) {
Json::Value removed;
value.removeMember(toRemove, &removed);
} else {
throw json_error(cmStrCat("REMOVE needs to be called with an "
"element of type ARRAY or OBJECT, got "_s,
JsonTypeToString(value.type())),
args);
}
makefile.AddDefinition(*outputVariable, WriteJson(json));
} else if (mode == "SET"_s) {
auto const& newValueStr = args.PopBack("missing new value remove"_s);
auto const& toAdd = args.PopBack("missing member name to add"_s);
auto& value = ResolvePath(json, args);
Json::Value newValue = ReadJson(newValueStr);
if (value.isObject()) {
value[toAdd] = newValue;
} else if (value.isArray()) {
auto const index =
ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 });
if (value.isValidIndex(index)) {
value[static_cast<int>(index)] = newValue;
} else {
value.append(newValue);
}
} else {
throw json_error(cmStrCat("SET needs to be called with an "
"element of type OBJECT or ARRAY, got "_s,
JsonTypeToString(value.type())));
}
makefile.AddDefinition(*outputVariable, WriteJson(json));
} else if (mode == "EQUAL"_s) {
auto const& jsonstr2 =
args.PopFront("missing second json string argument"_s);
Json::Value json2 = ReadJson(jsonstr2);
makefile.AddDefinitionBool(*outputVariable, json == json2);
}
makefile.AddDefinition(*outputVariable, WriteJson(json));
} else if (mode == "EQUAL"_s) {
auto const& jsonstr2 =
args.PopFront("missing second json string argument"_s);
Json::Value json2 = ReadJson(jsonstr2);
makefile.AddDefinitionBool(*outputVariable, json == json2);
}
} catch (json_error const& e) {

View File

@ -360,3 +360,19 @@ assert_json_equal("${error}" "${result}"
"foo" : "bar",
"array" : [5, "val", {"some": "other"}, null, "append"]
}]=])
# Test STRING_ENCODE
string(JSON result STRING_ENCODE Hello)
assert_strequal("${result}" "\"Hello\"")
string(JSON result STRING_ENCODE "\"Hello\"")
assert_strequal("${result}" "\"\\\"Hello\\\"\"")
string(JSON result STRING_ENCODE null)
assert_strequal("${result}" "\"null\"")
string(JSON result STRING_ENCODE 0)
assert_strequal("${result}" "\"0\"")
string(JSON result STRING_ENCODE false)
assert_strequal("${result}" "\"false\"")
string(JSON result STRING_ENCODE {})
assert_strequal("${result}" "\"{}\"")
string(JSON result STRING_ENCODE [])
assert_strequal("${result}" "\"[]\"")

View File

@ -1,5 +1,5 @@
CMake Error at JSONWrongMode\.cmake:1 \(string\):
string sub-command JSON got an invalid mode 'FOO', expected one of GET,
GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, EQUAL\.
GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, EQUAL, STRING_ENCODE\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)