cargo_regression/
args.rs

1use clap::Parser;
2use std::{
3  collections::HashSet,
4  ffi::OsString,
5  mem::take,
6  path::{Path, PathBuf},
7};
8
9use crate::regression::BuildError;
10
11#[derive(Debug, Parser)]
12#[command(version)]
13pub struct Args {
14  #[clap(long, help = "Debug mode flag, recommended")]
15  pub(crate) debug: bool,
16  #[clap(long, help = "Print errors [default: false, save errs to report]")]
17  pub(crate) print_errs: bool,
18  #[clap(long, help = "Default executable path", default_value_t = String::new())]
19  pub(crate) exe_path: String,
20  #[clap(long, help = "Default arguements", default_value = "{{name}}.{{extension}}", num_args = 1..)]
21  pub(crate) args: Vec<String>,
22  #[clap(long, help="Default input extensions(s)", num_args = 1..)]
23  pub(crate) extensions: Vec<String>,
24  #[clap(long, help="Input include. E.g., --include ./cases/*", num_args = 1..)]
25  include: Vec<PathBuf>,
26  #[clap(skip)]
27  include_set: HashSet<PathBuf>,
28  #[clap(long, help="Input exclude. E.g., --exclude ./cases/*", num_args = 1..)]
29  exclude: Vec<PathBuf>,
30  #[clap(skip)]
31  exclude_set: HashSet<PathBuf>,
32  #[clap(long, help = "Total permits to limit max parallelism", default_value_t = 1)]
33  pub(crate) permits: u32,
34  #[clap(long, help = "Change the directory to perform test", default_value = "./tmp")]
35  pub(crate) workdir: PathBuf,
36  #[clap(value_parser)]
37  pub(crate) rootdir: PathBuf,
38  #[clap(skip)]
39  pub(crate) rootdir_abs: PathBuf,
40}
41
42impl Args {
43  pub const fn debug(mut self) -> Self {
44    self.debug = true;
45    self
46  }
47  pub const fn print_errs(mut self) -> Self {
48    self.print_errs = true;
49    self
50  }
51  pub const fn permits(mut self, permits: u32) -> Self {
52    self.permits = permits;
53    self
54  }
55  pub fn exe_path(mut self, exe_path: impl AsRef<str>) -> Self {
56    self.exe_path = exe_path.as_ref().into();
57    self
58  }
59  pub fn args(mut self, iter: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
60    self.args = iter.into_iter().map(|s| s.as_ref().into()).collect();
61    self
62  }
63  pub fn workdir(mut self, dir: impl AsRef<Path>) -> Self {
64    self.workdir = dir.as_ref().to_path_buf();
65    self
66  }
67  pub fn extensions(mut self, iter: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
68    self.extensions = iter.into_iter().map(|s| s.as_ref().into()).collect();
69    self
70  }
71  pub fn include(mut self, iter: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
72    self.include = iter.into_iter().map(|s| s.as_ref().to_path_buf()).collect();
73    self
74  }
75  pub fn exclude(mut self, iter: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
76    self.exclude = iter.into_iter().map(|s| s.as_ref().to_path_buf()).collect();
77    self
78  }
79  pub fn new(rootdir: impl AsRef<Path>) -> Self {
80    <Self as Parser>::parse_from([Path::new(""), rootdir.as_ref()])
81  }
82  pub fn parse_from<I, T>(itr: I) -> Self
83  where
84    I: IntoIterator<Item = T>,
85    T: Into<OsString> + Clone,
86  {
87    <Self as Parser>::parse_from(itr)
88  }
89  pub(crate) fn rebuild(mut self) -> Result<&'static Self, BuildError> {
90    self.rootdir_abs = std::fs::canonicalize(&self.rootdir)
91      .map_err(|e| BuildError::ReadDir(self.rootdir.to_path_buf(), e))?;
92    self.include_set = take(&mut self.include)
93      .into_iter()
94      .map(|path| match std::fs::canonicalize(&path) {
95        Ok(p) => Ok(p),
96        Err(e) => Err(BuildError::ReadDir(path, e)),
97      })
98      .collect::<Result<HashSet<_>, _>>()?;
99    self.exclude_set = take(&mut self.exclude)
100      .into_iter()
101      .map(|path| match std::fs::canonicalize(&path) {
102        Ok(p) => Ok(p),
103        Err(e) => Err(BuildError::ReadDir(path, e)),
104      })
105      .collect::<Result<HashSet<_>, _>>()?;
106    if self.extensions.iter().any(|s| s == "toml") {
107      return Err(BuildError::InputExtToml);
108    }
109    Ok(Box::leak(Box::new(self)))
110  }
111  pub(super) fn filtered(&self, file: &Path) -> Result<bool, BuildError> {
112    let file_abs = std::fs::canonicalize(file)
113      .map_err(|e| BuildError::ReadDir(file.to_path_buf(), e))?;
114    let included = if self.include_set.is_empty() {
115      true
116    } else {
117      self.include_set.contains(&file_abs)
118    };
119    let excluded = if self.exclude_set.is_empty() {
120      false
121    } else {
122      self.exclude_set.contains(&file_abs)
123    };
124    Ok(!included || excluded)
125  }
126}