1use crate::{
3 doc::{
4 module::{ModuleInfo, ModulePrefixes},
5 Document, Documentation,
6 },
7 render::{
8 index::{AllDocIndex, ModuleIndex},
9 link::{DocLink, DocLinks},
10 title::BlockTitle,
11 util::format::docstring::DocStrings,
12 },
13 RenderPlan,
14};
15use anyhow::Result;
16use horrorshow::{box_html, helper::doctype, html, prelude::*};
17use rayon::prelude::*;
18use std::{
19 collections::BTreeMap,
20 ops::{Deref, DerefMut},
21};
22use sway_core::{language::ty::TyProgramKind, transform::Attributes};
23use sway_types::BaseIdent;
24
25pub mod index;
26pub mod item;
27pub mod link;
28mod search;
29mod sidebar;
30mod title;
31pub mod util;
32
33pub const ALL_DOC_FILENAME: &str = "all.html";
34pub const INDEX_FILENAME: &str = "index.html";
35pub const IDENTITY: &str = "#";
36
37type DocLinkMap = BTreeMap<BlockTitle, Vec<DocLink>>;
38type ModuleMap = BTreeMap<ModulePrefixes, DocLinkMap>;
39type RenderResult = (RenderedDocument, ModuleMap, DocLinks);
40
41pub(crate) trait Renderable {
43 fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>>;
44}
45impl Renderable for Document {
46 fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
47 let header = self.item_header.render(render_plan.clone())?;
48 let body = self.item_body.render(render_plan)?;
49 Ok(box_html! {
50 : header;
51 : body;
52 })
53 }
54}
55
56#[derive(Debug)]
58pub struct RenderedDocument {
59 pub module_info: ModuleInfo,
60 pub html_filename: String,
61 pub file_contents: HTMLString,
62}
63impl RenderedDocument {
64 fn from_doc(doc: &Document, render_plan: RenderPlan) -> Result<Self> {
65 Ok(Self {
66 module_info: doc.module_info.clone(),
67 html_filename: doc.html_filename(),
68 file_contents: HTMLString::from_rendered_content(doc.clone().render(render_plan)?)?,
69 })
70 }
71}
72
73#[derive(Default)]
74pub struct RenderedDocumentation(pub Vec<RenderedDocument>);
75
76impl RenderedDocumentation {
77 pub fn from_raw_docs(
79 raw_docs: Documentation,
80 render_plan: RenderPlan,
81 root_attributes: Option<Attributes>,
82 program_kind: &TyProgramKind,
83 forc_version: Option<String>,
84 ) -> Result<RenderedDocumentation> {
85 let mut rendered_docs: RenderedDocumentation = RenderedDocumentation::default();
86 let root_module = match raw_docs.0.first() {
87 Some(doc) => ModuleInfo::from_ty_module(
88 vec![doc.module_info.project_name().to_owned()],
89 root_attributes.map(|attrs_map| attrs_map.to_html_string()),
90 ),
91 None => panic!("Project does not contain a root module"),
92 };
93
94 let mut all_docs = DocLinks {
95 style: DocStyle::AllDoc(program_kind.as_title_str().to_string()),
96 links: BTreeMap::default(),
97 };
98 let rendered_results: Result<Vec<RenderResult>, anyhow::Error> = raw_docs
100 .0
101 .par_iter()
102 .map(|doc| {
103 let rendered_doc = RenderedDocument::from_doc(doc, render_plan.clone())?;
104 let mut local_module_map = ModuleMap::new();
105 let mut local_all_docs = DocLinks {
106 style: DocStyle::AllDoc(program_kind.as_title_str().to_string()),
107 links: BTreeMap::default(),
108 };
109
110 populate_decls(doc, &mut local_module_map);
111 populate_modules(doc, &mut local_module_map);
112 populate_doc_links(doc, &mut local_all_docs.links);
113
114 Ok((rendered_doc, local_module_map, local_all_docs))
115 })
116 .collect();
117
118 let mut module_map = ModuleMap::new();
120 for (rendered_doc, local_module_map, local_all_docs) in rendered_results? {
121 rendered_docs.0.push(rendered_doc);
122
123 for (key, value) in local_module_map {
125 let entry = module_map.entry(key).or_default();
126 for (block, mut links) in value {
127 let list = entry.entry(block).or_default();
128 for link in links.drain(..) {
130 if !list.contains(&link) {
131 list.push(link);
132 }
133 }
134 }
135 }
136
137 for (block, mut links) in local_all_docs.links {
139 let list = all_docs.links.entry(block).or_default();
140 for link in links.drain(..) {
141 if !list.contains(&link) {
142 list.push(link);
143 }
144 }
145 }
146 }
147
148 match module_map.get(&root_module.module_prefixes) {
150 Some(doc_links) => rendered_docs.push(RenderedDocument {
151 module_info: root_module.clone(),
152 html_filename: INDEX_FILENAME.to_string(),
153 file_contents: HTMLString::from_rendered_content(
154 ModuleIndex::new(
155 forc_version,
156 root_module.clone(),
157 DocLinks {
158 style: DocStyle::ProjectIndex(program_kind.as_title_str().to_string()),
159 links: doc_links.to_owned(),
160 },
161 )
162 .render(render_plan.clone())?,
163 )?,
164 }),
165 None => panic!("Project does not contain a root module."),
166 }
167 if module_map.len() > 1 {
168 module_map.remove_entry(&root_module.module_prefixes);
169
170 for (module_prefixes, doc_links) in module_map {
172 let module_info_opt = match doc_links.values().last() {
173 Some(doc_links) => doc_links
174 .first()
175 .map(|doc_link| doc_link.module_info.clone()),
176 None => None,
178 };
179 if let Some(module_info) = module_info_opt {
180 rendered_docs.push(RenderedDocument {
181 module_info: module_info.clone(),
182 html_filename: INDEX_FILENAME.to_string(),
183 file_contents: HTMLString::from_rendered_content(
184 ModuleIndex::new(
185 None,
186 module_info.clone(),
187 DocLinks {
188 style: DocStyle::ModuleIndex,
189 links: doc_links.to_owned(),
190 },
191 )
192 .render(render_plan.clone())?,
193 )?,
194 });
195 if module_info.module_prefixes != module_prefixes {
196 let module_info = ModuleInfo::from_ty_module(module_prefixes, None);
197 rendered_docs.push(RenderedDocument {
198 module_info: module_info.clone(),
199 html_filename: INDEX_FILENAME.to_string(),
200 file_contents: HTMLString::from_rendered_content(
201 ModuleIndex::new(
202 None,
203 module_info,
204 DocLinks {
205 style: DocStyle::ModuleIndex,
206 links: doc_links.clone(),
207 },
208 )
209 .render(render_plan.clone())?,
210 )?,
211 });
212 }
213 }
214 }
215 }
216 rendered_docs.push(RenderedDocument {
218 module_info: root_module.clone(),
219 html_filename: ALL_DOC_FILENAME.to_string(),
220 file_contents: HTMLString::from_rendered_content(
221 AllDocIndex::new(root_module, all_docs).render(render_plan)?,
222 )?,
223 });
224
225 Ok(rendered_docs)
226 }
227}
228
229impl Deref for RenderedDocumentation {
230 type Target = Vec<RenderedDocument>;
231 fn deref(&self) -> &Self::Target {
232 &self.0
233 }
234}
235
236impl DerefMut for RenderedDocumentation {
237 fn deref_mut(&mut self) -> &mut Self::Target {
238 &mut self.0
239 }
240}
241
242fn populate_doc_links(doc: &Document, doc_links: &mut DocLinkMap) {
244 let key = doc.item_body.ty.as_block_title();
245 match doc_links.get_mut(&key) {
246 Some(links) => links.push(doc.link()),
247 None => {
248 doc_links.insert(key, vec![doc.link()]);
249 }
250 }
251}
252fn populate_decls(doc: &Document, module_map: &mut ModuleMap) {
254 let module_prefixes = &doc.module_info.module_prefixes;
255 if let Some(doc_links) = module_map.get_mut(module_prefixes) {
256 populate_doc_links(doc, doc_links)
257 } else {
258 let mut doc_links = DocLinkMap::new();
259 populate_doc_links(doc, &mut doc_links);
260 module_map.insert(module_prefixes.clone(), doc_links);
261 }
262}
263fn populate_modules(doc: &Document, module_map: &mut ModuleMap) {
265 let mut module_clone = doc.module_info.clone();
266 while module_clone.parent().is_some() {
267 let html_filename = if module_clone.depth() > 2 {
268 format!("{}/{INDEX_FILENAME}", module_clone.location())
269 } else {
270 INDEX_FILENAME.to_string()
271 };
272 let module_link = DocLink {
273 name: module_clone.location().to_owned(),
274 module_info: module_clone.clone(),
275 html_filename,
276 preview_opt: doc.module_info.preview_opt(),
277 };
278 let module_prefixes = module_clone
279 .module_prefixes
280 .clone()
281 .split_last()
282 .unwrap()
283 .1
284 .to_vec();
285 if let Some(doc_links) = module_map.get_mut(&module_prefixes) {
286 match doc_links.get_mut(&BlockTitle::Modules) {
287 Some(links) => {
288 if !links.contains(&module_link) {
289 links.push(module_link);
290 }
291 }
292 None => {
293 doc_links.insert(BlockTitle::Modules, vec![module_link]);
294 }
295 }
296 } else {
297 let mut doc_links = DocLinkMap::new();
298 doc_links.insert(BlockTitle::Modules, vec![module_link]);
299 module_map.insert(module_prefixes.clone(), doc_links);
300 }
301 module_clone.module_prefixes.pop();
302 }
303}
304
305#[derive(Debug)]
307pub struct HTMLString(pub String);
308impl HTMLString {
309 pub fn from_rendered_content(rendered_content: Box<dyn RenderBox>) -> Result<Self> {
311 Ok(Self(
312 html! {
313 : doctype::HTML;
314 html {
315 : rendered_content
316 }
317 }
318 .into_string()?,
319 ))
320 }
321}
322
323#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
326pub enum DocStyle {
327 AllDoc(String),
328 ProjectIndex(String),
329 WorkspaceIndex,
330 ModuleIndex,
331 Item {
332 title: Option<BlockTitle>,
333 name: Option<BaseIdent>,
334 },
335}