json/pointer: add a trivial parser for json-pointer
This commit is contained in:
parent
493c91eace
commit
33dc5c7053
@ -296,6 +296,8 @@ list (
|
|||||||
json/except.hpp
|
json/except.hpp
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/json/flat.cpp
|
${CMAKE_CURRENT_BINARY_DIR}/json/flat.cpp
|
||||||
json/flat.hpp
|
json/flat.hpp
|
||||||
|
json/pointer.cpp
|
||||||
|
json/pointer.hpp
|
||||||
json/schema.cpp
|
json/schema.cpp
|
||||||
json/schema.hpp
|
json/schema.hpp
|
||||||
json/schema/fwd.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)
|
add_executable (util_${tool} tools/${tool}.cpp)
|
||||||
set_target_properties (util_${tool} PROPERTIES OUTPUT_NAME ${tool})
|
set_target_properties (util_${tool} PROPERTIES OUTPUT_NAME ${tool})
|
||||||
target_link_libraries (util_${tool} cruft-util)
|
target_link_libraries (util_${tool} cruft-util)
|
||||||
@ -593,18 +595,16 @@ if (TESTS)
|
|||||||
add_test(NAME util_${name} COMMAND util_${name})
|
add_test(NAME util_${name} COMMAND util_${name})
|
||||||
endforeach(t)
|
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)
|
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)
|
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)
|
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)
|
configure_file (test/cpp.sh.in util_test_cpp.sh @ONLY)
|
||||||
add_test (NAME util_test_cpp COMMAND util_test_cpp.sh)
|
add_test (NAME util_test_cpp COMMAND util_test_cpp.sh)
|
||||||
set_property (TEST util_test_cpp APPEND PROPERTY DEPENDS util_macro)
|
set_property (TEST util_test_cpp APPEND PROPERTY DEPENDS util_macro)
|
||||||
|
60
json/pointer.cpp
Normal file
60
json/pointer.cpp
Normal 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
26
json/pointer.hpp
Normal 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
44
test/json/pointer.py.in
Executable 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)
|
1
test/json/pointer/validate/0000_root_object.input.json
Symbolic link
1
test/json/pointer/validate/0000_root_object.input.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
object.json
|
1
test/json/pointer/validate/0000_root_object.pointer
Normal file
1
test/json/pointer/validate/0000_root_object.pointer
Normal file
@ -0,0 +1 @@
|
|||||||
|
#
|
1
test/json/pointer/validate/0000_root_object.truth.json
Symbolic link
1
test/json/pointer/validate/0000_root_object.truth.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
object.json
|
1
test/json/pointer/validate/0001_emptykey.input.json
Symbolic link
1
test/json/pointer/validate/0001_emptykey.input.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
object.json
|
1
test/json/pointer/validate/0001_emptykey.pointer
Normal file
1
test/json/pointer/validate/0001_emptykey.pointer
Normal file
@ -0,0 +1 @@
|
|||||||
|
#/
|
1
test/json/pointer/validate/0001_emptykey.truth.json
Normal file
1
test/json/pointer/validate/0001_emptykey.truth.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"empty"
|
1
test/json/pointer/validate/0002_key.input.json
Symbolic link
1
test/json/pointer/validate/0002_key.input.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
object.json
|
1
test/json/pointer/validate/0002_key.pointer
Normal file
1
test/json/pointer/validate/0002_key.pointer
Normal file
@ -0,0 +1 @@
|
|||||||
|
#/string
|
1
test/json/pointer/validate/0002_key.truth.json
Normal file
1
test/json/pointer/validate/0002_key.truth.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"value"
|
1
test/json/pointer/validate/0003_key_index.input.json
Symbolic link
1
test/json/pointer/validate/0003_key_index.input.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
object.json
|
1
test/json/pointer/validate/0003_key_index.pointer
Normal file
1
test/json/pointer/validate/0003_key_index.pointer
Normal file
@ -0,0 +1 @@
|
|||||||
|
#/array/2
|
1
test/json/pointer/validate/0003_key_index.truth.json
Normal file
1
test/json/pointer/validate/0003_key_index.truth.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
2
|
1
test/json/pointer/validate/0004_deep_traverse.input.json
Symbolic link
1
test/json/pointer/validate/0004_deep_traverse.input.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
object.json
|
1
test/json/pointer/validate/0004_deep_traverse.pointer
Normal file
1
test/json/pointer/validate/0004_deep_traverse.pointer
Normal file
@ -0,0 +1 @@
|
|||||||
|
#/object/inner/value/2
|
1
test/json/pointer/validate/0004_deep_traverse.truth.json
Normal file
1
test/json/pointer/validate/0004_deep_traverse.truth.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
4
|
11
test/json/pointer/validate/object.json
Normal file
11
test/json/pointer/validate/object.json
Normal 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
35
tools/json-pointer.cpp
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user