Skip to main content

forge_doc/writer/
as_doc.rs

1use crate::{
2    BaseInfo, CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document,
3    FunctionSource, GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput, VariableAttr,
4    document::{DocumentContent, read_context},
5    parser::ParseSource,
6    writer::BufWriter,
7};
8use itertools::Itertools;
9use std::path::Path;
10
11/// The result of [`AsDoc::as_doc`].
12pub type AsDocResult = Result<String, std::fmt::Error>;
13
14/// A trait for formatting a parse unit as documentation.
15pub trait AsDoc {
16    /// Formats a parse tree item into a doc string.
17    fn as_doc(&self) -> AsDocResult;
18}
19
20impl AsDoc for String {
21    fn as_doc(&self) -> AsDocResult {
22        Ok(self.to_owned())
23    }
24}
25
26impl AsDoc for Comments {
27    fn as_doc(&self) -> AsDocResult {
28        CommentsRef::from(self).as_doc()
29    }
30}
31
32impl AsDoc for CommentsRef<'_> {
33    // TODO: support other tags
34    fn as_doc(&self) -> AsDocResult {
35        let mut writer = BufWriter::default();
36
37        // Write title tag(s)
38        let titles = self.include_tag(CommentTag::Title);
39        if !titles.is_empty() {
40            writer.write_bold(&format!("Title{}:", if titles.len() == 1 { "" } else { "s" }))?;
41            writer.writeln_raw(titles.iter().map(|t| &t.value).join(", "))?;
42            writer.writeln()?;
43        }
44
45        // Write author tag(s)
46        let authors = self.include_tag(CommentTag::Author);
47        if !authors.is_empty() {
48            writer.write_bold(&format!("Author{}:", if authors.len() == 1 { "" } else { "s" }))?;
49            writer.writeln_raw(authors.iter().map(|a| &a.value).join(", "))?;
50            writer.writeln()?;
51        }
52
53        // Write notice tags
54        let notices = self.include_tag(CommentTag::Notice);
55        for n in notices.iter() {
56            writer.writeln_raw(&n.value)?;
57            writer.writeln()?;
58        }
59
60        // Write dev tags
61        let devs = self.include_tag(CommentTag::Dev);
62        for d in devs.iter() {
63            writer.write_dev_content(&d.value)?;
64            writer.writeln()?;
65        }
66
67        // Write custom tags
68        let customs = self.get_custom_tags();
69        if !customs.is_empty() {
70            writer.write_bold(&format!("Note{}:", if customs.len() == 1 { "" } else { "s" }))?;
71            for c in customs.iter() {
72                writer.writeln_raw(format!(
73                    "{}{}: {}",
74                    if customs.len() == 1 { "" } else { "- " },
75                    c.tag,
76                    c.value
77                ))?;
78                writer.writeln()?;
79            }
80        }
81
82        Ok(writer.finish())
83    }
84}
85
86impl AsDoc for BaseInfo {
87    fn as_doc(&self) -> AsDocResult {
88        Ok(self.name.clone())
89    }
90}
91
92impl AsDoc for Document {
93    fn as_doc(&self) -> AsDocResult {
94        let mut writer = BufWriter::default();
95
96        match &self.content {
97            DocumentContent::OverloadedFunctions(items) => {
98                writer
99                    .write_title(&format!("function {}", items.first().unwrap().source.ident()))?;
100                if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
101                    writer.write_link("Git Source", &git_source)?;
102                    writer.writeln()?;
103                }
104
105                for item in items {
106                    let heading = item.source.signature().replace(',', ", ");
107                    writer.write_heading(&heading)?;
108                    writer.write_section(&item.comments, &item.code)?;
109                }
110            }
111            DocumentContent::Constants(items) => {
112                writer.write_title("Constants")?;
113                if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
114                    writer.write_link("Git Source", &git_source)?;
115                    writer.writeln()?;
116                }
117
118                for item in items {
119                    let var = item.as_variable().unwrap();
120                    writer.write_heading(&var.name)?;
121                    writer.write_section(&item.comments, &item.code)?;
122                }
123            }
124            DocumentContent::Single(item) => {
125                writer.write_title(&item.source.ident())?;
126                if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
127                    writer.write_link("Git Source", &git_source)?;
128                    writer.writeln()?;
129                }
130
131                if let Some(deployments) = read_context!(self, DEPLOYMENTS_ID, Deployments) {
132                    writer.write_deployments_table(deployments)?;
133                }
134
135                match &item.source {
136                    ParseSource::Contract(contract) => {
137                        if !contract.bases.is_empty() {
138                            writer.write_bold("Inherits:")?;
139
140                            let mut bases = vec![];
141                            let linked =
142                                read_context!(self, CONTRACT_INHERITANCE_ID, ContractInheritance);
143                            for base in &contract.bases {
144                                let base_doc = base.as_doc()?;
145                                let base_ident = &base.ident;
146
147                                let link = linked
148                                    .as_ref()
149                                    .and_then(|link| {
150                                        link.get(base_ident).map(|path| {
151                                            let path = if cfg!(windows) {
152                                                Path::new("\\").join(path)
153                                            } else {
154                                                Path::new("/").join(path)
155                                            };
156                                            Markdown::Link(&base_doc, &path.display().to_string())
157                                                .as_doc()
158                                        })
159                                    })
160                                    .transpose()?
161                                    .unwrap_or(base_doc);
162
163                                bases.push(link);
164                            }
165
166                            writer.writeln_raw(bases.join(", "))?;
167                            writer.writeln()?;
168                        }
169
170                        writer.writeln_doc(&item.comments)?;
171
172                        if let Some(all_vars) = item.variables() {
173                            let (constants, state_vars): (Vec<_>, Vec<_>) =
174                                all_vars.into_iter().partition(|(item, _, _)| {
175                                    item.attrs.iter().any(|attr| {
176                                        matches!(
177                                            attr,
178                                            VariableAttr::Constant | VariableAttr::Immutable
179                                        )
180                                    })
181                                });
182
183                            if !constants.is_empty() {
184                                writer.write_subtitle("Constants")?;
185                                constants.into_iter().try_for_each(|(item, comments, code)| {
186                                    let comments = comments.merge_inheritdoc(
187                                        &item.name,
188                                        read_context!(self, INHERITDOC_ID, Inheritdoc),
189                                    );
190
191                                    writer.write_heading(&item.name)?;
192                                    writer.write_section(&comments, code)?;
193                                    writer.writeln()
194                                })?;
195                            }
196
197                            if !state_vars.is_empty() {
198                                writer.write_subtitle("State Variables")?;
199                                state_vars.into_iter().try_for_each(|(item, comments, code)| {
200                                    let comments = comments.merge_inheritdoc(
201                                        &item.name,
202                                        read_context!(self, INHERITDOC_ID, Inheritdoc),
203                                    );
204
205                                    writer.write_heading(&item.name)?;
206                                    writer.write_section(&comments, code)?;
207                                    writer.writeln()
208                                })?;
209                            }
210                        }
211
212                        if let Some(funcs) = item.functions() {
213                            writer.write_subtitle("Functions")?;
214
215                            for (func, comments, code) in &funcs {
216                                self.write_function(&mut writer, func, comments, code)?;
217                            }
218                        }
219
220                        if let Some(events) = item.events() {
221                            writer.write_subtitle("Events")?;
222                            events.into_iter().try_for_each(|(item, comments, code)| {
223                                writer.write_heading(&item.name)?;
224                                writer.write_section(comments, code)?;
225                                writer.try_write_events_table(&item.fields, comments)
226                            })?;
227                        }
228
229                        if let Some(errors) = item.errors() {
230                            writer.write_subtitle("Errors")?;
231                            errors.into_iter().try_for_each(|(item, comments, code)| {
232                                writer.write_heading(&item.name)?;
233                                writer.write_section(comments, code)?;
234                                writer.try_write_errors_table(&item.fields, comments)
235                            })?;
236                        }
237
238                        if let Some(structs) = item.structs() {
239                            writer.write_subtitle("Structs")?;
240                            structs.into_iter().try_for_each(|(item, comments, code)| {
241                                writer.write_heading(&item.name)?;
242                                writer.write_section(comments, code)?;
243                                writer.try_write_properties_table(&item.fields, comments)
244                            })?;
245                        }
246
247                        if let Some(enums) = item.enums() {
248                            writer.write_subtitle("Enums")?;
249                            enums.into_iter().try_for_each(|(item, comments, code)| {
250                                writer.write_heading(&item.name)?;
251                                writer.write_section(comments, code)?;
252                                writer.try_write_variant_table(item, comments)
253                            })?;
254                        }
255                    }
256
257                    ParseSource::Function(func) => {
258                        // TODO: cleanup
259                        // Write function docs
260                        writer.writeln_doc(
261                            &item.comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]),
262                        )?;
263
264                        // Write function header
265                        writer.write_code(&item.code)?;
266
267                        // Write function parameter comments in a table
268                        writer.try_write_param_table(
269                            CommentTag::Param,
270                            &func.params,
271                            &item.comments,
272                        )?;
273
274                        // Write function return parameter comments in a table
275                        writer.try_write_param_table(
276                            CommentTag::Return,
277                            &func.returns,
278                            &item.comments,
279                        )?;
280
281                        writer.writeln()?;
282                    }
283
284                    ParseSource::Struct(ty) => {
285                        writer.write_section(&item.comments, &item.code)?;
286                        writer.try_write_properties_table(&ty.fields, &item.comments)?;
287                    }
288                    ParseSource::Event(ev) => {
289                        writer.write_section(&item.comments, &item.code)?;
290                        writer.try_write_events_table(&ev.fields, &item.comments)?;
291                    }
292                    ParseSource::Error(err) => {
293                        writer.write_section(&item.comments, &item.code)?;
294                        writer.try_write_errors_table(&err.fields, &item.comments)?;
295                    }
296                    ParseSource::Variable(_) | ParseSource::Enum(_) | ParseSource::Type(_) => {
297                        writer.write_section(&item.comments, &item.code)?;
298                    }
299                }
300            }
301            DocumentContent::Empty => (),
302        };
303
304        Ok(writer.finish())
305    }
306}
307
308impl Document {
309    /// Writes a function to the buffer.
310    fn write_function(
311        &self,
312        writer: &mut BufWriter,
313        func: &FunctionSource,
314        comments: &Comments,
315        code: &str,
316    ) -> Result<(), std::fmt::Error> {
317        let func_name = func.name.as_deref().unwrap_or(&func.kind).to_string();
318        let func_sign = func.signature();
319        let comments =
320            comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc));
321
322        // Write function name
323        writer.write_heading(&func_name)?;
324
325        writer.writeln()?;
326
327        // Write function docs
328        writer.writeln_doc(&comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]))?;
329
330        // Write function header
331        writer.write_code(code)?;
332
333        // Write function parameter comments in a table
334        writer.try_write_param_table(CommentTag::Param, &func.params, &comments)?;
335
336        // Write function return parameter comments in a table
337        writer.try_write_param_table(CommentTag::Return, &func.returns, &comments)?;
338
339        writer.writeln()?;
340        Ok(())
341    }
342}