Skip to content

Commit 308ac76

Browse files
authored
Merge ebfc595 into e7dee6b
2 parents e7dee6b + ebfc595 commit 308ac76

4 files changed

Lines changed: 143 additions & 23 deletions

File tree

src/app/sub.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use pipe_trait::Pipe;
1717
use serde::Serialize;
1818
use std::{
1919
collections::HashMap,
20-
ffi::OsStr,
20+
ffi::{OsStr, OsString},
2121
io::stdout,
2222
iter::once,
2323
path::{Path, PathBuf},
@@ -133,7 +133,7 @@ where
133133
}
134134

135135
let min_ratio: f32 = min_ratio.into();
136-
let (data_tree, deduplication_record) = {
136+
let (mut data_tree, deduplication_record) = {
137137
let mut data_tree = data_tree;
138138
if min_ratio > 0.0 {
139139
data_tree.par_cull_insignificant_data(min_ratio);
@@ -144,11 +144,41 @@ where
144144
let deduplication_record = hardlinks_handler.deduplicate(&mut data_tree);
145145
if !only_one_arg {
146146
assert_eq!(data_tree.name().as_os_str().to_str(), Some(""));
147-
*data_tree.name_mut() = OsStringDisplay::os_string_from("(total)");
148147
}
149148
(data_tree, deduplication_record)
150149
};
151150

151+
// Build the coloring map while the multi-arg root is still "" (a valid path prefix)
152+
// so that file_color receives real filesystem paths.
153+
let mut leaf_color_map: Option<HashMap<Vec<OsString>, Color>> = color.as_ref().map(|_| {
154+
let mut map = HashMap::new();
155+
build_coloring_map(&data_tree, &mut Vec::new(), &mut map);
156+
map
157+
});
158+
159+
// Rename the synthetic root and rekey the coloring map to match.
160+
if !only_one_arg {
161+
*data_tree.name_mut() = OsStringDisplay::os_string_from("(total)");
162+
if let Some(map) = &mut leaf_color_map {
163+
let total = OsString::from("(total)");
164+
let empty = OsString::from("");
165+
*map = map
166+
.drain()
167+
.map(|(mut key, color)| {
168+
if key.first() == Some(&empty) {
169+
key[0] = total.clone();
170+
}
171+
(key, color)
172+
})
173+
.collect();
174+
}
175+
}
176+
177+
let coloring: Option<Coloring> = color.map(|ls_colors| {
178+
let map = leaf_color_map.take().unwrap();
179+
Coloring::new(ls_colors, map)
180+
});
181+
152182
GLOBAL_STATUS_BOARD.clear_line(0);
153183

154184
if let Some(json_output) = json_output {
@@ -198,12 +228,6 @@ where
198228
.or(deduplication_result);
199229
}
200230

201-
let coloring: Option<Coloring> = color.map(|ls_colors| {
202-
let mut map = HashMap::new();
203-
build_coloring_map(&data_tree, &mut Vec::new(), &mut map);
204-
Coloring::new(ls_colors, map)
205-
});
206-
207231
let visualizer = Visualizer {
208232
data_tree: &data_tree,
209233
bytes_format,
@@ -291,12 +315,12 @@ where
291315
fn build_coloring_map<'a>(
292316
node: &'a DataTree<OsStringDisplay, impl size::Size>,
293317
path_stack: &mut Vec<&'a OsStr>,
294-
map: &mut HashMap<Vec<&'a OsStr>, Color>,
318+
map: &mut HashMap<Vec<OsString>, Color>,
295319
) {
296320
path_stack.push(node.name().as_os_str());
297321
if node.children().is_empty() {
298322
let color = file_color(&path_stack.iter().collect::<PathBuf>());
299-
map.insert(path_stack.clone(), color);
323+
map.insert(path_stack.iter().map(|s| s.to_os_string()).collect(), color);
300324
} else {
301325
for child in node.children() {
302326
build_coloring_map(child, path_stack, map);

src/visualizer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ where
6262
/// Distribution and total number of characters/blocks can be placed in a line.
6363
pub column_width_distribution: ColumnWidthDistribution,
6464
/// Optional coloring configuration for colorful output, mapping full node paths to colors.
65-
pub coloring: Option<&'a Coloring<'a>>,
65+
pub coloring: Option<&'a Coloring>,
6666
}
6767

6868
mod copy;

src/visualizer/coloring.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
use super::{ChildPosition, TreeHorizontalSlice};
22
use crate::ls_colors::LsColors;
33
use derive_more::Display;
4-
use std::{collections::HashMap, ffi::OsStr, fmt};
4+
use std::{
5+
collections::HashMap,
6+
ffi::{OsStr, OsString},
7+
fmt,
8+
};
59
use zero_copy_pads::Width;
610

711
/// Coloring configuration: ANSI prefix strings from the environment and a full-path-to-color map.
812
#[derive(Debug)]
9-
pub struct Coloring<'a> {
13+
pub struct Coloring {
1014
ls_colors: LsColors,
11-
map: HashMap<Vec<&'a OsStr>, Color>,
15+
map: HashMap<Vec<OsString>, Color>,
1216
}
1317

14-
impl<'a> Coloring<'a> {
18+
impl Coloring {
1519
/// Create a new [`Coloring`] from LS_COLORS prefixes and a path-components-to-color map.
16-
pub fn new(ls_colors: LsColors, map: HashMap<Vec<&'a OsStr>, Color>) -> Self {
20+
pub fn new(ls_colors: LsColors, map: HashMap<Vec<OsString>, Color>) -> Self {
1721
Coloring { ls_colors, map }
1822
}
1923
}
@@ -91,7 +95,7 @@ impl Width for ColoredTreeHorizontalSlice<'_> {
9195
/// Path components are only constructed when coloring is enabled, avoiding
9296
/// unnecessary allocation in the common no-color case.
9397
pub(super) fn maybe_colored_slice<'a, 'b>(
94-
coloring: Option<&'b Coloring<'a>>,
98+
coloring: Option<&'b Coloring>,
9599
ancestors: impl Iterator<Item = &'a OsStr>,
96100
name: &'a OsStr,
97101
has_children: bool,
@@ -101,7 +105,10 @@ pub(super) fn maybe_colored_slice<'a, 'b>(
101105
Some(coloring) => coloring,
102106
None => return MaybeColoredTreeHorizontalSlice::Colorless(slice),
103107
};
104-
let path_components: Vec<&OsStr> = ancestors.chain(std::iter::once(name)).collect();
108+
let path_components: Vec<OsString> = ancestors
109+
.chain(std::iter::once(name))
110+
.map(OsString::from)
111+
.collect();
105112
let color = if has_children {
106113
Some(Color::Directory)
107114
} else {

tests/usual_cli.rs

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use parallel_disk_usage::{
2828
visualizer::{Color, Coloring},
2929
};
3030
#[cfg(unix)]
31-
use std::{collections::HashMap, ffi::OsStr};
31+
use std::{collections::HashMap, ffi::OsString};
3232

3333
fn stdio(command: Command) -> Command {
3434
command
@@ -857,9 +857,98 @@ fn color_always() {
857857
];
858858
let leaf_colors = HashMap::from(leaf_colors.map(|(path, color)| {
859859
(
860-
path.split('/')
861-
.map(AsRef::<OsStr>::as_ref)
862-
.collect::<Vec<_>>(),
860+
path.split('/').map(OsString::from).collect::<Vec<_>>(),
861+
color,
862+
)
863+
}));
864+
let coloring = Coloring::new(ls_colors, leaf_colors);
865+
866+
let visualizer = Visualizer::<OsStringDisplay, _> {
867+
data_tree: &data_tree,
868+
bytes_format: BytesFormat::MetricUnits,
869+
direction: Direction::BottomUp,
870+
bar_alignment: BarAlignment::Left,
871+
column_width_distribution: ColumnWidthDistribution::total(100),
872+
coloring: Some(&coloring),
873+
};
874+
let expected = format!("{visualizer}");
875+
let expected = expected.trim_end();
876+
eprintln!("EXPECTED:\n{expected}\n");
877+
878+
assert_eq!(actual, expected);
879+
}
880+
881+
#[cfg(unix)]
882+
#[test]
883+
fn color_always_multiple_args() {
884+
let workspace = SampleWorkspace::simple_tree_with_diverse_kinds();
885+
886+
let args = [
887+
"dir-a",
888+
"dir-b",
889+
"file-root.txt",
890+
"link-dir",
891+
"link-file.txt",
892+
"empty-dir-1",
893+
"empty-dir-2",
894+
];
895+
896+
let actual = {
897+
let mut cmd = Command::new(PDU);
898+
cmd = cmd
899+
.with_current_dir(&workspace)
900+
.with_arg("--color=always")
901+
.with_arg("--total-width=100")
902+
.with_arg("--min-ratio=0")
903+
.with_env("LS_COLORS", LS_COLORS);
904+
for arg in &args {
905+
cmd = cmd.with_arg(arg);
906+
}
907+
cmd.pipe(stdio)
908+
.output()
909+
.expect("spawn command with --color=always and multiple args")
910+
.pipe(stdout_text)
911+
};
912+
eprintln!("ACTUAL:\n{actual}\n");
913+
914+
let data_tree = args
915+
.iter()
916+
.map(|name| {
917+
let builder = FsTreeBuilder {
918+
root: workspace.to_path_buf().join(name),
919+
size_getter: DEFAULT_GET_SIZE,
920+
hardlinks_recorder: &HardlinkIgnorant,
921+
reporter: &ErrorOnlyReporter::new(ErrorReport::SILENT),
922+
max_depth: 10,
923+
};
924+
let mut data_tree: DataTree<OsStringDisplay, _> = builder.into();
925+
*data_tree.name_mut() = OsStringDisplay::os_string_from(name);
926+
data_tree
927+
})
928+
.pipe(|children| {
929+
DataTree::dir(
930+
OsStringDisplay::os_string_from("(total)"),
931+
0.into(),
932+
children.collect(),
933+
)
934+
})
935+
.into_par_sorted(|left, right| left.size().cmp(&right.size()).reverse());
936+
937+
let ls_colors = LsColors::from_str(LS_COLORS);
938+
let leaf_colors = [
939+
("(total)/dir-a/file-a1.txt", Color::Normal),
940+
("(total)/dir-a/file-a2.txt", Color::Normal),
941+
("(total)/dir-a/subdir-a/file-a3.txt", Color::Normal),
942+
("(total)/dir-b/file-b1.txt", Color::Normal),
943+
("(total)/file-root.txt", Color::Normal),
944+
("(total)/link-dir", Color::Symlink),
945+
("(total)/link-file.txt", Color::Symlink),
946+
("(total)/empty-dir-1", Color::Directory),
947+
("(total)/empty-dir-2", Color::Directory),
948+
];
949+
let leaf_colors = HashMap::from(leaf_colors.map(|(path, color)| {
950+
(
951+
path.split('/').map(OsString::from).collect::<Vec<_>>(),
863952
color,
864953
)
865954
}));

0 commit comments

Comments
 (0)