use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::core::recipe::{DetectionReport, FileAnalysis, Recipe, TransformReport};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PipelineContext {
pub project_root: PathBuf,
pub files: HashMap<PathBuf, FilePipelineState>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FilePipelineState {
pub original_path: PathBuf,
pub current_content: Option<String>,
pub stages_passed: Vec<String>,
pub stages_failed: Vec<FailedStage>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FailedStage {
pub recipe: String,
pub error: String,
}
impl PipelineContext {
pub fn new(project_root: PathBuf) -> Self {
Self {
project_root,
files: HashMap::new(),
}
}
pub fn add_file(&mut self, path: PathBuf, _analysis: FileAnalysis) {
self.files.insert(
path.clone(),
FilePipelineState {
original_path: path,
current_content: None,
stages_passed: Vec::new(),
stages_failed: Vec::new(),
},
);
}
pub fn mark_stage_passed(&mut self, path: &Path, recipe: &str) {
if let Some(state) = self.files.get_mut(path) {
state.stages_passed.push(recipe.to_string());
}
}
#[allow(dead_code)]
pub fn mark_stage_failed(&mut self, path: &Path, recipe: &str, error: &str) {
if let Some(state) = self.files.get_mut(path) {
state.stages_failed.push(FailedStage {
recipe: recipe.to_string(),
error: error.to_string(),
});
}
}
#[allow(dead_code)]
pub fn get_files_at_stage(&self, stage: &str) -> Vec<&PathBuf> {
self.files
.iter()
.filter(|(_, state)| state.stages_passed.contains(&stage.to_string()))
.map(|(path, _)| path)
.collect()
}
#[allow(dead_code)]
pub fn get_files_without_stage(&self, stage: &str) -> Vec<&PathBuf> {
self.files
.iter()
.filter(|(_, state)| !state.stages_passed.contains(&stage.to_string()))
.map(|(path, _)| path)
.collect()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PipelineStage {
pub recipe_name: &'static str,
pub detect_result: Option<DetectionReport>,
pub transform_result: Option<TransformReport>,
pub timing: StageTiming,
}
#[derive(Debug, Clone, Default)]
pub struct StageTiming {
pub detect_ms: u64,
pub transform_ms: u64,
pub total_ms: u64,
}
#[derive(Debug, Clone)]
pub struct RecipeDependency {
pub after: String,
pub before: String,
}
pub struct RecipeOrderer {
pub(crate) dependencies: Vec<RecipeDependency>,
}
impl RecipeOrderer {
pub fn new() -> Self {
Self {
dependencies: Vec::new(),
}
}
pub fn from_metadata(recipes: &[&dyn Recipe]) -> Self {
let mut orderer = Self::new();
for recipe in recipes {
let meta = recipe.metadata();
for before in meta.should_run_before {
orderer.dependencies.push(RecipeDependency {
after: meta.name.to_string(),
before: (*before).to_string(),
});
}
for after in meta.should_run_after {
orderer.dependencies.push(RecipeDependency {
after: meta.name.to_string(),
before: (*after).to_string(),
});
}
}
orderer
}
#[allow(dead_code)]
pub fn add_dependency_str(mut self, after: impl Into<String>, before: impl Into<String>) -> Self {
self.dependencies.push(RecipeDependency {
after: after.into(),
before: before.into(),
});
self
}
#[allow(dead_code)]
pub fn add_dependency(mut self, after: &'static str, before: &'static str) -> Self {
self.dependencies.push(RecipeDependency {
after: after.to_string(),
before: before.to_string(),
});
self
}
pub fn order<'a>(&self, recipes: &'a [&'a dyn Recipe]) -> Vec<&'a dyn Recipe> {
let mut ordered = Vec::from(recipes);
for dep in &self.dependencies {
let after_idx = ordered.iter().position(|r| r.metadata().name == dep.after);
let before_idx = ordered.iter().position(|r| r.metadata().name == dep.before);
if let (Some(ai), Some(bi)) = (after_idx, before_idx) {
if ai > bi {
} else if ai < bi {
ordered.swap(ai, bi);
}
}
}
ordered
}
}
impl Default for RecipeOrderer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum IncompatibilityReason {
DuplicateRecipe,
IncompatibleRecipe,
MissingRequiredRecipe,
RequiredRecipeOutOfOrder,
OrderingHintViolation,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct IncompatibleRecipe {
pub recipe_a: String,
pub recipe_b: String,
pub reason: IncompatibilityReason,
}
pub fn validate_recipe_order(recipes: &[&dyn Recipe]) -> Vec<IncompatibleRecipe> {
let mut incompatibilities = Vec::new();
let recipe_names: Vec<_> = recipes
.iter()
.map(|recipe| recipe.metadata().name)
.collect();
for (index, recipe) in recipes.iter().enumerate() {
let metadata = recipe.metadata();
if recipe_names[..index].contains(&metadata.name) {
incompatibilities.push(IncompatibleRecipe {
recipe_a: metadata.name.to_string(),
recipe_b: metadata.name.to_string(),
reason: IncompatibilityReason::DuplicateRecipe,
});
}
for required in metadata.required_recipes {
match recipe_names.iter().position(|name| name == required) {
Some(required_index) if required_index < index => {}
Some(_) => incompatibilities.push(IncompatibleRecipe {
recipe_a: metadata.name.to_string(),
recipe_b: (*required).to_string(),
reason: IncompatibilityReason::RequiredRecipeOutOfOrder,
}),
None => incompatibilities.push(IncompatibleRecipe {
recipe_a: metadata.name.to_string(),
recipe_b: (*required).to_string(),
reason: IncompatibilityReason::MissingRequiredRecipe,
}),
}
}
for incompatible in metadata.incompatible_recipes {
if recipe_names.contains(incompatible) {
incompatibilities.push(IncompatibleRecipe {
recipe_a: metadata.name.to_string(),
recipe_b: (*incompatible).to_string(),
reason: IncompatibilityReason::IncompatibleRecipe,
});
}
}
for before_recipe in metadata.should_run_before {
if let Some(before_idx) = recipe_names.iter().position(|n| n == before_recipe) {
if before_idx < index {
incompatibilities.push(IncompatibleRecipe {
recipe_a: metadata.name.to_string(),
recipe_b: (*before_recipe).to_string(),
reason: IncompatibilityReason::OrderingHintViolation,
});
}
}
}
for after_recipe in metadata.should_run_after {
if let Some(after_idx) = recipe_names.iter().position(|n| n == after_recipe) {
if after_idx > index {
incompatibilities.push(IncompatibleRecipe {
recipe_a: metadata.name.to_string(),
recipe_b: (*after_recipe).to_string(),
reason: IncompatibilityReason::OrderingHintViolation,
});
}
}
}
}
incompatibilities
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum ExecutionEvent {
StageStarted {
recipe: String,
},
StageCompleted {
recipe: String,
duration_ms: u64,
},
StageFailed {
recipe: String,
error: String,
},
FileProcessed {
path: PathBuf,
recipe: String,
success: bool,
},
StageSkipped {
recipe: String,
reason: String,
},
}