forc_doc/
search.rs

1use crate::doc::{module::ModuleInfo, Document, Documentation};
2use anyhow::Result;
3use serde::{Deserialize, Serialize};
4use std::{
5    collections::{BTreeMap, HashMap},
6    fs,
7    path::Path,
8};
9
10const JS_SEARCH_FILE_NAME: &str = "search.js";
11
12/// Creates the search index javascript file for the search bar.
13pub fn write_search_index(doc_path: &Path, docs: &Documentation) -> Result<()> {
14    let json_data = docs.to_search_index_json_value()?;
15    let module_export =
16        "\"object\"==typeof exports&&\"undefined\"!=typeof module&&(module.exports=SEARCH_INDEX);";
17    let js_data = format!("var SEARCH_INDEX={json_data};\n{module_export}");
18    Ok(fs::write(doc_path.join(JS_SEARCH_FILE_NAME), js_data)?)
19}
20
21impl Documentation {
22    /// Generates a mapping of program name to a vector of documentable items within the program
23    /// and returns the map as a `serde_json::Value`.
24    fn to_search_index_json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
25        let mut map = HashMap::with_capacity(self.len());
26        let mut modules = BTreeMap::new();
27        for doc in self.iter() {
28            let project_name = doc.module_info.project_name().to_string();
29            map.entry(project_name)
30                .or_insert_with(Vec::new)
31                .push(JsonSearchItem::from(doc));
32            modules.insert(
33                doc.module_info.module_prefixes.join("::"),
34                doc.module_info.clone(),
35            );
36        }
37
38        // Insert the modules themselves into the map.
39        for (_, module) in modules.iter() {
40            let project_name = module.project_name().to_string();
41            map.entry(project_name)
42                .or_insert_with(Vec::new)
43                .push(JsonSearchItem::from(module));
44        }
45
46        serde_json::to_value(map)
47    }
48}
49
50/// Item information used in the `search_pool.json`.
51/// The item name is what the fuzzy search will be
52/// matching on, all other information will be used
53/// in generating links to the item.
54#[derive(Clone, Debug, Serialize, Deserialize)]
55struct JsonSearchItem {
56    name: String,
57    html_filename: String,
58    module_info: Vec<String>,
59    preview: String,
60    type_name: String,
61}
62impl<'a> From<&'a Document> for JsonSearchItem {
63    fn from(value: &'a Document) -> Self {
64        Self {
65            name: value.item_body.item_name.to_string(),
66            html_filename: value.html_filename(),
67            module_info: value.module_info.module_prefixes.clone(),
68            preview: value
69                .preview_opt()
70                .unwrap_or_default()
71                .replace("<br>", "")
72                .replace("<p>", "")
73                .replace("</p>", ""),
74            type_name: value.item_body.ty.friendly_type_name().into(),
75        }
76    }
77}
78
79impl<'a> From<&'a ModuleInfo> for JsonSearchItem {
80    fn from(value: &'a ModuleInfo) -> Self {
81        Self {
82            name: value
83                .module_prefixes
84                .last()
85                .unwrap_or(&String::new())
86                .to_string(),
87            html_filename: "index.html".into(),
88            module_info: value.module_prefixes.clone(),
89            preview: value
90                .preview_opt()
91                .unwrap_or_default()
92                .replace("<br>", "")
93                .replace("<p>", "")
94                .replace("</p>", ""),
95            type_name: "module".into(),
96        }
97    }
98}