1use comfy_table::presets::UTF8_FULL_CONDENSED;
4use comfy_table::{Cell, ContentArrangement, Table};
5
6use crate::color::ColorMode;
7use crate::style::{HEADING, OPTION, styled};
8
9#[must_use]
11pub fn kv(
12 color: ColorMode,
13 rows: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
14) -> String {
15 let cells = rows
16 .into_iter()
17 .map(|(token, value)| vec![token.as_ref().to_owned(), value.as_ref().to_owned()]);
18 render(color, None, cells, true)
19}
20
21#[must_use]
23pub fn grid(
24 color: ColorMode,
25 headers: &[&str],
26 rows: impl IntoIterator<Item = Vec<String>>,
27) -> String {
28 render(color, Some(headers), rows, true)
29}
30
31fn paint(color: ColorMode, style: anstyle::Style, value: &str) -> String {
32 if color == ColorMode::Never {
33 value.to_owned()
34 } else {
35 styled(style, value)
36 }
37}
38
39fn render(
40 color: ColorMode,
41 headers: Option<&[&str]>,
42 rows: impl IntoIterator<Item = Vec<String>>,
43 token_first: bool,
44) -> String {
45 let mut table = Table::new();
46 table
47 .load_style(UTF8_FULL_CONDENSED)
48 .set_content_arrangement(ContentArrangement::Dynamic);
49 if let Some(headers) = headers {
50 table.set_header(
51 headers
52 .iter()
53 .map(|header| Cell::new(paint(color, HEADING, header))),
54 );
55 }
56 for row in rows {
57 let cells = row.into_iter().enumerate().map(|(index, cell)| {
58 if token_first && index == 0 {
59 Cell::new(paint(color, OPTION, &cell))
60 } else {
61 Cell::new(cell)
62 }
63 });
64 table.add_row(cells);
65 }
66 format!("{table}")
67}
68
69#[cfg(test)]
70mod tests {
71 use super::{grid, kv};
72 use crate::color::ColorMode;
73 use crate::style::OPTION;
74
75 #[test]
76 fn kv_is_a_table_not_spaces() {
77 let out = kv(
78 ColorMode::Never,
79 [("crate", "demo@0.0.1"), ("package", "@org/pkg@0.0.1")],
80 );
81 assert!(out.contains("crate"), "{out}");
82 assert!(out.contains("demo@0.0.1"), "{out}");
83 assert!(out.contains("package"), "{out}");
84 assert!(out.contains('│') || out.contains('|'), "{out}");
85 assert!(!out.contains(&OPTION.render().to_string()), "{out}");
86 }
87
88 #[test]
89 fn never_has_no_ansi() {
90 let out = kv(ColorMode::Never, [("release", "v0.0.1")]);
91 assert!(!out.contains('\u{1b}'), "{out:?}");
92 assert!(out.contains("release"), "{out}");
93 }
94
95 #[test]
96 fn grid_has_headers() {
97 let out = grid(
98 ColorMode::Never,
99 &["id", "runner"],
100 [vec!["linux-x64".into(), "ubuntu-latest".into()]],
101 );
102 assert!(out.contains("linux-x64"), "{out}");
103 assert!(out.contains("ubuntu-latest"), "{out}");
104 assert!(out.contains("id"), "{out}");
105 }
106}