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 = "NoDebug mode flag")]
15  pub(crate) nodebug: 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) cmd: 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 = "Timeout second for each task", default_value_t = 600)]
35  pub(crate) timeout: u64,
36  #[clap(long, help = "Change the directory to perform test", default_value = "./tmp")]
37  pub(crate) workdir: PathBuf,
38  #[clap(value_parser)]
39  pub(crate) rootdir: PathBuf,
40  #[clap(skip)]
41  pub(crate) rootdir_abs: PathBuf,
42}
43
44impl Args {
45  pub const fn nodebug(mut self) -> Self {
46    self.nodebug = true;
47    self
48  }
49  pub const fn print_errs(mut self) -> Self {
50    self.print_errs = true;
51    self
52  }
53  pub const fn permits(mut self, permits: u32) -> Self {
54    self.permits = permits;
55    self
56  }
57  pub const fn timeout(mut self, timeout: u64) -> Self {
58    self.timeout = timeout;
59    self
60  }
61  pub fn cmd(mut self, cmd: impl AsRef<str>) -> Self {
62    self.cmd = cmd.as_ref().into();
63    self
64  }
65  pub fn args(mut self, iter: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
66    self.args = iter.into_iter().map(|s| s.as_ref().into()).collect();
67    self
68  }
69  pub fn workdir(mut self, dir: impl AsRef<Path>) -> Self {
70    self.workdir = dir.as_ref().to_path_buf();
71    self
72  }
73  pub fn extensions(mut self, iter: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
74    self.extensions = iter.into_iter().map(|s| s.as_ref().into()).collect();
75    self
76  }
77  pub fn include(mut self, iter: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
78    self.include = iter.into_iter().map(|s| s.as_ref().to_path_buf()).collect();
79    self
80  }
81  pub fn exclude(mut self, iter: impl IntoIterator<Item = impl AsRef<Path>>) -> Self {
82    self.exclude = iter.into_iter().map(|s| s.as_ref().to_path_buf()).collect();
83    self
84  }
85  pub fn new(rootdir: impl AsRef<Path>) -> Self {
86    <Self as Parser>::parse_from([Path::new(""), rootdir.as_ref()])
87  }
88  pub fn parse_from<I, T>(itr: I) -> Self
89  where
90    I: IntoIterator<Item = T>,
91    T: Into<OsString> + Clone,
92  {
93    <Self as Parser>::parse_from(itr)
94  }
95  pub(crate) fn rebuild(mut self) -> Result<&'static Self, BuildError> {
96    self.rootdir_abs = std::fs::canonicalize(&self.rootdir)
97      .map_err(|e| BuildError::ReadDir(self.rootdir.to_path_buf(), e))?;
98    self.include_set = take(&mut self.include)
99      .into_iter()
100      .map(|path| match std::fs::canonicalize(&path) {
101        Ok(p) => Ok(p),
102        Err(e) => Err(BuildError::ReadDir(path, e)),
103      })
104      .collect::<Result<HashSet<_>, _>>()?;
105    self.exclude_set = take(&mut self.exclude)
106      .into_iter()
107      .map(|path| match std::fs::canonicalize(&path) {
108        Ok(p) => Ok(p),
109        Err(e) => Err(BuildError::ReadDir(path, e)),
110      })
111      .collect::<Result<HashSet<_>, _>>()?;
112    if self.extensions.iter().any(|s| s == "toml") {
113      return Err(BuildError::InputExtToml);
114    }
115    Ok(Box::leak(Box::new(self)))
116  }
117  pub(super) fn filtered(&self, file: &Path) -> Result<bool, BuildError> {
118    let file_abs = std::fs::canonicalize(file)
119      .map_err(|e| BuildError::ReadDir(file.to_path_buf(), e))?;
120    let included = if self.include_set.is_empty() {
121      true
122    } else {
123      self.include_set.contains(&file_abs)
124    };
125    let excluded = if self.exclude_set.is_empty() {
126      false
127    } else {
128      self.exclude_set.contains(&file_abs)
129    };
130    Ok(!included || excluded)
131  }
132}