1use std::collections::BTreeMap;
5use std::path::PathBuf;
6use std::sync::Arc;
7
8use crate::{Config, format};
9
10#[derive(Debug, Default, Clone, PartialEq)]
12pub struct Failure {
13 pub errors: Vec<String>,
14}
15
16impl std::fmt::Display for Failure {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 for e in &self.errors {
19 e.fmt(f)?;
20 writeln!(f)?;
21 }
22 Ok(())
23 }
24}
25
26pub async fn run(config: Config, fix: bool) -> cu::Result<Result<(), Failure>> {
32 let bar = cu::progress_unbounded_lowp(if fix {
33 "fixing files"
34 } else {
35 "processing files"
36 });
37
38 let mut path_map = BTreeMap::new();
39 let mut handles = Vec::new();
40 let mut no_match_glob = Vec::new();
41 let mut glob_errors = Vec::new();
42
43 let pool = cu::co::pool(1024);
45 for (glob, holder, license) in config.into_iter() {
46 let result = run_glob(
47 &glob,
48 holder,
49 license,
50 fix,
51 &pool,
52 &mut handles,
53 &mut path_map,
54 );
55 match result {
56 Ok(matched) => {
57 if !matched {
58 no_match_glob.push(glob);
59 }
60 }
61 Err(e) => {
62 glob_errors.push((glob, e));
63 }
64 }
65 }
66 let total = handles.len();
69 bar.set_total(total);
70 let mut set = cu::co::set(handles);
71
72 if !glob_errors.is_empty() {
74 for (glob, error) in &glob_errors {
75 cu::error!("while globbing '{glob}': {error}");
76 }
77 cu::error!(
78 "got {} errors while searching for files, see above",
79 glob_errors.len()
80 );
81 cu::bail!("error while searching for files");
82 }
83
84 let mut count = 0;
85 let mut errors = vec![];
86 while let Some(result) = set.next().await {
87 let (path, result) = result?;
89 if let Err(e) = result {
91 errors.push(e)
92 }
93 count += 1;
94 cu::progress!(&bar, count, "{}", path.display());
95 }
96
97 if !errors.is_empty() {
98 let failed = errors.len();
99 cu::error!("checked {total} files, found {failed} issue(s).");
100 cu::hint!("run with --fix to fix them automatically.");
101
102 let errors = errors.into_iter().map(|x| x.to_string()).collect();
103
104 return Ok(Err(Failure { errors }));
105 }
106
107 cu::info!("license check successful for {total} files.");
108 Ok(Ok(()))
109}
110
111fn run_glob(
112 glob: &str,
113 holder: Arc<String>,
114 license: Arc<String>,
115 fix: bool,
116 pool: &cu::co::Pool,
117 handles: &mut Vec<cu::co::Handle<(PathBuf, cu::Result<()>)>>,
118 path_map: &mut BTreeMap<PathBuf, (Arc<String>, Arc<String>)>,
119) -> cu::Result<bool> {
120 let mut matched = false;
121 for path in cu::fs::glob(glob)? {
122 let path = path?;
123 if !path.is_file() {
124 continue;
125 }
126 matched = true;
127 let holder = Arc::clone(&holder);
128 let license = Arc::clone(&license);
129
130 let handle = if fix {
133 use std::collections::btree_map::Entry;
134 match path_map.entry(path.clone()) {
135 Entry::Occupied(e) => {
136 let (existing_h, existing_l) = e.get();
137 if (existing_h, existing_l) != (&holder, &license) {
138 cu::error!(
139 "file '{}' matched by multiple globs of conflicting config!",
140 e.key().display()
141 );
142 cu::error!(
143 "- in one config, it has holder '{holder}' and license '{license}'"
144 );
145 cu::error!(
146 "- in another, it has holder '{existing_h}' and license '{existing_l}'"
147 );
148 cu::bail!(
149 "conflicting config found for '{}', while globbing '{glob}'",
150 e.key().display()
151 );
152 }
153 continue;
156 }
157 Entry::Vacant(e) => e.insert((Arc::clone(&holder), Arc::clone(&license))),
158 };
159 pool.spawn(async move {
160 let check_result = format::check_file(&path, &holder, &license);
161 let Err(e) = check_result else {
162 return (path, Ok(()));
163 };
164 cu::trace!("'{}': {e}", path.display());
165 cu::debug!("fixing '{}'", path.display());
166 let Err(e) = format::fix_file(&path, &holder, &license) else {
167 return (path, Ok(()));
168 };
169 cu::error!("failed to fix '{}': {e}", path.display());
170 (path, Err(e))
171 })
172 } else {
173 pool.spawn(async move {
174 let Err(e) = format::check_file(&path, &holder, &license) else {
175 return (path, Ok(()));
176 };
177 cu::warn!("'{}': {e}", path.display());
178 (path, Err(e))
179 })
180 };
181
182 handles.push(handle);
183 }
184
185 Ok(matched)
186}