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}