cargo_regression/
args.rs

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  /// to schedule tasks, default is 1
14  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  // #[setters(skip)]
20  pub(crate) root_dir_abs: &'static Path,
21  // TODO: use static hashset
22  pub(crate) extensions: &'static [&'static str],
23  // TODO: use static hashset
24  pub(crate) include: &'static [&'static Path],
25  pub(crate) exclude: &'static [&'static Path],
26}
27
28#[derive(Debug, Parser)]
29#[command(version)]
30// , separated by space
31struct 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}