diff --git a/Cargo.lock b/Cargo.lock index 7e798d6d26144..474b42eeaaf6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "basic-toml" version = "0.1.10" @@ -1081,6 +1087,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "either" version = "1.15.0" @@ -1931,6 +1943,46 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jaq-core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3dcf2d6c22e94aef9da5823024d0f7b726332c993ee5b337ee06bdf999672a" +dependencies = [ + "dyn-clone", + "once_cell", + "typed-arena", +] + +[[package]] +name = "jaq-json" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848f085cdaa2c0508c87e021392ec5fe3fd7da90d6d548f3c1790e1d499f3080" +dependencies = [ + "foldhash", + "indexmap", + "jaq-core", + "jaq-std", + "serde_json", +] + +[[package]] +name = "jaq-std" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9360182e2b7837fe24ad8da58e21b29c5bff9eb722e68603986535c1e1b39a46" +dependencies = [ + "aho-corasick", + "base64 0.22.1", + "chrono", + "jaq-core", + "libm", + "log", + "regex-lite", + "urlencoding", +] + [[package]] name = "jiff" version = "0.2.15" @@ -1977,14 +2029,15 @@ dependencies = [ [[package]] name = "jsondocck" -version = "0.1.0" +version = "0.0.0" dependencies = [ "fs-err", "getopts", - "jsonpath-rust", + "jaq-core", + "jaq-json", + "jaq-std", "regex", "serde_json", - "shlex", ] [[package]] @@ -2000,19 +2053,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "jsonpath-rust" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b37465feaf9d41f74df7da98c6c1c31ca8ea06d11b5bf7869c8f1ccc51a793f" -dependencies = [ - "pest", - "pest_derive", - "regex", - "serde_json", - "thiserror 2.0.12", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2694,51 +2734,6 @@ dependencies = [ "libc", ] -[[package]] -name = "pest" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" -dependencies = [ - "memchr", - "thiserror 2.0.12", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.103", -] - -[[package]] -name = "pest_meta" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "phf" version = "0.11.3" @@ -4663,7 +4658,7 @@ version = "0.0.0" dependencies = [ "arrayvec", "askama", - "base64", + "base64 0.21.7", "expect-test", "indexmap", "itertools", @@ -5524,6 +5519,12 @@ dependencies = [ "rustc-hash 2.1.1", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.18.0" @@ -5539,12 +5540,6 @@ dependencies = [ "regex-lite", ] -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "ui_test" version = "0.29.2" @@ -5716,6 +5711,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 2b203bb309c63..e3590b7ed031d 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -789,8 +789,7 @@ const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[ "!snapshot", ]; -const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = - &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"]; +const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = &["jq", "arg"]; /// The (partly) broken-down contents of a line containing a test directive, /// which [`iter_header`] passes to its callback function. diff --git a/src/tools/compiletest/src/runtest/rustdoc_json.rs b/src/tools/compiletest/src/runtest/rustdoc_json.rs index 9f88faca89268..3d6baaee377c0 100644 --- a/src/tools/compiletest/src/runtest/rustdoc_json.rs +++ b/src/tools/compiletest/src/runtest/rustdoc_json.rs @@ -13,13 +13,11 @@ impl TestCx<'_> { panic!("failed to remove and recreate output directory `{out_dir}`: {e}") }); - let proc_res = self.document(&out_dir, &self.testpaths); + let proc_res = self.document(&out_dir, self.testpaths); if !proc_res.status.success() { self.fatal_proc_rec("rustdoc failed!", &proc_res); } - let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap()); - json_out.set_extension("json"); let res = self.run_command_to_procres( Command::new(self.config.jsondocck_path.as_ref().unwrap()) .arg("--doc-dir") diff --git a/src/tools/jsondocck/Cargo.toml b/src/tools/jsondocck/Cargo.toml index 80fc26cbe6680..56f61ec124147 100644 --- a/src/tools/jsondocck/Cargo.toml +++ b/src/tools/jsondocck/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "jsondocck" -version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] -jsonpath-rust = "1.0.0" getopts = "0.2" -regex = "1.4" -shlex = "1.0" -serde_json = "1.0" -fs-err = "2.5.0" +regex = "1" +serde_json = "1" +fs-err = "2" +jaq-core = "2" +# FIXME: Depends on `jaq-std` with default features (a ton of unused by `jsondocck` stuff). Consider +# disabling them when it'll be possible. +jaq-json = { version = "1", default-features = false, features = [ + "serde_json", +] } +jaq-std = { version = "2", default-features = false } diff --git a/src/tools/jsondocck/src/cache.rs b/src/tools/jsondocck/src/cache.rs index 1369c8ded0070..d02a25c1be0bd 100644 --- a/src/tools/jsondocck/src/cache.rs +++ b/src/tools/jsondocck/src/cache.rs @@ -1,35 +1,77 @@ +use std::borrow::Cow; use std::collections::HashMap; -use std::path::Path; +use std::iter; +use std::path::{Path, PathBuf}; use fs_err as fs; +use jaq_core::load::{Arena, File, Loader}; +use jaq_core::{Compiler, Ctx, RcIter}; +use jaq_json::Val; use serde_json::Value; use crate::config::Config; #[derive(Debug)] pub struct Cache { - value: Value, - pub variables: HashMap, + value: Val, + global_vars: HashMap, } impl Cache { - /// Create a new cache, used to read files only once and otherwise store their contents. - pub fn new(config: &Config) -> Cache { - let root = Path::new(&config.doc_dir); + /// Create a new cache, used to read a documentation JSON file only once and otherwise store its + /// content. + pub fn new(Config { doc_dir, template }: &Config) -> Self { + let root = Path::new(doc_dir); // `filename` needs to replace `-` with `_` to be sure the JSON path will always be valid. - let filename = - Path::new(&config.template).file_stem().unwrap().to_str().unwrap().replace('-', "_"); - let file_path = root.join(&Path::with_extension(Path::new(&filename), "json")); - let content = fs::read_to_string(&file_path).expect("failed to read JSON file"); + let mut filename: PathBuf = + Path::new(template).file_stem().unwrap().to_str().unwrap().replace('-', "_").into(); + + filename.set_extension("json"); + + let content = fs::read(root.join(filename)).expect("failed to read a JSON file"); Cache { - value: serde_json::from_str::(&content).expect("failed to convert from JSON"), - variables: HashMap::from([("FILE".to_owned(), config.template.clone().into())]), + value: serde_json::from_slice::(&content) + .expect("failed to convert from JSON") + .into(), + // FIXME: Replace this with empty `HashMap` and use `input_filename` instead of `$FILE` + // in tests once https://github.com/01mf02/jaq/issues/144 is fixed. + global_vars: [("$FILE".into(), template.to_owned().into())].into(), } } - // FIXME: Make this failible, so jsonpath syntax error has line number. - pub fn select(&self, path: &str) -> Vec<&Value> { - jsonpath_rust::query::js_path_vals(path, &self.value).unwrap() + pub fn arg(&mut self, name: &str, value: Val) { + self.global_vars.insert(format!("${name}"), value); + } + + pub fn filter(&self, code: &str) -> Result> { + let arena = Arena::default(); + let modules = Loader::new( + // `tonumber` depends on `fromjson` that requires adding the extra "hifijson" + // dependency. + jaq_std::defs().chain(jaq_json::defs().filter(|def| !matches!(def.name, "tonumber"))), + ) + .load(&arena, File { code, path: () }) + .map_err(|e| format!("failed to parse the given filter: {:?}", e.first().unwrap().1))?; + let filter = Compiler::default() + .with_funs(jaq_std::funs().chain(jaq_json::base_funs())) + .with_global_vars(self.global_vars.keys().map(String::as_ref)) + .compile(modules) + .map_err(|e| { + format!("failed to compile the given filter: {:?}", e.first().unwrap().1) + })?; + let inputs = RcIter::new(iter::empty()); + let mut outputs = + filter.run((Ctx::new(self.global_vars.values().cloned(), &inputs), self.value.clone())); + let output = outputs + .next() + .ok_or::>("directive returned nothing".into())? + .map_err(|e| format!("failed to execute the given filter: {e}"))?; + + if outputs.next().is_some() { + return Err("expected a single output, received multiple".into()); + } + + Ok(output) } } diff --git a/src/tools/jsondocck/src/config.rs b/src/tools/jsondocck/src/config.rs index 6bef37c225973..e11f5141c789f 100644 --- a/src/tools/jsondocck/src/config.rs +++ b/src/tools/jsondocck/src/config.rs @@ -1,37 +1,44 @@ +use std::cell::LazyCell; +use std::env::Args; + use getopts::Options; #[derive(Debug)] pub struct Config { - /// The directory documentation output was generated in + /// The directory documentation output was generated in. pub doc_dir: String, - /// The file documentation was generated for, with docck directives to check + /// The file documentation was generated for, with `jsondocck` directives to check. pub template: String, } -/// Create a Config from a vector of command-line arguments -pub fn parse_config(args: Vec) -> Config { - let mut opts = Options::new(); - opts.reqopt("", "doc-dir", "Path to the documentation directory", "PATH") - .reqopt("", "template", "Path to the template file", "PATH") - .optflag("h", "help", "show this message"); - - let (argv0, args_) = args.split_first().unwrap(); - if args.len() == 1 { - let message = format!("Usage: {}