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
11pub type AsDocResult = Result<String, std::fmt::Error>;
13
14pub trait AsDoc {
16 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 fn as_doc(&self) -> AsDocResult {
35 let mut writer = BufWriter::default();
36
37 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 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 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 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 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 writer.writeln_doc(
261 &item.comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]),
262 )?;
263
264 writer.write_code(&item.code)?;
266
267 writer.try_write_param_table(
269 CommentTag::Param,
270 &func.params,
271 &item.comments,
272 )?;
273
274 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 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 writer.write_heading(&func_name)?;
324
325 writer.writeln()?;
326
327 writer.writeln_doc(&comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]))?;
329
330 writer.write_code(code)?;
332
333 writer.try_write_param_table(CommentTag::Param, &func.params, &comments)?;
335
336 writer.try_write_param_table(CommentTag::Return, &func.returns, &comments)?;
338
339 writer.writeln()?;
340 Ok(())
341 }
342}