Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 48 additions & 55 deletions src/shared/analyze/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::internal::prompts::UserInteraction;
use crate::models::HelpMetadata;
use crate::prelude::{
CaptureOpts, DefaultExecutionProvider, DoctorFix, ExecutionProvider, KnownError, OutputCapture,
OutputDestination, generate_env_vars,
};
use anyhow::Result;
use inquire::InquireError;
use std::collections::BTreeMap;
use std::path::PathBuf;
use tokio::io::{AsyncBufReadExt, AsyncRead};
Expand All @@ -13,15 +13,28 @@ use tracing::{debug, error, info, warn};
mod status;
pub use crate::shared::analyze::status::{AnalyzeStatus, report_result};

pub async fn process_lines<T>(
/// Process lines of input to detect known errors and optionally run fixes.
///
/// This function scans the input for known error patterns. When a match is found,
/// it will prompt the user (via the `UserInteraction` trait) to run the associated fix.
///
/// # Arguments
/// * `known_errors` - Map of known errors to detect
/// * `working_dir` - Directory to run fix commands in
/// * `input` - Async reader providing the lines to analyze
/// * `user_interaction` - Implementation of `UserInteraction` for prompting
///
/// # Returns
/// `AnalyzeStatus` indicating the outcome of the analysis
pub async fn process_lines<T, U>(
known_errors: &BTreeMap<String, KnownError>,
working_dir: &PathBuf,
input: T,
user_interaction: &U,
) -> Result<AnalyzeStatus>
where
T: AsyncRead,
T: AsyncBufReadExt,
T: Unpin,
T: AsyncRead + AsyncBufReadExt + Unpin,
U: UserInteraction,
{
let mut result = AnalyzeStatus::NoKnownErrorsFound;
let mut known_errors = known_errors.clone();
Expand All @@ -41,11 +54,8 @@ where
Some(fix) => {
info!(target: "always", "found a fix!");

tracing_indicatif::suspend_tracing_indicatif(|| {
let exec_path = ke.metadata.exec_path();
prompt_and_run_fix(working_dir, exec_path, fix)
})
.await?
let exec_path = ke.metadata.exec_path();
prompt_and_run_fix(working_dir, exec_path, fix, user_interaction).await?
}
None => AnalyzeStatus::KnownErrorFoundNoFixFound,
};
Expand All @@ -69,62 +79,45 @@ where
Ok(result)
}

async fn prompt_and_run_fix(
async fn prompt_and_run_fix<U: UserInteraction>(
working_dir: &PathBuf,
exec_path: String,
fix: &DoctorFix,
user_interaction: &U,
) -> Result<AnalyzeStatus> {
let fix_prompt = &fix.prompt.as_ref();
let prompt_text = fix_prompt
.map(|p| p.text.clone())
.unwrap_or("Would you like to run it?".to_string());
let extra_context = &fix_prompt.map(|p| p.extra_context.clone()).flatten();

let prompt = {
let base_prompt = inquire::Confirm::new(&prompt_text).with_default(false);
match extra_context {
Some(help_text) => base_prompt.with_help_message(help_text),
None => base_prompt,
}
};

match prompt.prompt() {
Ok(user_accepted) => {
if user_accepted {
// failure indicates an issue with us actually executing it,
// not the success/failure of the command itself.
let outputs = run_fix(working_dir, &exec_path, fix).await?;
let max_exit_code = outputs
.iter()
.map(|c| c.exit_code.unwrap_or(-1))
.max()
.unwrap();

match max_exit_code {
0 => Ok(AnalyzeStatus::KnownErrorFoundFixSucceeded),
_ => {
if let Some(help_text) = &fix.help_text {
error!(target: "user", "Fix Help: {}", help_text);
}
if let Some(help_url) = &fix.help_url {
error!(target: "user", "For more help, please visit {}", help_url);
}

Ok(AnalyzeStatus::KnownErrorFoundFixFailed)
}
let extra_context = fix_prompt.and_then(|p| p.extra_context.clone());

let user_accepted = user_interaction.confirm(&prompt_text, extra_context.as_deref());

if user_accepted {
// failure indicates an issue with us actually executing it,
// not the success/failure of the command itself.
let outputs = run_fix(working_dir, &exec_path, fix).await?;
let max_exit_code = outputs
.iter()
.map(|c| c.exit_code.unwrap_or(-1))
.max()
.unwrap();

match max_exit_code {
0 => Ok(AnalyzeStatus::KnownErrorFoundFixSucceeded),
_ => {
if let Some(help_text) = &fix.help_text {
error!(target: "user", "Fix Help: {}", help_text);
}
if let Some(help_url) = &fix.help_url {
error!(target: "user", "For more help, please visit {}", help_url);
}
} else {
Ok(AnalyzeStatus::KnownErrorFoundUserDenied)

Ok(AnalyzeStatus::KnownErrorFoundFixFailed)
}
}
Err(InquireError::NotTTY) => {
warn!(target: "user", "Prompting user for fix, but input device is not a TTY. Skipping fix.");
Ok(AnalyzeStatus::KnownErrorFoundUserDenied)
}
Err(e) => {
error!(target: "user", "Error prompting user for fix: {}", e);
Err(e.into())
}
} else {
Ok(AnalyzeStatus::KnownErrorFoundUserDenied)
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/shared/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Configuration loading abstractions for library-first design.
//!
//! This module provides CLI-independent configuration loading options
//! and functions that can be used programmatically.

mod options;

pub use options::ConfigLoadOptions;
6 changes: 3 additions & 3 deletions src/shared/directories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::path::PathBuf;
/// # Examples
///
/// ```
/// # use dev_scope::shared::directories::home;
/// # use dx_scope::shared::directories::home;
/// if let Some(home) = home() {
/// println!("Home directory: {}", home.display());
/// }
Expand All @@ -47,7 +47,7 @@ pub fn home() -> Option<PathBuf> {
/// # Examples
///
/// ```
/// # use dev_scope::shared::directories::config;
/// # use dx_scope::shared::directories::config;
/// if let Some(config_dir) = config() {
/// println!("Config directory: {}", config_dir.display());
/// }
Expand Down Expand Up @@ -79,7 +79,7 @@ pub fn config() -> Option<PathBuf> {
/// # Examples
///
/// ```
/// # use dev_scope::shared::directories::cache;
/// # use dx_scope::shared::directories::cache;
/// if let Some(cache_dir) = cache() {
/// println!("Cache directory: {}", cache_dir.display());
/// }
Expand Down
10 changes: 7 additions & 3 deletions src/shared/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use colored::Colorize;
use std::cmp::max;
use std::path::Path;

use crate::models::HelpMetadata;
use crate::report_stdout;
use std::cmp::max;
use std::path::Path;

mod capture;
pub mod config;
mod config_load;
mod logging;

Expand All @@ -23,10 +24,10 @@ pub mod prelude {
CaptureError, CaptureOpts, DefaultExecutionProvider, ExecutionProvider,
MockExecutionProvider, OutputCapture, OutputCaptureBuilder, OutputDestination,
};
pub use super::config::ConfigLoadOptions;
pub use super::config_load::{ConfigOptions, FoundConfig, build_config_path};
pub use super::logging::{LoggingOpts, STDERR_WRITER, STDOUT_WRITER, progress_bar_without_pos};
pub use super::models::prelude::*;
pub use super::print_details;
pub use super::report::{
ActionReport, ActionReportBuilder, ActionTaskReport, ActionTaskReportBuilder,
DefaultGroupedReportBuilder, DefaultUnstructuredReportBuilder, GroupReport,
Expand All @@ -39,6 +40,9 @@ pub(crate) fn convert_to_string(input: Vec<&str>) -> Vec<String> {
input.iter().map(|x| x.to_string()).collect()
}

/// Print details of configuration resources in a formatted table.
///
/// This is a CLI utility function that outputs directly to stdout.
pub async fn print_details<T>(working_dir: &Path, config: &Vec<T>)
where
T: HelpMetadata,
Expand Down
11 changes: 10 additions & 1 deletion src/shared/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,16 @@ pub(crate) mod tests {
use anyhow::Result;
use chrono::DateTime;

use crate::prelude::*;
use crate::models::prelude::ModelMetadata;
use crate::shared::models::prelude::{
ReportTemplates, ReportUploadLocation, ReportUploadLocationDestination,
};
use crate::shared::prelude::{FoundConfig, MockExecutionProvider, OutputCaptureBuilder};
use super::{
ActionReport, ActionTaskReport, DefaultGroupedReportBuilder,
DefaultUnstructuredReportBuilder, GroupReport, GroupedReportBuilder, ReportRenderer,
UnstructuredReportBuilder,
};

#[tokio::test]
async fn test_grouped_report_builder() -> Result<()> {
Expand Down
Loading