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