ferrous_forge/safety/
execution.rs

1//! Safety pipeline execution logic
2//!
3//! This module contains the core execution logic for safety checks,
4//! including progress display and parallel/sequential execution.
5
6use super::{
7    CheckType, PipelineStage, checks,
8    config::StageConfig,
9    report::{CheckResult, SafetyReport},
10};
11use crate::Result;
12use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
13use std::path::Path;
14use std::time::Duration;
15
16/// Progress display and execution coordinator
17pub struct ExecutionManager {
18    /// Whether to show progress indicators
19    show_progress: bool,
20    /// Whether to run checks in parallel
21    parallel_checks: bool,
22}
23
24impl ExecutionManager {
25    /// Create a new execution manager
26    pub fn new(show_progress: bool, parallel_checks: bool) -> Self {
27        Self {
28            show_progress,
29            parallel_checks,
30        }
31    }
32
33    /// Setup progress indicators and display header
34    pub fn setup_progress_display(&self, stage: PipelineStage) -> Option<MultiProgress> {
35        let multi_progress = if self.show_progress {
36            Some(MultiProgress::new())
37        } else {
38            None
39        };
40
41        println!(
42            "🛡️  Ferrous Forge Safety Pipeline - {}",
43            stage.display_name()
44        );
45        println!("{}", "=".repeat(50));
46
47        multi_progress
48    }
49
50    /// Execute checks for the stage
51    pub async fn execute_stage_checks(
52        &self,
53        stage_config: &StageConfig,
54        report: &mut SafetyReport,
55        multi_progress: Option<&MultiProgress>,
56        project_path: &Path,
57    ) -> Result<()> {
58        let checks = &stage_config.checks;
59
60        if self.parallel_checks && checks.len() > 1 {
61            self.run_checks_parallel(checks, report, multi_progress, project_path)
62                .await
63        } else {
64            self.run_checks_sequential(checks, report, multi_progress, project_path)
65                .await
66        }
67    }
68
69    /// Run checks sequentially with progress indicators
70    async fn run_checks_sequential(
71        &self,
72        checks: &[CheckType],
73        report: &mut SafetyReport,
74        multi_progress: Option<&MultiProgress>,
75        project_path: &Path,
76    ) -> Result<()> {
77        for check_type in checks {
78            let pb = if let Some(mp) = multi_progress {
79                let pb = mp.add(ProgressBar::new_spinner());
80                pb.set_style(
81                    ProgressStyle::default_spinner()
82                        .template("{spinner:.green} {msg}")
83                        .unwrap_or_else(|_| ProgressStyle::default_spinner()),
84                );
85                pb.set_message(format!("Running {}...", check_type.display_name()));
86                pb.enable_steady_tick(Duration::from_millis(100));
87                Some(pb)
88            } else {
89                None
90            };
91
92            let check_result = execute_check(*check_type, project_path).await?;
93
94            if let Some(pb) = pb {
95                pb.finish_with_message(format!(
96                    "{} {} ({:.2}s)",
97                    check_result.status_emoji(),
98                    check_type.display_name(),
99                    check_result.duration.as_secs_f64()
100                ));
101            } else {
102                println!(
103                    "  {} {} ({:.2}s)",
104                    check_result.status_emoji(),
105                    check_type.display_name(),
106                    check_result.duration.as_secs_f64()
107                );
108            }
109
110            report.add_check(check_result);
111        }
112
113        Ok(())
114    }
115
116    /// Run checks in parallel for better performance
117    async fn run_checks_parallel(
118        &self,
119        checks: &[CheckType],
120        report: &mut SafetyReport,
121        _multi_progress: Option<&MultiProgress>,
122        project_path: &Path,
123    ) -> Result<()> {
124        // For now, implement as sequential until we add proper parallel execution
125        // Parallel execution requires careful handling of stdout/stderr
126        self.run_checks_sequential(checks, report, _multi_progress, project_path)
127            .await
128    }
129}
130
131/// Execute a specific check type
132pub async fn execute_check(check_type: CheckType, project_path: &Path) -> Result<CheckResult> {
133    match check_type {
134        CheckType::Format => checks::format::run(project_path).await,
135        CheckType::Clippy => checks::clippy::run(project_path).await,
136        CheckType::Build => checks::build::run(project_path).await,
137        CheckType::Test => checks::test::run(project_path).await,
138        CheckType::Audit => checks::audit::run(project_path).await,
139        CheckType::Doc => checks::doc::run(project_path).await,
140        CheckType::PublishDryRun => checks::publish::run(project_path).await,
141        CheckType::Standards => checks::standards::run(project_path).await,
142        CheckType::DocCoverage => checks::doc::coverage_check(project_path).await,
143        CheckType::License => checks::license::run(project_path).await,
144        CheckType::Semver => checks::semver::run(project_path).await,
145    }
146}
147
148/// Handle check result and convert errors to check results
149pub fn handle_check_result(
150    check_result: Result<CheckResult>,
151    check_type: CheckType,
152) -> Result<CheckResult> {
153    match check_result {
154        Ok(result) => Ok(result),
155        Err(e) => {
156            let mut result = CheckResult::new(check_type);
157            result.add_error(format!("Check failed: {}", e));
158            result.add_suggestion("Check the error message above for details");
159            Ok(result)
160        }
161    }
162}
163
164/// Get the appropriate stage for a check type
165pub fn get_stage_for_check(check_type: CheckType) -> PipelineStage {
166    // Find the first stage that includes this check
167    for stage in [
168        PipelineStage::PreCommit,
169        PipelineStage::PrePush,
170        PipelineStage::Publish,
171    ] {
172        if CheckType::for_stage(stage).contains(&check_type) {
173            return stage;
174        }
175    }
176    PipelineStage::PreCommit // Default fallback
177}