diff options
author | Brad King <brad.king@kitware.com> | 2020-09-17 13:46:19 +0000 |
---|---|---|
committer | Kitware Robot <kwrobot@kitware.com> | 2020-09-17 09:47:43 -0400 |
commit | bb8afa0020759ee79149c89ca178236cb85adfb5 (patch) | |
tree | ae6b990951d2d61d9517284e0265ae36b68385b9 | |
parent | 5e53b58c4a0d2a859b8be406fbe9d2e4d5463ddd (diff) | |
parent | 8eab76eb846f6aabf6a814574573f16dd992832a (diff) | |
download | cmake-bb8afa0020759ee79149c89ca178236cb85adfb5.tar.gz |
Merge topic 'string-json-support'
8eab76eb84 string(JSON): Adds JSON parsing support to the string command
Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Ben Boeckel <ben.boeckel@kitware.com>
Acked-by: Cristian Adam <cristian.adam@gmail.com>
Acked-by: Michael Hirsch, Ph.D. <michael@scivision.dev>
Merge-request: !5159
-rw-r--r-- | Help/command/string.rst | 103 | ||||
-rw-r--r-- | Help/release/dev/string-json-support.rst | 5 | ||||
-rw-r--r-- | Source/cmStringCommand.cxx | 300 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSON.cmake | 342 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONNoArgs-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONNoArgs-stderr.txt | 4 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONNoArgs.cmake | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONNoJson-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONNoJson-stderr.txt | 4 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONNoJson.cmake | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONOneArg-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONOneArg-stderr.txt | 4 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONOneArg.cmake | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONWrongMode-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONWrongMode-stderr.txt | 5 | ||||
-rw-r--r-- | Tests/RunCMake/string/JSONWrongMode.cmake | 1 | ||||
-rw-r--r-- | Tests/RunCMake/string/RunCMakeTest.cmake | 7 | ||||
-rw-r--r-- | Tests/RunCMake/string/json/unicode.json | 8 |
18 files changed, 790 insertions, 0 deletions
diff --git a/Help/command/string.rst b/Help/command/string.rst index cfcf9147fa..0bc2b484d9 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -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. diff --git a/Help/release/dev/string-json-support.rst b/Help/release/dev/string-json-support.rst new file mode 100644 index 0000000000..bd7532879d --- /dev/null +++ b/Help/release/dev/string-json-support.rst @@ -0,0 +1,5 @@ +string-json-support +------------------- + +* The :command:`string` command gained set of new ``JSON`` sub commands to provide JSON + parsing capabilities. diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx index 4000a7de08..b28fca954c 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -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); diff --git a/Tests/RunCMake/string/JSON.cmake b/Tests/RunCMake/string/JSON.cmake new file mode 100644 index 0000000000..ab4194d26b --- /dev/null +++ b/Tests/RunCMake/string/JSON.cmake @@ -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"] +}]=]) diff --git a/Tests/RunCMake/string/JSONNoArgs-result.txt b/Tests/RunCMake/string/JSONNoArgs-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/string/JSONNoArgs-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/JSONNoArgs-stderr.txt b/Tests/RunCMake/string/JSONNoArgs-stderr.txt new file mode 100644 index 0000000000..426735ac47 --- /dev/null +++ b/Tests/RunCMake/string/JSONNoArgs-stderr.txt @@ -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\) diff --git a/Tests/RunCMake/string/JSONNoArgs.cmake b/Tests/RunCMake/string/JSONNoArgs.cmake new file mode 100644 index 0000000000..4463fe4a27 --- /dev/null +++ b/Tests/RunCMake/string/JSONNoArgs.cmake @@ -0,0 +1 @@ +string(JSON) diff --git a/Tests/RunCMake/string/JSONNoJson-result.txt b/Tests/RunCMake/string/JSONNoJson-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/string/JSONNoJson-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/JSONNoJson-stderr.txt b/Tests/RunCMake/string/JSONNoJson-stderr.txt new file mode 100644 index 0000000000..26804dfeb3 --- /dev/null +++ b/Tests/RunCMake/string/JSONNoJson-stderr.txt @@ -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\) diff --git a/Tests/RunCMake/string/JSONNoJson.cmake b/Tests/RunCMake/string/JSONNoJson.cmake new file mode 100644 index 0000000000..4f819f16e2 --- /dev/null +++ b/Tests/RunCMake/string/JSONNoJson.cmake @@ -0,0 +1 @@ +string(JSON var GET) diff --git a/Tests/RunCMake/string/JSONOneArg-result.txt b/Tests/RunCMake/string/JSONOneArg-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/string/JSONOneArg-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/JSONOneArg-stderr.txt b/Tests/RunCMake/string/JSONOneArg-stderr.txt new file mode 100644 index 0000000000..282139cf1c --- /dev/null +++ b/Tests/RunCMake/string/JSONOneArg-stderr.txt @@ -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\) diff --git a/Tests/RunCMake/string/JSONOneArg.cmake b/Tests/RunCMake/string/JSONOneArg.cmake new file mode 100644 index 0000000000..143f9ca1a4 --- /dev/null +++ b/Tests/RunCMake/string/JSONOneArg.cmake @@ -0,0 +1 @@ +string(JSON var) diff --git a/Tests/RunCMake/string/JSONWrongMode-result.txt b/Tests/RunCMake/string/JSONWrongMode-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/string/JSONWrongMode-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/JSONWrongMode-stderr.txt b/Tests/RunCMake/string/JSONWrongMode-stderr.txt new file mode 100644 index 0000000000..c70991b272 --- /dev/null +++ b/Tests/RunCMake/string/JSONWrongMode-stderr.txt @@ -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\) diff --git a/Tests/RunCMake/string/JSONWrongMode.cmake b/Tests/RunCMake/string/JSONWrongMode.cmake new file mode 100644 index 0000000000..7301f7a880 --- /dev/null +++ b/Tests/RunCMake/string/JSONWrongMode.cmake @@ -0,0 +1 @@ +string(JSON var FOO) diff --git a/Tests/RunCMake/string/RunCMakeTest.cmake b/Tests/RunCMake/string/RunCMakeTest.cmake index bb7cb174ef..ff0bb51a3b 100644 --- a/Tests/RunCMake/string/RunCMakeTest.cmake +++ b/Tests/RunCMake/string/RunCMakeTest.cmake @@ -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) diff --git a/Tests/RunCMake/string/json/unicode.json b/Tests/RunCMake/string/json/unicode.json new file mode 100644 index 0000000000..86fd232c4e --- /dev/null +++ b/Tests/RunCMake/string/json/unicode.json @@ -0,0 +1,8 @@ +{ + "backspace" : "\b", + "\b" : "backspace", + "formfeed" : "\f", + "\f" : "formfeed" , + "datalinkescape" : "\u0010", + "\u0010" : "datalinkescape" +} |