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 out.push_str(&format_entries(&listing.entries, config, &colorizer)?);
93 }
94 Ok(out)
95}
96
97fn format_entries(
98 entries: &[Entry],
99 config: &Config,
100 colorizer: &Colorizer,
101) -> std::result::Result<String, String> {
102 let icons = config.icons;
103 let indicator = config.indicator_style();
104 match config.effective_output() {
105 OutputMode::Long => Ok(format_long(entries, colorizer, config)),
106 OutputMode::OnePerLine => Ok(format_one_per_line_cfg(entries, colorizer, config)),
107 OutputMode::Commas => Ok(format_commas(entries, colorizer, config)),
108 OutputMode::Json => format_json(entries).map_err(|e| e.to_string()),
109 OutputMode::Tree => Ok(format_tree(entries, colorizer, icons, indicator)),
110 OutputMode::Csv => Ok(format_csv(entries)),
111 OutputMode::Tsv => Ok(format_tsv(entries)),
112 OutputMode::Across => Ok(format_columns_cfg(entries, colorizer, config, true)),
113 OutputMode::Default | OutputMode::Columns => {
114 Ok(format_columns_cfg(entries, colorizer, config, false))
115 }
116 }
117}
118
119fn format_commas(entries: &[Entry], colorizer: &Colorizer, config: &Config) -> String {
120 let hide_ctrl = config.hide_control_chars();
121 let icons = config.icons;
122 let indicator = config.indicator_style();
123 let hyper = config.hyperlink_enabled();
124 let mut parts = Vec::new();
125 for entry in entries.iter().filter(|e| !e.is_dir_header) {
126 let icon = icon_prefix(entry, icons);
127 let suffix = classify_suffix(entry, indicator);
128 let quoted = display_name(&entry.name, config.quoting_style, hide_ctrl);
129 let plain = format!("{icon}{quoted}{suffix}");
130 let painted = colorizer.paint_name(entry, &plain);
131 parts.push(hyperlink_name(&entry.path, &painted, hyper));
132 }
133 let mut out = parts.join(", ");
134 if !out.is_empty() {
135 out.push_str(config.line_ending());
136 }
137 out
138}