Skip to main content

swdir/helpers/
dir_node.rs

1use std::{cmp::Ordering, path::PathBuf};
2
3#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
4/// directory tree
5pub struct DirNode {
6    pub path: PathBuf,
7    pub sub_dirs: Vec<DirNode>, // subdirectories (recursion)
8    pub files: Vec<PathBuf>,    // files
9}
10
11impl DirNode {
12    /// new() with specific path
13    pub fn with_path<T: Into<PathBuf>>(path: T) -> Self {
14        Self {
15            path: path.into(),
16            sub_dirs: vec![],
17            files: vec![],
18        }
19    }
20
21    /// return path list
22    pub fn flatten_paths(&self) -> Vec<PathBuf> {
23        let mut ret = self.files.clone();
24        ret.extend(
25            self.sub_dirs
26                .iter()
27                .flat_map(|dir_node| dir_node.flatten_paths()),
28        );
29        // todo: sort alg: lower dir first ?
30        ret.sort_by(flatten_paths_sort);
31        ret
32    }
33}
34
35/// note: use extension() instead of is_dir() because file system i/o is heavier
36fn flatten_paths_sort(a: &PathBuf, b: &PathBuf) -> Ordering {
37    // 第一条件: ディレクトリ階層数
38    let depth_a = a.components().count();
39    let depth_b = b.components().count();
40
41    depth_a
42        .cmp(&depth_b)
43        .then_with(|| {
44            // 第二条件: パスが '/' で終わるかどうかでディレクトリ判定
45            // または拡張子がないものをディレクトリとみなす
46            let is_likely_dir_a = a.extension().is_none();
47            let is_likely_dir_b = b.extension().is_none();
48
49            // ディレクトリを先に(false < true なので逆にする)
50            is_likely_dir_b.cmp(&is_likely_dir_a)
51        })
52        .then_with(|| {
53            // 第三条件: ファイル名でソート
54            let name_a = a.file_name().unwrap_or_default();
55            let name_b = b.file_name().unwrap_or_default();
56            name_a.cmp(name_b)
57        })
58}