From 947cefe48f928468af8ec7da306515aa16dfa507 Mon Sep 17 00:00:00 2001 From: Nishant Arora <1895906+whizzzkid@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:32:13 +0000 Subject: [PATCH] feat: refactor binary structure and expand library exports Binary refactoring: - Extract command routing to src/bin/cli/commands.rs - Simplify src/bin/scope.rs to thin CLI wrapper - Update scope-intercept.rs to use explicit imports Library exports (src/lib.rs): - Add comprehensive crate-level documentation - Export CLI argument types (AnalyzeArgs, DoctorArgs, etc.) - Export capture/logging utilities for CLI tools - Export report builders and renderers - Deprecate prelude module with migration guidance Doctor module improvements: - Add make_prompt_fn for bridging UserInteraction trait - Update output format to use structured tracing fields - Fix if-let chain pattern in cache migration Co-Authored-By: Claude (global.anthropic.claude-opus-4-5-20251101-v1:0) --- src/bin/cli/commands.rs | 154 +++++++++++++++++++++++++++++++ src/bin/cli/mod.rs | 3 + src/bin/scope-intercept.rs | 6 +- src/bin/scope.rs | 182 ++++++------------------------------- src/doctor/commands/run.rs | 14 +-- src/doctor/runner.rs | 43 ++++++--- src/lib.rs | 139 ++++++++++++++++++++++++---- tests/scope_doctor.rs | 5 +- 8 files changed, 356 insertions(+), 190 deletions(-) create mode 100644 src/bin/cli/commands.rs create mode 100644 src/bin/cli/mod.rs diff --git a/src/bin/cli/commands.rs b/src/bin/cli/commands.rs new file mode 100644 index 0000000..0351c5d --- /dev/null +++ b/src/bin/cli/commands.rs @@ -0,0 +1,154 @@ +//! Command routing and utility functions for the CLI. +//! +//! This module contains all the command routing logic and helper functions +//! used by the binary, keeping the binary itself thin. + +use crate::{Cli, Command, VersionArgs}; +use anyhow::Result; +use clap::CommandFactory; +use colored::Colorize; +use dx_scope::{ + CONFIG_FILE_PATH_ENV, CaptureOpts, FoundConfig, OutputCapture, OutputDestination, + RUN_ID_ENV_VAR, analyze_root, doctor_root, lint_root, print_details, report_root, + report_stdout, +}; +use lazy_static::lazy_static; +use regex::Regex; +use std::collections::BTreeMap; +use std::ffi::OsString; +use tracing::{debug, info, instrument}; + +lazy_static! { + static ref SCOPE_SUBCOMMAND_REGEX: Regex = Regex::new("^scope-.*").unwrap(); +} + +/// Route a command to its appropriate handler. +pub async fn handle_command(found_config: &FoundConfig, command: &Command) -> Result { + match command { + Command::Doctor(args) => doctor_root(found_config, args).await, + Command::Report(args) => report_root(found_config, args).await, + Command::List => show_config(found_config).await.map(|_| 0), + Command::Version(args) => print_version(args).await, + Command::ExternalSubCommand(args) => exec_sub_command(found_config, args).await, + Command::Analyze(args) => analyze_root(found_config, args).await, + Command::Lint(args) => lint_root(found_config, args).await, + } +} + +/// Execute an external subcommand. +#[instrument("scope external-command", skip_all)] +async fn exec_sub_command(found_config: &FoundConfig, args: &[String]) -> Result { + let mut args = args.to_owned(); + let command = match args.first() { + None => return Err(anyhow::anyhow!("Sub command not provided")), + Some(cmd) => { + format!("scope-{cmd}") + } + }; + let _ = std::mem::replace(&mut args[0], command); + + debug!("Executing {:?}", args); + + let config_file_path = found_config.write_raw_config_to_disk()?; + let capture = OutputCapture::capture_output(CaptureOpts { + working_dir: &found_config.working_dir, + args: &args, + output_dest: OutputDestination::StandardOut, + path: &found_config.bin_path, + env_vars: BTreeMap::from([ + ( + CONFIG_FILE_PATH_ENV.to_string(), + config_file_path.display().to_string(), + ), + (RUN_ID_ENV_VAR.to_string(), found_config.run_id.clone()), + ]), + }) + .await?; + + capture + .exit_code + .ok_or_else(|| anyhow::anyhow!("Unable to exec {}", args.join(" "))) +} + +/// Show configuration and available commands. +#[instrument("scope list", skip_all)] +async fn show_config(found_config: &FoundConfig) -> Result<()> { + info!(target: "user", "Found Resources"); + print_details(&found_config.working_dir, &found_config.raw_config).await; + + info!(target: "user", ""); + info!(target: "user", "Commands"); + print_commands(found_config).await; + Ok(()) +} + +/// Print available commands (both built-in and external). +async fn print_commands(found_config: &FoundConfig) { + if let Ok(commands) = which::which_re_in( + SCOPE_SUBCOMMAND_REGEX.clone(), + Some(OsString::from(&found_config.bin_path)), + ) { + let mut command_map = BTreeMap::new(); + for command in commands { + let command_name = command.file_name().unwrap().to_str().unwrap().to_string(); + let command_name = command_name.replace("scope-", ""); + command_map.entry(command_name.clone()).or_insert_with(|| { + format!("External sub-command, run `scope {command_name}` for help") + }); + } + for command in Cli::command().get_subcommands() { + command_map + .entry(command.get_name().to_string()) + .or_insert_with(|| command.get_about().unwrap_or_default().to_string()); + } + + let mut command_names: Vec<_> = command_map.keys().collect(); + command_names.sort(); + + report_stdout!( + " {:20}{:60}", + "Name".white().bold(), + "Description".white().bold() + ); + for command_name in command_names { + let description = command_map.get(command_name.as_str()).unwrap(); + report_stdout!("- {:20}{:60}", command_name, description); + } + } +} + +/// Print version information. +#[instrument("scope version", skip_all)] +async fn print_version(args: &VersionArgs) -> Result { + if args.short { + report_stdout!("scope {}", env!("CARGO_PKG_VERSION")); + } else { + report_stdout!( + "{}: {:60}", + "Version".white().bold(), + env!("CARGO_PKG_VERSION") + ); + report_stdout!( + "{}: {:60}", + "Build Timestamp".white().bold(), + env!("VERGEN_BUILD_TIMESTAMP") + ); + report_stdout!( + "{}: {:60}", + "Describe".white().bold(), + env!("VERGEN_GIT_DESCRIBE") + ); + report_stdout!( + "{}: {:60}", + "Commit SHA".white().bold(), + env!("VERGEN_GIT_SHA") + ); + report_stdout!( + "{}: {:60}", + "Commit Date".white().bold(), + env!("VERGEN_GIT_COMMIT_DATE") + ); + } + + Ok(0) +} diff --git a/src/bin/cli/mod.rs b/src/bin/cli/mod.rs new file mode 100644 index 0000000..8d65282 --- /dev/null +++ b/src/bin/cli/mod.rs @@ -0,0 +1,3 @@ +//! CLI utilities and command routing for the scope binary. + +pub mod commands; diff --git a/src/bin/scope-intercept.rs b/src/bin/scope-intercept.rs index 3ebc43d..6d0602e 100644 --- a/src/bin/scope-intercept.rs +++ b/src/bin/scope-intercept.rs @@ -1,5 +1,9 @@ use clap::Parser; -use dx_scope::prelude::*; +use dx_scope::{ + CaptureOpts, ConfigOptions, DefaultExecutionProvider, DefaultUnstructuredReportBuilder, + FoundConfig, HelpMetadata, LoggingOpts, OutputCapture, OutputDestination, ReportRenderer, + UnstructuredReportBuilder, +}; use human_panic::setup_panic; use std::env; use std::sync::Arc; diff --git a/src/bin/scope.rs b/src/bin/scope.rs index 7d4954e..06e34a1 100644 --- a/src/bin/scope.rs +++ b/src/bin/scope.rs @@ -1,16 +1,14 @@ -use anyhow::Result; -use clap::CommandFactory; +//! Scope CLI binary - thin wrapper around the dx-scope library. +//! +//! This binary provides the command-line interface for scope. Most logic +//! lives in the library or cli module, keeping this file minimal. + +mod cli; + use clap::{Parser, Subcommand}; -use colored::Colorize; -use dx_scope::prelude::*; -use dx_scope::report_stdout; -use dx_scope::shared::print_details; +use dx_scope::{AnalyzeArgs, ConfigOptions, DoctorArgs, LintArgs, LoggingOpts, ReportArgs}; use human_panic::setup_panic; -use lazy_static::lazy_static; -use regex::Regex; -use std::collections::BTreeMap; -use std::ffi::OsString; -use tracing::{Level, debug, enabled, error, info, instrument}; +use tracing::{Level, enabled, error, info}; /// scope /// @@ -20,25 +18,25 @@ use tracing::{Level, debug, enabled, error, info, instrument}; /// for support's machine is setup correctly. #[derive(Parser)] #[clap(author, version, about)] -struct Cli { +pub(crate) struct Cli { #[clap(flatten)] - logging: LoggingOpts, + pub logging: LoggingOpts, #[clap(flatten)] - config: ConfigOptions, + pub config: ConfigOptions, #[clap(subcommand)] - command: Command, + pub command: Command, } #[derive(Parser, Debug)] -struct VersionArgs { +pub(crate) struct VersionArgs { #[arg(long, action)] pub short: bool, } #[derive(Debug, Subcommand)] -enum Command { +pub(crate) enum Command { /// Run checks that will "checkup" your machine. #[clap(alias("d"))] Doctor(DoctorArgs), @@ -64,18 +62,26 @@ enum Command { #[tokio::main] async fn main() { setup_panic!(); + + // Load environment files dotenvy::dotenv().ok(); let exe_path = std::env::current_exe().unwrap(); let env_path = exe_path.parent().unwrap().join("../etc/scope.env"); dotenvy::from_path(env_path).ok(); + + // Parse CLI arguments let opts = Cli::parse(); + // Setup logging let configured_logger = opts .logging .configure_logging(&opts.config.get_run_id(), "root") .await; - let error_code = run_subcommand(opts).await; + // Run command + let error_code = run_command(opts).await; + + // Show log location on error or debug if error_code != 0 || enabled!(Level::DEBUG) { info!(target: "user", "More detailed logs at {}", configured_logger.log_location); } @@ -84,149 +90,21 @@ async fn main() { std::process::exit(error_code); } -async fn run_subcommand(opts: Cli) -> i32 { - let loaded_config = match opts.config.load_config().await { +async fn run_command(opts: Cli) -> i32 { + // Load configuration + let config = match opts.config.load_config().await { + Ok(c) => c, Err(e) => { error!(target: "user", "Failed to load configuration: {}", e); return 2; } - Ok(c) => c, }; - handle_commands(&loaded_config, &opts.command) + // Route to command handler + cli::commands::handle_command(&config, &opts.command) .await .unwrap_or_else(|e| { error!(target: "user", "Critical Error. {}", e); 1 }) } - -async fn handle_commands(found_config: &FoundConfig, command: &Command) -> Result { - match command { - Command::Doctor(args) => doctor_root(found_config, args).await, - Command::Report(args) => report_root(found_config, args).await, - Command::List => show_config(found_config).await.map(|_| 0), - Command::Version(args) => print_version(args).await, - Command::ExternalSubCommand(args) => exec_sub_command(found_config, args).await, - Command::Analyze(args) => analyze_root(found_config, args).await, - Command::Lint(args) => lint_root(found_config, args).await, - } -} - -#[instrument("scope external-command", skip_all)] -async fn exec_sub_command(found_config: &FoundConfig, args: &[String]) -> Result { - let mut args = args.to_owned(); - let command = match args.first() { - None => return Err(anyhow::anyhow!("Sub command not provided")), - Some(cmd) => { - format!("scope-{cmd}") - } - }; - let _ = std::mem::replace(&mut args[0], command); - - debug!("Executing {:?}", args); - - let config_file_path = found_config.write_raw_config_to_disk()?; - let capture = OutputCapture::capture_output(CaptureOpts { - working_dir: &found_config.working_dir, - args: &args, - output_dest: OutputDestination::StandardOut, - path: &found_config.bin_path, - env_vars: BTreeMap::from([ - ( - CONFIG_FILE_PATH_ENV.to_string(), - config_file_path.display().to_string(), - ), - (RUN_ID_ENV_VAR.to_string(), found_config.run_id.clone()), - ]), - }) - .await?; - - capture - .exit_code - .ok_or_else(|| anyhow::anyhow!("Unable to exec {}", args.join(" "))) -} - -lazy_static! { - static ref SCOPE_SUBCOMMAND_REGEX: Regex = Regex::new("^scope-.*").unwrap(); -} - -#[instrument("scope list", skip_all)] -async fn show_config(found_config: &FoundConfig) -> Result<()> { - info!(target: "user", "Found Resources"); - print_details(&found_config.working_dir, &found_config.raw_config).await; - - info!(target: "user", ""); - info!(target: "user", "Commands"); - print_commands(found_config).await; - Ok(()) -} - -async fn print_commands(found_config: &FoundConfig) { - if let Ok(commands) = which::which_re_in( - SCOPE_SUBCOMMAND_REGEX.clone(), - Some(OsString::from(&found_config.bin_path)), - ) { - let mut command_map = BTreeMap::new(); - for command in commands { - let command_name = command.file_name().unwrap().to_str().unwrap().to_string(); - let command_name = command_name.replace("scope-", ""); - command_map.entry(command_name.clone()).or_insert_with(|| { - format!("External sub-command, run `scope {command_name}` for help") - }); - } - for command in Cli::command().get_subcommands() { - command_map - .entry(command.get_name().to_string()) - .or_insert_with(|| command.get_about().unwrap_or_default().to_string()); - } - - let mut command_names: Vec<_> = command_map.keys().collect(); - command_names.sort(); - - report_stdout!( - " {:20}{:60}", - "Name".white().bold(), - "Description".white().bold() - ); - for command_name in command_names { - let description = command_map.get(command_name.as_str()).unwrap(); - report_stdout!("- {:20}{:60}", command_name, description); - } - } -} - -#[instrument("scope version", skip_all)] -async fn print_version(args: &VersionArgs) -> Result { - if args.short { - report_stdout!("scope {}", env!("CARGO_PKG_VERSION")); - } else { - report_stdout!( - "{}: {:60}", - "Version".white().bold(), - env!("CARGO_PKG_VERSION") - ); - report_stdout!( - "{}: {:60}", - "Build Timestamp".white().bold(), - env!("VERGEN_BUILD_TIMESTAMP") - ); - report_stdout!( - "{}: {:60}", - "Describe".white().bold(), - env!("VERGEN_GIT_DESCRIBE") - ); - report_stdout!( - "{}: {:60}", - "Commit SHA".white().bold(), - env!("VERGEN_GIT_SHA") - ); - report_stdout!( - "{}: {:60}", - "Commit Date".white().bold(), - env!("VERGEN_GIT_COMMIT_DATE") - ); - } - - Ok(0) -} diff --git a/src/doctor/commands/run.rs b/src/doctor/commands/run.rs index 7fc75fd..4220611 100644 --- a/src/doctor/commands/run.rs +++ b/src/doctor/commands/run.rs @@ -54,12 +54,14 @@ fn get_cache(args: &DoctorRunArgs) -> Arc { let old_default_cache_path = PathBuf::from("/tmp/scope/cache-file.json"); // Handle backward compatibility: migrate from old location to new location - if cache_dir != "/tmp/scope" - && old_default_cache_path.exists() - && !cache_path.exists() - && let Err(e) = migrate_old_cache(&old_default_cache_path, &cache_path) - { - warn!("Unable to migrate cache from old location: {:?}", e); + let should_migrate = + cache_dir != "/tmp/scope" && old_default_cache_path.exists() && !cache_path.exists(); + + #[allow(clippy::collapsible_if)] + if should_migrate { + if let Err(e) = migrate_old_cache(&old_default_cache_path, &cache_path) { + warn!("Unable to migrate cache from old location: {:?}", e); + } } match FileBasedCache::new(&cache_path) { diff --git a/src/doctor/runner.rs b/src/doctor/runner.rs index 9c82d70..fcf4564 100644 --- a/src/doctor/runner.rs +++ b/src/doctor/runner.rs @@ -1,12 +1,12 @@ use super::check::{ActionRunResult, ActionRunStatus, DoctorActionRun}; use crate::doctor::check::RuntimeError; +use crate::internal::prompts::UserInteraction; use crate::models::HelpMetadata; -use crate::prelude::{ - ActionReport, ActionTaskReport, CaptureOpts, ExecutionProvider, GroupReport, OutputDestination, - SkipSpec, generate_env_vars, progress_bar_without_pos, +use crate::models::prelude::SkipSpec; +use crate::shared::prelude::{ + ActionReport, ActionTaskReport, CaptureOpts, DoctorGroup, ExecutionProvider, GroupReport, + OutputDestination, generate_env_vars, progress_bar_without_pos, }; -use crate::report_stdout; -use crate::shared::prelude::DoctorGroup; use anyhow::Result; use colored::Colorize; use opentelemetry::trace::Status; @@ -350,6 +350,10 @@ where } } +/// Prompt the user for confirmation using the inquire crate. +/// +/// This function wraps inquire::Confirm and handles TTY detection. +/// It's used when `yolo` mode is disabled. fn prompt_user(prompt_text: &str, maybe_help_text: &Option) -> bool { tracing_indicatif::suspend_tracing_indicatif(|| { let prompt = { @@ -364,14 +368,33 @@ fn prompt_user(prompt_text: &str, maybe_help_text: &Option) -> bool { }) } +/// Auto-approve all prompts. +/// +/// This function automatically approves all prompts, used in `yolo` mode +/// or when running non-interactively. fn auto_approve(prompt_text: &str, maybe_help_text: &Option) -> bool { - println!("{} Yes (auto-approved)", prompt_text); + info!(target: "progress", prompt = %prompt_text, "Auto-approved"); if let Some(help_text) = maybe_help_text { - println!("[{}]", help_text); + debug!(target: "progress", help = %help_text, "Auto-approve help text"); } true } +/// Create a prompt function from a UserInteraction implementation. +/// +/// This bridges the gap between the trait-based UserInteraction interface +/// and the function pointer interface used by DoctorActionRun. +#[allow(dead_code)] +pub fn make_prompt_fn( + user_interaction: &U, +) -> impl Fn(&str, &Option) -> bool + '_ { + move |prompt_text: &str, maybe_help_text: &Option| { + tracing_indicatif::suspend_tracing_indicatif(|| { + user_interaction.confirm(prompt_text, maybe_help_text.as_deref()) + }) + } +} + async fn report_action_output( group_name: &str, action: &T, @@ -445,10 +468,8 @@ async fn print_pretty_result( let task_reports = action_task_reports_for_display(&result.action_report); for task in task_reports { if let Some(text) = task.output { - let line_prefix = format!("{group_name}/{action_name}"); for line in text.lines() { - let output_line = format!("{}: {}", line_prefix.dimmed(), line); - report_stdout!("{}", output_line); + error!(target: "user", group = group_name, action = action_name, "{}", line); } } } @@ -527,7 +548,7 @@ mod tests { }; use crate::doctor::runner::{GroupActionContainer, RunGroups, compute_group_order}; use crate::doctor::tests::{group_noop, make_root_model_additional}; - use crate::prelude::{ActionReport, ActionTaskReport, MockExecutionProvider}; + use crate::shared::prelude::{ActionReport, ActionTaskReport, MockExecutionProvider}; use anyhow::Result; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; diff --git a/src/lib.rs b/src/lib.rs index 1212815..cd721c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,52 @@ +//! # dev-scope Library +//! +//! This library provides the core functionality for the `scope` tool, +//! designed for library-first usage with a thin CLI wrapper. +//! +//! ## Key Features +//! +//! - **Analyze**: Detect known errors in command output and log files +//! - **Doctor**: Run health checks with automatic fixes +//! - **Config**: Load and manage scope configuration files +//! +//! ## Library Usage +//! +//! The library can be used programmatically without CLI dependencies: +//! +//! ```rust +//! use dx_scope::{ +//! AnalyzeOptions, AnalyzeInput, AutoApprove, +//! DoctorRunOptions, FoundConfig, +//! }; +//! +//! // Create configuration +//! let working_dir = std::env::current_dir().unwrap(); +//! let config = FoundConfig::empty(working_dir); +//! +//! // Configure analyze options +//! let analyze_options = AnalyzeOptions::default(); +//! let input = AnalyzeInput::from_lines(vec!["test".to_string()]); +//! // Use dx_scope::analyze::process_input(&analyze_options, input, &AutoApprove).await +//! +//! // Configure doctor options for CI mode +//! let doctor_options = DoctorRunOptions::ci_mode(); +//! // Use dx_scope::doctor::run(&config, doctor_options).await +//! ``` +//! +//! ## Modules +//! +//! - [`analyze`] - Log and output analysis for known errors +//! - [`doctor`] - Health checks and automatic fixes +//! - [`internal`] - Abstraction traits (UserInteraction, ProgressReporter) +//! - [`shared`] - Shared utilities and configuration loading +//! - [`models`] - Data model definitions +//! +//! ## CLI Module +//! +//! The `cli` module contains CLI-specific implementations and is not exported +//! as part of the public library API. However, `InquireInteraction` is re-exported +//! at the crate root for convenience when building CLI applications. + pub mod analyze; pub mod doctor; pub mod internal; @@ -7,34 +56,88 @@ pub mod report; pub mod shared; // CLI module is internal - not part of public library API +// Only InquireInteraction is re-exported for CLI usage pub(crate) mod cli; -pub mod prelude { - pub use crate::analyze::prelude::*; - pub use crate::doctor::prelude::*; - pub use crate::lint::prelude::*; - pub use crate::models::prelude::*; - pub use crate::report::prelude::*; - pub use crate::shared::prelude::*; -} +// Re-export key types at crate root for convenience +// Analyze module +pub use analyze::{AnalyzeInput, AnalyzeOptions, AnalyzeStatus}; + +// Doctor module +pub use doctor::{DoctorRunOptions, PathRunResult}; + +// Config module +pub use shared::config::ConfigLoadOptions; +pub use shared::prelude::FoundConfig; -// Re-export internal abstractions at crate root for convenience +// Internal abstractions (for library implementors) pub use internal::progress::{NoOpProgress, ProgressReporter}; pub use internal::prompts::{AutoApprove, DenyAll, UserInteraction}; -// Re-export CLI implementation for interactive applications +// CLI implementations pub use cli::InquireInteraction; -// Re-export analyze types at crate root -pub use analyze::{AnalyzeInput, AnalyzeOptions}; -pub use shared::analyze::AnalyzeStatus; +// Capture module (for CLI tools that intercept commands) +pub use shared::prelude::{ + CaptureOpts, DefaultExecutionProvider, OutputCapture, OutputDestination, +}; -// Re-export config types at crate root -pub use shared::config::ConfigLoadOptions; -pub use shared::prelude::FoundConfig; +// Logging module (for CLI tools) +pub use shared::prelude::LoggingOpts; -// Re-export doctor types at crate root -pub use doctor::{DoctorRunOptions, PathRunResult}; +// Config loading (for CLI tools) +pub use shared::prelude::ConfigOptions; + +// Report builders (for CLI tools) +pub use shared::prelude::{DefaultGroupedReportBuilder, DefaultUnstructuredReportBuilder}; + +// Report traits (for CLI tools that need to render reports) +pub use shared::prelude::{ReportRenderer, UnstructuredReportBuilder}; + +// Model traits (for accessing metadata on config models) +pub use models::HelpMetadata; + +// CLI argument types (for CLI binaries) +pub use analyze::prelude::{AnalyzeArgs, analyze_root}; +pub use doctor::prelude::{DoctorArgs, doctor_root}; +pub use lint::cli::LintArgs; +pub use lint::commands::lint_root; +pub use report::prelude::{ReportArgs, report_root}; + +// Shared utilities (for CLI binaries) +pub use shared::{CONFIG_FILE_PATH_ENV, RUN_ID_ENV_VAR, print_details}; + +/// Prelude module for convenient glob imports. +/// +/// **DEPRECATED**: This module will be removed in a future version. +/// For new code, use explicit imports from the crate root or specific modules instead. +/// +/// # Migration +/// +/// Instead of: +/// ```rust +/// # #[allow(deprecated)] +/// use dx_scope::prelude::*; +/// ``` +/// +/// Use explicit imports: +/// ```rust +/// use dx_scope::{DoctorRunOptions, AnalyzeOptions, FoundConfig}; +/// use dx_scope::doctor; +/// use dx_scope::analyze; +/// ``` +#[deprecated( + since = "2026.1.13", + note = "Use explicit imports from crate root or specific modules instead of prelude" +)] +pub mod prelude { + pub use crate::analyze::prelude::*; + pub use crate::doctor::prelude::*; + pub use crate::lint::prelude::*; + pub use crate::models::prelude::*; + pub use crate::report::prelude::*; + pub use crate::shared::prelude::*; +} /// Preferred way to output data to users. This macro will write the output to tracing for debugging /// and to stdout using the global stdout writer. Because we use the stdout writer, the calls diff --git a/tests/scope_doctor.rs b/tests/scope_doctor.rs index 90569fb..51a1964 100644 --- a/tests/scope_doctor.rs +++ b/tests/scope_doctor.rs @@ -34,8 +34,9 @@ fn test_run_check_fix_then_recheck_fails_shows_output() { "Check initially failed, fix ran, verification failed, group: \"path-exists\", name: \"file-exists\"", )) .stdout(predicate::str::contains("file-mod.txt")) - .stdout(predicate::str::contains("path-exists/file-exists: /")) - .stdout(predicate::str::contains("path-exists/file-exists: found file /")) + // Output now uses tracing with structured fields: group: "path-exists", action: "file-exists" + .stdout(predicate::str::contains("found file /")) + .stdout(predicate::str::contains("group: \"path-exists\", action: \"file-exists\"")) .stdout(predicate::str::contains("Summary: 0 groups succeeded, 1 groups failed")); helper.clean_work_dir();