Skip to content

Commit 2b58d7e

Browse files
committed
improve markdown table
1 parent d5a3e03 commit 2b58d7e

File tree

2 files changed

+146
-40
lines changed

2 files changed

+146
-40
lines changed

turbopack/crates/turbo-tasks-backend/src/backend/mod.rs

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,37 +1238,39 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
12381238
print_markdown_table(
12391239
[
12401240
"Task",
1241-
"Total Size",
1242-
"Data Size",
1243-
"Data Count x Avg",
1244-
"Meta Size",
1245-
"Meta Count x Avg",
1246-
"Uppers",
1247-
"Coll",
1248-
"Agg Coll",
1249-
"Children",
1250-
"Followers",
1251-
"Coll Deps",
1252-
"Agg Dirty",
1253-
"Output Size",
1241+
" Total Size",
1242+
" Data Size",
1243+
" Data Count x Avg",
1244+
" Data Count x Avg",
1245+
" Meta Size",
1246+
" Meta Count x Avg",
1247+
" Meta Count x Avg",
1248+
" Uppers",
1249+
" Coll",
1250+
" Agg Coll",
1251+
" Children",
1252+
" Followers",
1253+
" Coll Deps",
1254+
" Agg Dirty",
1255+
" Output Size",
12541256
],
12551257
task_cache_stats.iter(),
12561258
|(task_desc, stats)| {
12571259
[
12581260
task_desc.to_string(),
12591261
format!(
1260-
"{} ({})",
1262+
" {} ({})",
12611263
FormatBytes(stats.data_compressed + stats.meta_compressed),
12621264
FormatBytes(stats.data + stats.meta)
12631265
),
12641266
format!(
1265-
"{} ({})",
1267+
" {} ({})",
12661268
FormatBytes(stats.data_compressed),
12671269
FormatBytes(stats.data)
12681270
),
1271+
format!(" {} x", stats.data_count,),
12691272
format!(
1270-
"{} x {} ({})",
1271-
stats.data_count,
1273+
"{} ({})",
12721274
FormatBytes(
12731275
stats
12741276
.data_compressed
@@ -1280,13 +1282,13 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
12801282
),
12811283
),
12821284
format!(
1283-
"{} ({})",
1285+
" {} ({})",
12841286
FormatBytes(stats.meta_compressed),
12851287
FormatBytes(stats.meta)
12861288
),
1289+
format!(" {} x", stats.meta_count,),
12871290
format!(
1288-
"{} x {} ({})",
1289-
stats.meta_count,
1291+
"{} ({})",
12901292
FormatBytes(
12911293
stats
12921294
.meta_compressed
@@ -1297,14 +1299,14 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
12971299
stats.meta.checked_div(stats.meta_count).unwrap_or(0)
12981300
),
12991301
),
1300-
stats.upper_count.to_string(),
1301-
stats.collectibles_count.to_string(),
1302-
stats.aggregated_collectibles_count.to_string(),
1303-
stats.children_count.to_string(),
1304-
stats.followers_count.to_string(),
1305-
stats.collectibles_dependents_count.to_string(),
1306-
stats.aggregated_dirty_containers_count.to_string(),
1307-
FormatBytes(stats.output_size).to_string(),
1302+
format!(" {}", stats.upper_count),
1303+
format!(" {}", stats.collectibles_count),
1304+
format!(" {}", stats.aggregated_collectibles_count),
1305+
format!(" {}", stats.children_count),
1306+
format!(" {}", stats.followers_count),
1307+
format!(" {}", stats.collectibles_dependents_count),
1308+
format!(" {}", stats.aggregated_dirty_containers_count),
1309+
format!(" {}", FormatBytes(stats.output_size)),
13081310
]
13091311
},
13101312
);
Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,156 @@
11
use std::fmt::Write;
22

3+
/// Prints a markdown table to stdout.
4+
/// See `write_markdown_table` for details.
35
pub fn print_markdown_table<T, const N: usize>(
46
headers: [&str; N],
57
data: impl IntoIterator<Item = T> + Clone,
68
get_fields: impl Fn(&T) -> [String; N],
79
) {
8-
let mut sizes = headers.map(|h| h.len());
10+
write_markdown_table(&mut std::io::stdout(), headers, data, get_fields);
11+
}
12+
13+
/// Writes a markdown table to the given writer.
14+
/// The headers and fields can specify alignment by starting or ending with a space:
15+
/// - " Text" - right aligned
16+
/// - " Text " - center aligned
17+
/// - "Text" - left aligned
18+
/// Also, if multiple consecutive headers are identical, they will be form a merged header cell.
19+
pub fn write_markdown_table<T, const N: usize>(
20+
write: &mut impl std::io::Write,
21+
headers: [&str; N],
22+
data: impl IntoIterator<Item = T> + Clone,
23+
get_fields: impl Fn(&T) -> [String; N],
24+
) {
25+
let mut merged = headers.map(|_| false);
26+
for i in 1..N {
27+
if headers[i].trim() == headers[i - 1].trim() {
28+
merged[i] = true;
29+
}
30+
}
931
// Measure max field size
32+
let mut sizes = headers.map(|_| 1);
1033
for item in data.clone() {
1134
let fields = get_fields(&item);
1235
for (i, field) in fields.iter().enumerate() {
13-
let field_size = field.len();
36+
let field_size = field.trim().len();
1437
if field_size > sizes[i] {
1538
sizes[i] = field_size;
1639
}
1740
}
1841
}
42+
// Add header size
43+
let mut headers_sizes = sizes;
44+
for (i, header) in headers.iter().enumerate() {
45+
if merged[i] {
46+
headers_sizes[i] = 0;
47+
continue;
48+
}
49+
let header_size = header.trim().len();
50+
let current_size = sizes[i]
51+
+ (i + 1..N)
52+
.take_while(|&j| merged[j])
53+
.map(|j| sizes[j] + 1)
54+
.sum::<usize>();
55+
if header_size > current_size {
56+
sizes[i] += header_size - current_size;
57+
headers_sizes[i] = header_size;
58+
} else {
59+
headers_sizes[i] = current_size;
60+
}
61+
}
1962
// Print headers
2063
{
2164
let mut line = String::new();
2265
for (i, header) in headers.iter().enumerate() {
23-
let size = sizes[i];
24-
let escaped_header = escape_markdown_cell(header);
25-
write!(line, "| {:<width$} ", escaped_header, width = size).unwrap();
66+
let size = headers_sizes[i];
67+
if size == 0 {
68+
continue;
69+
}
70+
let right = header.starts_with(' ');
71+
let center = header.ends_with(' ') && right;
72+
let escaped_header = escape_markdown_cell(header.trim());
73+
if center {
74+
write!(line, "| {:^width$} ", escaped_header, width = size).unwrap();
75+
} else if right {
76+
write!(line, "| {:>width$} ", escaped_header, width = size).unwrap();
77+
} else {
78+
write!(line, "| {:<width$} ", escaped_header, width = size).unwrap();
79+
}
2680
}
27-
println!("{} |", line);
81+
writeln!(write, "{}|", line).unwrap();
2882
}
2983
// Print separator
3084
{
3185
let mut line = String::new();
32-
for size in sizes.iter() {
33-
write!(line, "| {:-<width$} ", "", width = *size + 2).unwrap();
86+
for size in headers_sizes.iter() {
87+
if *size == 0 {
88+
continue;
89+
}
90+
write!(line, "| {:-<width$} ", "", width = *size).unwrap();
3491
}
35-
println!("{} |", line);
92+
writeln!(write, "{}|", line).unwrap();
3693
}
3794
// Print rows
3895
for item in data {
3996
let row = get_fields(&item);
4097
let mut line = String::new();
4198
for (i, field) in row.iter().enumerate() {
4299
let size = sizes[i];
43-
let escaped_field = escape_markdown_cell(field);
44-
write!(line, "| {:<width$} ", escaped_field, width = size).unwrap();
100+
let right = field.starts_with(' ');
101+
let center = field.ends_with(' ') && right;
102+
let escaped_field = escape_markdown_cell(field.trim());
103+
let separator = if merged[i] { "" } else { "| " };
104+
if center {
105+
write!(line, "{separator}{:^width$} ", escaped_field, width = size).unwrap();
106+
} else if right {
107+
write!(line, "{separator}{:>width$} ", escaped_field, width = size).unwrap();
108+
} else {
109+
write!(line, "{separator}{:<width$} ", escaped_field, width = size).unwrap();
110+
}
45111
}
46-
println!("{} |", line);
112+
writeln!(write, "{}|", line).unwrap();
47113
}
48114
}
49115

50116
fn escape_markdown_cell(content: &str) -> String {
51117
content.replace('|', "\\|").replace('\n', " ")
52118
}
119+
120+
#[cfg(test)]
121+
mod tests {
122+
use super::write_markdown_table;
123+
124+
#[test]
125+
fn test_write_markdown_table() {
126+
let headers = [" Name ", " Age", " Birthday (Age)", " Birthday (Age)"];
127+
let data = vec![
128+
(" Alice ", " 30", " 1990-01-01", "(30)"),
129+
(" Bob ", " 25", " 2024", "(9)"),
130+
(" Charlie ", " 35", " 1985-08-20", "(35)"),
131+
(" N/A ", " ?", " N/A", "(?)"),
132+
];
133+
let mut output = Vec::new();
134+
write_markdown_table(&mut output, headers, data, |item| {
135+
[
136+
item.0.to_string(),
137+
item.1.to_string(),
138+
item.2.to_string(),
139+
item.3.to_string(),
140+
]
141+
});
142+
let output = String::from_utf8(output).unwrap();
143+
let expected = r#"
144+
| Name | Age | Birthday (Age) |
145+
| ------- | --- | --------------- |
146+
| Alice | 30 | 1990-01-01 (30) |
147+
| Bob | 25 | 2024 (9) |
148+
| Charlie | 35 | 1985-08-20 (35) |
149+
| N/A | ? | N/A (?) |
150+
"#;
151+
assert_eq!(
152+
output.trim().lines().collect::<Vec<_>>(),
153+
expected.trim().lines().collect::<Vec<_>>()
154+
);
155+
}
156+
}

0 commit comments

Comments
 (0)