const CLI_FORMAT_ALIASES: &[(&str, &str, &str)] = &[("rest", "dir-json", "REST (directory only format)")];
pub fn resolve_cli_format(fmt: &str) -> String {
let fmt_lower = fmt.to_lowercase();
for (alias, canonical, _display) in CLI_FORMAT_ALIASES {
if alias.eq_ignore_ascii_case(&fmt_lower) {
return canonical.to_string();
}
}
fmt.to_string()
}
pub fn get_cli_only_formats() -> Vec<(String, String, String, String)> {
CLI_FORMAT_ALIASES
.iter()
.map(|(name, _canonical, display)| (name.to_string(), ".json".to_string(), String::new(), display.to_string()))
.collect()
}
pub fn format_columns<S: AsRef<str>>(lines: &[Vec<S>], prefix: &str) -> Vec<String> {
if lines.is_empty() {
return Vec::new();
}
let ncols = lines.iter().map(|l| l.len()).max().unwrap_or(0);
if ncols == 0 {
return Vec::new();
}
let maxlen: Vec<usize> = (0..ncols.saturating_sub(1))
.map(|c| lines.iter().map(|l| l.get(c).map(|s| s.as_ref().len()).unwrap_or(0)).max().unwrap_or(0))
.collect();
lines
.iter()
.map(|l| {
let mut parts: Vec<String> = Vec::new();
for (c, s) in l.iter().enumerate() {
if c < ncols - 1 {
let pad = maxlen.get(c).copied().unwrap_or(0).saturating_sub(s.as_ref().len()) + 2;
parts.push(format!("{}{}", s.as_ref(), " ".repeat(pad)));
} else {
parts.push(s.as_ref().to_string());
}
}
format!("{}{}", prefix, parts.join("").trim_end())
})
.collect()
}
pub fn format_map_columns<K: AsRef<str>, V: AsRef<str>>(items: &[(K, V)], prefix: &str) -> Vec<String> {
let lines: Vec<Vec<String>> =
items.iter().map(|(k, v)| vec![k.as_ref().to_string(), v.as_ref().to_string()]).collect();
format_columns(&lines, prefix)
}
pub fn format_table<S: AsRef<str>>(headers: &[S], rows: &[Vec<String>]) -> Vec<String> {
if headers.is_empty() {
return Vec::new();
}
let ncols = headers.len();
let col_widths: Vec<usize> = (0..ncols)
.map(|c| {
let header_width = headers[c].as_ref().len();
let row_width = rows.iter().map(|r| r.get(c).map(|s| s.len()).unwrap_or(0)).max().unwrap_or(0);
std::cmp::max(header_width, row_width)
})
.collect();
let make_separator = |left: &str, _mid: &str, right: &str, cross: &str| -> String {
let mut parts: Vec<String> = Vec::new();
parts.push(left.to_string());
for (i, w) in col_widths.iter().enumerate() {
parts.push("─".repeat(*w + 2)); if i < ncols - 1 {
parts.push(cross.to_string());
}
}
parts.push(right.to_string());
parts.join("")
};
let mut result = Vec::new();
result.push(make_separator("┌", "─", "┐", "┬"));
let mut header_parts: Vec<String> = Vec::new();
header_parts.push("│".to_string());
for (c, h) in headers.iter().enumerate() {
let width = col_widths[c];
header_parts.push(format!(" {:width$} ", h.as_ref(), width = width));
header_parts.push("│".to_string());
}
result.push(header_parts.join(""));
result.push(make_separator("├", "─", "┤", "┼"));
for row in rows {
let mut row_parts: Vec<String> = Vec::new();
row_parts.push("│".to_string());
for (c, val) in row.iter().enumerate() {
let width = col_widths.get(c).copied().unwrap_or(0);
let display_val = if val.len() > width { &val[..width] } else { val.as_str() };
row_parts.push(format!(" {:width$} ", display_val, width = width));
row_parts.push("│".to_string());
}
result.push(row_parts.join(""));
}
result.push(make_separator("└", "─", "┘", "┴"));
result
}