1use anyhow::Result;
6
7use crate::args::Args;
8use crate::config::Config;
9use crate::header_source::HeaderSource;
10use crate::json_printer::JsonPrinter;
11use crate::manifest_resolver::ManifestResolver;
12use crate::offence::Offence;
13use crate::offence_threshold::OffenceThreshold;
14use crate::output_format::OutputFormat;
15use crate::report_printer::ReportPrinter;
16use crate::rule_registry::RuleRegistry;
17use crate::run_outcome::RunOutcome;
18use crate::source_reader::SourceReader;
19use crate::source_walker::SourceWalker;
20
21pub struct Runner;
37
38impl Runner {
39 pub fn run(args: Args) -> Result<RunOutcome> {
40 let config = Self::config_from(args)?;
41 let registry = RuleRegistry::from_config(&config);
42 if registry.is_empty() {
43 return Err(anyhow::anyhow!(
44 "no rules are configured, so nothing would be checked -- pass --header-file to \
45 enable the header rule"
46 ));
47 }
48
49 let roots = ManifestResolver::package_roots(&config)?;
50 let mut offences = Vec::new();
51 let mut files_scanned = 0usize;
52
53 for root in &roots {
54 let mut files = Vec::new();
59 for path in SourceWalker::walk(root) {
60 files_scanned += 1;
61 match SourceReader::read(root, &path) {
62 Ok(file) => files.push(file),
63 Err(offence) => offences.push(*offence),
64 }
65 }
66 for file in &files {
67 offences.extend(registry.check(file));
68 }
69 offences.extend(registry.check_workspace(&files));
70 }
71
72 offences.sort_by(|left, right| left.sort_key().cmp(&right.sort_key()));
77 Self::report(&config, files_scanned, &offences);
78 Ok(RunOutcome::of(offences.len()))
79 }
80
81 fn config_from(args: Args) -> Result<Config> {
82 let expected_header = match &args.header_file {
83 Some(path) => HeaderSource::read(path)?,
84 None => Vec::new(),
85 };
86 Ok(Config {
87 manifest_path: args.manifest_path,
88 packages: args.packages,
89 expected_header,
90 format: args.format,
91 offence_threshold: OffenceThreshold::new(args.offence_threshold),
92 })
93 }
94
95 fn report(config: &Config, files_scanned: usize, offences: &[Offence]) {
96 let threshold = config.offence_threshold;
97 match config.format {
98 OutputFormat::Text => ReportPrinter::new(files_scanned)
99 .with_threshold(threshold)
100 .print(offences),
101 OutputFormat::Json => JsonPrinter::new(files_scanned)
102 .with_threshold(threshold)
103 .print(offences),
104 }
105 }
106}