criterion_table/formatter/
gfm.rs1use crate::{ColumnInfo, Comparison, Formatter, TimeUnit};
2use flexstr::{flex_fmt, FlexStr, IntoFlex, ToCase, ToFlexStr};
3use indexmap::IndexMap;
4
5const CT_URL: &str = "https://github.com/nu11ptr/criterion-table";
6
7const FIRST_COL_EXTRA_WIDTH: usize = "**``**".len();
10const USED_EXTRA_WIDTH: usize = "() ``****XX".len();
13
14pub struct GFMFormatter;
18
19impl GFMFormatter {
20 fn pad(buffer: &mut String, ch: char, max_width: usize, written: usize) {
21 let remaining = max_width - written;
23
24 for _ in 0..=remaining {
25 buffer.push(ch);
26 }
27 }
28
29 #[inline]
30 fn encode_link(s: &str) -> FlexStr {
31 s.replace(' ', "-").into_flex().to_lower()
32 }
33
34 fn write_toc_entry(buffer: &mut String, entry: &str, indent: bool) {
35 if indent {
36 buffer.push_str(" ");
37 }
38 buffer.push_str("- [");
39 buffer.push_str(entry);
40 buffer.push_str("](#");
41 buffer.push_str(&Self::encode_link(entry));
42 buffer.push_str(")\n");
43 }
44}
45
46impl Formatter for GFMFormatter {
47 fn start(
48 &mut self,
49 buffer: &mut String,
50 top_comments: &IndexMap<FlexStr, FlexStr>,
51 tables: &[&FlexStr],
52 ) {
53 buffer.push_str("# Benchmarks\n\n");
54 buffer.push_str("## Table of Contents\n\n");
55
56 for section_entry in top_comments.keys() {
58 Self::write_toc_entry(buffer, section_entry, false);
59 }
60
61 Self::write_toc_entry(buffer, "Benchmark Results", false);
62
63 for &table_entry in tables {
65 Self::write_toc_entry(buffer, table_entry, true);
66 }
67
68 buffer.push('\n');
69
70 for (header, comment) in top_comments {
72 buffer.push_str("## ");
73 buffer.push_str(header);
74 buffer.push_str("\n\n");
75 buffer.push_str(comment);
76 buffer.push('\n');
77 }
78
79 buffer.push_str("## Benchmark Results\n\n");
80 }
81
82 fn end(&mut self, buffer: &mut String) {
83 buffer.push_str("---\n");
84 buffer.push_str("Made with [criterion-table](");
85 buffer.push_str(CT_URL);
86 buffer.push_str(")\n");
87 }
88
89 fn start_table(
90 &mut self,
91 buffer: &mut String,
92 name: &FlexStr,
93 comment: Option<&FlexStr>,
94 columns: &[ColumnInfo],
95 ) {
96 buffer.push_str("### ");
99 buffer.push_str(name);
100 buffer.push_str("\n\n");
101
102 if let Some(comments) = comment {
103 buffer.push_str(comments);
104 buffer.push('\n');
105 }
106
107 buffer.push_str("| ");
110 let first_col_max_width = columns[0].max_width + FIRST_COL_EXTRA_WIDTH;
112 Self::pad(buffer, ' ', first_col_max_width, 0);
113
114 for column in &columns[1..] {
116 let max_width = column.max_width + USED_EXTRA_WIDTH;
117
118 buffer.push_str("| `");
119 buffer.push_str(&column.name);
120 buffer.push('`');
121 Self::pad(buffer, ' ', max_width, column.name.chars().count() + 2);
122 }
123
124 buffer.push_str(" |\n");
125
126 buffer.push_str("|:");
130 Self::pad(buffer, '-', first_col_max_width, 0);
131
132 for column in &columns[1..] {
134 let max_width = column.max_width + USED_EXTRA_WIDTH;
135
136 buffer.push_str("|:");
137 Self::pad(buffer, '-', max_width, 0);
138 }
139
140 buffer.push_str(" |\n");
141 }
142
143 fn end_table(&mut self, buffer: &mut String) {
144 buffer.push('\n');
145 }
146
147 fn start_row(&mut self, buffer: &mut String, name: &FlexStr, max_width: usize) {
148 let written = if !name.is_empty() {
150 buffer.push_str("| **`");
151 buffer.push_str(name);
152 buffer.push_str("`**");
153 name.chars().count() + FIRST_COL_EXTRA_WIDTH
154 } else {
156 buffer.push_str("| ");
157 0
158 };
159
160 Self::pad(buffer, ' ', max_width + FIRST_COL_EXTRA_WIDTH, written);
161 }
162
163 fn end_row(&mut self, buffer: &mut String) {
164 buffer.push_str(" |\n");
165 }
166
167 fn used_column(
168 &mut self,
169 buffer: &mut String,
170 time: TimeUnit,
171 compare: Comparison,
172 max_width: usize,
173 ) {
174 let (time_str, speedup_str) = (time.to_flex_str(), compare.to_flex_str());
175
176 let data = if compare >= 1.8 {
178 flex_fmt!("`{time_str}` (🚀 **{speedup_str}**)")
180 } else if compare > 0.9 {
182 flex_fmt!("`{time_str}` (✅ **{speedup_str}**)")
184 } else if compare < 0.9 {
186 flex_fmt!("`{time_str}` (❌ *{speedup_str}*)")
188 } else {
189 flex_fmt!("`{time_str}` ({speedup_str})")
191 };
192
193 buffer.push_str("| ");
194 buffer.push_str(&data);
195
196 let max_width = max_width + USED_EXTRA_WIDTH;
197 Self::pad(buffer, ' ', max_width, data.chars().count());
198 }
199
200 fn unused_column(&mut self, buffer: &mut String, max_width: usize) {
201 buffer.push_str("| ");
202 let data = "`N/A`";
203 buffer.push_str(data);
204
205 Self::pad(
206 buffer,
207 ' ',
208 max_width + USED_EXTRA_WIDTH,
209 data.chars().count(),
210 );
211 }
212}