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