From 5da9c7fa4e21f9b96f1ef05efff059bb662b245c Mon Sep 17 00:00:00 2001 From: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:02:22 +0000 Subject: [PATCH] refactor: update shared modules to use new abstractions - Update shared/mod.rs with new re-exports - Update shared/config/mod.rs with Config type changes - Update shared/directories.rs with cache directory helpers - Update shared/report.rs with report builder types - Refactor shared/analyze/mod.rs to use UserInteraction trait Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) --- src/shared/analyze/mod.rs | 103 ++++++++++++++++++-------------------- src/shared/config/mod.rs | 8 +++ src/shared/directories.rs | 6 +-- src/shared/mod.rs | 10 ++-- src/shared/report.rs | 11 +++- 5 files changed, 76 insertions(+), 62 deletions(-) create mode 100644 src/shared/config/mod.rs diff --git a/src/shared/analyze/mod.rs b/src/shared/analyze/mod.rs index 2d86f5b..ec8f77f 100644 --- a/src/shared/analyze/mod.rs +++ b/src/shared/analyze/mod.rs @@ -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}; @@ -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( +/// 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( known_errors: &BTreeMap, working_dir: &PathBuf, input: T, + user_interaction: &U, ) -> Result 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(); @@ -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, }; @@ -69,62 +79,45 @@ where Ok(result) } -async fn prompt_and_run_fix( +async fn prompt_and_run_fix( working_dir: &PathBuf, exec_path: String, fix: &DoctorFix, + user_interaction: &U, ) -> Result { 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) } } diff --git a/src/shared/config/mod.rs b/src/shared/config/mod.rs new file mode 100644 index 0000000..6852c4b --- /dev/null +++ b/src/shared/config/mod.rs @@ -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; diff --git a/src/shared/directories.rs b/src/shared/directories.rs index 9bc13f7..3a9746d 100644 --- a/src/shared/directories.rs +++ b/src/shared/directories.rs @@ -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()); /// } @@ -47,7 +47,7 @@ pub fn home() -> Option { /// # 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()); /// } @@ -79,7 +79,7 @@ pub fn config() -> Option { /// # 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()); /// } diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 7d28589..c6e4ab2 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -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; @@ -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, @@ -39,6 +40,9 @@ pub(crate) fn convert_to_string(input: Vec<&str>) -> Vec { 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(working_dir: &Path, config: &Vec) where T: HelpMetadata, diff --git a/src/shared/report.rs b/src/shared/report.rs index c492142..9a9e9d8 100644 --- a/src/shared/report.rs +++ b/src/shared/report.rs @@ -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<()> {