mod color;
mod columns;
mod csv;
mod human;
mod hyperlink;
mod icons;
mod json;
mod long;
mod perms;
mod quoting;
mod tree;
pub use color::Colorizer;
pub use columns::{
format_columns, format_columns_cfg, format_one_per_line, format_one_per_line_cfg,
};
pub use csv::{format_csv, format_tsv};
pub use human::{
block_display, block_display_with_unit, format_size_bytes, human_size, human_size_si,
};
pub use hyperlink::hyperlink_name;
pub use icons::{icon_for, icon_prefix};
pub use json::format_json;
pub use long::{
format_long, format_long_line, format_long_line_simple, format_long_simple, format_time_style,
};
pub use perms::{classify_suffix, classify_suffix_bool, format_permissions};
pub use quoting::{display_name, quote_name};
pub use tree::format_tree;
use f00_core::{Config, Entry, Listing, OutputMode};
pub fn format_listing(listing: &Listing, config: &Config) -> std::result::Result<String, String> {
let colorizer = Colorizer::new(config.color_enabled());
format_entries(&listing.entries, config, &colorizer)
}
pub fn format_listings(
listings: &[Listing],
config: &Config,
) -> std::result::Result<String, String> {
let colorizer = Colorizer::new(config.color_enabled());
let multi = listings.len() > 1 || listings.iter().any(|l| l.root_is_dir && listings.len() > 1);
if matches!(config.effective_output(), OutputMode::Json) {
let mut all = Vec::new();
for l in listings {
all.extend(l.entries.iter().cloned());
}
return format_json(&all).map_err(|e| e.to_string());
}
if matches!(config.effective_output(), OutputMode::Csv | OutputMode::Tsv) {
let mut all = Vec::new();
for l in listings {
all.extend(l.entries.iter().cloned());
}
return Ok(match config.effective_output() {
OutputMode::Csv => format_csv(&all),
OutputMode::Tsv => format_tsv(&all),
_ => unreachable!(),
});
}
let mut out = String::new();
let ending = config.line_ending();
for (i, listing) in listings.iter().enumerate() {
if i > 0 {
if !config.zero {
out.push('\n');
} else {
}
}
let is_dir_self = listing.root_is_dir
&& listing.entries.len() == 1
&& listing.entries[0].path == listing.root;
if (multi || listings.len() > 1) && listing.root_is_dir && !is_dir_self {
out.push_str(&format!("{}:", listing.root.display()));
out.push_str(ending);
}
out.push_str(&format_entries(&listing.entries, config, &colorizer)?);
}
Ok(out)
}
fn format_entries(
entries: &[Entry],
config: &Config,
colorizer: &Colorizer,
) -> std::result::Result<String, String> {
let icons = config.icons;
let indicator = config.indicator_style();
match config.effective_output() {
OutputMode::Long => Ok(format_long(entries, colorizer, config)),
OutputMode::OnePerLine => Ok(format_one_per_line_cfg(entries, colorizer, config)),
OutputMode::Commas => Ok(format_commas(entries, colorizer, config)),
OutputMode::Json => format_json(entries).map_err(|e| e.to_string()),
OutputMode::Tree => Ok(format_tree(entries, colorizer, icons, indicator)),
OutputMode::Csv => Ok(format_csv(entries)),
OutputMode::Tsv => Ok(format_tsv(entries)),
OutputMode::Across => Ok(format_columns_cfg(entries, colorizer, config, true)),
OutputMode::Default | OutputMode::Columns => {
Ok(format_columns_cfg(entries, colorizer, config, false))
}
}
}
fn format_commas(entries: &[Entry], colorizer: &Colorizer, config: &Config) -> String {
let hide_ctrl = config.hide_control_chars();
let icons = config.icons;
let indicator = config.indicator_style();
let hyper = config.hyperlink_enabled();
let mut parts = Vec::new();
for entry in entries.iter().filter(|e| !e.is_dir_header) {
let icon = icon_prefix(entry, icons);
let suffix = classify_suffix(entry, indicator);
let quoted = display_name(&entry.name, config.quoting_style, hide_ctrl);
let plain = format!("{icon}{quoted}{suffix}");
let painted = colorizer.paint_name(entry, &plain);
parts.push(hyperlink_name(&entry.path, &painted, hyper));
}
let mut out = parts.join(", ");
if !out.is_empty() {
out.push_str(config.line_ending());
}
out
}