use fslite_core::Node;
use crate::CommandOutput;
fn is_bidi_override(ch: char) -> bool {
matches!(
ch,
'\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}' | '\u{200E}' | '\u{200F}' | '\u{061C}'
)
}
fn is_unicode_linebreak(ch: char) -> bool {
matches!(ch, '\u{2028}' | '\u{2029}')
}
pub fn sanitize_for_terminal(raw: &str) -> String {
raw.chars()
.filter(|&ch| {
ch == '\n'
|| ch == '\t'
|| is_unicode_linebreak(ch)
|| (!ch.is_control() && !is_bidi_override(ch))
})
.collect()
}
pub fn sanitize_name(raw: &str) -> String {
raw.chars()
.filter(|&ch| !ch.is_control() && !is_bidi_override(ch) && !is_unicode_linebreak(ch))
.collect()
}
pub fn sanitize_preview(raw: &str) -> String {
let mut escaped = String::with_capacity(raw.len());
for ch in sanitize_for_terminal(raw).chars() {
match ch {
'\n' => escaped.push_str("\\n"),
'\t' => escaped.push_str("\\t"),
'\u{2028}' => escaped.push_str("\\u{2028}"),
'\u{2029}' => escaped.push_str("\\u{2029}"),
other => escaped.push(other),
}
}
escaped
}
fn render_node_line(node: &Node) -> String {
format!(
"{:<10} {:>10} {}",
format!("{:?}", node.kind).to_lowercase(),
node.logical_size,
sanitize_name(&node.name)
)
}
pub fn render_human(output: &CommandOutput) -> String {
match output {
CommandOutput::Usage(usage) => format!(
"active: {} bytes / {} nodes\ntrashed: {} bytes / {} nodes\nquota: {} bytes / {} nodes",
usage.active_logical_bytes,
usage.active_nodes,
usage.trashed_logical_bytes,
usage.trashed_nodes,
usage.max_logical_bytes,
usage.max_nodes,
),
CommandOutput::Node(node) => render_node_line(node),
CommandOutput::Exists(found) => found.to_string(),
CommandOutput::Nodes(page) => page
.items
.iter()
.map(render_node_line)
.collect::<Vec<_>>()
.join("\n"),
CommandOutput::Tree(page) => page
.items
.iter()
.map(|entry| {
format!(
"{}{}",
" ".repeat(entry.depth as usize),
sanitize_name(entry.path.as_str())
)
})
.collect::<Vec<_>>()
.join("\n"),
CommandOutput::Content { bytes, .. } => String::from_utf8_lossy(bytes).into_owned(),
CommandOutput::Unit => "ok".to_string(),
CommandOutput::LinkTarget(target) => sanitize_name(target.as_str()),
CommandOutput::Trash(entry) => format!(
"{} (was {})",
entry.id,
sanitize_name(entry.original_path.as_str())
),
CommandOutput::TrashList(page) => page
.items
.iter()
.map(|entry| {
format!(
"{} {}",
entry.id,
sanitize_name(entry.original_path.as_str())
)
})
.collect::<Vec<_>>()
.join("\n"),
CommandOutput::SearchMatches(page) => page
.items
.iter()
.map(|m| {
format!(
"{}: {}",
sanitize_name(m.path.as_str()),
sanitize_preview(&String::from_utf8_lossy(&m.preview))
)
})
.collect::<Vec<_>>()
.join("\n"),
CommandOutput::Changes(page) => page
.items
.iter()
.map(|change| format!("{} {:?}", change.sequence, change.kind))
.collect::<Vec<_>>()
.join("\n"),
CommandOutput::Batch(results) => format!("{} operations completed", results.len()),
}
}
pub fn render_json(output: &CommandOutput) -> String {
serde_json::to_string_pretty(output).expect("CommandOutput always serializes")
}