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