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_dev_content(&mut self, text: &str) -> fmt::Result {
96 for line in text.lines() {
97 writeln!(self.buf, "{line}")?;
98 }
99
100 Ok(())
101 }
102
103 pub fn write_bold(&mut self, text: &str) -> fmt::Result {
105 writeln!(self.buf, "{}", Markdown::Bold(text))
106 }
107
108 pub fn write_link(&mut self, name: &str, path: &str) -> fmt::Result {
110 writeln!(self.buf, "{}", Markdown::Link(name, path))
111 }
112
113 pub fn write_list_item(&mut self, item: &str, depth: usize) -> fmt::Result {
115 let indent = " ".repeat(depth * 2);
116 writeln!(self.buf, "{indent}- {item}")
117 }
118
119 pub fn write_link_list_item(&mut self, name: &str, path: &str, depth: usize) -> fmt::Result {
121 let link = Markdown::Link(name, path);
122 self.write_list_item(&link.as_doc()?, depth)
123 }
124
125 pub fn write_code(&mut self, code: &str) -> fmt::Result {
127 writeln!(self.buf, "{}", Markdown::CodeBlock(SOLIDITY, code))
128 }
129
130 pub fn write_section(&mut self, comments: &Comments, code: &str) -> fmt::Result {
132 self.writeln_raw(comments.as_doc()?)?;
133 self.write_code(code)?;
134 self.writeln()
135 }
136
137 fn try_write_table<T>(
140 &mut self,
141 tag: CommentTag,
142 params: &[T],
143 comments: &Comments,
144 heading: &str,
145 ) -> fmt::Result
146 where
147 T: ParamLike,
148 {
149 let comments = comments.include_tag(tag.clone());
150
151 if params.is_empty() || comments.is_empty() {
153 return Ok(());
154 }
155
156 self.write_bold(heading)?;
157 self.writeln()?;
158
159 self.write_piped(&PARAM_TABLE_HEADERS.join("|"))?;
160 self.write_piped(&PARAM_TABLE_SEPARATOR)?;
161
162 for (index, param) in params.iter().enumerate() {
163 let param_name = param.name();
164
165 let mut comment = param_name.as_ref().and_then(|name| {
166 comments.iter().find_map(|comment| comment.match_first_word(name))
167 });
168
169 if comment.is_none() && matches!(tag, CommentTag::Return) {
172 comment = comments.get(index).map(|c| &*c.value);
173 }
174
175 let row = [
176 Markdown::Code(param_name.unwrap_or("<none>")).as_doc()?,
177 Markdown::Code(¶m.type_name()).as_doc()?,
178 comment.unwrap_or_default().replace('\n', " "),
179 ];
180 self.write_piped(&row.join("|"))?;
181 }
182
183 self.writeln()?;
184
185 Ok(())
186 }
187
188 pub fn try_write_properties_table(
191 &mut self,
192 params: &[VariableDeclaration],
193 comments: &Comments,
194 ) -> fmt::Result {
195 self.try_write_table(CommentTag::Param, params, comments, "Properties")
196 }
197
198 pub fn try_write_variant_table(
201 &mut self,
202 params: &EnumDefinition,
203 comments: &Comments,
204 ) -> fmt::Result {
205 let comments = comments.include_tags(&[CommentTag::Param]);
206
207 if comments.is_empty() {
209 return Ok(());
210 }
211
212 self.write_bold("Variants")?;
213 self.writeln()?;
214
215 self.write_piped(&VARIANTS_TABLE_HEADERS.join("|"))?;
216 self.write_piped(&VARIANTS_TABLE_SEPARATOR)?;
217
218 for value in ¶ms.values {
219 let param_name = value.as_ref().map(|v| v.name.clone());
220
221 let comment = param_name.as_ref().and_then(|name| {
222 comments.iter().find_map(|comment| comment.match_first_word(name))
223 });
224
225 let row = [
226 Markdown::Code(¶m_name.unwrap_or("<none>".to_string())).as_doc()?,
227 comment.unwrap_or_default().replace('\n', " "),
228 ];
229 self.write_piped(&row.join("|"))?;
230 }
231
232 self.writeln()?;
233
234 Ok(())
235 }
236
237 pub fn try_write_events_table(
240 &mut self,
241 params: &[EventParameter],
242 comments: &Comments,
243 ) -> fmt::Result {
244 self.try_write_table(CommentTag::Param, params, comments, "Parameters")
245 }
246
247 pub fn try_write_errors_table(
250 &mut self,
251 params: &[ErrorParameter],
252 comments: &Comments,
253 ) -> fmt::Result {
254 self.try_write_table(CommentTag::Param, params, comments, "Parameters")
255 }
256
257 pub fn try_write_param_table(
260 &mut self,
261 tag: CommentTag,
262 params: &[&Parameter],
263 comments: &Comments,
264 ) -> fmt::Result {
265 let heading = match &tag {
266 CommentTag::Param => "Parameters",
267 CommentTag::Return => "Returns",
268 _ => return Err(fmt::Error),
269 };
270
271 self.try_write_table(tag, params, comments, heading)
272 }
273
274 pub fn write_deployments_table(&mut self, deployments: Vec<Deployment>) -> fmt::Result {
276 self.write_bold("Deployments")?;
277 self.writeln()?;
278
279 self.write_piped(&DEPLOYMENTS_TABLE_HEADERS.join("|"))?;
280 self.write_piped(&DEPLOYMENTS_TABLE_SEPARATOR)?;
281
282 for deployment in deployments {
283 let mut network = deployment.network.ok_or(fmt::Error)?;
284 network[0..1].make_ascii_uppercase();
285
286 let row = [
287 Markdown::Bold(&network).as_doc()?,
288 Markdown::Code(&format!("{:?}", deployment.address)).as_doc()?,
289 ];
290 self.write_piped(&row.join("|"))?;
291 }
292
293 self.writeln()?;
294
295 Ok(())
296 }
297
298 pub fn write_piped(&mut self, content: &str) -> fmt::Result {
300 self.write_raw("|")?;
301 self.write_raw(content)?;
302 self.writeln_raw("|")
303 }
304
305 pub fn finish(self) -> String {
307 self.buf
308 }
309}