diff --git a/.config/supply-chain/audits.toml b/.config/supply-chain/audits.toml index 42ce71aa..65dca58c 100644 --- a/.config/supply-chain/audits.toml +++ b/.config/supply-chain/audits.toml @@ -6,6 +6,11 @@ who = "Jean Mertz " criteria = "safe-to-deploy" delta = "0.4.4 -> 0.5.2" +[[audits.bzip2]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "0.5.2 -> 0.6.1" + [[audits.bzip2-sys]] who = "Jean Mertz " criteria = "safe-to-deploy" @@ -76,11 +81,21 @@ who = "Jean Mertz " criteria = "safe-to-deploy" version = "0.19.0" +[[audits.libbz2-rs-sys]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "0.1.1 -> 0.2.5" + [[audits.libsqlite3-sys]] who = "Jean Mertz " criteria = "safe-to-deploy" delta = "0.35.0 -> 0.36.0" +[[audits.libz-rs-sys]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "0.5.2 -> 0.5.5" + [[audits.mac]] who = "Jean Mertz " criteria = "safe-to-deploy" @@ -206,6 +221,11 @@ who = "Jean Mertz " criteria = "safe-to-deploy" delta = "0.3.7 -> 0.3.8" +[[audits.simd-adler32]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "0.3.8 -> 0.3.9" + [[audits.siphasher]] who = "Jean Mertz " criteria = "safe-to-deploy" @@ -261,6 +281,11 @@ who = "Jean Mertz " criteria = "safe-to-deploy" delta = "0.4.5 -> 0.5.1" +[[audits.typed-path]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +version = "0.12.3" + [[audits.unarray]] who = "Jean Mertz " criteria = "safe-to-deploy" @@ -281,6 +306,16 @@ who = "Jean Mertz " criteria = "safe-to-deploy" version = "0.36.1" +[[audits.zip]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "2.4.2 -> 8.6.0" + +[[audits.zlib-rs]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "0.6.3 -> 0.5.5" + [[trusted.aho-corasick]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) @@ -611,6 +646,12 @@ user-id = 55123 # rust-lang-owner start = "2024-08-15" end = "2027-02-13" +[[trusted.libz-rs-sys]] +criteria = "safe-to-deploy" +user-id = 1303 +start = "2024-02-23" +end = "2027-05-25" + [[trusted.linkme]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) @@ -1265,6 +1306,12 @@ user-id = 6743 # Ed Page (epage) start = "2023-02-22" end = "2027-02-13" +[[trusted.zlib-rs]] +criteria = "safe-to-deploy" +user-id = 1303 +start = "2024-02-23" +end = "2027-05-25" + [[trusted.zmij]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) diff --git a/.config/supply-chain/imports.lock b/.config/supply-chain/imports.lock index f5f18ebd..5e9cea2b 100644 --- a/.config/supply-chain/imports.lock +++ b/.config/supply-chain/imports.lock @@ -43,13 +43,6 @@ user-id = 6743 user-login = "epage" user-name = "Ed Page" -[[publisher.arbitrary]] -version = "1.4.2" -when = "2025-08-14" -user-id = 696 -user-login = "fitzgen" -user-name = "Nick Fitzgerald" - [[publisher.async-trait]] version = "0.1.89" when = "2025-08-14" @@ -91,13 +84,6 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" -[[publisher.bzip2-sys]] -version = "0.1.11+1.0.8" -when = "2021-06-09" -user-id = 1 -user-login = "alexcrichton" -user-name = "Alex Crichton" - [[publisher.cargo-platform]] version = "0.3.2" when = "2025-12-11" @@ -165,13 +151,6 @@ user-id = 2699 user-login = "matklad" user-name = "Alex Kladov" -[[publisher.derive_arbitrary]] -version = "1.4.2" -when = "2025-08-14" -user-id = 696 -user-login = "fitzgen" -user-name = "Nick Fitzgerald" - [[publisher.dtoa]] version = "1.0.11" when = "2025-12-27" @@ -1160,14 +1139,6 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" -[[audits.bytecode-alliance.wildcard-audits.arbitrary]] -who = "Nick Fitzgerald " -criteria = "safe-to-deploy" -user-id = 696 # Nick Fitzgerald (fitzgen) -start = "2020-01-14" -end = "2026-08-21" -notes = "I am an author of this crate." - [[audits.bytecode-alliance.wildcard-audits.bumpalo]] who = "Nick Fitzgerald " criteria = "safe-to-deploy" @@ -1175,14 +1146,6 @@ user-id = 696 # Nick Fitzgerald (fitzgen) start = "2019-03-16" end = "2026-08-21" -[[audits.bytecode-alliance.wildcard-audits.derive_arbitrary]] -who = "Nick Fitzgerald " -criteria = "safe-to-deploy" -user-id = 696 # Nick Fitzgerald (fitzgen) -start = "2020-01-14" -end = "2026-08-21" -notes = "I am an author of this crate" - [[audits.bytecode-alliance.wildcard-audits.wasip2]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -2611,6 +2574,55 @@ who = "J.C. Jones " criteria = "safe-to-deploy" delta = "1.0.1 -> 1.0.3" +[[audits.isrg.audits.libbz2-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = """ +libbz2-rs-sys mainly uses unsafe around the C FFI boundary, for libc interop, +and for custom allocation support. Most end-user-facing decompression logic +is in safe Rust. I have fuzzed and reviewed its code, and to the best of my +ability I believe it's free of any serious security vulnerabilities. + +libbz2-rs-sys only depends on the libc crate, which is widely used and +maintained by the Rust project. +""" + +[[audits.isrg.audits.libz-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +version = "0.4.0" +notes = """ +This crate uses unsafe since it's for C to Rust FFI. I have reviewed and fuzzed it, and I believe it is free of any serious security problems. + +The only dependency is zlib-rs, which is maintained by the same maintainers as this crate. +""" + +[[audits.isrg.audits.libz-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.4.0 -> 0.4.1" + +[[audits.isrg.audits.libz-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.4.1 -> 0.4.2" + +[[audits.isrg.audits.libz-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.4.2 -> 0.5.0" + +[[audits.isrg.audits.libz-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.5.0 -> 0.5.1" + +[[audits.isrg.audits.libz-rs-sys]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.5.1 -> 0.5.2" + [[audits.isrg.audits.rand]] who = "David Cook " criteria = "safe-to-deploy" @@ -2745,6 +2757,46 @@ who = "Brandon Pitman " criteria = "safe-to-deploy" delta = "1.0.40 -> 1.0.43" +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +version = "0.4.0" +notes = """ +zlib-rs uses unsafe Rust for invoking compiler intrinsics (i.e. SIMD), eschewing bounds checks, along the FFI boundary, and for interacting with pointers sourced from C. I have extensively reviewed and fuzzed the unsafe code. All findings from that work have been resolved as of version 0.4.0. To the best of my ability, I believe it's free of any serious security problems. + +zlib-rs does not require any external dependencies. +""" + +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.4.0 -> 0.4.1" + +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.4.1 -> 0.4.2" + +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.4.2 -> 0.5.0" + +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.5.0 -> 0.5.1" + +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.5.1 -> 0.5.2" + +[[audits.isrg.audits.zlib-rs]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "0.5.2 -> 0.6.3" + [[audits.mozilla.wildcard-audits.encoding_rs]] who = "Henri Sivonen " criteria = "safe-to-deploy" diff --git a/Cargo.lock b/Cargo.lock index f18844d5..8c05e077 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,15 +124,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arraydeque" version = "0.5.1" @@ -328,9 +319,11 @@ dependencies = [ name = "bookworm" version = "0.1.0" dependencies = [ + "camino-tempfile", "chrono", "clap", "convert_case 0.11.0", + "directories", "htmd", "indoc", "jp_tool", @@ -396,21 +389,11 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" -dependencies = [ - "bzip2-sys", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "cc", - "pkg-config", + "libbz2-rs-sys", ] [[package]] @@ -878,17 +861,6 @@ dependencies = [ "chrono", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "derive_builder" version = "0.20.2" @@ -1228,6 +1200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -2572,6 +2545,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + [[package]] name = "libc" version = "0.2.175" @@ -2599,6 +2578,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linkme" version = "0.3.33" @@ -4754,6 +4742,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -5552,20 +5546,24 @@ dependencies = [ [[package]] name = "zip" -version = "2.4.2" +version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "arbitrary", "bzip2", "crc32fast", - "crossbeam-utils", - "displaydoc", + "flate2", "indexmap", "memchr", - "thiserror 2.0.18", + "typed-path", ] +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + [[package]] name = "zmij" version = "1.0.17" diff --git a/Cargo.toml b/Cargo.toml index 2be32280..55048b9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,7 +144,7 @@ unicode-width = { version = "0.2", default-features = false } url = { version = "2", default-features = false } which = { version = "8", default-features = false } windows-sys = { version = "0.61", default-features = false } -zip = { version = "2", default-features = false, features = ["bzip2"] } +zip = { version = "8", default-features = false } [patch.crates-io] openai_responses = { git = "https://github.com/JeanMertz/openai-responses-rs" } # diff --git a/crates/contrib/bookworm/Cargo.toml b/crates/contrib/bookworm/Cargo.toml index 3f073941..6aea8cbc 100644 --- a/crates/contrib/bookworm/Cargo.toml +++ b/crates/contrib/bookworm/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "bookworm" description = "Rust crate documentation MCP server" edition = "2024" license = "MIT" +name = "bookworm" publish = false version = "0.1.0" @@ -12,6 +12,7 @@ jp_tool = { workspace = true } chrono = { workspace = true, features = ["serde"] } clap = { workspace = true, features = ["std", "derive", "help"] } convert_case = { workspace = true } +directories = { workspace = true } htmd = { workspace = true } indoc = { workspace = true } reqwest = { workspace = true, features = ["json", "rustls-tls"] } @@ -27,7 +28,10 @@ tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } url = { workspace = true, features = ["serde"] } -zip = { workspace = true } +zip = { workspace = true, features = ["bzip2", "deflate-flate2-zlib-rs"] } + +[dev-dependencies] +camino-tempfile = { workspace = true } [lints] workspace = true diff --git a/crates/contrib/bookworm/src/dl.rs b/crates/contrib/bookworm/src/dl.rs index dd37d98c..d8444aa8 100644 --- a/crates/contrib/bookworm/src/dl.rs +++ b/crates/contrib/bookworm/src/dl.rs @@ -1,11 +1,12 @@ use std::{ collections::HashSet, - env, fs, + fs, future::Future, io, path::{Path, PathBuf}, }; +use directories::ProjectDirs; use reqwest::header::ETAG; use url::Url; use zip::ZipArchive; @@ -14,6 +15,23 @@ use crate::error::Error; const DOCS_RS: &str = "https://docs.rs"; +/// Default root for cached crate documentation. +/// +/// Resolves to the OS-specific user cache directory +/// (e.g. `~/Library/Caches/bookworm/crates` on macOS, +/// `~/.cache/bookworm/crates` on Linux), so downloaded crate documentation +/// survives reboots and is trivial to locate or wipe. +/// +/// Falls back to the system temp directory if `ProjectDirs` can't resolve +/// a user cache directory (e.g. when `$HOME` is unset). +#[must_use] +pub fn default_crates_root() -> PathBuf { + ProjectDirs::from("", "", "bookworm").map_or_else( + || std::env::temp_dir().join("bookworm/crates"), + |p| p.cache_dir().join("crates"), + ) +} + #[derive(Default)] pub struct Config { pub root: Option, @@ -97,7 +115,7 @@ pub async fn download(config: Config) -> Result { let destination = config .root - .unwrap_or_else(env::temp_dir) + .unwrap_or_else(default_crates_root) .join(format!("{}/{version}/{etag}", config.crate_name)); if destination.is_dir() { @@ -114,7 +132,7 @@ pub async fn download(config: Config) -> Result { .await?; unzip(&bytes, &destination)?; - sanitize(&destination, &config.crate_name)?; + sanitize(&destination)?; rewrite_urls(&destination, &config.client).await?; Ok(destination) @@ -147,17 +165,42 @@ fn unzip(bytes: &[u8], destination: &Path) -> Result<(), Error> { Ok(()) } -fn sanitize(path: &Path, crate_name: &str) -> Result<(), Error> { - // Some generated docsets contain more than the default platform. For now, - // it is OK to only parse the "main" platform and remove all the others +/// Remove auxiliary directories from a freshly-extracted docs.rs archive, +/// keeping only the default platform's docs. +/// +/// docs.rs can ship multi-platform docsets, where each non-default platform +/// lives in a target-triple-named directory (`x86_64-unknown-linux-gnu/`, +/// `wasm32-unknown-unknown/`, …) that re-nests the full rustdoc layout. +/// The downstream indexer can't tell those apart from the real docs, so +/// would produce bogus module paths if they remained. +/// +/// Detection is structural rather than name-based: rustdoc places each +/// crate's HTML docs in a directory that has an `index.html` file directly +/// inside it. Target-triple wrapper directories don't — they only contain +/// nested crate directories. Keeping dirs that look like crate docs dirs +/// handles hyphenated crate names (`ra-ap-rustc_lexer` -> `ra_ap_rustc_lexer/`), +/// custom `[lib] name = "…"` declarations, and any other naming variation, +/// without needing to know the crate's lib name in advance. +/// +/// `src/` and `implementors/` are kept by explicit allow-list — they're part +/// of the rustdoc layout but don't have a top-level `index.html`. +fn sanitize(path: &Path) -> Result<(), Error> { for item in path.read_dir()? { let item = item?; - if item.path().is_dir() - && ![crate_name, "src", "implementors"] - .contains(&item.file_name().to_string_lossy().as_ref()) - { - fs::remove_dir_all(item.path())?; + if !item.path().is_dir() { + continue; + } + + let name = item.file_name(); + if matches!(name.to_string_lossy().as_ref(), "src" | "implementors") { + continue; + } + + if item.path().join("index.html").is_file() { + continue; } + + fs::remove_dir_all(item.path())?; } Ok(()) @@ -265,3 +308,7 @@ where Ok(()) } + +#[cfg(test)] +#[path = "dl_tests.rs"] +mod tests; diff --git a/crates/contrib/bookworm/src/dl_tests.rs b/crates/contrib/bookworm/src/dl_tests.rs new file mode 100644 index 00000000..6e2ad6b0 --- /dev/null +++ b/crates/contrib/bookworm/src/dl_tests.rs @@ -0,0 +1,154 @@ +use std::fs; + +use camino_tempfile::tempdir; + +use super::*; + +/// Build a fake docs.rs extraction layout under `root`. +/// +/// Each `(dir, has_index)` tuple creates `root//` and, when +/// `has_index` is true, also creates `root//index.html` with a +/// placeholder body. Used by the sanitize tests to assert which kinds of +/// directories survive based purely on the presence of `index.html`. +fn populate(root: &Path, entries: &[(&str, bool)]) { + for (dir, has_index) in entries { + let path = root.join(dir); + fs::create_dir_all(&path).expect("create dir"); + if *has_index { + fs::write(path.join("index.html"), b"").expect("write index.html"); + } + } +} + +#[test] +fn keeps_crate_docs_directory_with_index_html() { + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[("serde_json", true)]); + + sanitize(root).expect("sanitize"); + + assert!(root.join("serde_json").is_dir()); + assert!(root.join("serde_json/index.html").is_file()); +} + +#[test] +fn keeps_hyphenated_crate_docs_directory() { + // Regression: `ra-ap-rustc_lexer` has docs under `ra_ap_rustc_lexer/` + // because cargo replaces `-` with `_` in the lib name. The old + // name-based sanitize deleted this directory. + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[("ra_ap_rustc_lexer", true)]); + + sanitize(root).expect("sanitize"); + + assert!( + root.join("ra_ap_rustc_lexer").is_dir(), + "hyphenated-crate docs directory must be preserved" + ); +} + +#[test] +fn keeps_directory_with_custom_lib_name() { + // A crate published as `foo` may declare `[lib] name = "fooz"`, in which + // case rustdoc emits its docs under `fooz/`. The structural detection + // doesn't care about the crates.io name, only about `index.html`. + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[("fooz", true)]); + + sanitize(root).expect("sanitize"); + + assert!(root.join("fooz").is_dir()); +} + +#[test] +fn keeps_src_and_implementors_without_index_html() { + // `src/` and `implementors/` are part of rustdoc's output layout but + // do not have a top-level `index.html`. They are kept by allow-list. + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[("src", false), ("implementors", false)]); + + sanitize(root).expect("sanitize"); + + assert!(root.join("src").is_dir()); + assert!(root.join("implementors").is_dir()); +} + +#[test] +fn removes_target_triple_directory_without_index_html() { + // Multi-platform docsets nest each platform's full layout under a + // target-triple directory. The triple directory itself has no direct + // `index.html` (its child crate directory does), so it is removed. + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[ + ("serde_json", true), + ("x86_64-unknown-linux-gnu", false), + ("x86_64-unknown-linux-gnu/serde_json", true), + ]); + + sanitize(root).expect("sanitize"); + + assert!( + root.join("serde_json").is_dir(), + "default-platform docs kept" + ); + assert!( + !root.join("x86_64-unknown-linux-gnu").exists(), + "target-triple directory removed" + ); +} + +#[test] +fn removes_arbitrary_directory_without_index_html() { + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[("nuisance", false)]); + + sanitize(root).expect("sanitize"); + + assert!(!root.join("nuisance").exists()); +} + +#[test] +fn leaves_top_level_files_alone() { + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + fs::write(root.join("help.html"), b"help").expect("write file"); + fs::write(root.join("settings.html"), b"settings").expect("write file"); + + sanitize(root).expect("sanitize"); + + assert!(root.join("help.html").is_file()); + assert!(root.join("settings.html").is_file()); +} + +#[test] +fn realistic_docs_rs_layout() { + // End-to-end: a directory structure resembling what `unzip` produces + // from a real docs.rs archive for a hyphenated crate. Default-platform + // docs, `src/`, files, and a multi-platform wrapper. + let dir = tempdir().expect("tempdir"); + let root = dir.path().as_std_path(); + populate(root, &[ + ("ra_ap_rustc_lexer", true), + ("src", false), + ("implementors", false), + ("wasm32-unknown-unknown", false), + ("wasm32-unknown-unknown/ra_ap_rustc_lexer", true), + ]); + fs::write(root.join("help.html"), b"help").expect("write file"); + fs::write(root.join("settings.html"), b"settings").expect("write file"); + + sanitize(root).expect("sanitize"); + + assert!(root.join("ra_ap_rustc_lexer/index.html").is_file()); + assert!(root.join("src").is_dir()); + assert!(root.join("implementors").is_dir()); + assert!(!root.join("wasm32-unknown-unknown").exists()); + assert!(root.join("help.html").is_file()); + assert!(root.join("settings.html").is_file()); +} diff --git a/crates/contrib/bookworm/src/main.rs b/crates/contrib/bookworm/src/main.rs index 7e1226ca..41de8f93 100644 --- a/crates/contrib/bookworm/src/main.rs +++ b/crates/contrib/bookworm/src/main.rs @@ -33,7 +33,8 @@ enum Command { #[arg(short, long)] version: Option, - /// Root directory to save the documentation to (defaults to temp dir). + /// Root directory to save the documentation to (defaults to the + /// user cache directory, e.g. `~/Library/Caches/bookworm/crates`). #[arg(short, long)] root: Option, }, diff --git a/crates/contrib/bookworm/src/query/client.rs b/crates/contrib/bookworm/src/query/client.rs index 03ec4366..5d762a4f 100644 --- a/crates/contrib/bookworm/src/query/client.rs +++ b/crates/contrib/bookworm/src/query/client.rs @@ -2,6 +2,8 @@ use std::{path::PathBuf, sync::LazyLock}; use reqwest::header::{self, USER_AGENT}; +use crate::dl; + pub(crate) static GLOBAL_CLIENT: LazyLock = LazyLock::new(Client::default); pub(crate) struct Client { @@ -23,7 +25,7 @@ impl Default for Client { .expect("Client::default()"); Self { - crates_path: std::env::temp_dir().join("bookworm/crates"), + crates_path: dl::default_crates_root(), http_client, } } diff --git a/deny.toml b/deny.toml index 16d24209..669b30ef 100644 --- a/deny.toml +++ b/deny.toml @@ -10,6 +10,7 @@ allow = [ "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", + "bzip2-1.0.6", "CDLA-Permissive-2.0", "ISC", "MIT", diff --git a/justfile b/justfile index 79a9c02e..c1fd300a 100644 --- a/justfile +++ b/justfile @@ -1631,7 +1631,7 @@ plugin-build-local: _install-jp (plugin-build "") # Run all ci tasks. [group('ci')] -ci: lint-ci fmt-ci fmt-comments-ci test-ci docs-ci coverage-ci deny-ci insta-ci shear-ci vet-ci +ci: lint-ci fmt-ci test-ci docs-ci coverage-ci deny-ci insta-ci shear-ci vet-ci # Lint the code on CI. [group('ci')]