1pub mod analysis;
2pub mod cli;
3pub mod error;
4pub mod output;
5pub mod traversal;
6pub mod update;
7
8use cli::AnalyzeArgs;
9use error::{ArboristError, ExitReport};
10
11pub fn run(args: &AnalyzeArgs) -> Result<ExitReport, ArboristError> {
12 let is_stdin = args.paths.is_empty() && atty::is(atty::Stream::Stdin).not_tty();
13
14 if is_stdin {
15 let language = args.language.as_deref().ok_or(ArboristError::NoLanguage)?;
16 let config = analysis::build_config(args);
17 let report = analysis::analyze_stdin(language, &config)?;
18 let reports = vec![report];
19 let (reports, threshold_exceeded) = analysis::apply_filters(&reports, args);
20 output::write_output(&reports, args, threshold_exceeded)?;
21 return Ok(ExitReport {
22 threshold_exceeded,
23 had_errors: false,
24 });
25 }
26
27 if args.paths.is_empty() {
28 return Err(ArboristError::InvalidArgument(
29 "no files or directories specified and stdin is not a pipe".into(),
30 ));
31 }
32
33 let config = analysis::build_config(args);
34 let (reports, errors) = analysis::analyze_paths(&args.paths, &config, args)?;
35
36 for err in &errors {
37 eprintln!("warning: {err}");
38 }
39
40 let (filtered, threshold_exceeded) = analysis::apply_filters(&reports, args);
41 output::write_output(&filtered, args, threshold_exceeded)?;
42
43 Ok(ExitReport {
44 threshold_exceeded,
45 had_errors: !errors.is_empty(),
46 })
47}
48
49mod atty {
50 pub enum Stream {
51 Stdin,
52 }
53
54 pub struct AttyResult(bool);
55
56 impl AttyResult {
57 pub fn not_tty(self) -> bool {
58 !self.0
59 }
60 }
61
62 pub fn is(stream: Stream) -> AttyResult {
63 match stream {
64 Stream::Stdin => {
65 use std::io::IsTerminal;
66 AttyResult(std::io::stdin().is_terminal())
67 }
68 }
69 }
70}