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}