json/pointer: add a trivial parser for json-pointer

This commit is contained in:
Danny Robson 2018-07-13 16:08:24 +10:00
parent 493c91eace
commit 33dc5c7053
21 changed files with 200 additions and 9 deletions

View File

@ -296,6 +296,8 @@ list (
json/except.hpp
${CMAKE_CURRENT_BINARY_DIR}/json/flat.cpp
json/flat.hpp
json/pointer.cpp
json/pointer.hpp
json/schema.cpp
json/schema.hpp
json/schema/fwd.hpp
@ -479,7 +481,7 @@ target_link_libraries(cruft-util dl)
###############################################################################
foreach (tool cpuid json-clean json-schema json-validate json-compare poisson macro scratch)
foreach (tool cpuid json-clean json-schema json-validate json-compare json-pointer poisson macro scratch)
add_executable (util_${tool} tools/${tool}.cpp)
set_target_properties (util_${tool} PROPERTIES OUTPUT_NAME ${tool})
target_link_libraries (util_${tool} cruft-util)
@ -593,18 +595,16 @@ if (TESTS)
add_test(NAME util_${name} COMMAND util_${name})
endforeach(t)
foreach (jtest compare schema pointer)
configure_file ("test/json/${jtest}.py.in" "util_test_json_${jtest}.py" @ONLY)
add_test(NAME "util_test_json_${jtest}" COMMAND "util_test_json_${jtest}.py")
set_property(TEST "util_test_json_${jtest}" APPEND PROPERTY DEPENDS "util_json-${jtest}")
endforeach()
configure_file (test/json-parse.sh.in util_test_json_parse.sh @ONLY)
add_test(NAME util_test_json_parse COMMAND util_test_json_parse.sh)
set_property(TEST util_test_json_parse APPEND PROPERTY DEPENDS util_json-validate)
configure_file (test/json/compare.py.in util_test_json_compare.py @ONLY)
add_test(NAME util_test_json_compare COMMAND util_test_json_compare.py)
set_property(TEST util_test_json_compare APPEND PROPERTY DEPENDS util_json-compare)
configure_file (test/json/schema.py.in util_test_json_schema.py @ONLY)
add_test(NAME util_test_json_schema COMMAND util_test_json_schema.py)
set_property(TEST util_test_json_schema APPEND PROPERTY DEPENDS util_json-schema)
configure_file (test/cpp.sh.in util_test_cpp.sh @ONLY)
add_test (NAME util_test_cpp COMMAND util_test_cpp.sh)
set_property (TEST util_test_cpp APPEND PROPERTY DEPENDS util_macro)

60
json/pointer.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "pointer.hpp"
#include "tree.hpp"
#include "../parse.hpp"
#include <algorithm>
#include <stdexcept>
///////////////////////////////////////////////////////////////////////////////
::json::tree::node&
util::json::pointer::resolve (std::string const &path,
::json::tree::node &root)
{
// we only handle fragment represenations currently, which start with a '#'
auto cursor = path.begin ();
if (cursor == path.end () || *cursor != '#')
throw std::invalid_argument ("must be fragment representation");
++cursor;
auto obj = &root;
// * step over each path segment
// * index into the current node
// * advance the node
// * repeat until we reach the end of the query string
for ( ; cursor != path.end (); ) {
// step over the delimiting '/'
if (*cursor != '/')
break;
++cursor;
// find the start of the next segment (or the end of the string)
// and extract the key.
auto next = std::find (cursor, path.end (), '/');
std::string const key { cursor, next };
// try to index into the node and advance the node we're operating on
switch (obj->type ()) {
case ::json::tree::OBJECT:
obj = &(*obj)[key];
break;
case ::json::tree::ARRAY:
obj = &(*obj)[util::parse<size_t> (key)];
break;
case ::json::tree::STRING:
case ::json::tree::NUMBER:
case ::json::tree::BOOLEAN:
case ::json::tree::NONE:
throw std::invalid_argument ("indexing into wrong type");
}
// step to the beginning of the next component
cursor = next;
}
return *obj;
}

26
json/pointer.hpp Normal file
View File

@ -0,0 +1,26 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "fwd.hpp"
#include <string>
namespace util::json::pointer {
::json::tree::node&
resolve (std::string const &path, ::json::tree::node &root);
}

44
test/json/pointer.py.in Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env python3
import os.path
import tempfile
import subprocess
from glob import glob
if __name__ == '__main__':
json_tests = "@CMAKE_CURRENT_SOURCE_DIR@/test/json/pointer/validate/"
json_pointer = "@CMAKE_CURRENT_BINARY_DIR@/json-pointer"
json_compare = "@CMAKE_CURRENT_BINARY_DIR@/json-compare"
paths = glob(os.path.join(json_tests, "*.pointer"))
paths = (os.path.splitext(p)[0] for p in paths)
names = sorted(p for p in paths)
failures = 0
for n in names:
input = f"{n}.input.json"
truth = f"{n}.truth.json"
with open(f"{n}.pointer") as f: pointer = f.read()
success = False
try:
with tempfile.NamedTemporaryFile(delete=True) as result:
do_query = [json_pointer, pointer.rstrip(), input]
do_compare = [json_compare, result.name, truth]
#print(do_query)
#print(do_compare)
subprocess.check_call(do_query, stdout=result)
subprocess.check_call(do_compare)
success = True
except subprocess.CalledProcessError:
failures += 1
prefix = "not " if not success else ""
print(f"{prefix}ok - {n}")
exit(1 if failures else 0)

View File

@ -0,0 +1 @@
object.json

View File

@ -0,0 +1 @@
#

View File

@ -0,0 +1 @@
object.json

View File

@ -0,0 +1 @@
object.json

View File

@ -0,0 +1 @@
#/

View File

@ -0,0 +1 @@
"empty"

View File

@ -0,0 +1 @@
object.json

View File

@ -0,0 +1 @@
#/string

View File

@ -0,0 +1 @@
"value"

View File

@ -0,0 +1 @@
object.json

View File

@ -0,0 +1 @@
#/array/2

View File

@ -0,0 +1 @@
2

View File

@ -0,0 +1 @@
object.json

View File

@ -0,0 +1 @@
#/object/inner/value/2

View File

@ -0,0 +1 @@
4

View File

@ -0,0 +1,11 @@
{
"": "empty",
"string": "value",
"array": [ 0, 1, 2, 3 ],
"object": {
"boolean": true,
"inner": {
"value": [ 3, 1, 4 ]
}
}
}

35
tools/json-pointer.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "json/tree.hpp"
#include "json/pointer.hpp"
#include <iostream>
enum {
ARG_SELF,
ARG_QUERY,
ARG_INPUT,
NUM_ARGS,
};
int
main (int argc, char **argv)
{
if (argc != NUM_ARGS) {
std::cerr << argv[ARG_SELF] << " <query> <input>\n";
return EXIT_FAILURE;
}
auto const root = json::tree::parse (argv[ARG_INPUT]);
std::string const query = argv[ARG_QUERY];
try {
std::cout << util::json::pointer::resolve (query, *root) << '\n';
} catch (std::exception const &x) {
std::cerr << "error" << x.what () << '\n';
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}