pub(super) fn render_object<T: serde::Serialize>(data: &T) -> String {
let value = serde_json::to_value(data).expect("command results are serializable");
let mut lines = Vec::new();
for (name, value) in value.as_object().expect("command result object") {
render_named_value_complete(&mut lines, 0, name, value);
}
lines.join("\n")
}
pub(super) fn push_optional(lines: &mut Vec<String>, label: &str, value: Option<&str>) {
if let Some(value) = value {
lines.push(format!("{label}: {}", escape_terminal_controls(value)));
}
}
pub(super) fn push_optional_bool(lines: &mut Vec<String>, label: &str, value: Option<bool>) {
if let Some(value) = value {
lines.push(format!("{label}: {}", if value { "yes" } else { "no" }));
}
}
pub(super) fn render_named_value(
lines: &mut Vec<String>,
indent: usize,
name: &str,
value: &serde_json::Value,
) {
let padding = " ".repeat(indent);
match value {
serde_json::Value::Array(values) => {
lines.push(format!("{padding}{name}: [{}]", values.len()));
for (index, value) in values.iter().enumerate() {
render_named_value(lines, indent + 2, &format!("[{index}]"), value);
}
}
serde_json::Value::Object(values) => {
lines.push(format!("{padding}{name}:"));
for (name, value) in values {
if !value.is_null() {
render_named_value(lines, indent + 2, name, value);
}
}
}
serde_json::Value::String(value) => lines.push(format!(
"{padding}{name}: {}",
escape_terminal_controls(value)
)),
other => lines.push(format!("{padding}{name}: {other}")),
}
}
pub(super) fn render_named_value_complete(
lines: &mut Vec<String>,
indent: usize,
name: &str,
value: &serde_json::Value,
) {
let name = escape_terminal_controls(name);
let padding = " ".repeat(indent);
match value {
serde_json::Value::Array(values) => {
lines.push(format!("{padding}{name}: [{}]", values.len()));
for (index, value) in values.iter().enumerate() {
render_named_value_complete(lines, indent + 2, &format!("[{index}]"), value);
}
}
serde_json::Value::Object(values) => {
lines.push(format!("{padding}{name}:"));
for (name, value) in values {
render_named_value_complete(lines, indent + 2, name, value);
}
}
serde_json::Value::String(value) => lines.push(format!(
"{padding}{name}: {}",
escape_terminal_controls(value)
)),
other => lines.push(format!("{padding}{name}: {other}")),
}
}
pub(super) fn two_column_table(
left_header: &str,
right_header: &str,
rows: &[(String, String)],
) -> String {
let left_width = rows
.iter()
.map(|(left, _)| left.len())
.max()
.unwrap_or(left_header.len())
.max(left_header.len());
let mut lines = vec![format!("{left_header:<left_width$} {right_header}")];
lines.extend(
rows.iter()
.map(|(left, right)| format!("{left:<left_width$} {right}")),
);
lines.join("\n")
}
pub(super) fn escape_terminal_controls(value: &str) -> String {
let mut escaped = String::with_capacity(value.len());
for character in value.chars() {
if character.is_control() {
escaped.extend(character.escape_default());
} else {
escaped.push(character);
}
}
escaped
}