pub mod converters;
pub mod detect;
pub mod parsers;
pub mod validate;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tool {
ESLint,
Prettier,
Black,
Isort,
}
impl Tool {
pub fn as_str(&self) -> &'static str {
match self {
Tool::ESLint => "eslint",
Tool::Prettier => "prettier",
Tool::Black => "black",
Tool::Isort => "isort",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"eslint" => Some(Tool::ESLint),
"prettier" => Some(Tool::Prettier),
"black" => Some(Tool::Black),
"isort" => Some(Tool::Isort),
_ => None,
}
}
}
#[derive(Debug)]
pub struct DetectedConfig {
pub tool: Tool,
pub path: PathBuf,
pub language: String,
}
#[derive(Debug, Clone, Copy)]
pub enum WarningSeverity {
Info,
Warning,
Error,
}
#[derive(Debug)]
pub struct MigrationWarning {
pub source: String,
pub message: String,
pub severity: WarningSeverity,
}
#[derive(Debug, Default)]
pub struct MigrationResult {
pub created_files: Vec<PathBuf>,
pub backed_up_files: Vec<PathBuf>,
pub config_changes: Vec<String>,
pub warnings: Vec<MigrationWarning>,
pub suggestions: Vec<String>,
}
#[derive(Debug, Default)]
pub struct MigrationOptions {
pub dry_run: bool,
pub backup: bool,
pub tool_filter: Option<Tool>,
pub verbose: bool,
}
pub fn migrate_configs(
project_root: &Path,
options: &MigrationOptions,
) -> Result<MigrationResult, String> {
let mut result = MigrationResult::default();
let detected = detect::detect_configs(project_root, options.tool_filter)?;
if detected.is_empty() {
result.warnings.push(MigrationWarning {
source: "detect".to_string(),
message: "No supported configuration files found".to_string(),
severity: WarningSeverity::Info,
});
return Ok(result);
}
for config in &detected {
match convert_config(config, project_root, options) {
Ok(conversion) => {
if options.dry_run {
result.config_changes.extend(conversion.changes);
} else {
if options.backup {
if let Some(backup_path) = backup_original(&config.path)? {
result.backed_up_files.push(backup_path);
}
}
result.created_files.extend(conversion.created_files);
result.config_changes.extend(conversion.changes);
}
result.warnings.extend(conversion.warnings);
}
Err(e) => {
result.warnings.push(MigrationWarning {
source: config.tool.as_str().to_string(),
message: format!("Failed to convert {}: {}", config.path.display(), e),
severity: WarningSeverity::Error,
});
}
}
}
let validation = validate::validate_migration(&result, project_root)?;
result.warnings.extend(validation.warnings);
result.suggestions.extend(validation.suggestions);
Ok(result)
}
pub(crate) struct ConversionResult {
pub created_files: Vec<PathBuf>,
pub changes: Vec<String>,
pub warnings: Vec<MigrationWarning>,
}
fn convert_config(
config: &DetectedConfig,
project_root: &Path,
options: &MigrationOptions,
) -> Result<ConversionResult, String> {
match config.tool {
Tool::ESLint => converters::eslint::convert(config, project_root, options.dry_run),
Tool::Prettier => converters::prettier::convert(config, project_root, options.dry_run),
Tool::Black => converters::python::convert_black(config, project_root, options.dry_run),
Tool::Isort => converters::python::convert_isort(config, project_root, options.dry_run),
}
}
fn backup_original(path: &Path) -> Result<Option<PathBuf>, String> {
if path.exists() {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
let backup_path = if ext.is_empty() {
path.with_extension("backup")
} else {
path.with_extension(format!("{}.backup", ext))
};
std::fs::copy(path, &backup_path)
.map_err(|e| format!("Failed to backup {}: {}", path.display(), e))?;
Ok(Some(backup_path))
} else {
Ok(None)
}
}