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