use std::path::Path;
use anyhow::{Context, Result, bail};
use indicatif::ProgressBar;
use crate::core::recipe::FileClassification;
use crate::core::registry::RecipeRegistry;
use crate::utils::terminal;
pub fn execute(path: &Path) -> Result<()> {
if !path.exists() {
bail!("File does not exist: {}", path.display());
}
if !path.is_file() {
bail!("Explain expects a file path: {}", path.display());
}
let path = if path.is_relative() {
std::env::current_dir()?.join(path)
} else {
path.to_path_buf()
};
println!("{}", terminal::label("Migration Explanation"));
println!("file: {}", path.display());
let registry = RecipeRegistry::new();
let progress = ProgressBar::hidden();
let mut explained = false;
for recipe in registry.all() {
let metadata = recipe.metadata();
if !has_supported_extension(&path, metadata.supported_extensions) {
continue;
}
let report = recipe
.detect(&path, &progress)
.with_context(|| format!("Failed to run detection for `{}`", metadata.name))?;
println!();
println!("recipe: {}", metadata.name);
if let Some(failure) = report.failed_files.iter().find(|failure| failure.path == path) {
println!("status: skipped");
println!("reason: detection failed ({})", failure.error);
explained = true;
continue;
}
if let Some(analysis) = report.analyses.iter().find(|analysis| analysis.path == path) {
println!(
"status: {}",
if analysis.is_transform_safe {
"eligible"
} else {
"skipped"
}
);
println!(
"reason: {}",
explanation_reason(analysis.classification, analysis.is_transform_safe)
);
println!("confidence: {}%", analysis.confidence_score);
println!("patterns: {}", analysis.detected_patterns.join(", "));
explained = true;
} else {
println!("status: skipped");
println!("reason: no migration patterns matched");
println!("confidence: 0%");
explained = true;
}
}
if !explained {
println!();
println!("status: skipped");
println!("reason: no registered recipe supports this file extension");
println!("confidence: 0%");
}
Ok(())
}
fn has_supported_extension(path: &Path, supported_extensions: &[&str]) -> bool {
path.extension()
.and_then(|extension| extension.to_str())
.is_some_and(|extension| supported_extensions.contains(&extension))
}
fn explanation_reason(classification: FileClassification, is_transform_safe: bool) -> &'static str {
match (classification, is_transform_safe) {
(FileClassification::Safe, true) => {
"matched supported migration patterns and is within the safe transform subset"
}
(FileClassification::Safe, false) => {
"matched migration patterns, but this recipe does not mark it transform-safe"
}
(FileClassification::Risky, _) => {
"matched migration patterns, but risky or unsupported patterns were detected"
}
}
}