1use anyhow::{Context, Result};
2use std::collections::HashSet;
3use std::env;
4use std::fs;
5use std::io::Read;
6use std::path::{Path, PathBuf};
7use syn::Item;
8
9const CARGO_TOML: &str = "Cargo.toml";
10
11pub fn root_path() -> Result<PathBuf> {
13 let manifest = search_manifest()?;
14 let root = manifest
15 .parent()
16 .with_context(|| format!("{:?} has no parent", manifest))?;
17 fs::canonicalize(root).context("root path convert to absolute err")
19}
20
21pub fn search_manifest() -> Result<PathBuf> {
23 search_manifest_from(
24 &env::current_dir().context("get current dir err")?,
25 CARGO_TOML,
26 )
27}
28
29pub fn search_manifest_from(start_dir: &PathBuf, file_name: &str) -> Result<PathBuf> {
31 let mut path = start_dir.as_path();
32 loop {
33 let toml = path.join(file_name);
34 if toml.exists() {
35 return fs::canonicalize(toml).context("manifest path convert to absolute path err");
36 }
37
38 path = path
39 .parent()
40 .with_context(|| format!("Cargo.toml not found search from: {:?}", start_dir))?
41 }
42}
43
44pub fn find_main_file(dir: &Path) -> Result<Vec<PathBuf>> {
47 let mut ignored_folders = HashSet::new();
48 for folder in ["target", "src/bin", ".git", ".github"].iter() {
49 ignored_folders.insert(dir.join(*folder));
50 }
51 let mut files = vec![];
52
53 fn find(dir: &Path, files: &mut Vec<PathBuf>, ignored: &HashSet<PathBuf>) -> Result<()> {
54 if !dir.is_dir() {
55 return Ok(());
56 }
57 if ignored.contains(&dir.to_path_buf()) {
58 return Ok(());
59 }
60 for entry in fs::read_dir(dir).with_context(|| format!("read_dir err, dir: {:?}", dir))? {
61 let entry = entry.with_context(|| "dir entry err")?;
62 let path = entry.path();
63 if path.is_dir() {
64 find(&path, files, ignored)?;
65 continue;
66 }
67
68 let ext = path
69 .as_path()
70 .extension()
71 .map_or("", |v| v.to_str().unwrap_or(""));
72 if ext != "rs" {
73 continue;
74 }
75
76 if contains_main(&path)? {
77 files.push(path)
78 }
79 }
80 Ok(())
81 }
82
83 find(dir, &mut files, &ignored_folders)?;
84
85 Ok(files)
86}
87
88fn contains_main(path: &Path) -> Result<bool> {
90 let mut file = fs::File::open(path).with_context(|| format!("open file {:?} err", path))?;
91 let mut content = String::new();
92 file.read_to_string(&mut content)
93 .with_context(|| format!("read file {:?} err", path))?;
94
95 let ast = syn::parse_file(&content)?;
96
97 let is_main = ast.items.iter().any(|v| match v {
98 Item::Fn(item) => item.sig.ident == "main",
99 _ => false,
100 });
101
102 Ok(is_main)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn search() {
111 let dir = env::current_dir().unwrap().join("misc");
112 let file_path = search_manifest_from(&dir, "test-cargo.toml").expect("search should be ok");
113 println!("file_path: {:?}", file_path);
114 }
115}