string(JSON): Adds JSON parsing support to the string command
Adds a set of sub commands to the string command for parsing JSON, the JSON commands are: GET, TYPE, MEMBER, LENGTH, REMOVE, SET, and EQUAL. Closes: #19501
This commit is contained in:
parent
5b3644fba6
commit
8eab76eb84
@ -43,6 +43,19 @@ Synopsis
|
||||
string(`TIMESTAMP`_ <out-var> [<format string>] [UTC])
|
||||
string(`UUID`_ <out-var> ...)
|
||||
|
||||
`JSON`_
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
{`GET`_ | `TYPE`_ | :ref:`LENGTH <JSONLENGTH>` | `REMOVE`_}
|
||||
<json-string> <member|index> [<member|index> ...])
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
`MEMBER`_ <json-string>
|
||||
[<member|index> ...] <index>)
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
`SET`_ <json-string>
|
||||
<member|index> [<member|index> ...] <value>)
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
`EQUAL`_ <json-string1> <json-string2>)
|
||||
|
||||
Search and Replace
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -470,3 +483,93 @@ A UUID has the format ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``
|
||||
where each ``x`` represents a lower case hexadecimal character.
|
||||
Where required, an uppercase representation can be requested
|
||||
with the optional ``UPPER`` flag.
|
||||
|
||||
JSON
|
||||
^^^^
|
||||
|
||||
.. _JSON:
|
||||
|
||||
Functionality for querying a JSON string
|
||||
|
||||
.. _GET:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
|
||||
GET <json-string> <member|index> [<member|index> ...])
|
||||
|
||||
Get an element from ``<json-string>`` at the location given
|
||||
by the list of ``<member|index>`` arguments.
|
||||
Array and object elements will be returned as a JSON string.
|
||||
Boolean elements will be returned as ``ON`` or ``OFF``.
|
||||
Null elements will be returned as an empty string.
|
||||
Number and string types will be returned as strings.
|
||||
|
||||
.. _TYPE:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
|
||||
TYPE <json-string> <member|index> [<member|index> ...])
|
||||
|
||||
Get the type of an element in ``<json-string>`` at the location
|
||||
given by the list of ``<member|index>`` arguments. The ``<out-var>``
|
||||
will be set to one of ``NULL``, ``NUMBER``, ``STRING``, ``BOOLEAN``,
|
||||
``ARRAY``, or ``OBJECT``.
|
||||
|
||||
.. _MEMBER:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
MEMBER <json-string>
|
||||
[<member|index> ...] <index>)
|
||||
|
||||
Get the name of the ``<index>``:th member in ``<json-string>`` at the location
|
||||
given by the list of ``<member|index>`` arguments.
|
||||
Requires an element of object type.
|
||||
|
||||
.. _JSONLENGTH:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
|
||||
LENGTH <json-string> <member|index> [<member|index> ...])
|
||||
|
||||
Get the length of an element in ``<json-string>`` at the location
|
||||
given by the list of ``<member|index>`` arguments.
|
||||
Required an element of array or object type.
|
||||
|
||||
.. _REMOVE:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
|
||||
REMOVE <json-string> <member|index> [<member|index> ...])
|
||||
|
||||
Remove an element from ``<json-string>`` at the location
|
||||
given by the list of ``<member|index>`` arguments. The JSON string
|
||||
without the removed element will we written in ``<out-var>``.
|
||||
|
||||
.. _SET:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
|
||||
SET <json-string> <member|index> [<member|index> ...] <value>)
|
||||
|
||||
Set an element in ``<json-string>`` at the location
|
||||
given by the list of ``<member|index>`` arguments to ``<value>``.
|
||||
The contents of ``<value>`` should be valid JSON.
|
||||
|
||||
.. _EQUAL:
|
||||
.. code-block:: cmake
|
||||
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
EQUAL <json-string1> <json-string2>)
|
||||
|
||||
Compare the two JSON objects given by ``<json-string1>`` and ``<json-string2>``
|
||||
for equality
|
||||
|
||||
|
||||
If the optional ``ERROR_VARIABLE`` argument is given errors will be
|
||||
reported in ``<error-variable>``. If no error occurs the ``<error-variable>``
|
||||
will be set to ``NOTFOUND``. If ``ERROR_VARIABLE`` is not set a CMake error
|
||||
will be issued.
|
||||
When an error occurs the ``<out-var>`` will be set to
|
||||
``<member|index>-[<member|index>...]-NOTFOUND`` with the path elements up to
|
||||
the point where the error occurred.
|
||||
|
5
Help/release/dev/string-json-support.rst
Normal file
5
Help/release/dev/string-json-support.rst
Normal file
@ -0,0 +1,5 @@
|
||||
string-json-support
|
||||
-------------------
|
||||
|
||||
* The :command:`string` command gained set of new ``JSON`` sub commands to provide JSON
|
||||
parsing capabilities.
|
@ -8,12 +8,21 @@
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <cm/iterator>
|
||||
#include <cm/optional>
|
||||
#include <cm/string_view>
|
||||
#include <cmext/string_view>
|
||||
|
||||
#include <cm3p/json/reader.h>
|
||||
#include <cm3p/json/value.h>
|
||||
#include <cm3p/json/writer.h>
|
||||
|
||||
#include "cmsys/RegularExpression.hxx"
|
||||
|
||||
#include "cmCryptoHash.h"
|
||||
@ -930,6 +939,296 @@ bool HandleUuidCommand(std::vector<std::string> const& args,
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(CMAKE_BOOTSTRAP)
|
||||
|
||||
// Helpers for string(JSON ...)
|
||||
struct Args : cmRange<typename std::vector<std::string>::const_iterator>
|
||||
{
|
||||
using cmRange<typename std::vector<std::string>::const_iterator>::cmRange;
|
||||
|
||||
auto PopFront(cm::string_view error) -> const std::string&;
|
||||
auto PopBack(cm::string_view error) -> const std::string&;
|
||||
};
|
||||
|
||||
class json_error : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
json_error(std::initializer_list<cm::string_view> message,
|
||||
cm::optional<Args> errorPath = cm::nullopt)
|
||||
: std::runtime_error(cmCatViews(message))
|
||||
, ErrorPath{
|
||||
std::move(errorPath) // NOLINT(performance-move-const-arg)
|
||||
}
|
||||
{
|
||||
}
|
||||
cm::optional<Args> ErrorPath;
|
||||
};
|
||||
|
||||
const std::string& Args::PopFront(cm::string_view error)
|
||||
{
|
||||
if (empty()) {
|
||||
throw json_error({ error });
|
||||
}
|
||||
const std::string& res = *begin();
|
||||
advance(1);
|
||||
return res;
|
||||
}
|
||||
|
||||
const std::string& Args::PopBack(cm::string_view error)
|
||||
{
|
||||
if (empty()) {
|
||||
throw json_error({ error });
|
||||
}
|
||||
const std::string& res = *(end() - 1);
|
||||
retreat(1);
|
||||
return res;
|
||||
}
|
||||
|
||||
cm::string_view JsonTypeToString(Json::ValueType type)
|
||||
{
|
||||
switch (type) {
|
||||
case Json::ValueType::nullValue:
|
||||
return "NULL"_s;
|
||||
case Json::ValueType::intValue:
|
||||
case Json::ValueType::uintValue:
|
||||
case Json::ValueType::realValue:
|
||||
return "NUMBER"_s;
|
||||
case Json::ValueType::stringValue:
|
||||
return "STRING"_s;
|
||||
case Json::ValueType::booleanValue:
|
||||
return "BOOLEAN"_s;
|
||||
case Json::ValueType::arrayValue:
|
||||
return "ARRAY"_s;
|
||||
case Json::ValueType::objectValue:
|
||||
return "OBJECT"_s;
|
||||
}
|
||||
throw json_error({ "invalid JSON type found"_s });
|
||||
}
|
||||
|
||||
int ParseIndex(
|
||||
const std::string& str, cm::optional<Args> const& progress = cm::nullopt,
|
||||
Json::ArrayIndex max = std::numeric_limits<Json::ArrayIndex>::max())
|
||||
{
|
||||
unsigned long lindex;
|
||||
if (!cmStrToULong(str, &lindex)) {
|
||||
throw json_error({ "expected an array index, got: '"_s, str, "'"_s },
|
||||
progress);
|
||||
}
|
||||
Json::ArrayIndex index = static_cast<Json::ArrayIndex>(lindex);
|
||||
if (index >= max) {
|
||||
cmAlphaNum sizeStr{ max };
|
||||
throw json_error({ "expected an index less then "_s, sizeStr.View(),
|
||||
" got '"_s, str, "'"_s },
|
||||
progress);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
Json::Value& ResolvePath(Json::Value& json, Args path)
|
||||
{
|
||||
Json::Value* search = &json;
|
||||
|
||||
for (auto curr = path.begin(); curr != path.end(); ++curr) {
|
||||
const std::string& field = *curr;
|
||||
Args progress{ path.begin(), curr + 1 };
|
||||
|
||||
if (search->isArray()) {
|
||||
auto index = ParseIndex(field, progress, search->size());
|
||||
search = &(*search)[index];
|
||||
|
||||
} else if (search->isObject()) {
|
||||
if (!search->isMember(field)) {
|
||||
const auto progressStr = cmJoin(progress, " "_s);
|
||||
throw json_error({ "member '"_s, progressStr, "' not found"_s },
|
||||
progress);
|
||||
}
|
||||
search = &(*search)[field];
|
||||
} else {
|
||||
const auto progressStr = cmJoin(progress, " "_s);
|
||||
throw json_error(
|
||||
{ "invalid path '"_s, progressStr,
|
||||
"', need element of OBJECT or ARRAY type to lookup '"_s, field,
|
||||
"' got "_s, JsonTypeToString(search->type()) },
|
||||
progress);
|
||||
}
|
||||
}
|
||||
return *search;
|
||||
};
|
||||
|
||||
Json::Value ReadJson(const std::string& jsonstr)
|
||||
{
|
||||
Json::CharReaderBuilder builder;
|
||||
builder["collectComments"] = false;
|
||||
auto jsonReader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
|
||||
Json::Value json;
|
||||
std::string error;
|
||||
if (!jsonReader->parse(jsonstr.data(), jsonstr.data() + jsonstr.size(),
|
||||
&json, &error)) {
|
||||
throw json_error({ "failed parsing json string: "_s, error });
|
||||
}
|
||||
return json;
|
||||
}
|
||||
std::string WriteJson(const Json::Value& value)
|
||||
{
|
||||
Json::StreamWriterBuilder writer;
|
||||
writer["indentation"] = " ";
|
||||
writer["commentStyle"] = "None";
|
||||
return Json::writeString(writer, value);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool HandleJSONCommand(std::vector<std::string> const& arguments,
|
||||
cmExecutionStatus& status)
|
||||
{
|
||||
#if !defined(CMAKE_BOOTSTRAP)
|
||||
|
||||
auto& makefile = status.GetMakefile();
|
||||
Args args{ arguments.begin() + 1, arguments.end() };
|
||||
|
||||
const std::string* errorVariable = nullptr;
|
||||
const std::string* outputVariable = nullptr;
|
||||
bool success = true;
|
||||
|
||||
try {
|
||||
outputVariable = &args.PopFront("missing out-var argument"_s);
|
||||
|
||||
if (!args.empty() && *args.begin() == "ERROR_VARIABLE"_s) {
|
||||
args.PopFront("");
|
||||
errorVariable = &args.PopFront("missing error-var argument"_s);
|
||||
makefile.AddDefinition(*errorVariable, "NOTFOUND"_s);
|
||||
}
|
||||
|
||||
const auto& mode = args.PopFront("missing mode argument"_s);
|
||||
if (mode != "GET"_s && mode != "TYPE"_s && mode != "MEMBER"_s &&
|
||||
mode != "LENGTH"_s && mode != "REMOVE"_s && mode != "SET"_s &&
|
||||
mode != "EQUAL"_s) {
|
||||
throw json_error(
|
||||
{ "got an invalid mode '"_s, mode,
|
||||
"', expected one of GET, GET_ARRAY, TYPE, MEMBER, MEMBERS,"
|
||||
" LENGTH, REMOVE, SET, EQUAL"_s });
|
||||
}
|
||||
|
||||
const auto& jsonstr = args.PopFront("missing json string argument"_s);
|
||||
Json::Value json = ReadJson(jsonstr);
|
||||
|
||||
if (mode == "GET"_s) {
|
||||
const auto& 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 == "TYPE"_s) {
|
||||
const auto& value = ResolvePath(json, args);
|
||||
makefile.AddDefinition(*outputVariable, JsonTypeToString(value.type()));
|
||||
|
||||
} else if (mode == "MEMBER"_s) {
|
||||
const auto& indexStr = args.PopBack("missing member index"_s);
|
||||
const auto& value = ResolvePath(json, args);
|
||||
if (!value.isObject()) {
|
||||
throw json_error({ "MEMBER needs to be called with an element of "
|
||||
"type OBJECT, got "_s,
|
||||
JsonTypeToString(value.type()) },
|
||||
args);
|
||||
}
|
||||
const auto index = ParseIndex(
|
||||
indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
|
||||
const auto memIt = std::next(value.begin(), index);
|
||||
makefile.AddDefinition(*outputVariable, memIt.name());
|
||||
|
||||
} else if (mode == "LENGTH"_s) {
|
||||
const auto& value = ResolvePath(json, args);
|
||||
if (!value.isArray() && !value.isObject()) {
|
||||
throw json_error({ "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) {
|
||||
const auto& toRemove =
|
||||
args.PopBack("missing member or index to remove"_s);
|
||||
auto& value = ResolvePath(json, args);
|
||||
|
||||
if (value.isArray()) {
|
||||
const auto 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({ "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) {
|
||||
const auto& newValueStr = args.PopBack("missing new value remove"_s);
|
||||
const auto& 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()) {
|
||||
const auto 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({ "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) {
|
||||
const auto& jsonstr2 =
|
||||
args.PopFront("missing second json string argument"_s);
|
||||
Json::Value json2 = ReadJson(jsonstr2);
|
||||
makefile.AddDefinitionBool(*outputVariable, json == json2);
|
||||
}
|
||||
|
||||
} catch (const json_error& e) {
|
||||
if (outputVariable && e.ErrorPath) {
|
||||
const auto errorPath = cmJoin(*e.ErrorPath, "-");
|
||||
makefile.AddDefinition(*outputVariable,
|
||||
cmCatViews({ errorPath, "-NOTFOUND"_s }));
|
||||
} else if (outputVariable) {
|
||||
makefile.AddDefinition(*outputVariable, "NOTFOUND"_s);
|
||||
}
|
||||
|
||||
if (errorVariable) {
|
||||
makefile.AddDefinition(*errorVariable, e.what());
|
||||
} else {
|
||||
status.SetError(cmCatViews({ "sub-command JSON "_s, e.what(), "."_s }));
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
#else
|
||||
status.SetError(cmStrCat(arguments[0], " not available during bootstrap"_s));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool cmStringCommand(std::vector<std::string> const& args,
|
||||
@ -973,6 +1272,7 @@ bool cmStringCommand(std::vector<std::string> const& args,
|
||||
{ "MAKE_C_IDENTIFIER"_s, HandleMakeCIdentifierCommand },
|
||||
{ "GENEX_STRIP"_s, HandleGenexStripCommand },
|
||||
{ "UUID"_s, HandleUuidCommand },
|
||||
{ "JSON"_s, HandleJSONCommand },
|
||||
};
|
||||
|
||||
return subcommand(args[0], args, status);
|
||||
|
342
Tests/RunCMake/string/JSON.cmake
Normal file
342
Tests/RunCMake/string/JSON.cmake
Normal file
@ -0,0 +1,342 @@
|
||||
function(assert_strequal actual expected)
|
||||
if(NOT expected STREQUAL actual)
|
||||
message(SEND_ERROR "Output:\n${actual}\nDid not match expected:\n${expected}\n")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(assert_strequal_error actual expected error)
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
assert_strequal("${actual}" "${expected}")
|
||||
endfunction()
|
||||
|
||||
function(assert_json_equal error actual expected)
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
string(JSON eql EQUAL "${actual}" "${expected}")
|
||||
if(NOT eql)
|
||||
message(SEND_ERROR "Expected equality got\n ${actual}\n expected\n${expected}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# test EQUAL
|
||||
string(JSON result EQUAL
|
||||
[=[ {"foo":"bar"} ]=]
|
||||
[=[
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
]=])
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "Expected ON got ${result}")
|
||||
endif()
|
||||
|
||||
string(JSON result EQUAL
|
||||
[=[ {"foo":"bar"} ]=]
|
||||
[=[
|
||||
{
|
||||
"foo1": "bar"
|
||||
}
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "Expected OFF got ${result}")
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
set(json1 [=[
|
||||
{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null],
|
||||
"types" : {
|
||||
"null" : null,
|
||||
"number" : 5,
|
||||
"string" : "foo",
|
||||
"boolean" : false,
|
||||
"array" : [1,2,3],
|
||||
"object" : {}
|
||||
},
|
||||
"values" : {
|
||||
"null" : null,
|
||||
"number" : 5,
|
||||
"string" : "foo",
|
||||
"false" : false,
|
||||
"true" : true
|
||||
},
|
||||
"special" : {
|
||||
"foo;bar" : "value1",
|
||||
";" : "value2",
|
||||
"semicolon" : ";",
|
||||
"list" : ["one", "two;three", "four"],
|
||||
"quote" : "\"",
|
||||
"\"" : "quote",
|
||||
"backslash" : "\\",
|
||||
"\\" : "backslash",
|
||||
"slash" : "\/",
|
||||
"\/" : "slash",
|
||||
"newline" : "\n",
|
||||
"\n" : "newline",
|
||||
"return" : "\r",
|
||||
"\r" : "return",
|
||||
"tab" : "\t",
|
||||
"\t" : "tab",
|
||||
"backspace" : "\b",
|
||||
"\b" : "backspace",
|
||||
"formfeed" : "\f",
|
||||
"\f" : "formfeed"
|
||||
}
|
||||
}
|
||||
]=])
|
||||
|
||||
string(JSON result GET "${json1}" foo)
|
||||
assert_strequal("${result}" bar)
|
||||
string(JSON result GET "${json1}" array 0)
|
||||
assert_strequal("${result}" 5)
|
||||
string(JSON result GET "${json1}" array 1)
|
||||
assert_strequal("${result}" val)
|
||||
string(JSON result GET "${json1}" array 2 some)
|
||||
assert_strequal("${result}" other)
|
||||
|
||||
string(JSON result GET "${json1}" values null)
|
||||
assert_strequal("${result}" "")
|
||||
string(JSON result GET "${json1}" values number)
|
||||
assert_strequal("${result}" 5)
|
||||
string(JSON result GET "${json1}" values string)
|
||||
assert_strequal("${result}" "foo")
|
||||
string(JSON result GET "${json1}" values true)
|
||||
assert_strequal("${result}" "ON")
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "Output did not match expected: TRUE actual: ${result}")
|
||||
endif()
|
||||
string(JSON result GET "${json1}" values false)
|
||||
assert_strequal("${result}" "OFF")
|
||||
if(result)
|
||||
message(SEND_ERROR "Output did not match expected: FALSE actual: ${result}")
|
||||
endif()
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" foo)
|
||||
assert_strequal_error("${result}" "bar" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" notThere)
|
||||
assert_strequal("${result}" "notThere-NOTFOUND")
|
||||
assert_strequal("${error}" "member 'notThere' not found")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" 0)
|
||||
assert_strequal("${result}" "0-NOTFOUND")
|
||||
assert_strequal("${error}" "member '0' not found")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" array 10)
|
||||
assert_strequal("${result}" "array-10-NOTFOUND")
|
||||
assert_strequal("${error}" "expected an index less then 4 got '10'")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" array 2 some notThere)
|
||||
assert_strequal("${result}" "array-2-some-notThere-NOTFOUND")
|
||||
assert_strequal("${error}" "invalid path 'array 2 some notThere', need element of OBJECT or ARRAY type to lookup 'notThere' got STRING")
|
||||
|
||||
# special chars
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "foo;bar")
|
||||
assert_strequal_error("${result}" "value1" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special ";")
|
||||
assert_strequal_error("${result}" "value2" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special semicolon)
|
||||
assert_strequal_error("${result}" ";" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special list 1)
|
||||
assert_strequal_error("${result}" "two;three" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}")
|
||||
assert_json_equal("${error}" "${result}" "${json1}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" array)
|
||||
assert_json_equal("${error}" "${result}" [=[ [5, "val", {"some": "other"}, null] ]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special quote)
|
||||
assert_strequal_error("${result}" "\"" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "\"")
|
||||
assert_strequal_error("${result}" "quote" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special backslash)
|
||||
assert_strequal_error("${result}" "\\" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "\\")
|
||||
assert_strequal_error("${result}" "backslash" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special slash)
|
||||
assert_strequal_error("${result}" "/" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "/")
|
||||
assert_strequal_error("${result}" "slash" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special newline)
|
||||
assert_strequal_error("${result}" "\n" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "\n")
|
||||
assert_strequal_error("${result}" "newline" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special return)
|
||||
assert_strequal_error("${result}" "\r" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "\r")
|
||||
assert_strequal_error("${result}" "return" "${error}")
|
||||
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special tab)
|
||||
assert_strequal_error("${result}" "\t" "${error}")
|
||||
string(JSON result ERROR_VARIABLE error GET "${json1}" special "\t")
|
||||
assert_strequal_error("${result}" "tab" "${error}")
|
||||
|
||||
file(READ ${CMAKE_CURRENT_LIST_DIR}/json/unicode.json unicode)
|
||||
string(JSON char ERROR_VARIABLE error GET "${unicode}" backspace)
|
||||
string(JSON result ERROR_VARIABLE error GET "${unicode}" "${char}")
|
||||
assert_strequal_error("${result}" "backspace" "${error}")
|
||||
|
||||
file(READ ${CMAKE_CURRENT_LIST_DIR}/json/unicode.json unicode)
|
||||
string(JSON char ERROR_VARIABLE error GET "${unicode}" backspace)
|
||||
string(JSON result ERROR_VARIABLE error GET "${unicode}" "${char}")
|
||||
assert_strequal_error("${result}" "backspace" "${error}")
|
||||
|
||||
string(JSON char ERROR_VARIABLE error GET "${unicode}" formfeed)
|
||||
string(JSON result ERROR_VARIABLE error GET "${unicode}" "${char}")
|
||||
assert_strequal_error("${result}" "formfeed" "${error}")
|
||||
|
||||
string(JSON char ERROR_VARIABLE error GET "${unicode}" datalinkescape)
|
||||
string(JSON result ERROR_VARIABLE error GET "${unicode}" "${char}")
|
||||
assert_strequal_error("${result}" "datalinkescape" "${error}")
|
||||
|
||||
# Test TYPE
|
||||
string(JSON result TYPE "${json1}" types null)
|
||||
assert_strequal("${result}" NULL)
|
||||
string(JSON result TYPE "${json1}" types number)
|
||||
assert_strequal("${result}" NUMBER)
|
||||
string(JSON result TYPE "${json1}" types string)
|
||||
assert_strequal("${result}" STRING)
|
||||
string(JSON result TYPE "${json1}" types boolean)
|
||||
assert_strequal("${result}" BOOLEAN)
|
||||
string(JSON result TYPE "${json1}" types array)
|
||||
assert_strequal("${result}" ARRAY)
|
||||
string(JSON result TYPE "${json1}" types object)
|
||||
assert_strequal("${result}" OBJECT)
|
||||
|
||||
# Test LENGTH
|
||||
string(JSON result ERROR_VARIABLE error LENGTH "${json1}")
|
||||
assert_strequal("${result}" 5)
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
|
||||
string(JSON result ERROR_VARIABLE error LENGTH "${json1}" array)
|
||||
assert_strequal("${result}" 4)
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
|
||||
string(JSON result ERROR_VARIABLE error LENGTH "${json1}" foo)
|
||||
assert_strequal("${result}" "foo-NOTFOUND")
|
||||
assert_strequal("${error}" "LENGTH needs to be called with an element of type ARRAY or OBJECT, got STRING")
|
||||
|
||||
# Test MEMBER
|
||||
string(JSON result ERROR_VARIABLE error MEMBER "${json1}" values 2)
|
||||
assert_strequal("${result}" "number")
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
|
||||
string(JSON result ERROR_VARIABLE error MEMBER "${json1}" values 100)
|
||||
assert_strequal("${result}" "values-100-NOTFOUND")
|
||||
assert_strequal("${error}" "expected an index less then 5 got '100'")
|
||||
|
||||
# Test length loops
|
||||
string(JSON arrayLength ERROR_VARIABLE error LENGTH "${json1}" types array)
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
set(values "")
|
||||
math(EXPR arrayLength "${arrayLength}-1")
|
||||
foreach(index RANGE ${arrayLength})
|
||||
string(JSON value ERROR_VARIABLE error GET "${json1}" types array ${index})
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
list(APPEND values "${value}")
|
||||
endforeach()
|
||||
assert_strequal("${values}" "1;2;3")
|
||||
|
||||
string(JSON valuesLength ERROR_VARIABLE error LENGTH "${json1}" values)
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
set(values "")
|
||||
set(members "")
|
||||
math(EXPR valuesLength "${valuesLength}-1")
|
||||
foreach(index RANGE ${valuesLength})
|
||||
string(JSON member ERROR_VARIABLE error MEMBER "${json1}" values ${index})
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
string(JSON value ERROR_VARIABLE error GET "${json1}" values ${member})
|
||||
if(error)
|
||||
message(SEND_ERROR "Unexpected error: ${error}")
|
||||
endif()
|
||||
|
||||
list(APPEND members "${member}")
|
||||
list(APPEND values "${value}")
|
||||
endforeach()
|
||||
assert_strequal("${members}" "false;null;number;string;true")
|
||||
assert_strequal("${values}" "OFF;;5;foo;ON")
|
||||
|
||||
# Test REMOVE
|
||||
set(json2 [=[{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null]
|
||||
}]=])
|
||||
string(JSON result ERROR_VARIABLE error REMOVE ${json2} foo)
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"array" : [5, "val", {"some": "other"}, null]
|
||||
}]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error REMOVE ${json2} array 1)
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"foo" : "bar",
|
||||
"array" : [5, {"some": "other"}, null]
|
||||
}]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error REMOVE ${json2} array 100)
|
||||
assert_strequal("${result}" "array-100-NOTFOUND")
|
||||
assert_strequal("${error}" "expected an index less then 4 got '100'")
|
||||
|
||||
# Test SET
|
||||
string(JSON result ERROR_VARIABLE error SET ${json2} new 5)
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null],
|
||||
"new" : 5
|
||||
}]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error SET ${json2} new [=[ {"obj" : false} ]=])
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null],
|
||||
"new" : {"obj" : false}
|
||||
}]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error SET ${json2} array 0 6)
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"foo" : "bar",
|
||||
"array" : [6, "val", {"some": "other"}, null]
|
||||
}]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error SET ${json2} array 5 [["append"]])
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null, "append"]
|
||||
}]=])
|
||||
|
||||
string(JSON result ERROR_VARIABLE error SET ${json2} array 100 [["append"]])
|
||||
assert_json_equal("${error}" "${result}"
|
||||
[=[{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null, "append"]
|
||||
}]=])
|
1
Tests/RunCMake/string/JSONNoArgs-result.txt
Normal file
1
Tests/RunCMake/string/JSONNoArgs-result.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
4
Tests/RunCMake/string/JSONNoArgs-stderr.txt
Normal file
4
Tests/RunCMake/string/JSONNoArgs-stderr.txt
Normal file
@ -0,0 +1,4 @@
|
||||
CMake Error at JSONNoArgs.cmake:1 \(string\):
|
||||
string sub-command JSON missing out-var argument.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
1
Tests/RunCMake/string/JSONNoArgs.cmake
Normal file
1
Tests/RunCMake/string/JSONNoArgs.cmake
Normal file
@ -0,0 +1 @@
|
||||
string(JSON)
|
1
Tests/RunCMake/string/JSONNoJson-result.txt
Normal file
1
Tests/RunCMake/string/JSONNoJson-result.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
4
Tests/RunCMake/string/JSONNoJson-stderr.txt
Normal file
4
Tests/RunCMake/string/JSONNoJson-stderr.txt
Normal file
@ -0,0 +1,4 @@
|
||||
CMake Error at JSONNoJson.cmake:1 \(string\):
|
||||
string sub-command JSON missing json string argument.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
1
Tests/RunCMake/string/JSONNoJson.cmake
Normal file
1
Tests/RunCMake/string/JSONNoJson.cmake
Normal file
@ -0,0 +1 @@
|
||||
string(JSON var GET)
|
1
Tests/RunCMake/string/JSONOneArg-result.txt
Normal file
1
Tests/RunCMake/string/JSONOneArg-result.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
4
Tests/RunCMake/string/JSONOneArg-stderr.txt
Normal file
4
Tests/RunCMake/string/JSONOneArg-stderr.txt
Normal file
@ -0,0 +1,4 @@
|
||||
CMake Error at JSONOneArg.cmake:1 \(string\):
|
||||
string sub-command JSON missing mode argument.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
1
Tests/RunCMake/string/JSONOneArg.cmake
Normal file
1
Tests/RunCMake/string/JSONOneArg.cmake
Normal file
@ -0,0 +1 @@
|
||||
string(JSON var)
|
1
Tests/RunCMake/string/JSONWrongMode-result.txt
Normal file
1
Tests/RunCMake/string/JSONWrongMode-result.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
5
Tests/RunCMake/string/JSONWrongMode-stderr.txt
Normal file
5
Tests/RunCMake/string/JSONWrongMode-stderr.txt
Normal file
@ -0,0 +1,5 @@
|
||||
CMake Error at JSONWrongMode.cmake:1 \(string\):
|
||||
string sub-command JSON got an invalid mode 'FOO', expected one of GET,
|
||||
GET_ARRAY, TYPE, MEMBER, MEMBERS, LENGTH, REMOVE, SET, EQUAL.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
1
Tests/RunCMake/string/JSONWrongMode.cmake
Normal file
1
Tests/RunCMake/string/JSONWrongMode.cmake
Normal file
@ -0,0 +1 @@
|
||||
string(JSON var FOO)
|
@ -1,5 +1,12 @@
|
||||
include(RunCMake)
|
||||
|
||||
run_cmake(JSON)
|
||||
|
||||
run_cmake(JSONNoJson)
|
||||
run_cmake(JSONWrongMode)
|
||||
run_cmake(JSONOneArg)
|
||||
run_cmake(JSONNoArgs)
|
||||
|
||||
run_cmake(Append)
|
||||
run_cmake(AppendNoArgs)
|
||||
|
||||
|
8
Tests/RunCMake/string/json/unicode.json
Normal file
8
Tests/RunCMake/string/json/unicode.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"backspace" : "\b",
|
||||
"\b" : "backspace",
|
||||
"formfeed" : "\f",
|
||||
"\f" : "formfeed" ,
|
||||
"datalinkescape" : "\u0010",
|
||||
"\u0010" : "datalinkescape"
|
||||
}
|
Loading…
Reference in New Issue
Block a user