ferrous_forge/safety/
execution.rs1use 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
16pub struct ExecutionManager {
18 show_progress: bool,
20 parallel_checks: bool,
22}
23
24impl ExecutionManager {
25 pub fn new(show_progress: bool, parallel_checks: bool) -> Self {
27 Self {
28 show_progress,
29 parallel_checks,
30 }
31 }
32
33 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 pub async fn execute_stage_checks(
56 &self,
57 stage_config: &StageConfig,
58 report: &mut SafetyReport,
59 multi_progress: Option<&MultiProgress>,
60 project_path: &Path,
61 ) -> Result<()> {
62 let checks = &stage_config.checks;
63
64 if self.parallel_checks && checks.len() > 1 {
65 self.run_checks_parallel(checks, report, multi_progress, project_path)
66 .await
67 } else {
68 self.run_checks_sequential(checks, report, multi_progress, project_path)
69 .await
70 }
71 }
72
73 async fn run_checks_sequential(
75 &self,
76 checks: &[CheckType],
77 report: &mut SafetyReport,
78 multi_progress: Option<&MultiProgress>,
79 project_path: &Path,
80 ) -> Result<()> {
81 for check_type in checks {
82 let pb = if let Some(mp) = multi_progress {
83 let pb = mp.add(ProgressBar::new_spinner());
84 pb.set_style(
85 ProgressStyle::default_spinner()
86 .template("{spinner:.green} {msg}")
87 .unwrap_or_else(|_| ProgressStyle::default_spinner()),
88 );
89 pb.set_message(format!("Running {}...", check_type.display_name()));
90 pb.enable_steady_tick(Duration::from_millis(100));
91 Some(pb)
92 } else {
93 None
94 };
95
96 let check_result = execute_check(*check_type, project_path).await?;
97
98 if let Some(pb) = pb {
99 pb.finish_with_message(format!(
100 "{} {} ({:.2}s)",
101 check_result.status_emoji(),
102 check_type.display_name(),
103 check_result.duration.as_secs_f64()
104 ));
105 } else {
106 println!(
107 " {} {} ({:.2}s)",
108 check_result.status_emoji(),
109 check_type.display_name(),
110 check_result.duration.as_secs_f64()
111 );
112 }
113
114 report.add_check(check_result);
115 }
116
117 Ok(())
118 }
119
120 async fn run_checks_parallel(
122 &self,
123 checks: &[CheckType],
124 report: &mut SafetyReport,
125 _multi_progress: Option<&MultiProgress>,
126 project_path: &Path,
127 ) -> Result<()> {
128 self.run_checks_sequential(checks, report, _multi_progress, project_path)
131 .await
132 }
133}
134
135pub async fn execute_check(check_type: CheckType, project_path: &Path) -> Result<CheckResult> {
141 match check_type {
142 CheckType::Format => checks::format::run(project_path).await,
143 CheckType::Clippy => checks::clippy::run(project_path).await,
144 CheckType::Build => checks::build::run(project_path).await,
145 CheckType::Test => checks::test::run(project_path).await,
146 CheckType::Audit => checks::audit::run(project_path).await,
147 CheckType::Doc => checks::doc::run(project_path).await,
148 CheckType::PublishDryRun => checks::publish::run(project_path).await,
149 CheckType::Standards => checks::standards::run(project_path).await,
150 CheckType::DocCoverage => checks::doc::coverage_check(project_path).await,
151 CheckType::License => checks::license::run(project_path).await,
152 CheckType::Semver => checks::semver::run(project_path).await,
153 }
154}
155
156pub fn handle_check_result(
163 check_result: Result<CheckResult>,
164 check_type: CheckType,
165) -> Result<CheckResult> {
166 match check_result {
167 Ok(result) => Ok(result),
168 Err(e) => {
169 let mut result = CheckResult::new(check_type);
170 result.add_error(format!("Check failed: {}", e));
171 result.add_suggestion("Check the error message above for details");
172 Ok(result)
173 }
174 }
175}
176
177pub fn get_stage_for_check(check_type: CheckType) -> PipelineStage {
179 for stage in [
181 PipelineStage::PreCommit,
182 PipelineStage::PrePush,
183 PipelineStage::Publish,
184 ] {
185 if CheckType::for_stage(stage).contains(&check_type) {
186 return stage;
187 }
188 }
189 PipelineStage::PreCommit }