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