1use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown, writer::traits::ParamLike};
2use itertools::Itertools;
3use solang_parser::pt::{
4 EnumDefinition, ErrorParameter, EventParameter, Parameter, VariableDeclaration,
5};
6use std::{
7 fmt::{self, Display, Write},
8 sync::LazyLock,
9};
10
11const SOLIDITY: &str = "solidity";
13
14const PARAM_TABLE_HEADERS: &[&str] = &["Name", "Type", "Description"];
16static PARAM_TABLE_SEPARATOR: LazyLock<String> =
17 LazyLock::new(|| PARAM_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));
18
19const DEPLOYMENTS_TABLE_HEADERS: &[&str] = &["Network", "Address"];
21static DEPLOYMENTS_TABLE_SEPARATOR: LazyLock<String> =
22 LazyLock::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));
23
24const VARIANTS_TABLE_HEADERS: &[&str] = &["Name", "Description"];
26static VARIANTS_TABLE_SEPARATOR: LazyLock<String> =
27 LazyLock::new(|| VARIANTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));
28
29#[derive(Debug, Default)]
32pub struct BufWriter {
33 buf: String,
34}
35
36impl BufWriter {
37 pub fn new(content: impl ToString) -> Self {
39 Self { buf: content.to_string() }
40 }
41
42 pub fn is_empty(&self) -> bool {
44 self.buf.is_empty()
45 }
46
47 pub fn write_doc<T: AsDoc>(&mut self, doc: &T) -> fmt::Result {
49 write!(self.buf, "{}", doc.as_doc()?)
50 }
51
52 pub fn writeln_doc<T: AsDoc>(&mut self, doc: &T) -> fmt::Result {
54 writeln!(self.buf, "{}", doc.as_doc()?)
55 }
56
57 pub fn write_raw<T: Display>(&mut self, content: T) -> fmt::Result {
59 write!(self.buf, "{content}")
60 }
61
62 pub fn writeln_raw<T: Display>(&mut self, content: T) -> fmt::Result {
64 writeln!(self.buf, "{content}")
65 }
66
67 pub fn writeln(&mut self) -> fmt::Result {
69 writeln!(self.buf)
70 }
71
72 pub fn write_title(&mut self, title: &str) -> fmt::Result {
74 writeln!(self.buf, "{}", Markdown::H1(title))
75 }
76
77 pub fn write_subtitle(&mut self, subtitle: &str) -> fmt::Result {
79 writeln!(self.buf, "{}", Markdown::H2(subtitle))
80 }
81
82 pub fn write_heading(&mut self, heading: &str) -> fmt::Result {
84 writeln!(self.buf, "{}", Markdown::H3(heading))
85 }
86
87 pub fn write_italic(&mut self, text: &str) -> fmt::Result {
89 writeln!(self.buf, "{}", Markdown::Italic(text))
90 }
91
92 pub fn write_bold(&mut self, text: &str) -> fmt::Result {
94 writeln!(self.buf, "{}", Markdown::Bold(text))
95 }
96
97 pub fn write_link(&mut self, name: &str, path: &str) -> fmt::Result {
99 writeln!(self.buf, "{}", Markdown::Link(name, path))
100 }
101
102 pub fn write_list_item(&mut self, item: &str, depth: usize) -> fmt::Result {
104 let indent = " ".repeat(depth * 2);
105 writeln!(self.buf, "{indent}- {item}")
106 }
107
108 pub fn write_link_list_item(&mut self, name: &str, path: &str, depth: usize) -> fmt::Result {
110 let link = Markdown::Link(name, path);
111 self.write_list_item(&link.as_doc()?, depth)
112 }
113
114 pub fn write_code(&mut self, code: &str) -> fmt::Result {
116 writeln!(self.buf, "{}", Markdown::CodeBlock(SOLIDITY, code))
117 }
118
119 pub fn write_section(&mut self, comments: &Comments, code: &str) -> fmt::Result {
121 self.writeln_raw(comments.as_doc()?)?;
122 self.write_code(code)?;
123 self.writeln()
124 }
125
126 fn try_write_table<T>(
129 &mut self,
130 tag: CommentTag,
131 params: &[T],
132 comments: &Comments,
133 heading: &str,
134 ) -> fmt::Result
135 where
136 T: ParamLike,
137 {
138 let comments = comments.include_tag(tag.clone());
139
140 if params.is_empty() || comments.is_empty() {
142 return Ok(());
143 }
144
145 self.write_bold(heading)?;
146 self.writeln()?;
147
148 self.write_piped(&PARAM_TABLE_HEADERS.join("|"))?;
149 self.write_piped(&PARAM_TABLE_SEPARATOR)?;
150
151 for (index, param) in params.iter().enumerate() {
152 let param_name = param.name();
153
154 let mut comment = param_name.as_ref().and_then(|name| {
155 comments.iter().find_map(|comment| comment.match_first_word(name))
156 });
157
158 if comment.is_none() && matches!(tag, CommentTag::Return) {
161 comment = comments.get(index).map(|c| &*c.value);
162 }
163
164 let row = [
165 Markdown::Code(param_name.unwrap_or("<none>")).as_doc()?,
166 Markdown::Code(¶m.type_name()).as_doc()?,
167 comment.unwrap_or_default().replace('\n', " "),
168 ];
169 self.write_piped(&row.join("|"))?;
170 }
171
172 self.writeln()?;
173
174 Ok(())
175 }
176
177 pub fn try_write_properties_table(
180 &mut self,
181 params: &[VariableDeclaration],
182 comments: &Comments,
183 ) -> fmt::Result {
184 self.try_write_table(CommentTag::Param, params, comments, "Properties")
185 }
186
187 pub fn try_write_variant_table(
190 &mut self,
191 params: &EnumDefinition,
192 comments: &Comments,
193 ) -> fmt::Result {
194 let comments = comments.include_tags(&[CommentTag::Param]);
195
196 if comments.is_empty() {
198 return Ok(());
199 }
200
201 self.write_bold("Variants")?;
202 self.writeln()?;
203
204 self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?;
205 self.write_piped(&VARIANTS_TABLE_SEPARATOR)?;
206
207 for value in ¶ms.values {
208 let param_name = value.as_ref().map(|v| v.name.clone());
209
210 let comment = param_name.as_ref().and_then(|name| {
211 comments.iter().find_map(|comment| comment.match_first_word(name))
212 });
213
214 let row = [
215 Markdown::Code(¶m_name.unwrap_or("<none>".to_string())).as_doc()?,
216 comment.unwrap_or_default().replace('\n', " "),
217 ];
218 self.write_piped(&row.join("|"))?;
219 }
220
221 self.writeln()?;
222
223 Ok(())
224 }
225
226 pub fn try_write_events_table(
229 &mut self,
230 params: &[EventParameter],
231 comments: &Comments,
232 ) -> fmt::Result {
233 self.try_write_table(CommentTag::Param, params, comments, "Parameters")
234 }
235
236 pub fn try_write_errors_table(
239 &mut self,
240 params: &[ErrorParameter],
241 comments: &Comments,
242 ) -> fmt::Result {
243 self.try_write_table(CommentTag::Param, params, comments, "Parameters")
244 }
245
246 pub fn try_write_param_table(
249 &mut self,
250 tag: CommentTag,
251 params: &[&Parameter],
252 comments: &Comments,
253 ) -> fmt::Result {
254 let heading = match &tag {
255 CommentTag::Param => "Parameters",
256 CommentTag::Return => "Returns",
257 _ => return Err(fmt::Error),
258 };
259
260 self.try_write_table(tag, params, comments, heading)
261 }
262
263 pub fn write_deployments_table(&mut self, deployments: Vec<Deployment>) -> fmt::Result {
265 self.write_bold("Deployments")?;
266 self.writeln()?;
267
268 self.write_piped(&DEPLOYMENTS_TABLE_HEADERS.join("|"))?;
269 self.write_piped(&DEPLOYMENTS_TABLE_SEPARATOR)?;
270
271 for deployment in deployments {
272 let mut network = deployment.network.ok_or(fmt::Error)?;
273 network[0..1].make_ascii_uppercase();
274
275 let row = [
276 Markdown::Bold(&network).as_doc()?,
277 Markdown::Code(&format!("{:?}", deployment.address)).as_doc()?,
278 ];
279 self.write_piped(&row.join("|"))?;
280 }
281
282 self.writeln()?;
283
284 Ok(())
285 }
286
287 pub fn write_piped(&mut self, content: &str) -> fmt::Result {
289 self.write_raw("|")?;
290 self.write_raw(content)?;
291 self.writeln_raw("|")
292 }
293
294 pub fn finish(self) -> String {
296 self.buf
297 }
298}