1mod color;
4mod columns;
5mod csv;
6mod human;
7mod hyperlink;
8mod icons;
9mod json;
10mod long;
11mod perms;
12mod quoting;
13mod tree;
14
15pub use color::Colorizer;
16pub use columns::{
17 format_columns, format_columns_cfg, format_one_per_line, format_one_per_line_cfg,
18};
19pub use csv::{format_csv, format_tsv};
20pub use human::{
21 block_display, block_display_with_unit, format_size_bytes, human_size, human_size_si,
22};
23pub use hyperlink::hyperlink_name;
24pub use icons::{icon_for, icon_prefix};
25pub use json::format_json;
26pub use long::{
27 format_long, format_long_line, format_long_line_simple, format_long_simple, format_time_style,
28};
29pub use perms::{classify_suffix, classify_suffix_bool, format_permissions};
30pub use quoting::{display_name, quote_name};
31pub use tree::format_tree;
32
33use f00_core::{Config, Entry, Listing, OutputMode};
34
35pub fn format_listing(listing: &Listing, config: &Config) -> std::result::Result<String, String> {
37 let colorizer = Colorizer::new(config.color_enabled());
38 format_entries(&listing.entries, config, &colorizer)
39}
40
41pub fn format_listings(
43 listings: &[Listing],
44 config: &Config,
45) -> std::result::Result<String, String> {
46 let colorizer = Colorizer::new(config.color_enabled());
47 let multi = listings.len() > 1 || listings.iter().any(|l| l.root_is_dir && listings.len() > 1);
48
49 if matches!(config.effective_output(), OutputMode::Json) {
51 let mut all = Vec::new();
52 for l in listings {
53 all.extend(l.entries.iter().cloned());
54 }
55 return format_json(&all).map_err(|e| e.to_string());
56 }
57
58 if matches!(config.effective_output(), OutputMode::Csv | OutputMode::Tsv) {
60 let mut all = Vec::new();
61 for l in listings {
62 all.extend(l.entries.iter().cloned());
63 }
64 return Ok(match config.effective_output() {
65 OutputMode::Csv => format_csv(&all),
66 OutputMode::Tsv => format_tsv(&all),
67 _ => unreachable!(),
68 });
69 }
70
71 let mut out = String::new();
72 let ending = config.line_ending();
73 for (i, listing) in listings.iter().enumerate() {
74 if i > 0 {
75 if !config.zero {
76 out.push('\n');
77 } else {
78 }
81 }
82 let is_dir_self = listing.root_is_dir
85 && listing.entries.len() == 1
86 && listing.entries[0].path == listing.root;
87 if (multi || listings.len() > 1) && listing.root_is_dir && !is_dir_self {
88 out.push_str(&format!("{}:", listing.root.display()));
89 out.push_str(ending);
90 }
91
92 let mut cfg = config.clone();
94 cfg.emit_block_total = listing.root_is_dir && !is_dir_self;
95 out.push_str(&format_entries(&listing.entries, &cfg, &colorizer)?);
96 }
97 Ok(out)
98}
99
100fn format_entries(
101 entries: &[Entry],
102 config: &Config,
103 colorizer: &Colorizer,
104) -> std::result::Result<String, String> {
105 let icons = config.icons;
106 let indicator = config.indicator_style();
107 match config.effective_output() {
108 OutputMode::Long => Ok(format_long(entries, colorizer, config)),
109 OutputMode::OnePerLine => Ok(format_one_per_line_cfg(entries, colorizer, config)),
110 OutputMode::Commas => Ok(format_commas(entries, colorizer, config)),
111 OutputMode::Json => format_json(entries).map_err(|e| e.to_string()),
112 OutputMode::Tree => Ok(format_tree(entries, colorizer, icons, indicator)),
113 OutputMode::Csv => Ok(format_csv(entries)),
114 OutputMode::Tsv => Ok(format_tsv(entries)),
115 OutputMode::Across => Ok(format_columns_cfg(entries, colorizer, config, true)),
116 OutputMode::Default | OutputMode::Columns => {
117 Ok(format_columns_cfg(entries, colorizer, config, false))
118 }
119 }
120}
121
122fn format_commas(entries: &[Entry], colorizer: &Colorizer, config: &Config) -> String {
123 let hide_ctrl = config.hide_control_chars();
124 let icons = config.icons;
125 let indicator = config.indicator_style();
126 let hyper = config.hyperlink_enabled();
127 let mut parts = Vec::new();
128 for entry in entries.iter().filter(|e| !e.is_dir_header) {
129 let icon = icon_prefix(entry, icons);
130 let suffix = classify_suffix(entry, indicator);
131 let quoted = display_name(&entry.name, config.quoting_style, hide_ctrl);
132 let plain = format!("{icon}{quoted}{suffix}");
133 let painted = colorizer.paint_name(entry, &plain);
134 parts.push(hyperlink_name(&entry.path, &painted, hyper));
135 }
136 let mut out = parts.join(", ");
137 if !out.is_empty() {
138 out.push_str(config.line_ending());
139 }
140 out
141}