ferritin_common/sources/
std.rs1use crate::CrateName;
2use crate::RustdocData;
3use crate::navigator::CrateInfo;
4use crate::sources::CrateProvenance;
5use crate::sources::Source;
6use fieldwork::Fieldwork;
7use rustc_hash::FxHashMap;
8use rustdoc_types::{Crate, FORMAT_VERSION};
9use semver::Version;
10use semver::VersionReq;
11use std::borrow::Cow;
12use std::path::PathBuf;
13use std::process::Command;
14
15const STD_DESCRIPTIONS: [(&str, &str); 6] = [
17 ("std", "The Rust Standard Library"),
18 ("alloc", "The Rust core allocation and collections library"),
19 ("core", "The Rust Core Library"),
20 (
21 "proc_macro",
22 "A support library for macro authors when defining new macros",
23 ),
24 (
25 "test",
26 "Support code for rustc's built in unit-test and micro-benchmarking framework",
27 ),
28 ("std_detect", ""), ];
30
31#[derive(Debug, Clone, Fieldwork)]
33#[field(get)]
34pub struct StdSource {
35 docs_path: PathBuf,
36 rustc_version: Version,
37 crates: FxHashMap<&'static str, CrateInfo>,
38}
39
40impl StdSource {
41 pub fn from_rustup() -> Option<Self> {
43 let sysroot = Command::new("rustup")
44 .args(["run", "nightly", "rustc", "--print", "sysroot"])
45 .output()
46 .ok()?;
47
48 if !sysroot.status.success() {
49 return None;
50 }
51
52 let s = std::str::from_utf8(&sysroot.stdout).ok()?;
53 let docs_path = PathBuf::from(s.trim()).join("share/doc/rust/json/");
54 if !docs_path.exists() {
55 return None;
56 }
57
58 let version = Command::new("rustup")
59 .args(["run", "nightly", "rustc", "--version", "--verbose"])
60 .output()
61 .ok()?;
62
63 if !version.status.success() {
64 return None;
65 }
66
67 let rustc_version = std::str::from_utf8(&version.stdout)
68 .ok()?
69 .lines()
70 .find_map(|line| line.strip_prefix("release: "))?
71 .trim();
72
73 let rustc_version = Version::parse(rustc_version).ok()?;
74
75 let crates = STD_DESCRIPTIONS
76 .into_iter()
77 .map(|(name, description)| {
78 (
79 name,
80 CrateInfo {
81 provenance: CrateProvenance::Std,
82 version: Some(rustc_version.clone()),
83 description: Some(description.to_string()),
84 name: name.to_string(),
85 default_crate: false,
86 used_by: vec![],
87 json_path: (name != "std_detect")
88 .then(|| docs_path.join(format!("{name}.json"))),
89 },
90 )
91 })
92 .collect();
93
94 Some(Self {
95 docs_path,
96 rustc_version,
97 crates,
98 })
99 }
100}
101
102impl Source for StdSource {
103 fn lookup<'a>(&'a self, name: &str, _version_req: &VersionReq) -> Option<Cow<'a, CrateInfo>> {
104 let canonical = self.canonicalize(name)?;
105 self.crates.get(&*canonical).map(Cow::Borrowed)
106 }
107
108 fn load(&self, crate_name: &str, _version: Option<&Version>) -> Option<RustdocData> {
109 let crate_info = self.lookup(crate_name, &VersionReq::STAR)?;
110 let json_path = crate_info.json_path.as_ref()?.to_owned();
111 let content = std::fs::read(&json_path).ok()?;
112
113 let Ok(FORMAT_VERSION) = sonic_rs::get_from_slice(&content, &["format_version"])
114 .ok()?
115 .as_raw_str()
116 .parse()
117 else {
118 return None;
119 };
120
121 let crate_data: Crate = sonic_rs::serde::from_slice(&content).ok()?;
122 Some(RustdocData {
123 crate_data,
124 name: crate_name.to_string(),
125 provenance: CrateProvenance::Std,
126 fs_path: json_path,
127 version: Some(self.rustc_version.clone()),
128 path_to_id: Default::default(),
129 })
130 }
131
132 fn list_available<'a>(&'a self) -> Box<dyn Iterator<Item = &'a CrateInfo> + '_> {
133 Box::new(
134 self.crates
135 .values()
136 .filter(|crate_info| crate_info.json_path.is_some()),
137 )
138 }
139
140 fn canonicalize(&self, input_name: &str) -> Option<CrateName<'static>> {
141 let canonical = match input_name {
142 "std" | "std_crate" => "std",
143 "core" | "core_crate" => "core",
144 "alloc" | "alloc_crate" => "alloc",
145 "proc_macro" | "proc_macro_crate" => "proc_macro",
146 "test" | "test_crate" => "test",
147 "std_detect" => "std_detect", _ => return None,
149 };
150
151 Some(CrateName::from(canonical))
152 }
153}