use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use super::schema::MorphCliSchema;
pub struct ConfigLoader {
#[allow(dead_code)]
search_dirs: Vec<PathBuf>,
}
impl ConfigLoader {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
search_dirs: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_search_dir(&mut self, dir: impl AsRef<Path>) {
self.search_dirs.push(dir.as_ref().to_path_buf());
}
#[allow(dead_code)]
pub fn load(&self) -> Result<MorphCliSchema> {
if let Some(config_path) = self.find_config()? {
let content = fs::read_to_string(&config_path)
.with_context(|| format!("Failed to read {}", config_path.display()))?;
toml::from_str(&content)
.with_context(|| format!("Failed to parse {}", config_path.display()))
} else {
Ok(MorphCliSchema::default())
}
}
#[allow(dead_code)]
pub fn find_config(&self) -> Result<Option<PathBuf>> {
if self.search_dirs.is_empty() {
let current = std::env::current_dir().context("Failed to get current directory")?;
return self.find_config_upward(¤t);
}
for dir in &self.search_dirs {
if let Some(path) = self.find_config_upward(dir)? {
return Ok(Some(path));
}
}
Ok(None)
}
#[allow(dead_code)]
fn find_config_upward(&self, start: &Path) -> Result<Option<PathBuf>> {
let mut current = start.to_path_buf();
loop {
let config_path = current.join("morph-cli.toml");
if config_path.exists() {
return Ok(Some(config_path));
}
if !current.pop() {
return Ok(None);
}
}
}
pub fn generate_config(path: &Path) -> Result<PathBuf> {
let config_path = path.join("morph-cli.toml");
if config_path.exists() {
anyhow::bail!("morph-cli.toml already exists at {}", path.display());
}
let content = Self::default_config_content();
fs::write(&config_path, content)
.with_context(|| format!("Failed to write {}", config_path.display()))?;
Ok(config_path)
}
fn default_config_content() -> String {
r#"# morph-cli Configuration
# https://github.com/s0r0j/morph-cli
# Enabled recipes for transformation
enabled_recipes = ["commonjs_to_esm"]
# Paths to exclude from scanning
# excluded_paths = ["node_modules", "dist", "build", ".git"]
# Maximum file size in KB (files exceeding this are skipped)
# max_file_size_kb = 500
# Default to dry-run mode
# dry_run_default = true
# Enable backup before transformations
# backup_enabled = true
# Number of lines to show in preview (0 = unlimited)
# preview_lines = 100
# Allow risky transformations (requires manual review)
# allow_risky_transforms = false
"#
.to_string()
}
}
impl Default for ConfigLoader {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
pub fn load_config_for_path(path: &Path) -> Result<MorphCliSchema> {
let mut loader = ConfigLoader::new();
loader.add_search_dir(path);
loader.load()
}
#[allow(dead_code)]
pub fn find_config_upward(start: &Path) -> Result<Option<PathBuf>> {
let mut current = start.to_path_buf();
loop {
let config_path = current.join("morph-cli.toml");
if config_path.exists() {
return Ok(Some(config_path));
}
if !current.pop() {
return Ok(None);
}
}
}