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
188 changes: 188 additions & 0 deletions src/analyze/api.rs
Original file line number Diff line number Diff line change
@@ -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<String> = 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<U>(
options: &AnalyzeOptions,
input: AnalyzeInput,
interaction: &U,
) -> Result<AnalyzeStatus>
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<U>(
options: &AnalyzeOptions,
text: &str,
interaction: &U,
) -> Result<AnalyzeStatus>
where
U: UserInteraction,
{
let lines: Vec<String> = text.lines().map(|s| s.to_string()).collect();
let input = AnalyzeInput::from_lines(lines);
process_input(options, input, interaction).await
}
6 changes: 6 additions & 0 deletions src/analyze/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::error::AnalyzeError;
use crate::cli::InquireInteraction;
use crate::prelude::{
CaptureError, CaptureOpts, DefaultExecutionProvider, ExecutionProvider, OutputDestination,
};
Expand Down Expand Up @@ -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<i32> {
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?
}
Expand All @@ -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?
}
Expand All @@ -75,6 +79,7 @@ async fn analyze_logs(found_config: &FoundConfig, args: &AnalyzeLogsArgs) -> Res

async fn analyze_command(found_config: &FoundConfig, args: &AnalyzeCommandArgs) -> Result<i32> {
let exec_runner = DefaultExecutionProvider::default();
let interaction = InquireInteraction;

let command = args.command.clone();
let path = env::var("PATH").unwrap_or_default();
Expand All @@ -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?;

Expand Down
9 changes: 9 additions & 0 deletions src/analyze/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Loading