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
|
||||
${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
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…
Reference in New Issue
Block a user