1use anyhow::{anyhow, Result};
4use clap::Parser;
5use log::{debug, info, LevelFilter};
6use std::path::PathBuf;
7use std::thread;
8
9use crate::setup;
10use crate::simplelog;
11use super::handlers;
12
13#[derive(Debug, Parser)]
14#[command(version, about, long_about = None)]
15struct Options {
16 #[arg(value_name = "path")]
18 pub inputs: Vec<PathBuf>,
19
20 #[arg(long = "handler")]
23 pub handlers: Vec<String>,
24
25 #[arg(long)]
27 pub brp: bool,
28
29 #[arg(short, long)]
31 pub verbose: bool,
32
33 #[arg(long)]
35 pub check: bool,
36
37 #[arg(long)]
39 pub ignore_extension: bool,
40
41 #[arg(short, long,
43 conflicts_with = "brp",
44 conflicts_with = "check",
45 conflicts_with = "jobs")]
46 pub print: bool,
47
48 #[arg(short = 'j',
50 value_name = "N",
51 num_args = 0..=1,
52 default_missing_value = "0")]
53 pub jobs: Option<u32>,
54}
55
56pub struct Config {
57 pub inputs: Vec<PathBuf>,
58 pub brp: bool,
59 pub print: bool,
60 pub check: bool,
61 pub ignore_extension: bool,
62 pub jobs: Option<u32>,
63 pub source_date_epoch: Option<i64>,
64 pub handler_names: Vec<&'static str>,
65 pub strict_handlers: bool,
66}
67
68fn filter_by_name(name: &str, enabled_by_default: bool, filter: &[&str]) -> bool {
69 let mut negative_filter = true;
70
71 for f in filter.iter().rev() {
72 if let Some(f) = f.strip_prefix('-') {
73 if name == f {
74 return false;
75 }
76 } else {
77 negative_filter = false;
78
79 if name == *f {
80 return true;
81 }
82 }
83 }
84
85 enabled_by_default && negative_filter
86}
87
88pub fn requested_handlers(filter: &[&str]) -> Result<(Vec<&'static str>, bool)> {
89 if filter.iter().any(|x| x.starts_with('-')) &&
90 filter.iter().any(|x| !x.starts_with('-')) {
91 return Err(anyhow!("Cannot mix --handler options with '-' and without"));
92 }
93
94 if let Some(name) = filter
95 .iter()
96 .map(|x| x.strip_prefix('-').unwrap_or(x))
97 .find(|x| !handlers::handler_names().contains(x))
98 {
99 return Err(anyhow!("Unknown handler name: {:?}", name));
100 }
101
102 let list: Vec<&'static str> = handlers::HANDLERS
103 .iter()
104 .filter(|(name, enabled_by_default, _)| filter_by_name(name, *enabled_by_default, filter))
105 .map(|(name, _, _)| *name)
106 .collect();
107
108 if list.is_empty() {
109 return Err(anyhow!("Requested handler list is empty, will have nothing to do"));
110 }
111
112 let strict = !filter.is_empty();
113 debug!("Requested handlers: {} (strict={})", list.join(", "), strict);
114 Ok((list, strict))
115}
116
117impl Config {
118 pub fn make() -> Result<Option<Self>> {
119 let options = Options::parse();
120
121 let handlers: Vec<&str> = options.handlers.iter().flat_map(|s| s.split(',')).collect();
124
125 if handlers.contains(&"list") {
126 println!("{}", handlers::handler_names().join("\n"));
127 return Ok(None);
128 }
129
130 let log_level = if options.verbose { LevelFilter::Debug } else { LevelFilter::Info };
133 let show_pid = options.verbose && options.jobs.is_some();
136 simplelog::init(log_level, show_pid)?;
137
138 let (handler_names, strict_handlers) = requested_handlers(&handlers)?;
139
140 if options.inputs.is_empty() && !options.brp {
143 info!("No arguments specified, nothing to do. 😎");
144 }
145
146 let source_date_epoch = setup::source_date_epoch()?;
148
149 let jobs = options.jobs.map(
150 |n| if n > 0 { n } else {
151 let j = thread::available_parallelism().unwrap().get();
152 debug!("Will use {j} workers");
153 j as u32
154 }
155 );
156
157 Ok(Some(Self {
158 inputs: options.inputs,
159 brp: options.brp,
160 print: options.print,
161 check: options.check,
162 ignore_extension: options.ignore_extension,
163 jobs,
164 source_date_epoch,
165 handler_names,
166 strict_handlers,
167 }))
168 }
169
170 #[allow(dead_code)]
171 pub const fn empty(source_date_epoch: i64, check: bool) -> Self {
173 Self {
174 inputs: vec![],
175 brp: false,
176 print: false,
177 check,
178 ignore_extension: false,
179 jobs: None,
180 source_date_epoch: Some(source_date_epoch),
181 handler_names: vec![],
182 strict_handlers: false,
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_filter_by_name() {
193 assert_eq!(filter_by_name("x", true, &vec!["x", "y"]), true);
194 assert_eq!(filter_by_name("x", true, &vec!["x"]), true);
195 assert_eq!(filter_by_name("x", true, &vec![]), true);
196 assert_eq!(filter_by_name("x", true, &vec!["-x"]), false);
197 assert_eq!(filter_by_name("x", true, &vec!["-y"]), true);
198
199 assert_eq!(filter_by_name("x", false, &vec!["x", "y"]), true);
200 assert_eq!(filter_by_name("x", false, &vec!["x"]), true);
201 assert_eq!(filter_by_name("x", false, &vec![]), false);
202 assert_eq!(filter_by_name("x", false, &vec!["-x"]), false);
203 assert_eq!(filter_by_name("x", false, &vec!["-y"]), false);
204 }
205
206 #[test]
207 fn test_requested_handlers() {
208 let (list, strict) = requested_handlers(&vec![]).unwrap();
209 assert_eq!(list, vec!["ar", "jar", "javadoc", "gzip", "pyc", "zip"]);
210 assert_eq!(strict, false);
211
212 let (list, strict) = requested_handlers(&vec!["ar", "pyc-zero-mtime"]).unwrap();
213 assert_eq!(list, vec!["ar", "pyc-zero-mtime"]);
214 assert_eq!(strict, true);
215
216 let (list, strict) = requested_handlers(&vec!["-pyc-zero-mtime"]).unwrap();
217 assert_eq!(list, vec!["ar", "jar", "javadoc", "gzip", "pyc", "zip"]);
218 assert_eq!(strict, true);
219 }
220}