diff --git a/src/analyze/api.rs b/src/analyze/api.rs new file mode 100644 index 0000000..e5fa879 --- /dev/null +++ b/src/analyze/api.rs @@ -0,0 +1,188 @@ +//! Public API for the analyze module. +//! +//! This module provides the main library entry points for programmatic usage +//! of the analyze functionality without CLI dependencies. +//! +//! # Examples +//! +//! ## Analyze Text for Known Errors +//! +//! ```rust,no_run +//! use dx_scope::{ +//! AnalyzeOptions, AnalyzeInput, AutoApprove, FoundConfig, +//! }; +//! use dx_scope::analyze::process_input; +//! +//! #[tokio::main] +//! async fn main() -> anyhow::Result<()> { +//! // Load configuration +//! let working_dir = std::env::current_dir()?; +//! let config = FoundConfig::empty(working_dir); +//! +//! // Prepare input +//! let log_content = std::fs::read_to_string("error.log")?; +//! let lines: Vec = log_content.lines().map(|s| s.to_string()).collect(); +//! let input = AnalyzeInput::from_lines(lines); +//! +//! // Configure options +//! let options = AnalyzeOptions::new( +//! config.known_error.clone(), +//! config.working_dir.clone(), +//! ); +//! +//! // Run analysis with auto-approve for fixes +//! let interaction = AutoApprove; +//! let status = process_input(&options, input, &interaction).await?; +//! +//! match status { +//! dx_scope::AnalyzeStatus::NoKnownErrorsFound => { +//! println!("No errors detected"); +//! } +//! dx_scope::AnalyzeStatus::KnownErrorFoundFixSucceeded => { +//! println!("Error found and fixed!"); +//! } +//! _ => println!("Error handling completed: {:?}", status), +//! } +//! +//! Ok(()) +//! } +//! ``` + +use crate::analyze::options::{AnalyzeInput, AnalyzeOptions}; +use crate::internal::prompts::UserInteraction; +use crate::shared::analyze::{AnalyzeStatus, process_lines as process_lines_internal}; +use anyhow::Result; +use std::io::Cursor; +use tokio::io::BufReader; +use tracing::{debug, info}; + +/// Process input for known errors and optionally run fixes. +/// +/// This is the main library entry point for analyzing text/logs programmatically. +/// It scans the input for known error patterns and can automatically apply fixes. +/// +/// # Arguments +/// +/// * `options` - Analyze options containing known errors and working directory +/// * `input` - Input source (file, stdin, or in-memory lines) +/// * `interaction` - Implementation of `UserInteraction` for fix prompts (use `AutoApprove` or `DenyAll`) +/// +/// # Returns +/// +/// Returns `AnalyzeStatus` indicating the outcome: +/// - `NoKnownErrorsFound` - No matches found +/// - `KnownErrorFoundNoFixFound` - Error matched but no fix available +/// - `KnownErrorFoundUserDenied` - User declined to run the fix +/// - `KnownErrorFoundFixFailed` - Fix was attempted but failed +/// - `KnownErrorFoundFixSucceeded` - Fix was successfully applied +/// +/// # Examples +/// +/// ## Analyze In-Memory Text +/// +/// ```rust +/// use dx_scope::{AnalyzeOptions, AnalyzeInput, AutoApprove}; +/// use dx_scope::analyze::process_input; +/// +/// let lines = vec![ +/// "Building project...".to_string(), +/// "error: dependency not found".to_string(), +/// ]; +/// let input = AnalyzeInput::from_lines(lines); +/// let options = AnalyzeOptions::default(); +/// // Call: process_input(&options, input, &AutoApprove).await +/// ``` +/// +/// ## Analyze a File +/// +/// ```rust +/// use dx_scope::{AnalyzeOptions, AnalyzeInput, DenyAll}; +/// use dx_scope::analyze::process_input; +/// +/// let options = AnalyzeOptions::default(); +/// let input = AnalyzeInput::from_file("/var/log/build.log"); +/// // Call: process_input(&options, input, &DenyAll).await +/// ``` +pub async fn process_input( + options: &AnalyzeOptions, + input: AnalyzeInput, + interaction: &U, +) -> Result +where + U: UserInteraction, +{ + debug!("Starting analyze with input type: {:?}", input); + + match input { + AnalyzeInput::File(path) => { + info!("Analyzing file: {:?}", path); + let file = tokio::fs::File::open(&path).await?; + let reader = BufReader::new(file); + process_lines_internal( + &options.known_errors, + &options.working_dir, + reader, + interaction, + ) + .await + } + AnalyzeInput::Stdin => { + info!("Analyzing stdin"); + let stdin = tokio::io::stdin(); + let reader = BufReader::new(stdin); + process_lines_internal( + &options.known_errors, + &options.working_dir, + reader, + interaction, + ) + .await + } + AnalyzeInput::Lines(lines) => { + info!("Analyzing {} lines from memory", lines.len()); + let text = lines.join("\n"); + let cursor = Cursor::new(text); + let reader = BufReader::new(cursor); + process_lines_internal( + &options.known_errors, + &options.working_dir, + reader, + interaction, + ) + .await + } + } +} + +/// Analyze text content directly for known errors. +/// +/// Convenience function for analyzing a string without creating an `AnalyzeInput`. +/// +/// # Arguments +/// +/// * `options` - Analyze options containing known errors and working directory +/// * `text` - Text content to analyze +/// * `interaction` - Implementation of `UserInteraction` for fix prompts +/// +/// # Examples +/// +/// ```rust +/// use dx_scope::{AnalyzeOptions, DenyAll}; +/// use dx_scope::analyze::process_text; +/// +/// let log_output = "error: compilation failed\nSome other output"; +/// let options = AnalyzeOptions::default(); +/// // Call: process_text(&options, log_output, &DenyAll).await +/// ``` +pub async fn process_text( + options: &AnalyzeOptions, + text: &str, + interaction: &U, +) -> Result +where + U: UserInteraction, +{ + let lines: Vec = text.lines().map(|s| s.to_string()).collect(); + let input = AnalyzeInput::from_lines(lines); + process_input(options, input, interaction).await +} diff --git a/src/analyze/cli.rs b/src/analyze/cli.rs index 25345a6..b677862 100644 --- a/src/analyze/cli.rs +++ b/src/analyze/cli.rs @@ -1,4 +1,5 @@ use super::error::AnalyzeError; +use crate::cli::InquireInteraction; use crate::prelude::{ CaptureError, CaptureOpts, DefaultExecutionProvider, ExecutionProvider, OutputDestination, }; @@ -50,12 +51,14 @@ pub async fn analyze_root(found_config: &FoundConfig, args: &AnalyzeArgs) -> Res } async fn analyze_logs(found_config: &FoundConfig, args: &AnalyzeLogsArgs) -> Result { + let interaction = InquireInteraction; let result = match args.location.as_str() { "-" => { analyze::process_lines( &found_config.known_error, &found_config.working_dir, read_from_stdin().await?, + &interaction, ) .await? } @@ -64,6 +67,7 @@ async fn analyze_logs(found_config: &FoundConfig, args: &AnalyzeLogsArgs) -> Res &found_config.known_error, &found_config.working_dir, read_from_file(file_path).await?, + &interaction, ) .await? } @@ -75,6 +79,7 @@ async fn analyze_logs(found_config: &FoundConfig, args: &AnalyzeLogsArgs) -> Res async fn analyze_command(found_config: &FoundConfig, args: &AnalyzeCommandArgs) -> Result { let exec_runner = DefaultExecutionProvider::default(); + let interaction = InquireInteraction; let command = args.command.clone(); let path = env::var("PATH").unwrap_or_default(); @@ -91,6 +96,7 @@ async fn analyze_command(found_config: &FoundConfig, args: &AnalyzeCommandArgs) &found_config.known_error, &found_config.working_dir, read_from_command(&exec_runner, capture_opts).await?, + &interaction, ) .await?; diff --git a/src/analyze/mod.rs b/src/analyze/mod.rs index 37630a6..2a3193d 100644 --- a/src/analyze/mod.rs +++ b/src/analyze/mod.rs @@ -1,6 +1,15 @@ +mod api; mod cli; mod error; +pub mod options; pub mod prelude { pub use super::cli::{AnalyzeArgs, analyze_root}; } + +// Re-export key types for library usage +pub use options::{AnalyzeInput, AnalyzeOptions}; +pub use crate::shared::analyze::{process_lines, AnalyzeStatus, report_result}; + +// Public API functions +pub use api::{process_input, process_text};