1use clap::Parser;
2use std::{
3 ffi::{OsStr, OsString},
4 path::{Path, PathBuf},
5};
6
7use crate::regression::BuildError;
8
9#[derive(Debug, Clone, Copy)]
10pub struct Args {
11 pub(crate) debug: bool,
12 pub(crate) print_errs: bool,
13 pub(crate) permits: u32,
15 pub(crate) exe_path: &'static str,
16 pub(crate) args: &'static [&'static str],
17 pub(crate) work_dir: &'static Path,
18 pub(crate) root_dir: &'static Path,
19 pub(crate) root_dir_abs: &'static Path,
21 pub(crate) extensions: &'static [&'static str],
23 pub(crate) include: &'static [&'static Path],
25 pub(crate) exclude: &'static [&'static Path],
26}
27
28#[derive(Debug, Parser)]
29#[command(version)]
30struct ArgsBuilder {
32 #[clap(long, help = "Debug mode flag, recommended")]
33 debug: bool,
34 #[clap(long, help = "Print errors [default: false, save errs to report]")]
35 print_errs: bool,
36 #[clap(long, help = "Default executable path")]
37 exe_path: Option<String>,
38 #[clap(long, help = "Default arguements", num_args = 1..)]
39 args: Vec<String>,
40 #[clap(long, help="Default input extensions(s)", num_args = 1..)]
41 extensions: Vec<String>,
42 #[clap(long, help="Input include. E.g., --include ./cases/*", default_value = None, num_args = 1..)]
43 include: Vec<String>,
44 #[clap(long, help="Input exclude. E.g., --exclude ./cases/*", default_value = None, num_args = 1..)]
45 exclude: Vec<String>,
46 #[clap(long, help = "Total permits to limit max parallelism", default_value_t = 1)]
47 permits: u32,
48 #[clap(long, default_value_t = String::from("./tmp"))]
49 work_dir: String,
50 #[clap(value_parser)]
51 root_dir: String,
52}
53
54impl Default for Args {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl Args {
61 pub const fn debug(mut self) -> Self {
62 self.debug = true;
63 self
64 }
65 pub const fn print_errs(mut self) -> Self {
66 self.print_errs = true;
67 self
68 }
69 pub const fn permits(mut self, permits: u32) -> Self {
70 self.permits = permits;
71 self
72 }
73 pub const fn exe_path(mut self, exe_path: &'static str) -> Self {
74 self.exe_path = exe_path;
75 self
76 }
77 pub const fn args(mut self, args: &'static [&'static str]) -> Self {
78 self.args = args;
79 self
80 }
81 pub fn work_dir(mut self, dir: &'static str) -> Self {
82 self.work_dir = Path::new(dir);
83 self
84 }
85 pub fn root_dir(mut self, dir: &'static str) -> Self {
86 self.root_dir = Path::new(dir);
87 self
88 }
89 pub const fn extensions(mut self, extensions: &'static [&'static str]) -> Self {
90 self.extensions = extensions;
91 self
92 }
93 pub fn include(mut self, files: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
94 self.include = leak_path_vec(files);
95 self
96 }
97 pub fn exclude(mut self, files: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
98 self.exclude = leak_path_vec(files);
99 self
100 }
101 pub fn new() -> Self {
102 Self {
103 debug: false,
104 print_errs: false,
105 permits: 1,
106 exe_path: "",
107 args: &[],
108 work_dir: Path::new(""),
109 root_dir: Path::new(""),
110 root_dir_abs: Path::new(""),
111 extensions: &[],
112 include: &[],
113 exclude: &[],
114 }
115 }
116 pub(crate) fn rebuild(mut self) -> Result<Self, BuildError> {
117 self.root_dir_abs = leak_path(
118 std::fs::canonicalize(self.root_dir)
119 .map_err(|e| BuildError::ReadDir(self.root_dir.to_path_buf(), e))?,
120 );
121 self.include = leak_path_vec_res(self.include.iter().map(|path| {
122 match std::fs::canonicalize(path) {
123 Ok(p) => Ok(p),
124 Err(e) => Err(BuildError::ReadDir(path.to_path_buf(), e)),
125 }
126 }))?;
127 self.exclude = leak_path_vec_res(self.exclude.iter().map(|path| {
128 match std::fs::canonicalize(path) {
129 Ok(p) => Ok(p),
130 Err(e) => Err(BuildError::ReadDir(path.to_path_buf(), e)),
131 }
132 }))?;
133 if self.extensions.iter().any(|&s| s == "toml") {
134 return Err(BuildError::InputExtToml);
135 }
136 Ok(self)
137 }
138 pub fn parse_from<I, T>(itr: I) -> Self
139 where
140 I: IntoIterator<Item = T>,
141 T: Into<OsString> + Clone,
142 {
143 let builder = ArgsBuilder::parse_from(itr);
144 Args {
145 debug: builder.debug,
146 print_errs: builder.print_errs,
147 permits: builder.permits,
148 exe_path: builder.exe_path.map_or("", leak_string),
149 args: leak_string_vec(builder.args),
150 extensions: leak_string_vec(builder.extensions),
151 include: leak_path_vec(builder.include),
152 exclude: leak_path_vec(builder.exclude),
153 work_dir: leak_path(builder.work_dir),
154 root_dir: leak_path(builder.root_dir),
155 root_dir_abs: Path::new(""),
156 }
157 }
158 pub(super) fn filtered(&self, file: &Path) -> Result<bool, BuildError> {
159 let file_abs = std::fs::canonicalize(file)
160 .map_err(|e| BuildError::ReadDir(file.to_path_buf(), e))?;
161 let included = if self.include.is_empty() {
162 true
163 } else {
164 self.include.iter().any(|pattern| *pattern == file_abs)
165 };
166 let excluded = if self.exclude.is_empty() {
167 false
168 } else {
169 self.exclude.iter().any(|pattern| *pattern == file_abs)
170 };
171 Ok(!included || excluded)
172 }
173}
174
175fn leak_string(s: String) -> &'static str {
176 Box::leak(s.into_boxed_str())
177}
178fn leak_path(s: impl AsRef<Path>) -> &'static Path {
179 Box::leak(s.as_ref().to_path_buf().into_boxed_path())
180}
181fn leak_string_vec(iter: impl IntoIterator<Item = String>) -> &'static [&'static str] {
182 Box::leak(
183 iter
184 .into_iter()
185 .map(leak_string)
186 .collect::<Vec<_>>()
187 .into_boxed_slice(),
188 )
189}
190fn leak_path_vec(
191 iter: impl IntoIterator<Item = impl AsRef<Path>>,
192) -> &'static [&'static Path] {
193 Box::leak(iter.into_iter().map(leak_path).collect::<Vec<_>>().into_boxed_slice())
194}
195fn leak_path_vec_res<E>(
196 iter: impl IntoIterator<Item = Result<PathBuf, E>>,
197) -> Result<&'static [&'static Path], E> {
198 fn leak_path(s: PathBuf) -> &'static Path {
199 Box::leak(s.into_boxed_path())
200 }
201 Ok(Box::leak(
202 iter
203 .into_iter()
204 .map(|res| res.map(leak_path))
205 .collect::<Result<Vec<_>, _>>()?
206 .into_boxed_slice(),
207 ))
208}
209
210pub(crate) fn match_extension<I: Iterator<Item = impl AsRef<str>>>(
211 file: &Path,
212 extensions: I,
213) -> bool {
214 file
215 .extension()
216 .and_then(OsStr::to_str)
217 .and_then(|s| {
218 if extensions.into_iter().any(|ext_s| ext_s.as_ref() == s) {
219 Some(())
220 } else {
221 None
222 }
223 })
224 .is_some()
225}