1use std::path::{Path, PathBuf};
2
3pub enum FindUpKind {
4 File,
5 Dir,
6}
7
8pub struct FindUpOptions<'a> {
9 pub cwd: &'a Path,
10 pub kind: FindUpKind,
11}
12
13impl Default for FindUpOptions<'_> {
14 fn default() -> Self {
15 Self {
16 cwd: Path::new("."),
17 kind: FindUpKind::File,
18 }
19 }
20}
21
22#[inline]
23pub fn find_up<T: AsRef<Path>>(file_name: T) -> std::io::Result<Option<PathBuf>> {
26 find_up_with(file_name, Default::default())
27}
28
29pub fn find_up_with<T: AsRef<Path>>(
31 file_name: T,
32 options: FindUpOptions,
33) -> std::io::Result<Option<PathBuf>> {
34 let target_file_name = file_name.as_ref();
35 let cwd_buf = std::env::current_dir().unwrap();
36 let cwd = if options.cwd.eq(Path::new(".")) {
37 Path::new(&cwd_buf)
38 } else {
39 options.cwd
40 };
41 let mut target_dir = Some(cwd);
42 let is_search_dir = matches!(options.kind, FindUpKind::Dir);
43
44 while let Some(dir) = target_dir {
45 let mut dir_iterator = std::fs::read_dir(dir)?.peekable();
46 if dir_iterator.peek().is_none() {
47 target_dir = dir.parent();
48 continue;
49 }
50 for entry in dir_iterator {
51 let entry = entry?;
52 let path = entry.path();
53 if path.is_dir() {
54 if is_search_dir {
55 if let Some(file_name) = path.file_name() {
56 if target_file_name == file_name {
57 return Ok(Some(path));
58 }
59 }
60 }
61 } else if let Some(file_name) = path.file_name() {
62 if target_file_name == file_name {
63 return Ok(Some(path));
64 }
65 }
66 target_dir = dir.parent()
67 }
68 }
69 Ok(None)
70}