1use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown};
2use itertools::Itertools;
3use solang_parser::pt::{ErrorParameter, EventParameter, Parameter, VariableDeclaration};
4use std::{
5 fmt::{self, Display, Write},
6 sync::LazyLock,
7};
8
9const SOLIDITY: &str = "solidity";
11
12const PARAM_TABLE_HEADERS: &[&str] = &["Name", "Type", "Description"];
14static PARAM_TABLE_SEPARATOR: LazyLock<String> =
15 LazyLock::new(|| PARAM_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));
16
17const DEPLOYMENTS_TABLE_HEADERS: &[&str] = &["Network", "Address"];
19static DEPLOYMENTS_TABLE_SEPARATOR: LazyLock<String> =
20 LazyLock::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));
21
22#[derive(Debug, Default)]
25pub struct BufWriter {
26 buf: String,
27}
28
29impl BufWriter {
30 pub fn new(content: impl ToString) -> Self {
32 Self { buf: content.to_string() }
33 }
34
35 pub fn is_empty(&self) -> bool {
37 self.buf.is_empty()
38 }
39
40 pub fn write_doc<T: AsDoc>(&mut self, doc: &T) -> fmt::Result {
42 write!(self.buf, "{}", doc.as_doc()?)
43 }
44
45 pub fn writeln_doc<T: AsDoc>(&mut self, doc: &T) -> fmt::Result {
47 writeln!(self.buf, "{}", doc.as_doc()?)
48 }
49
50 pub fn write_raw<T: Display>(&mut self, content: T) -> fmt::Result {
52 write!(self.buf, "{content}")
53 }
54
55 pub fn writeln_raw<T: Display>(&mut self, content: T) -> fmt::Result {
57 writeln!(self.buf, "{content}")
58 }
59
60 pub fn writeln(&mut self) -> fmt::Result {
62 writeln!(self.buf)
63 }
64
65 pub fn write_title(&mut self, title: &str) -> fmt::Result {
67 writeln!(self.buf, "{}", Markdown::H1(title))
68 }
69
70 pub fn write_subtitle(&mut self, subtitle: &str) -> fmt::Result {
72 writeln!(self.buf, "{}", Markdown::H2(subtitle))
73 }
74
75 pub fn write_heading(&mut self, heading: &str) -> fmt::Result {
77 writeln!(self.buf, "{}", Markdown::H3(heading))
78 }
79
80 pub fn write_italic(&mut self, text: &str) -> fmt::Result {
82 writeln!(self.buf, "{}", Markdown::Italic(text))
83 }
84
85 pub fn write_bold(&mut self, text: &str) -> fmt::Result {
87 writeln!(self.buf, "{}", Markdown::Bold(text))
88 }
89
90 pub fn write_link(&mut self, name: &str, path: &str) -> fmt::Result {
92 writeln!(self.buf, "{}", Markdown::Link(name, path))
93 }
94
95 pub fn write_list_item(&mut self, item: &str, depth: usize) -> fmt::Result {
97 let indent = " ".repeat(depth * 2);
98 writeln!(self.buf, "{indent}- {item}")
99 }
100
101 pub fn write_link_list_item(&mut self, name: &str, path: &str, depth: usize) -> fmt::Result {
103 let link = Markdown::Link(name, path);
104 self.write_list_item(&link.as_doc()?, depth)
105 }
106
107 pub fn write_code(&mut self, code: &str) -> fmt::Result {
109 writeln!(self.buf, "{}", Markdown::CodeBlock(SOLIDITY, code))
110 }
111
112 pub fn write_section(&mut self, comments: &Comments, code: &str) -> fmt::Result {
114 self.writeln_raw(comments.as_doc()?)?;
115 self.write_code(code)?;
116 self.writeln()
117 }
118
119 fn try_write_table<T>(
122 &mut self,
123 tag: CommentTag,
124 params: &[T],
125 comments: &Comments,
126 heading: &str,
127 ) -> fmt::Result
128 where
129 T: ParamLike,
130 {
131 let comments = comments.include_tag(tag.clone());
132
133 if params.is_empty() || comments.is_empty() {
135 return Ok(())
136 }
137
138 self.write_bold(heading)?;
139 self.writeln()?;
140
141 self.write_piped(&PARAM_TABLE_HEADERS.join("|"))?;
142 self.write_piped(&PARAM_TABLE_SEPARATOR)?;
143
144 for (index, param) in params.iter().enumerate() {
145 let param_name = param.name();
146
147 let mut comment = param_name.as_ref().and_then(|name| {
148 comments.iter().find_map(|comment| comment.match_first_word(name))
149 });
150
151 if comment.is_none() && matches!(tag, CommentTag::Return) {
154 comment = comments.get(index).map(|c| &*c.value);
155 }
156
157 let row = [
158 Markdown::Code(param_name.unwrap_or("<none>")).as_doc()?,
159 Markdown::Code(¶m.type_name()).as_doc()?,
160 comment.unwrap_or_default().replace('\n', " "),
161 ];
162 self.write_piped(&row.join("|"))?;
163 }
164
165 self.writeln()?;
166
167 Ok(())
168 }
169
170 pub fn try_write_properties_table(
173 &mut self,
174 params: &[VariableDeclaration],
175 comments: &Comments,
176 ) -> fmt::Result {
177 self.try_write_table(CommentTag::Param, params, comments, "Properties")
178 }
179
180 pub fn try_write_events_table(
183 &mut self,
184 params: &[EventParameter],
185 comments: &Comments,
186 ) -> fmt::Result {
187 self.try_write_table(CommentTag::Param, params, comments, "Parameters")
188 }
189
190 pub fn try_write_errors_table(
193 &mut self,
194 params: &[ErrorParameter],
195 comments: &Comments,
196 ) -> fmt::Result {
197 self.try_write_table(CommentTag::Param, params, comments, "Parameters")
198 }
199
200 pub fn try_write_param_table(
203 &mut self,
204 tag: CommentTag,
205 params: &[&Parameter],
206 comments: &Comments,
207 ) -> fmt::Result {
208 let heading = match &tag {
209 CommentTag::Param => "Parameters",
210 CommentTag::Return => "Returns",
211 _ => return Err(fmt::Error),
212 };
213
214 self.try_write_table(tag, params, comments, heading)
215 }
216
217 pub fn write_deployments_table(&mut self, deployments: Vec<Deployment>) -> fmt::Result {
219 self.write_bold("Deployments")?;
220 self.writeln()?;
221
222 self.write_piped(&DEPLOYMENTS_TABLE_HEADERS.join("|"))?;
223 self.write_piped(&DEPLOYMENTS_TABLE_SEPARATOR)?;
224
225 for deployment in deployments {
226 let mut network = deployment.network.ok_or(fmt::Error)?;
227 network[0..1].make_ascii_uppercase();
228
229 let row = [
230 Markdown::Bold(&network).as_doc()?,
231 Markdown::Code(&format!("{:?}", deployment.address)).as_doc()?,
232 ];
233 self.write_piped(&row.join("|"))?;
234 }
235
236 self.writeln()?;
237
238 Ok(())
239 }
240
241 pub fn write_piped(&mut self, content: &str) -> fmt::Result {
243 self.write_raw("|")?;
244 self.write_raw(content)?;
245 self.writeln_raw("|")
246 }
247
248 pub fn finish(self) -> String {
250 self.buf
251 }
252}