1use std::cmp::max;
2use std::collections::HashMap;
3use std::fs;
4use std::io::Error;
5use std::io::ErrorKind;
6use std::path::{Path, PathBuf};
7
8use globset::GlobSet;
9
10use crate::ignore::load_ignore_patterns;
11use crate::text_fmt::{color_branch, color_path};
12
13type DirectoryBody = HashMap<PathBuf, Vec<PathBuf>>;
14
15const WALL: &str = "│ ";
16const MIDDLE_CHILD: &str = "├── ";
17const LAST_CHILD: &str = "└── ";
18const SPACE: &str = " ";
19
20#[derive(Debug)]
21pub struct Directory {
22 root: PathBuf,
23 body: DirectoryBody,
24 max_depth: u8,
25 ignore_patterns: GlobSet,
26}
27
28impl Directory {
29 pub fn new(root: &Path, ignore: bool) -> Result<Self, Error> {
30 let mut res = Self {
31 root: root.to_path_buf(),
32 body: HashMap::new(),
33 max_depth: 0,
34 ignore_patterns: if ignore {
35 load_ignore_patterns(root.to_path_buf())?
36 } else {
37 GlobSet::empty()
38 },
39 };
40 res.walk_dir(root, 1)?;
41 Ok(res)
42 }
43
44 pub fn new_with_empty_body(root: &Path, ignore: bool) -> Result<Self, Error> {
45 let res = Self {
46 root: root.to_path_buf(),
47 body: HashMap::new(),
48 max_depth: 0,
49 ignore_patterns: if ignore {
50 load_ignore_patterns(root.to_path_buf())?
51 } else {
52 GlobSet::empty()
53 },
54 };
55 Ok(res)
56 }
57
58 fn walk_dir(&mut self, from: &Path, depth: u8) -> Result<(), Error> {
60 if !from.to_path_buf().is_dir() {
61 let err_msg = format!("{} is not a directory", from.display());
62 return Err(Error::new(ErrorKind::InvalidInput, err_msg));
63 }
64 self.max_depth = max(self.max_depth, depth);
65
66 for entry in fs::read_dir(from)? {
67 let entry = entry?;
68 let path = entry.path();
69 if !self.ignore_patterns.is_match(&path) {
72 match self.body.get_mut(from) {
73 Some(v) => v.push(path.clone()),
74 None => {
75 self.body.insert(from.to_path_buf(), vec![path.clone()]);
76 }
77 }
78 if path.is_dir() {
80 self.walk_dir(&path, depth + 1)?;
81 }
82 }
83 }
84 return Ok(());
85 }
86
87 pub fn print_body(&self) -> Result<(), Error> {
89 self.print_dir(None, &mut Vec::new(), None)?;
90 println!();
91 Ok(())
92 }
93
94 fn print_dir(
97 &self,
98 cur: Option<&Path>,
99 wall_list: &mut Vec<bool>,
100 pos: Option<&str>,
101 ) -> Result<(), Error> {
102 let cur = cur.unwrap_or(&self.root);
103 let pos = pos.unwrap_or(LAST_CHILD);
104
105 println!();
107 for (i, w) in wall_list.iter().enumerate() {
108 if *w {
109 print!("{}", color_branch(WALL, i as u8, self.max_depth));
110 } else {
111 print!("{}", color_branch(SPACE, i as u8, self.max_depth))
112 }
113 }
114 print!(
115 "{}",
116 color_branch(pos, wall_list.len() as u8, self.max_depth)
117 );
118 print!("{}", color_path(cur));
119
120 wall_list.push(pos != LAST_CHILD);
122
123 let mut children = self.body.get(cur).unwrap_or(&Vec::new()).clone();
125 children.sort_by(|a, b| a.to_str().unwrap().cmp(b.to_str().unwrap()));
126 for (i, child) in children.iter().enumerate() {
127 let child_pos = if i == children.len() - 1 {
128 LAST_CHILD
129 } else {
130 MIDDLE_CHILD
131 };
132 self.print_dir(Some(&child), wall_list, Some(child_pos))?;
133 }
134
135 wall_list.pop();
137
138 Ok(())
139 }
140
141 pub fn fast_print_body(&self) -> Result<(), Error> {
144 self.fast_print_dir(None, &mut Vec::new(), None)?;
145 println!();
146 Ok(())
147 }
148
149 fn fast_print_dir(
151 &self,
152 cur: Option<&Path>,
153 wall_list: &mut Vec<bool>,
154 pos: Option<&str>,
155 ) -> Result<(), Error> {
156 let cur = cur.unwrap_or(&self.root);
157 let pos = pos.unwrap_or(LAST_CHILD);
158
159 println!();
161 for w in wall_list.iter() {
162 if *w {
163 print!("{}", WALL);
164 } else {
165 print!("{}", SPACE)
166 }
167 }
168 print!("{}", pos);
169 print!("{}", color_path(cur));
170
171 wall_list.push(pos != LAST_CHILD);
173
174 if !cur.to_path_buf().is_dir() {
176 wall_list.pop();
177 return Ok(());
178 }
179
180 let mut children = Vec::new();
182 for entry in fs::read_dir(cur)? {
183 let entry = entry?;
184 let path = entry.path();
185 if !self.ignore_patterns.is_match(&path) {
187 children.push(path);
188 }
189 }
190 children.sort_by(|a, b| a.to_str().unwrap().cmp(b.to_str().unwrap()));
191 for (i, child) in children.iter().enumerate() {
192 let child_pos = if i == children.len() - 1 {
193 LAST_CHILD
194 } else {
195 MIDDLE_CHILD
196 };
197 self.fast_print_dir(Some(&child), wall_list, Some(child_pos))?;
198 }
199
200 wall_list.pop();
202
203 Ok(())
204 }
205}