1mod column_formatter;
2mod dir_comparer;
3mod file_comparer;
4mod file_hash_cache;
5mod file_hasher;
6mod file_item;
7mod file_iterator;
8mod progress;
9mod sort_stream;
10mod system_time_ext;
11
12pub(crate) use column_formatter::ColumnFormatter;
13pub use dir_comparer::{DirectoryComparer, FileComparisonMethod};
14pub use file_comparer::{Classification, FileComparer, FileComparisonResult};
15pub(crate) use file_hash_cache::FileHashCache;
16pub use file_hasher::{DuplicatedFiles, FileHasher};
17pub use file_item::FileItem;
18pub(crate) use file_iterator::FileIterator;
19pub(crate) use progress::Progress;
20pub use progress::ProgressBuilder;
21pub(crate) use progress::ProgressValue;
22pub(crate) use progress::SharedProgress;
23pub(crate) use sort_stream::sort_stream;
24pub(crate) use system_time_ext::SystemTimeExt;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum OutputFormat {
29 Default,
30 Symbol,
31 Yaml,
32 Shell,
33 PowerShell,
34}
35
36use std::path::{Path, PathBuf};
37
38pub(crate) fn build_thread_pool(
39 threads: usize,
40) -> Result<rayon::ThreadPool, rayon::ThreadPoolBuildError> {
41 rayon::ThreadPoolBuilder::new().num_threads(threads).build()
42}
43
44pub(crate) fn human_readable_size(size: u64) -> String {
45 const KB: u64 = 1024;
46 if size < KB {
47 return format!("{} bytes", size);
48 }
49 const KB_AS_F: f64 = KB as f64;
50 let mut size = size as f64;
51 for unit in ["KB", "MB"] {
52 size /= KB_AS_F;
53 if size < KB_AS_F {
54 return format!("{:.1}{}", size, unit);
55 }
56 }
57 format!("{:.1}GB", size / KB_AS_F)
58}
59
60pub(crate) fn common_ancestor(paths: &[impl AsRef<Path>]) -> Option<PathBuf> {
61 if paths.is_empty() {
62 return None;
63 }
64 let mut iter = paths.iter();
65 let mut common = iter.next()?.as_ref().to_path_buf();
66 for path in iter {
67 let path = path.as_ref();
68 let mut new_common = PathBuf::new();
69 for (c, p) in common.components().zip(path.components()) {
70 if c == p {
71 new_common.push(c);
72 } else {
73 break;
74 }
75 }
76 common = new_common;
77 if common.as_os_str().is_empty() {
78 return None;
79 }
80 }
81 Some(common)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn human_readable_size_tests() {
90 assert_eq!(human_readable_size(0), "0 bytes");
91 assert_eq!(human_readable_size(1), "1 bytes");
92 assert_eq!(human_readable_size(1023), "1023 bytes");
93 assert_eq!(human_readable_size(1024), "1.0KB");
94 assert_eq!(human_readable_size(1024 * 1024), "1.0MB");
95 assert_eq!(human_readable_size(1024 * 1024 * 1024), "1.0GB");
96 assert_eq!(human_readable_size(1024 * 1024 * 1024 * 1024), "1024.0GB");
97 }
98
99 #[test]
100 fn common_ancestor_tests() {
101 let empty: &[PathBuf] = &[];
102 assert_eq!(common_ancestor(empty), None);
103
104 let p1 = Path::new("/a/b/c");
105 assert_eq!(common_ancestor(&[p1]), Some(PathBuf::from("/a/b/c")));
106
107 let p2 = Path::new("/a/b/d");
108 assert_eq!(common_ancestor(&[p1, p2]), Some(PathBuf::from("/a/b")));
109
110 let p3 = Path::new("/a/x/y");
111 assert_eq!(common_ancestor(&[p1, p2, p3]), Some(PathBuf::from("/a")));
112
113 let p4 = Path::new("/b/c");
114 assert_eq!(common_ancestor(&[p1, p4]), Some(PathBuf::from("/")));
115
116 let p5 = Path::new("/a/b");
118 assert_eq!(common_ancestor(&[p1, p5]), Some(PathBuf::from("/a/b")));
119
120 let r1 = Path::new("a/b");
122 let r2 = Path::new("c/d");
123 assert_eq!(common_ancestor(&[r1, r2]), None);
124
125 let a1 = Path::new("/a/b");
127 let r3 = Path::new("a/b");
128 assert_eq!(common_ancestor(&[a1, r3]), None);
129 }
130}