Support new search index format (Rust 1.52)

This path adds support for the new search index format introduced in
Rust 1.52.0.  With this new format, the index items are no longer
serialized as an array of structs but as arrays of the struct fields.
This commit is contained in:
Robin Krahl 2021-06-06 13:00:50 +02:00
parent 37ff920d30
commit 48405481d0
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
6 changed files with 173 additions and 55 deletions

View File

@ -1,5 +1,5 @@
<!--- <!---
SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org> SPDX-FileCopyrightText: 2020-2021 Robin Krahl <robin.krahl@ireas.org>
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--> -->
@ -9,6 +9,7 @@ SPDX-License-Identifier: MIT
- Add `o` command for opening a documentation item to the tui viewer. - Add `o` command for opening a documentation item to the tui viewer.
- Add tests for Rust 1.48.0, 1.49.0, 1.50.0, 1.51.0, 1.52.0 and 1.52.1. - Add tests for Rust 1.48.0, 1.49.0, 1.50.0, 1.51.0, 1.52.0 and 1.52.1.
- Add support for the new search index format introduced in Rust 1.52.0.
## v0.4.1 (2020-10-11) ## v0.4.1 (2020-10-11)

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org> // SPDX-FileCopyrightText: 2020-2021 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! Search index for a documentation source. //! Search index for a documentation source.
@ -6,14 +6,17 @@
//! The search index is read from the `search-index.js` file generated by rustdoc. It contains a //! The search index is read from the `search-index.js` file generated by rustdoc. It contains a
//! list of items grouped by their crate. //! list of items grouped by their crate.
//! //!
//! For details on the format of the search index, see the `html/render.rs` file in `librustdoc`. //! For details on the format of the search index, see the `html/render/mod.rs` (previously
//! Note that the format of the search index changed in April 2020 (Rust 1.44.0) with commit //! `html/render.rs`) file in `librustdoc`. Note that the format of the search index changed in
//! b4fb3069ce82f61f84a9487d17fb96389d55126a. We only support the new format as the old format is //! April 2020 (Rust 1.44.0) with commit b4fb3069ce82f61f84a9487d17fb96389d55126a. We only support
//! much harder to parse. //! the new format as the old format is much harder to parse.
//! //!
//! For details on the generation of the search index, see the `html/render/cache.rs` file in //! For details on the generation of the search index, see the `html/render/cache.rs` file in
//! `librustdoc`. //! `librustdoc`.
mod v1_44;
mod v1_52;
use std::collections; use std::collections;
use std::fmt; use std::fmt;
use std::fs; use std::fs;
@ -57,18 +60,37 @@ struct Data {
crates: collections::HashMap<String, CrateData>, crates: collections::HashMap<String, CrateData>,
} }
#[derive(Debug, Default, PartialEq, serde::Deserialize)] #[derive(Debug, Default, PartialEq)]
struct CrateData { struct CrateData {
#[serde(rename = "i")]
items: Vec<ItemData>, items: Vec<ItemData>,
#[serde(rename = "p")]
paths: Vec<(usize, String)>, paths: Vec<(usize, String)>,
} }
impl<'de> serde::Deserialize<'de> for CrateData {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
CrateDataVersions::deserialize(deserializer).map(From::from)
}
}
#[derive(Debug, PartialEq, serde::Deserialize)]
#[serde(untagged)]
enum CrateDataVersions {
V1_44(v1_44::CrateData),
V1_52(v1_52::CrateData),
}
impl From<CrateDataVersions> for CrateData {
fn from(versions: CrateDataVersions) -> Self {
match versions {
CrateDataVersions::V1_44(data) => data.into(),
CrateDataVersions::V1_52(data) => data.into(),
}
}
}
#[derive(Debug, PartialEq, serde_tuple::Deserialize_tuple)] #[derive(Debug, PartialEq, serde_tuple::Deserialize_tuple)]
struct ItemData { struct ItemData {
#[serde(deserialize_with = "deserialize_item_type")] ty: ItemType,
ty: doc::ItemType,
name: String, name: String,
path: String, path: String,
desc: String, desc: String,
@ -76,41 +98,56 @@ struct ItemData {
_ignored: serde_json::Value, _ignored: serde_json::Value,
} }
fn deserialize_item_type<'de, D>(d: D) -> Result<doc::ItemType, D::Error> #[derive(Clone, Copy, Debug, PartialEq)]
where struct ItemType(doc::ItemType);
D: serde::de::Deserializer<'de>,
{
use doc::ItemType;
use serde::de::{Deserialize, Error};
match u8::deserialize(d)? { impl From<doc::ItemType> for ItemType {
0 => Ok(ItemType::Module), fn from(ty: doc::ItemType) -> Self {
1 => Ok(ItemType::ExternCrate), Self(ty)
2 => Ok(ItemType::Import), }
3 => Ok(ItemType::Struct), }
4 => Ok(ItemType::Enum),
5 => Ok(ItemType::Function), impl From<ItemType> for doc::ItemType {
6 => Ok(ItemType::Typedef), fn from(ty: ItemType) -> Self {
7 => Ok(ItemType::Static), ty.0
8 => Ok(ItemType::Trait), }
9 => Ok(ItemType::Impl), }
10 => Ok(ItemType::TyMethod),
11 => Ok(ItemType::Method), impl<'de> serde::Deserialize<'de> for ItemType {
12 => Ok(ItemType::StructField), fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
13 => Ok(ItemType::Variant), use doc::ItemType;
14 => Ok(ItemType::Macro), use serde::de::Error;
15 => Ok(ItemType::Primitive),
16 => Ok(ItemType::AssocType), match u8::deserialize(deserializer)? {
17 => Ok(ItemType::Constant), 0 => Ok(ItemType::Module),
18 => Ok(ItemType::AssocConst), 1 => Ok(ItemType::ExternCrate),
19 => Ok(ItemType::Union), 2 => Ok(ItemType::Import),
20 => Ok(ItemType::ForeignType), 3 => Ok(ItemType::Struct),
21 => Ok(ItemType::Keyword), 4 => Ok(ItemType::Enum),
22 => Ok(ItemType::OpaqueTy), 5 => Ok(ItemType::Function),
23 => Ok(ItemType::ProcAttribute), 6 => Ok(ItemType::Typedef),
24 => Ok(ItemType::ProcDerive), 7 => Ok(ItemType::Static),
25 => Ok(ItemType::TraitAlias), 8 => Ok(ItemType::Trait),
_ => Err(D::Error::custom("Unexpected item type")), 9 => Ok(ItemType::Impl),
10 => Ok(ItemType::TyMethod),
11 => Ok(ItemType::Method),
12 => Ok(ItemType::StructField),
13 => Ok(ItemType::Variant),
14 => Ok(ItemType::Macro),
15 => Ok(ItemType::Primitive),
16 => Ok(ItemType::AssocType),
17 => Ok(ItemType::Constant),
18 => Ok(ItemType::AssocConst),
19 => Ok(ItemType::Union),
20 => Ok(ItemType::ForeignType),
21 => Ok(ItemType::Keyword),
22 => Ok(ItemType::OpaqueTy),
23 => Ok(ItemType::ProcAttribute),
24 => Ok(ItemType::ProcDerive),
25 => Ok(ItemType::TraitAlias),
_ => Err(D::Error::custom("Unexpected item type")),
}
.map(Self)
} }
} }
@ -185,7 +222,8 @@ impl Index {
&item.path &item.path
}; };
if item.ty == doc::ItemType::AssocType { let ty = doc::ItemType::from(item.ty);
if ty == doc::ItemType::AssocType {
continue; continue;
} }
@ -201,7 +239,7 @@ impl Index {
log::info!("Found index match '{}'", full_name); log::info!("Found index match '{}'", full_name);
matches.push(IndexItem { matches.push(IndexItem {
name: full_name, name: full_name,
ty: item.ty, ty,
description: item.desc.clone(), description: item.desc.clone(),
}); });
} }
@ -241,7 +279,7 @@ mod tests {
let mut expected: Data = Default::default(); let mut expected: Data = Default::default();
let mut krate: CrateData = Default::default(); let mut krate: CrateData = Default::default();
krate.items.push(ItemData { krate.items.push(ItemData {
ty: ItemType::Module, ty: ItemType::Module.into(),
name: "name".to_owned(), name: "name".to_owned(),
path: "path".to_owned(), path: "path".to_owned(),
desc: "desc".to_owned(), desc: "desc".to_owned(),
@ -277,7 +315,7 @@ mod tests {
assert_eq!(empty, index.find(&"NodeDataReff".to_owned().into())); assert_eq!(empty, index.find(&"NodeDataReff".to_owned().into()));
}); });
with_rustdoc(">=1.50.0,<1.52.0", |_, path| { with_rustdoc(">=1.50.0", |_, path| {
let index = Index::load(path.join("search-index.js")).unwrap().unwrap(); let index = Index::load(path.join("search-index.js")).unwrap().unwrap();
let empty: Vec<IndexItem> = Vec::new(); let empty: Vec<IndexItem> = Vec::new();
@ -296,7 +334,7 @@ mod tests {
assert_eq!(empty, index.find(&"NodeDataReff".to_owned().into())); assert_eq!(empty, index.find(&"NodeDataReff".to_owned().into()));
}); });
with_rustdoc(">=1.44.0,<1.52.0", |_, path| { with_rustdoc(">=1.44.0", |_, path| {
let index = Index::load(path.join("search-index.js")).unwrap().unwrap(); let index = Index::load(path.join("search-index.js")).unwrap().unwrap();
let empty: Vec<IndexItem> = Vec::new(); let empty: Vec<IndexItem> = Vec::new();

26
src/index/v1_44.rs Normal file
View File

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2021 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
//! Search index format as of Rust 1.44.0.
//!
//! This module contains data structures specific to the search index format introduced with Rust
//! 1.44.0 (commit [b4fb3069ce82f61f84a9487d17fb96389d55126a][]).
//!
//! [b4fb3069ce82f61f84a9487d17fb96389d55126a]: https://github.com/rust-lang/rust/commit/b4fb3069ce82f61f84a9487d17fb96389d55126a
#[derive(Debug, Default, PartialEq, serde::Deserialize)]
pub struct CrateData {
#[serde(rename = "i")]
items: Vec<super::ItemData>,
#[serde(rename = "p")]
paths: Vec<(usize, String)>,
}
impl From<CrateData> for super::CrateData {
fn from(data: CrateData) -> Self {
Self {
items: data.items,
paths: data.paths,
}
}
}

53
src/index/v1_52.rs Normal file
View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2021 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
//! Search index format as of Rust 1.52.0.
//!
//! This module contains data structures specific to the search index format introduced with Rust
//! 1.52.0 (commit [3934dd1b3e7514959202de6ca0d2636bcae21830][]).
//!
//! [3934dd1b3e7514959202de6ca0d2636bcae21830]: https://github.com/rust-lang/rust/commit/3934dd1b3e7514959202de6ca0d2636bcae21830
#[derive(Debug, Default, PartialEq, serde::Deserialize)]
pub struct CrateData {
#[serde(rename = "t")]
item_types: Vec<super::ItemType>,
#[serde(rename = "n")]
item_names: Vec<String>,
#[serde(rename = "q")]
item_paths: Vec<String>,
#[serde(rename = "d")]
item_descs: Vec<String>,
#[serde(rename = "i")]
item_parents: Vec<usize>,
#[serde(rename = "p")]
paths: Vec<(usize, String)>,
}
impl From<CrateData> for super::CrateData {
fn from(data: CrateData) -> Self {
let items = data
.item_types
.into_iter()
.zip(data.item_names.into_iter())
.zip(data.item_paths.into_iter())
.zip(data.item_descs.into_iter())
.zip(data.item_parents.into_iter())
.map(|((((ty, name), path), desc), parent)| super::ItemData {
ty,
name,
path,
desc,
parent: match parent {
0 => None,
parent => Some(parent - 1),
},
_ignored: Default::default(),
})
.collect();
Self {
items,
paths: data.paths,
}
}
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org> // SPDX-FileCopyrightText: 2020-2021 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! rusty-man is a command-line viewer for documentation generated by `rustdoc`. //! rusty-man is a command-line viewer for documentation generated by `rustdoc`.
@ -27,9 +27,9 @@
//! For details on the structure of the HTML files and the search index, you have to look at the //! For details on the structure of the HTML files and the search index, you have to look at the
//! `html::render` module in the `librustdoc` source code. //! `html::render` module in the `librustdoc` source code.
//! //!
//! Note that the format of the search index changed in a recent Rust version (> 1.40 and <= 1.44). //! Note that the format of the search index changed in Rust 1.44. We dont support the old index
//! We dont support the old index format. As the format of the HTML files is not specified, //! format. As the format of the HTML files is not specified, rusty-man might not work with new
//! rusty-man might not work with new Rust versions that change the documentation format. //! Rust versions that change the documentation format.
// We have to disable some clippy lints as our MSRV is 1.40: // We have to disable some clippy lints as our MSRV is 1.40:
#![allow( #![allow(

View File

@ -224,7 +224,7 @@ fn create_cursive(
cursive.add_global_callback('o', open_doc_dialog); cursive.add_global_callback('o', open_doc_dialog);
let mut theme = theme::Theme { let mut theme = theme::Theme {
shadow: false, shadow: false,
..Default::default() ..Default::default()
}; };
theme.palette[theme::PaletteColor::Background] = theme::Color::TerminalDefault; theme.palette[theme::PaletteColor::Background] = theme::Color::TerminalDefault;