1use super::error::AnalyzeError;
2use crate::models::HelpMetadata;
3use crate::shared::prelude::FoundConfig;
4use anyhow::Result;
5use clap::{Args, Subcommand};
6use std::collections::BTreeMap;
7use std::path::PathBuf;
8use tokio::fs::File;
9use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader, Stdin};
10use tracing::{debug, info, warn};
11
12#[derive(Debug, Args)]
13pub struct AnalyzeArgs {
14 #[clap(subcommand)]
15 command: AnalyzeCommands,
16}
17
18#[derive(Debug, Subcommand)]
19enum AnalyzeCommands {
20 #[clap(alias("log"))]
22 Logs(AnalyzeLogsArgs),
23}
24
25#[derive(Debug, Args)]
26struct AnalyzeLogsArgs {
27 location: String,
29}
30
31pub async fn analyze_root(found_config: &FoundConfig, args: &AnalyzeArgs) -> Result<i32> {
32 match &args.command {
33 AnalyzeCommands::Logs(args) => analyze_logs(found_config, args).await,
34 }
35}
36
37async fn analyze_logs(found_config: &FoundConfig, args: &AnalyzeLogsArgs) -> Result<i32> {
38 let has_known_error = match args.location.as_str() {
39 "-" => process_lines(found_config, read_from_stdin().await?).await?,
40 file_path => process_lines(found_config, read_from_file(file_path).await?).await?,
41 };
42
43 if has_known_error {
44 Ok(1)
45 } else {
46 Ok(0)
47 }
48}
49
50async fn process_lines<T>(found_config: &FoundConfig, input: T) -> Result<bool>
51where
52 T: AsyncRead,
53 T: AsyncBufReadExt,
54 T: Unpin,
55{
56 let mut has_known_error = false;
57 let mut known_errors: BTreeMap<_, _> = found_config.known_error.clone();
58 let mut line_number = 0;
59
60 let mut lines = input.lines();
61
62 while let Some(line) = lines.next_line().await? {
63 let mut known_errors_to_remove = Vec::new();
64 for (name, ke) in &known_errors {
65 debug!("Checking known error {}", ke.name());
66 if ke.regex.is_match(&line) {
67 warn!(target: "always", "Known error '{}' found on line {}", ke.name(), line_number);
68 info!(target: "always", "\t==> {}", ke.help_text);
69 known_errors_to_remove.push(name.clone());
70 has_known_error = true;
71 }
72 }
73
74 for name in known_errors_to_remove {
75 known_errors.remove(&name);
76 }
77
78 line_number += 1;
79
80 if known_errors.is_empty() {
81 info!(target: "always", "All known errors detected, ignoring rest of output.");
82 break;
83 }
84 }
85
86 Ok(has_known_error)
87}
88
89async fn read_from_stdin() -> Result<BufReader<Stdin>, AnalyzeError> {
90 Ok(BufReader::new(tokio::io::stdin()))
91}
92
93async fn read_from_file(file_name: &str) -> Result<BufReader<File>, AnalyzeError> {
94 let file_path = PathBuf::from(file_name);
95 if !file_path.exists() {
96 return Err(AnalyzeError::FileNotFound {
97 file_name: file_name.to_string(),
98 });
99 }
100 Ok(BufReader::new(File::open(file_path).await?))
101}