browser_automation_cli/
find_paths.rs1use std::path::{Path, PathBuf};
4
5use ignore::WalkBuilder;
6use regex::RegexBuilder;
7use serde_json::{json, Value};
8
9use crate::error::{CliError, ErrorKind};
10
11#[derive(Debug, Clone)]
13pub struct FindPathsOpts {
14 pub pattern: String,
16 pub roots: Vec<PathBuf>,
18 pub extension: Option<String>,
20 pub hidden: bool,
22 pub no_ignore: bool,
24 pub max_depth: Option<usize>,
26 pub entry_type: Option<String>,
28 pub limit: usize,
30}
31
32impl Default for FindPathsOpts {
33 fn default() -> Self {
34 Self {
35 pattern: String::new(),
36 roots: vec![PathBuf::from(".")],
37 extension: None,
38 hidden: false,
39 no_ignore: false,
40 max_depth: None,
41 entry_type: None,
42 limit: 10_000,
43 }
44 }
45}
46
47pub fn find_paths(opts: &FindPathsOpts) -> Result<Value, CliError> {
49 let roots = if opts.roots.is_empty() {
50 vec![PathBuf::from(".")]
51 } else {
52 opts.roots.clone()
53 };
54 let re = if opts.pattern.is_empty() {
55 None
56 } else {
57 Some(
58 RegexBuilder::new(&opts.pattern)
59 .case_insensitive(true)
60 .build()
61 .map_err(|e| {
62 CliError::new(ErrorKind::Usage, format!("invalid find-paths pattern: {e}"))
63 })?,
64 )
65 };
66 let mut paths = Vec::new();
67 for root in &roots {
68 let mut builder = WalkBuilder::new(root);
69 builder.hidden(!opts.hidden);
70 builder.git_ignore(!opts.no_ignore);
71 builder.git_global(!opts.no_ignore);
72 builder.git_exclude(!opts.no_ignore);
73 builder.ignore(!opts.no_ignore);
74 if let Some(d) = opts.max_depth {
75 builder.max_depth(Some(d));
76 }
77 builder.threads(
78 std::thread::available_parallelism()
79 .map(|n| n.get())
80 .unwrap_or(2),
81 );
82 for entry in builder.build() {
83 let entry = match entry {
84 Ok(e) => e,
85 Err(_) => continue,
86 };
87 let ft = entry.file_type();
88 let is_file = ft.map(|t| t.is_file()).unwrap_or(false);
89 let is_dir = ft.map(|t| t.is_dir()).unwrap_or(false);
90 if let Some(ref t) = opts.entry_type {
91 match t.as_str() {
92 "f" | "file" if !is_file => continue,
93 "d" | "dir" | "directory" if !is_dir => continue,
94 _ => {}
95 }
96 }
97 let path = entry.path();
98 let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
99 if let Some(ref ext) = opts.extension {
100 let e = path.extension().and_then(|x| x.to_str()).unwrap_or("");
101 if !e.eq_ignore_ascii_case(ext.trim_start_matches('.')) {
102 continue;
103 }
104 }
105 if let Some(ref re) = re {
106 if !re.is_match(name) && !re.is_match(&path.to_string_lossy()) {
107 continue;
108 }
109 }
110 paths.push(path.display().to_string());
111 if opts.limit > 0 && paths.len() >= opts.limit {
112 break;
113 }
114 }
115 if opts.limit > 0 && paths.len() >= opts.limit {
116 break;
117 }
118 }
119 Ok(json!({
120 "count": paths.len(),
121 "paths": paths,
122 "pattern": opts.pattern,
123 "engine": "ignore",
124 "chrome": false,
125 }))
126}
127
128pub fn roots_from(paths: &[String]) -> Vec<PathBuf> {
130 if paths.is_empty() {
131 vec![PathBuf::from(".")]
132 } else {
133 paths.iter().map(|p| Path::new(p).to_path_buf()).collect()
134 }
135}