Skip to content
Open
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
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
148 changes: 148 additions & 0 deletions src/analyze/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//! CLI-independent options types for the analyze module.
//!
//! This module provides options types that can be used to configure
//! analyze operations without depending on CLI argument parsing (clap).
//!
//! # Overview
//!
//! The analyze functionality detects known errors in command output or log files
//! and can optionally run fixes for detected errors.
//!
//! # Examples
//!
//! ## Basic Configuration
//!
//! ```rust
//! use dx_scope::AnalyzeOptions;
//! use std::collections::BTreeMap;
//! use std::path::PathBuf;
//!
//! let options = AnalyzeOptions {
//! known_errors: BTreeMap::new(),
//! working_dir: PathBuf::from("/path/to/project"),
//! };
//! assert!(options.known_errors.is_empty());
//! ```
//!
//! ## Different Input Sources
//!
//! ```rust,no_run
//! use dx_scope::analyze::options::AnalyzeInput;
//!
//! // From a file
//! let input = AnalyzeInput::from_file("/var/log/build.log");
//!
//! // From stdin
//! let input = AnalyzeInput::Stdin;
//!
//! // From in-memory lines (useful for testing or library usage)
//! let input = AnalyzeInput::from_lines(vec![
//! "Building project...".to_string(),
//! "error: missing dependency".to_string(),
//! ]);
//! ```

use crate::shared::prelude::KnownError;
use std::collections::BTreeMap;
use std::path::PathBuf;

/// Options for running analyze operations.
///
/// This struct contains all the configuration needed to run analysis,
/// without any CLI-specific dependencies like clap.
///
/// # Example
///
/// ```rust
/// use dx_scope::AnalyzeOptions;
/// use std::collections::BTreeMap;
/// use std::path::PathBuf;
///
/// let options = AnalyzeOptions {
/// known_errors: BTreeMap::new(),
/// working_dir: PathBuf::from("/path/to/project"),
/// };
/// assert!(options.known_errors.is_empty());
/// ```
#[derive(Debug, Clone)]
pub struct AnalyzeOptions {
/// Map of known errors to detect and potentially fix
pub known_errors: BTreeMap<String, KnownError>,
/// Working directory for running fix commands
pub working_dir: PathBuf,
}

impl AnalyzeOptions {
/// Create new analyze options with the given parameters.
pub fn new(known_errors: BTreeMap<String, KnownError>, working_dir: PathBuf) -> Self {
Self {
known_errors,
working_dir,
}
}
}

impl Default for AnalyzeOptions {
fn default() -> Self {
Self {
known_errors: BTreeMap::new(),
working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
}
}
}

/// Specifies the input source for analysis.
///
/// The analyze functionality can process input from various sources.
/// This enum allows callers to specify where the input should come from.
#[derive(Debug, Clone)]
pub enum AnalyzeInput {
/// Read from a file at the given path
File(PathBuf),
/// Read from standard input
Stdin,
/// Process the given lines directly (useful for library usage)
Lines(Vec<String>),
}

impl AnalyzeInput {
/// Create input from a file path.
pub fn from_file(path: impl Into<PathBuf>) -> Self {
Self::File(path.into())
}

/// Create input from a vector of strings.
pub fn from_lines(lines: Vec<String>) -> Self {
Self::Lines(lines)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_analyze_options_default() {
let options = AnalyzeOptions::default();
assert!(options.known_errors.is_empty());
}

#[test]
fn test_analyze_input_from_file() {
let input = AnalyzeInput::from_file("/path/to/file");
match input {
AnalyzeInput::File(path) => assert_eq!(path, PathBuf::from("/path/to/file")),
_ => panic!("Expected File variant"),
}
}

#[test]
fn test_analyze_input_from_lines() {
let lines = vec!["line1".to_string(), "line2".to_string()];
let input = AnalyzeInput::from_lines(lines.clone());
match input {
AnalyzeInput::Lines(l) => assert_eq!(l, lines),
_ => panic!("Expected Lines variant"),
}
}
}
1 change: 1 addition & 0 deletions src/bin/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand};
use colored::Colorize;
use dx_scope::prelude::*;
use dx_scope::report_stdout;
use dx_scope::shared::print_details;
use human_panic::setup_panic;
use lazy_static::lazy_static;
use regex::Regex;
Expand Down
5 changes: 4 additions & 1 deletion src/doctor/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{cmp, vec};
use tokio::io::BufReader;
use tracing::debug;

use crate::internal::prompts::AutoApprove;
use crate::models::HelpMetadata;
use crate::prelude::{
ActionReport, ActionReportBuilder, ActionTaskReport, DoctorCommand, KnownError,
Expand Down Expand Up @@ -672,7 +673,9 @@ impl DefaultDoctorActionRun {
.collect::<Vec<String>>();

let buffer = BufReader::new(StringVecReader::new(&lines));
analyze::process_lines(&self.known_errors, &self.working_dir, buffer).await
// Use AutoApprove for self-healing: known errors during doctor runs
// are automatically fixed without user prompts
analyze::process_lines(&self.known_errors, &self.working_dir, buffer, &AutoApprove).await
}
}

Expand Down
152 changes: 152 additions & 0 deletions src/doctor/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//! CLI-independent options types for the doctor module.
//!
//! This module provides options types that can be used to configure
//! doctor operations without depending on CLI argument parsing (clap).
//!
//! # Overview
//!
//! The doctor functionality runs health checks on your development environment
//! and can automatically apply fixes when issues are detected.
//!
//! # Examples
//!
//! ## Common Configurations
//!
//! ```rust
//! use dx_scope::doctor::options::DoctorRunOptions;
//!
//! // CI mode - run checks without fixes
//! let ci_options = DoctorRunOptions::ci_mode();
//!
//! // Interactive mode - prompt for fixes
//! let interactive_options = DoctorRunOptions::with_fixes();
//!
//! // Run specific groups only
//! let targeted_options = DoctorRunOptions::for_groups(vec![
//! "rust".to_string(),
//! "docker".to_string(),
//! ]);
//! ```
//!
//! ## Full Customization
//!
//! ```rust
//! use dx_scope::doctor::options::DoctorRunOptions;
//! use std::path::PathBuf;
//!
//! let options = DoctorRunOptions {
//! only_groups: Some(vec!["build".to_string()]),
//! run_fix: true,
//! cache_dir: Some(PathBuf::from("/tmp/my-cache")),
//! no_cache: false,
//! auto_publish_report: true,
//! };
//! ```
//!
//! # Caching
//!
//! The doctor module supports caching to avoid re-running checks when
//! source files haven't changed. Control caching with:
//!
//! - `cache_dir`: Custom cache location (default: system cache directory)
//! - `no_cache`: Disable caching entirely (useful for debugging)

use std::path::PathBuf;

/// Options for running doctor operations.
///
/// This struct contains all the configuration needed to run doctor checks,
/// without any CLI-specific dependencies like clap.
///
/// # Example
///
/// ```rust,no_run
/// use dx_scope::DoctorRunOptions;
///
/// // Run all default groups with fixes enabled
/// let options = DoctorRunOptions::default();
///
/// // Run only specific groups
/// let options = DoctorRunOptions {
/// only_groups: Some(vec!["build".to_string(), "test".to_string()]),
/// ..Default::default()
/// };
///
/// // Run in CI mode (no interactive fixes)
/// let options = DoctorRunOptions::ci_mode();
/// ```
#[derive(Debug, Clone, Default)]
pub struct DoctorRunOptions {
/// Run only these groups (None = run all default groups)
pub only_groups: Option<Vec<String>>,
/// Whether to run fixes when checks fail
pub run_fix: bool,
/// Custom cache directory path
pub cache_dir: Option<PathBuf>,
/// Disable caching
pub no_cache: bool,
/// Automatically publish report on failure
pub auto_publish_report: bool,
}

impl DoctorRunOptions {
/// Create new options with default values but fixes enabled.
pub fn with_fixes() -> Self {
Self {
run_fix: true,
..Default::default()
}
}

/// Create new options for CI/non-interactive mode (no fixes).
pub fn ci_mode() -> Self {
Self {
run_fix: false,
..Default::default()
}
}

/// Create options to run specific groups only.
pub fn for_groups(groups: Vec<String>) -> Self {
Self {
only_groups: Some(groups),
run_fix: true,
..Default::default()
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_default_options() {
let options = DoctorRunOptions::default();
assert!(options.only_groups.is_none());
assert!(!options.run_fix);
assert!(options.cache_dir.is_none());
assert!(!options.no_cache);
assert!(!options.auto_publish_report);
}

#[test]
fn test_with_fixes() {
let options = DoctorRunOptions::with_fixes();
assert!(options.run_fix);
}

#[test]
fn test_ci_mode() {
let options = DoctorRunOptions::ci_mode();
assert!(!options.run_fix);
}

#[test]
fn test_for_groups() {
let groups = vec!["group1".to_string(), "group2".to_string()];
let options = DoctorRunOptions::for_groups(groups.clone());
assert_eq!(options.only_groups, Some(groups));
assert!(options.run_fix);
}
}
Loading