morph_cli/core/config/
loader.rs1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result};
5
6use super::schema::MorphCliSchema;
7
8pub struct ConfigLoader {
9 #[allow(dead_code)]
10 search_dirs: Vec<PathBuf>,
11}
12
13impl ConfigLoader {
14 #[allow(dead_code)]
15 pub fn new() -> Self {
16 Self {
17 search_dirs: Vec::new(),
18 }
19 }
20
21 #[allow(dead_code)]
22 pub fn add_search_dir(&mut self, dir: impl AsRef<Path>) {
23 self.search_dirs.push(dir.as_ref().to_path_buf());
24 }
25
26 #[allow(dead_code)]
27 pub fn load(&self) -> Result<MorphCliSchema> {
28 if let Some(config_path) = self.find_config()? {
29 let content = fs::read_to_string(&config_path)
30 .with_context(|| format!("Failed to read {}", config_path.display()))?;
31
32 toml::from_str(&content)
33 .with_context(|| format!("Failed to parse {}", config_path.display()))
34 } else {
35 Ok(MorphCliSchema::default())
36 }
37 }
38
39 #[allow(dead_code)]
40 pub fn find_config(&self) -> Result<Option<PathBuf>> {
41 if self.search_dirs.is_empty() {
42 let current = std::env::current_dir().context("Failed to get current directory")?;
43 return self.find_config_upward(¤t);
44 }
45
46 for dir in &self.search_dirs {
47 if let Some(path) = self.find_config_upward(dir)? {
48 return Ok(Some(path));
49 }
50 }
51
52 Ok(None)
53 }
54
55 #[allow(dead_code)]
56 fn find_config_upward(&self, start: &Path) -> Result<Option<PathBuf>> {
57 let mut current = start.to_path_buf();
58
59 loop {
60 let config_path = current.join("morph-cli.toml");
61 if config_path.exists() {
62 return Ok(Some(config_path));
63 }
64
65 if !current.pop() {
66 return Ok(None);
67 }
68 }
69 }
70
71 pub fn generate_config(path: &Path) -> Result<PathBuf> {
72 let config_path = path.join("morph-cli.toml");
73
74 if config_path.exists() {
75 anyhow::bail!("morph-cli.toml already exists at {}", path.display());
76 }
77
78 let content = Self::default_config_content();
79 fs::write(&config_path, content)
80 .with_context(|| format!("Failed to write {}", config_path.display()))?;
81
82 Ok(config_path)
83 }
84
85 fn default_config_content() -> String {
86 r#"# morph-cli Configuration
87# https://github.com/s0r0j/morph-cli
88
89# Enabled recipes for transformation
90enabled_recipes = ["commonjs_to_esm"]
91
92# Paths to exclude from scanning
93# excluded_paths = ["node_modules", "dist", "build", ".git"]
94
95# Maximum file size in KB (files exceeding this are skipped)
96# max_file_size_kb = 500
97
98# Default to dry-run mode
99# dry_run_default = true
100
101# Enable backup before transformations
102# backup_enabled = true
103
104# Number of lines to show in preview (0 = unlimited)
105# preview_lines = 100
106
107# Allow risky transformations (requires manual review)
108# allow_risky_transforms = false
109"#
110 .to_string()
111 }
112}
113
114impl Default for ConfigLoader {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120#[allow(dead_code)]
121pub fn load_config_for_path(path: &Path) -> Result<MorphCliSchema> {
122 let mut loader = ConfigLoader::new();
123 loader.add_search_dir(path);
124 loader.load()
125}
126
127#[allow(dead_code)]
128pub fn find_config_upward(start: &Path) -> Result<Option<PathBuf>> {
129 let mut current = start.to_path_buf();
130
131 loop {
132 let config_path = current.join("morph-cli.toml");
133 if config_path.exists() {
134 return Ok(Some(config_path));
135 }
136
137 if !current.pop() {
138 return Ok(None);
139 }
140 }
141}