From 8eab76eb846f6aabf6a814574573f16dd992832a Mon Sep 17 00:00:00 2001 From: Peter Steneteg Date: Mon, 24 Aug 2020 11:14:45 +0200 Subject: 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 --- Help/command/string.rst | 103 ++++++++ Help/release/dev/string-json-support.rst | 5 + Source/cmStringCommand.cxx | 300 ++++++++++++++++++++++ Tests/RunCMake/string/JSON.cmake | 342 +++++++++++++++++++++++++ Tests/RunCMake/string/JSONNoArgs-result.txt | 1 + Tests/RunCMake/string/JSONNoArgs-stderr.txt | 4 + Tests/RunCMake/string/JSONNoArgs.cmake | 1 + Tests/RunCMake/string/JSONNoJson-result.txt | 1 + Tests/RunCMake/string/JSONNoJson-stderr.txt | 4 + Tests/RunCMake/string/JSONNoJson.cmake | 1 + Tests/RunCMake/string/JSONOneArg-result.txt | 1 + Tests/RunCMake/string/JSONOneArg-stderr.txt | 4 + Tests/RunCMake/string/JSONOneArg.cmake | 1 + Tests/RunCMake/string/JSONWrongMode-result.txt | 1 + Tests/RunCMake/string/JSONWrongMode-stderr.txt | 5 + Tests/RunCMake/string/JSONWrongMode.cmake | 1 + Tests/RunCMake/string/RunCMakeTest.cmake | 7 + Tests/RunCMake/string/json/unicode.json | 8 + 18 files changed, 790 insertions(+) create mode 100644 Help/release/dev/string-json-support.rst create mode 100644 Tests/RunCMake/string/JSON.cmake create mode 100644 Tests/RunCMake/string/JSONNoArgs-result.txt create mode 100644 Tests/RunCMake/string/JSONNoArgs-stderr.txt create mode 100644 Tests/RunCMake/string/JSONNoArgs.cmake create mode 100644 Tests/RunCMake/string/JSONNoJson-result.txt create mode 100644 Tests/RunCMake/string/JSONNoJson-stderr.txt create mode 100644 Tests/RunCMake/string/JSONNoJson.cmake create mode 100644 Tests/RunCMake/string/JSONOneArg-result.txt create mode 100644 Tests/RunCMake/string/JSONOneArg-stderr.txt create mode 100644 Tests/RunCMake/string/JSONOneArg.cmake create mode 100644 Tests/RunCMake/string/JSONWrongMode-result.txt create mode 100644 Tests/RunCMake/string/JSONWrongMode-stderr.txt create mode 100644 Tests/RunCMake/string/JSONWrongMode.cmake create mode 100644 Tests/RunCMake/string/json/unicode.json diff --git a/Help/command/string.rst b/Help/command/string.rst index cfcf914..0bc2b48 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -43,6 +43,19 @@ Synopsis string(`TIMESTAMP`_ [] [UTC]) string(`UUID`_ ...) + `JSON`_ + string(JSON [ERROR_VARIABLE ] + {`GET`_ | `TYPE`_ | :ref:`LENGTH ` | `REMOVE`_} + [ ...]) + string(JSON [ERROR_VARIABLE ] + `MEMBER`_ + [ ...] ) + string(JSON [ERROR_VARIABLE ] + `SET`_ + [ ...] ) + string(JSON [ERROR_VARIABLE ] + `EQUAL`_ ) + 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 [ERROR_VARIABLE ] + GET [ ...]) + +Get an element from ```` at the location given +by the list of ```` 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 [ERROR_VARIABLE ] + TYPE [ ...]) + +Get the type of an element in ```` at the location +given by the list of ```` arguments. The ```` +will be set to one of ``NULL``, ``NUMBER``, ``STRING``, ``BOOLEAN``, +``ARRAY``, or ``OBJECT``. + +.. _MEMBER: +.. code-block:: cmake + + string(JSON [ERROR_VARIABLE ] + MEMBER + [ ...] ) + +Get the name of the ````:th member in ```` at the location +given by the list of ```` arguments. +Requires an element of object type. + +.. _JSONLENGTH: +.. code-block:: cmake + + string(JSON [ERROR_VARIABLE ] + LENGTH [ ...]) + +Get the length of an element in ```` at the location +given by the list of ```` arguments. +Required an element of array or object type. + +.. _REMOVE: +.. code-block:: cmake + + string(JSON [ERROR_VARIABLE ] + REMOVE [ ...]) + +Remove an element from ```` at the location +given by the list of ```` arguments. The JSON string +without the removed element will we written in ````. + +.. _SET: +.. code-block:: cmake + + string(JSON [ERROR_VARIABLE ] + SET [ ...] ) + +Set an element in ```` at the location +given by the list of ```` arguments to ````. +The contents of ```` should be valid JSON. + +.. _EQUAL: +.. code-block:: cmake + + string(JSON [ERROR_VARIABLE ] + EQUAL ) + +Compare the two JSON objects given by ```` and ```` +for equality + + +If the optional ``ERROR_VARIABLE`` argument is given errors will be +reported in ````. If no error occurs the ```` +will be set to ``NOTFOUND``. If ``ERROR_VARIABLE`` is not set a CMake error +will be issued. +When an error occurs the ```` will be set to +``-[...]-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 0000000..bd75328 --- /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 4000a7d..b28fca9 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -8,12 +8,21 @@ #include #include #include +#include +#include #include +#include +#include #include +#include #include #include +#include +#include +#include + #include "cmsys/RegularExpression.hxx" #include "cmCryptoHash.h" @@ -930,6 +939,296 @@ bool HandleUuidCommand(std::vector const& args, #endif } +#if !defined(CMAKE_BOOTSTRAP) + +// Helpers for string(JSON ...) +struct Args : cmRange::const_iterator> +{ + using cmRange::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 message, + cm::optional errorPath = cm::nullopt) + : std::runtime_error(cmCatViews(message)) + , ErrorPath{ + std::move(errorPath) // NOLINT(performance-move-const-arg) + } + { + } + cm::optional 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 const& progress = cm::nullopt, + Json::ArrayIndex max = std::numeric_limits::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(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(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 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(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 const& args, @@ -973,6 +1272,7 @@ bool cmStringCommand(std::vector 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 0000000..ab4194d --- /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 0000000..d00491f --- /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 0000000..426735a --- /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 0000000..4463fe4 --- /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 0000000..d00491f --- /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 0000000..26804df --- /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 0000000..4f819f1 --- /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 0000000..d00491f --- /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 0000000..282139c --- /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 0000000..143f9ca --- /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 0000000..d00491f --- /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 0000000..c70991b --- /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 0000000..7301f7a --- /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 bb7cb17..ff0bb51 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 0000000..86fd232 --- /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" +} -- cgit v0.12