dev_scope/analyze/
cli.rs

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    /// Run checks against your machine, generating support output.
21    #[clap(alias("log"))]
22    Logs(AnalyzeLogsArgs),
23}
24
25#[derive(Debug, Args)]
26struct AnalyzeLogsArgs {
27    /// Location that the logs should be searched, for stdin use '-'
28    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}