Skip to content

Commit 08d214b

Browse files
committed
rewrite jsondocck from JSONPath to jq
i tried to keep the original code structure, but it's an almost entire rewrite. thankfully, there are more deletions than insertions, i expect jq to be flexible enough to supersede almost the entire previous set of directives. no tests yet, but it seems to be working. jaq's guts are a bit arcane to me, but it seems to be only related to its setup, a resulted API turned out to be pretty straightforward
1 parent 8a8df06 commit 08d214b

File tree

6 files changed

+289
-297
lines changed

6 files changed

+289
-297
lines changed

Cargo.lock

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ version = "0.21.7"
246246
source = "registry+https://github.com/rust-lang/crates.io-index"
247247
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
248248

249+
[[package]]
250+
name = "base64"
251+
version = "0.22.1"
252+
source = "registry+https://github.com/rust-lang/crates.io-index"
253+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
254+
249255
[[package]]
250256
name = "basic-toml"
251257
version = "0.1.10"
@@ -1107,6 +1113,12 @@ version = "1.0.10"
11071113
source = "registry+https://github.com/rust-lang/crates.io-index"
11081114
checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921"
11091115

1116+
[[package]]
1117+
name = "dyn-clone"
1118+
version = "1.0.19"
1119+
source = "registry+https://github.com/rust-lang/crates.io-index"
1120+
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
1121+
11101122
[[package]]
11111123
name = "either"
11121124
version = "1.15.0"
@@ -1306,6 +1318,15 @@ dependencies = [
13061318
"autocfg",
13071319
]
13081320

1321+
[[package]]
1322+
name = "fs-err"
1323+
version = "3.1.1"
1324+
source = "registry+https://github.com/rust-lang/crates.io-index"
1325+
checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683"
1326+
dependencies = [
1327+
"autocfg",
1328+
]
1329+
13091330
[[package]]
13101331
name = "fs_extra"
13111332
version = "1.3.0"
@@ -1998,6 +2019,46 @@ version = "1.0.15"
19982019
source = "registry+https://github.com/rust-lang/crates.io-index"
19992020
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
20002021

2022+
[[package]]
2023+
name = "jaq-core"
2024+
version = "2.2.1"
2025+
source = "registry+https://github.com/rust-lang/crates.io-index"
2026+
checksum = "77526a72eb79412c29fd141767a6549bbfcb1cb40e00556fe16532d5e878e098"
2027+
dependencies = [
2028+
"dyn-clone",
2029+
"once_cell",
2030+
"typed-arena",
2031+
]
2032+
2033+
[[package]]
2034+
name = "jaq-json"
2035+
version = "1.1.3"
2036+
source = "registry+https://github.com/rust-lang/crates.io-index"
2037+
checksum = "01dbdbd07b076e8403abac68ce7744d93e2ecd953bbc44bf77bf00e1e81172bc"
2038+
dependencies = [
2039+
"foldhash",
2040+
"indexmap",
2041+
"jaq-core",
2042+
"jaq-std",
2043+
"serde_json",
2044+
]
2045+
2046+
[[package]]
2047+
name = "jaq-std"
2048+
version = "2.1.2"
2049+
source = "registry+https://github.com/rust-lang/crates.io-index"
2050+
checksum = "2c264fe397c981705976c71f1bfe020382b9eda52ae950e57fe885e147bdd67d"
2051+
dependencies = [
2052+
"aho-corasick",
2053+
"base64 0.22.1",
2054+
"chrono",
2055+
"jaq-core",
2056+
"libm",
2057+
"log",
2058+
"regex-lite",
2059+
"urlencoding",
2060+
]
2061+
20012062
[[package]]
20022063
name = "jiff"
20032064
version = "0.2.15"
@@ -2046,12 +2107,15 @@ dependencies = [
20462107
name = "jsondocck"
20472108
version = "0.0.0"
20482109
dependencies = [
2049-
"fs-err",
2110+
"foldhash",
2111+
"fs-err 3.1.1",
20502112
"getopts",
2051-
"jsonpath-rust",
2113+
"indexmap",
2114+
"jaq-core",
2115+
"jaq-json",
2116+
"jaq-std",
20522117
"regex",
20532118
"serde_json",
2054-
"shlex",
20552119
]
20562120

20572121
[[package]]
@@ -2060,26 +2124,13 @@ version = "0.1.0"
20602124
dependencies = [
20612125
"anyhow",
20622126
"clap",
2063-
"fs-err",
2127+
"fs-err 2.11.0",
20642128
"rustc-hash 2.1.1",
20652129
"rustdoc-json-types",
20662130
"serde",
20672131
"serde_json",
20682132
]
20692133

2070-
[[package]]
2071-
name = "jsonpath-rust"
2072-
version = "1.0.3"
2073-
source = "registry+https://github.com/rust-lang/crates.io-index"
2074-
checksum = "7d057f8fd19e20c3f14d3663983397155739b6bc1148dc5cd4c4a1a5b3130eb0"
2075-
dependencies = [
2076-
"pest",
2077-
"pest_derive",
2078-
"regex",
2079-
"serde_json",
2080-
"thiserror 2.0.12",
2081-
]
2082-
20832134
[[package]]
20842135
name = "lazy_static"
20852136
version = "1.5.0"
@@ -2780,50 +2831,6 @@ dependencies = [
27802831
"libc",
27812832
]
27822833

2783-
[[package]]
2784-
name = "pest"
2785-
version = "2.8.1"
2786-
source = "registry+https://github.com/rust-lang/crates.io-index"
2787-
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
2788-
dependencies = [
2789-
"memchr",
2790-
"thiserror 2.0.12",
2791-
"ucd-trie",
2792-
]
2793-
2794-
[[package]]
2795-
name = "pest_derive"
2796-
version = "2.8.1"
2797-
source = "registry+https://github.com/rust-lang/crates.io-index"
2798-
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
2799-
dependencies = [
2800-
"pest",
2801-
"pest_generator",
2802-
]
2803-
2804-
[[package]]
2805-
name = "pest_generator"
2806-
version = "2.8.1"
2807-
source = "registry+https://github.com/rust-lang/crates.io-index"
2808-
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
2809-
dependencies = [
2810-
"pest",
2811-
"pest_meta",
2812-
"proc-macro2",
2813-
"quote",
2814-
"syn 2.0.104",
2815-
]
2816-
2817-
[[package]]
2818-
name = "pest_meta"
2819-
version = "2.8.1"
2820-
source = "registry+https://github.com/rust-lang/crates.io-index"
2821-
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
2822-
dependencies = [
2823-
"pest",
2824-
"sha2",
2825-
]
2826-
28272834
[[package]]
28282835
name = "phf"
28292836
version = "0.11.3"
@@ -4753,7 +4760,7 @@ version = "0.0.0"
47534760
dependencies = [
47544761
"arrayvec",
47554762
"askama",
4756-
"base64",
4763+
"base64 0.21.7",
47574764
"expect-test",
47584765
"indexmap",
47594766
"itertools",
@@ -5682,6 +5689,12 @@ dependencies = [
56825689
"rustc-hash 2.1.1",
56835690
]
56845691

5692+
[[package]]
5693+
name = "typed-arena"
5694+
version = "2.0.2"
5695+
source = "registry+https://github.com/rust-lang/crates.io-index"
5696+
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
5697+
56855698
[[package]]
56865699
name = "typenum"
56875700
version = "1.18.0"
@@ -5697,12 +5710,6 @@ dependencies = [
56975710
"regex-lite",
56985711
]
56995712

5700-
[[package]]
5701-
name = "ucd-trie"
5702-
version = "0.1.7"
5703-
source = "registry+https://github.com/rust-lang/crates.io-index"
5704-
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
5705-
57065713
[[package]]
57075714
name = "ui_test"
57085715
version = "0.29.2"
@@ -5874,6 +5881,12 @@ dependencies = [
58745881
"percent-encoding",
58755882
]
58765883

5884+
[[package]]
5885+
name = "urlencoding"
5886+
version = "2.1.3"
5887+
source = "registry+https://github.com/rust-lang/crates.io-index"
5888+
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
5889+
58775890
[[package]]
58785891
name = "utf-8"
58795892
version = "0.7.6"

src/tools/compiletest/src/directives.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,8 +790,7 @@ const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[
790790
"!snapshot",
791791
];
792792

793-
const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
794-
&["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"];
793+
const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = &["jq", "arg", "eq", "ne"];
795794

796795
/// The (partly) broken-down contents of a line containing a test directive,
797796
/// which [`iter_directives`] passes to its callback function.

src/tools/jsondocck/Cargo.toml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ name = "jsondocck"
33
edition = "2024"
44

55
[dependencies]
6-
jsonpath-rust = "1.0.0"
76
getopts = "0.2"
8-
regex = "1.4"
9-
shlex = "1.0"
10-
serde_json = "1.0"
11-
fs-err = "2.5.0"
7+
regex = "1"
8+
serde_json = "1"
9+
fs-err = "3"
10+
jaq-core = "2"
11+
# FIXME: Depends on `jaq-std` with default features (a ton of unused by `jsondocck` stuff). Consider
12+
# disabling them when it'll be possible.
13+
jaq-json = { version = "1", default-features = false, features = [
14+
"serde_json",
15+
] }
16+
jaq-std = { version = "2", default-features = false }
17+
indexmap = "2"
18+
foldhash = "0.1"

src/tools/jsondocck/src/cache.rs

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,115 @@
1-
use std::collections::HashMap;
2-
use std::path::Path;
1+
use std::borrow::Cow;
2+
use std::iter::{self, Empty};
3+
use std::path::{Path, PathBuf};
34

5+
use foldhash::fast::RandomState;
46
use fs_err as fs;
7+
use indexmap::IndexMap as InnerIndexMap;
8+
use jaq_core::load::{Arena, File, Loader};
9+
use jaq_core::{Compiler, Ctx, Filter as InnerFilter, Native, RcIter};
10+
use jaq_json::Val;
511
use serde_json::Value;
612

713
use crate::config::Config;
814

15+
type IndexMap<K, V> = InnerIndexMap<K, V, RandomState>;
16+
917
#[derive(Debug)]
1018
pub struct Cache {
11-
value: Value,
12-
pub variables: HashMap<String, Value>,
19+
value: Val,
20+
global_vars: IndexMap<String, Val>,
1321
}
1422

1523
impl Cache {
16-
/// Create a new cache, used to read files only once and otherwise store their contents.
17-
pub fn new(config: &Config) -> Cache {
18-
let root = Path::new(&config.doc_dir);
24+
/// Create a new cache, used to read a documentation JSON file only once and otherwise store its
25+
/// content.
26+
pub fn new(Config { doc_dir, template }: &Config) -> Self {
27+
let root = Path::new(doc_dir);
1928
// `filename` needs to replace `-` with `_` to be sure the JSON path will always be valid.
20-
let filename =
21-
Path::new(&config.template).file_stem().unwrap().to_str().unwrap().replace('-', "_");
22-
let file_path = root.join(&Path::with_extension(Path::new(&filename), "json"));
23-
let content = fs::read_to_string(&file_path).expect("failed to read JSON file");
29+
let mut filename: PathBuf =
30+
Path::new(template).file_stem().unwrap().to_str().unwrap().replace('-', "_").into();
31+
32+
filename.set_extension("json");
33+
34+
let content = fs::read(root.join(filename)).expect("failed to read a JSON file");
2435

2536
Cache {
26-
value: serde_json::from_str::<Value>(&content).expect("failed to convert from JSON"),
27-
variables: HashMap::from([("FILE".to_owned(), config.template.clone().into())]),
37+
value: serde_json::from_slice::<Value>(&content)
38+
.expect("failed to convert from JSON")
39+
.into(),
40+
// FIXME: Replace this with empty `HashMap` and use `input_filename` instead of `$FILE`
41+
// in tests once https://github.com/01mf02/jaq/issues/144 is fixed.
42+
global_vars: [("$FILE".into(), template.to_owned().into())].into_iter().collect(),
2843
}
2944
}
3045

31-
// FIXME: Make this failible, so jsonpath syntax error has line number.
32-
pub fn select(&self, path: &str) -> Vec<&Value> {
33-
jsonpath_rust::query::js_path_vals(path, &self.value).unwrap()
46+
pub fn arg(&mut self, name: &str, value: Val) {
47+
self.global_vars.insert(format!("${name}"), value);
48+
}
49+
50+
pub fn filter(&self, code: &str) -> Result<Filter, Cow<'static, str>> {
51+
let arena = Arena::default();
52+
let modules = Loader::new(
53+
// `tonumber` depends on `fromjson` that requires adding the extra "hifijson"
54+
// dependency.
55+
jaq_std::defs().chain(jaq_json::defs().filter(|def| !matches!(def.name, "tonumber"))),
56+
)
57+
.load(&arena, File { code, path: () })
58+
.map_err(|e| format!("failed to parse the given filter: {e:?}"))?;
59+
60+
Ok(Filter {
61+
inner: Compiler::default()
62+
.with_funs(jaq_std::funs().chain(jaq_json::base_funs()))
63+
.with_global_vars(self.global_vars.keys().map(String::as_ref))
64+
.compile(modules)
65+
.map_err(|e| format!("failed to compile the given filter: {e:?}"))?,
66+
inputs: RcIter::new(iter::empty()),
67+
})
68+
}
69+
}
70+
71+
pub struct Filter {
72+
inner: InnerFilter<Native<Val>>,
73+
inputs: RcIter<Empty<Result<Val, String>>>,
74+
}
75+
76+
impl Filter {
77+
pub fn run(&self, cache: &Cache) -> Values<impl Iterator<Item = Result<Val, String>> + '_> {
78+
Values {
79+
inner: self
80+
.inner
81+
.run((
82+
Ctx::new(cache.global_vars.values().cloned(), &self.inputs),
83+
cache.value.clone(),
84+
))
85+
.map(|r| r.map_err(|e| format!("failed to execute the given filter: {e}"))),
86+
counter: 0,
87+
}
88+
}
89+
}
90+
91+
pub struct Values<I> {
92+
inner: I,
93+
counter: usize,
94+
}
95+
96+
impl<I: Iterator<Item = Result<Val, String>>> Values<I> {
97+
pub fn is_empty(&mut self) -> Result<(), String> {
98+
if self.inner.next().is_none() {
99+
Ok(())
100+
} else {
101+
Err(format!(
102+
"expected {expected} value(s), received more than {expected}",
103+
expected = self.counter
104+
))
105+
}
106+
}
107+
108+
pub fn next(&mut self) -> Result<Val, String> {
109+
self.counter += 1;
110+
111+
self.inner
112+
.next()
113+
.ok_or_else(|| format!("{} value was expected, received nothing", self.counter))?
34114
}
35115
}

0 commit comments

Comments
 (0)