Skip to main content

itools_config/
lib.rs

1#![warn(missing_docs)]
2
3//! iTools 配置解析模块
4//! 
5//! 提供基于 fragment 的配置解析功能,支持将配置文件分割成多个片段进行管理和加载。
6
7use serde::{Deserialize, Serialize};
8use oak_json::{JsonValueNode, ast::{JsonObject, JsonField, JsonString, JsonBoolean, JsonNumber}};
9use std::{collections::HashMap, env, error::Error, fs, path::Path};
10use tracing::info;
11
12/// 国际化功能 - 使用 itools-localization 模块的国际化实现
13pub use itools_localization::{t, t_with_args};
14
15/// 命令行参数解析 trait
16pub trait Parse {
17    /// 从命令行参数中解析当前类型
18    fn parse(args: &mut Vec<String>) -> Self;
19}
20
21/// 为 String 实现 Parse trait
22impl Parse for String {
23    fn parse(args: &mut Vec<String>) -> Self {
24        if args.is_empty() {
25            panic!("{}", t("errors.expected_string_argument"));
26        }
27        args.remove(0)
28    }
29}
30
31/// 为 i32 实现 Parse trait
32impl Parse for i32 {
33    fn parse(args: &mut Vec<String>) -> Self {
34        if args.is_empty() {
35            panic!("{}", t("errors.expected_integer_argument"));
36        }
37        args.remove(0).parse().expect(&t("errors.invalid_integer"))
38    }
39}
40
41/// 为 u32 实现 Parse trait
42impl Parse for u32 {
43    fn parse(args: &mut Vec<String>) -> Self {
44        if args.is_empty() {
45            panic!("{}", t("errors.expected_unsigned_integer_argument"));
46        }
47        args.remove(0).parse().expect(&t("errors.invalid_unsigned_integer"))
48    }
49}
50
51/// 为 bool 实现 Parse trait
52impl Parse for bool {
53    fn parse(args: &mut Vec<String>) -> Self {
54        if args.is_empty() {
55            panic!("{}", t("errors.expected_boolean_argument"));
56        }
57        args.remove(0).parse().expect(&t("errors.invalid_boolean"))
58    }
59}
60
61/// 配置片段
62#[derive(Debug, Deserialize, Serialize, Clone)]
63pub struct ConfigFragment {
64    /// 配置数据
65    pub data: JsonValueNode,
66    /// 优先级
67    pub priority: u32,
68}
69
70/// 配置管理器
71#[derive(Debug)]
72pub struct ConfigManager {
73    /// 配置片段
74    fragments: Vec<ConfigFragment>,
75    /// 合并后的配置
76    merged_config: JsonValueNode,
77}
78
79impl ConfigManager {
80    /// 创建新的配置管理器
81    pub fn new() -> Self {
82        Self { fragments: Vec::new(), merged_config: JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() }) }
83    }
84
85    /// 加载配置片段
86    pub fn load_fragment(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
87        info!("{}", t_with_args("messages.loading_config_fragment", &[("path", &path.to_string_lossy())]));
88
89        let content = fs::read_to_string(path)
90            .map_err(|_e| format!("{}", t_with_args("errors.config_not_found", &[("path", &path.to_string_lossy())])))?;
91
92        let data = match path.extension().and_then(|ext| ext.to_str()) {
93            Some("json") => {
94                #[cfg(feature = "json")]
95                {
96                    oak_json::from_str(&content)?
97                }
98                #[cfg(not(feature = "json"))]
99                {
100                    return Err("JSON feature is not enabled".into());
101                }
102            }
103            Some("toml") => {
104                #[cfg(feature = "toml")]
105                {
106                    oak_toml::from_str(&content)?
107                }
108                #[cfg(not(feature = "toml"))]
109                {
110                    return Err("TOML feature is not enabled".into());
111                }
112            }
113            Some("yaml") | Some("yml") => {
114                #[cfg(feature = "yaml")]
115                {
116                    oak_yaml::from_str(&content)?
117                }
118                #[cfg(not(feature = "yaml"))]
119                {
120                    return Err("YAML feature is not enabled".into());
121                }
122            }
123            _ => return Err("Unsupported config file format".into()),
124        };
125
126        // 验证配置
127        self.validate_config(&data)?;
128
129        let fragment = ConfigFragment { data, priority: self.fragments.len() as u32 };
130
131        self.fragments.push(fragment);
132        self.merge_configs();
133
134        Ok(())
135    }
136
137    /// 验证配置
138    pub fn validate_config(&self, config: &JsonValueNode) -> Result<(), Box<dyn Error>> {
139        // 这里可以添加配置验证逻辑
140        // 例如:检查必需的配置项、验证配置值的类型和范围等
141
142        // 示例:检查是否存在必需的配置项
143        if let oak_json::JsonValueNode::Object(_map) = config {
144            // 这里可以添加具体的验证逻辑
145        }
146
147        Ok(())
148    }
149
150    /// 验证合并后的配置
151    pub fn validate_merged_config(&self) -> Result<(), Box<dyn Error>> {
152        self.validate_config(&self.merged_config)
153    }
154
155    /// 加载目录中的配置片段
156    pub fn load_directory(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
157        if !path.exists() || !path.is_dir() {
158            return Ok(());
159        }
160
161        let entries = fs::read_dir(path)?;
162        for entry in entries {
163            let entry = entry?;
164            let entry_path = entry.path();
165
166            if entry_path.is_dir() {
167                self.load_directory(&entry_path)?;
168            }
169            else if let Some(ext) = entry_path.extension() {
170                if ["json", "toml", "yaml", "yml"].contains(&ext.to_str().unwrap_or("")) {
171                    self.load_fragment(&entry_path)?;
172                }
173            }
174        }
175
176        Ok(())
177    }
178
179    /// 合并配置
180    fn merge_configs(&mut self) {
181        // 按优先级排序(优先级高的在后面,会覆盖前面的)
182        self.fragments.sort_by(|a, b| a.priority.cmp(&b.priority));
183
184        // 初始化合并后的配置
185        self.merged_config = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
186
187        // 合并所有配置片段
188        for fragment in &self.fragments {
189            self.merged_config = self.merge_values(self.merged_config.clone(), fragment.data.clone());
190        }
191    }
192
193    /// 合并两个值
194    fn merge_values(&self, target: JsonValueNode, source: JsonValueNode) -> JsonValueNode {
195        match (target, source) {
196            (JsonValueNode::Object(target_obj), JsonValueNode::Object(source_obj)) => {
197                // 构建target字段的HashMap以便快速查找
198                let mut target_fields: HashMap<String, JsonValueNode> = target_obj.fields.into_iter()
199                    .map(|field| (field.name.value, field.value))
200                    .collect();
201                
202                // 处理source对象的所有字段
203                for source_field in source_obj.fields {
204                    let key = source_field.name.value;
205                    let source_value = source_field.value;
206                    
207                    if target_fields.contains_key(&key) {
208                        let target_value = target_fields.remove(&key).unwrap();
209                        target_fields.insert(key, self.merge_values(target_value, source_value));
210                    }
211                    else {
212                        target_fields.insert(key, source_value);
213                    }
214                }
215                
216                // 将HashMap转换回Vec<JsonField>
217                let mut fields: Vec<JsonField> = target_fields.into_iter()
218                    .map(|(k, v)| JsonField {
219                        name: JsonString { value: k, span: (0..0).into() },
220                        value: v,
221                        span: (0..0).into()
222                    })
223                    .collect();
224                
225                // 按字段名排序以保持一致性
226                fields.sort_by(|a, b| a.name.value.cmp(&b.name.value));
227                
228                JsonValueNode::Object(JsonObject { fields, span: (0..0).into() })
229            }
230            (_, source) => source, // 其他类型直接替换
231        }
232    }
233
234    /// 获取合并后的配置
235    pub fn get_config(&self) -> &JsonValueNode {
236        &self.merged_config
237    }
238
239    /// 从命令行参数覆盖配置
240    pub fn override_from_args(&mut self, args: &[String]) {
241        // 简单实现:支持 --config.key=value 格式
242        for arg in args {
243            if arg.starts_with("--config.") {
244                let parts: Vec<&str> = arg.split('=').collect();
245                if parts.len() == 2 {
246                    let key_path = parts[0].trim_start_matches("--config.");
247                    let value = parts[1];
248                    self.set_config_value(key_path, value);
249                }
250            }
251        }
252    }
253
254    /// 从环境变量覆盖配置
255    pub fn override_from_env(&mut self) {
256        // 简单实现:支持 ITOOLS_CONFIG_ 前缀的环境变量
257        for (key, value) in env::vars() {
258            if key.starts_with("ITOOLS_CONFIG_") {
259                let key_path = key.trim_start_matches("ITOOLS_CONFIG_").to_lowercase().replace('_', ".");
260                self.set_config_value(&key_path, &value);
261            }
262        }
263    }
264
265    /// 添加默认配置值
266    pub fn add_defaults(&mut self, defaults: JsonValueNode) {
267        let fragment = ConfigFragment { data: defaults, priority: 0 };
268        self.fragments.push(fragment);
269        self.merge_configs();
270    }
271
272    /// 设置配置值
273    fn set_config_value(&mut self, key_path: &str, value: &str) {
274        let keys: Vec<&str> = key_path.split('.').collect();
275        let mut config = self.merged_config.clone();
276
277        // 导航到目标位置
278        let mut current = &mut config;
279        for (i, key) in keys.iter().enumerate() {
280            if i == keys.len() - 1 {
281                // 最后一个键,设置值
282                *current = self.parse_value(value);
283            }
284            else {
285                // 确保当前是对象
286                if !matches!(current, JsonValueNode::Object(_)) {
287                    *current = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
288                }
289                
290                // 查找或创建字段
291                match current {
292                    JsonValueNode::Object(current_obj) => {
293                        // 先检查是否存在该字段
294                        let field_index = current_obj.fields.iter()
295                            .position(|field| field.name.value == *key);
296                        
297                        if let Some(index) = field_index {
298                            // 确保字段值是对象
299                            if !matches!(&current_obj.fields[index].value, JsonValueNode::Object(_)) {
300                                current_obj.fields[index].value = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
301                            }
302                            current = &mut current_obj.fields[index].value;
303                        }
304                        else {
305                            // 创建新字段
306                            current_obj.fields.push(JsonField {
307                                name: JsonString { value: key.to_string(), span: (0..0).into() },
308                                value: JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() }),
309                                span: (0..0).into()
310                            });
311                            // 找到刚添加的字段并设置current
312                            current = &mut current_obj.fields.last_mut().unwrap().value;
313                        }
314                    }
315                    _ => {
316                        // 不应该发生,因为上面已经确保是对象
317                        *current = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
318                        if let JsonValueNode::Object(current_obj) = current {
319                            // 创建新字段
320                            current_obj.fields.push(JsonField {
321                                name: JsonString { value: key.to_string(), span: (0..0).into() },
322                                value: JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() }),
323                                span: (0..0).into()
324                            });
325                            // 找到刚添加的字段并设置current
326                            current = &mut current_obj.fields.last_mut().unwrap().value;
327                        }
328                    }
329                }
330            }
331        }
332
333        self.merged_config = config;
334    }
335
336    /// 解析值
337    fn parse_value(&self, value: &str) -> JsonValueNode {
338        // 尝试解析为不同类型
339        if value == "true" {
340            JsonValueNode::Boolean(JsonBoolean { value: true, span: (0..0).into() })
341        }
342        else if value == "false" {
343            JsonValueNode::Boolean(JsonBoolean { value: false, span: (0..0).into() })
344        }
345        else if let Ok(num) = value.parse::<i64>() {
346            JsonValueNode::Number(JsonNumber { value: num as f64, span: (0..0).into() })
347        }
348        else if let Ok(num) = value.parse::<f64>() {
349            JsonValueNode::Number(JsonNumber { value: num, span: (0..0).into() })
350        }
351        else {
352            JsonValueNode::String(JsonString { value: value.to_string(), span: (0..0).into() })
353        }
354    }
355}
356
357/// iTools 命令行工具
358#[derive(Debug)]
359pub struct Cli {
360    /// 命令
361    pub command: Command,
362}
363
364/// 命令枚举
365#[derive(Debug)]
366pub enum Command {
367    /// 初始化新项目
368    Init {
369        /// 项目名称
370        name: String,
371    },
372    /// 构建项目
373    Build,
374    /// 开发服务器
375    Dev,
376    /// 检查项目
377    Check,
378}
379
380/// 为 Cli 实现 Parse trait
381impl Parse for Cli {
382    fn parse(args: &mut Vec<String>) -> Self {
383        Self { command: Command::parse(args) }
384    }
385}
386
387/// 为 Command 实现 Parse trait
388impl Parse for Command {
389    fn parse(args: &mut Vec<String>) -> Self {
390        if args.is_empty() {
391            panic!("{}", t("errors.expected_command"));
392        }
393        let cmd = args.remove(0);
394        match cmd.as_str() {
395            "init" => Self::Init { name: String::parse(args) },
396            "build" => Self::Build,
397            "dev" => Self::Dev,
398            "check" => Self::Check,
399            _ => panic!("{}", t_with_args("errors.unknown_command", &[("command", &cmd)])),
400        }
401    }
402}
403
404/// 初始化 CLI 环境
405pub fn init_cli() {
406    // 初始化日志
407    tracing_subscriber::fmt().with_env_filter(tracing_subscriber::EnvFilter::from_default_env()).init();
408
409    info!("{}", t("messages.cli_initialized"));
410}
411
412/// TUI 功能模块
413pub mod tui {
414    use std::{
415        io::{self, Write},
416        thread,
417        time::Duration,
418    };
419
420    /// 进度条
421    pub struct ProgressBar {
422        total: u64,
423        current: u64,
424        width: u16,
425    }
426
427    impl ProgressBar {
428        /// 创建新的进度条
429        pub fn new(total: u64, width: u16) -> Self {
430            Self { total, current: 0, width }
431        }
432
433        /// 更新进度
434        pub fn update(&mut self, current: u64) {
435            self.current = current;
436            self.draw();
437        }
438
439        /// 增加进度
440        pub fn inc(&mut self, delta: u64) {
441            self.current += delta;
442            self.draw();
443        }
444
445        /// 完成进度
446        pub fn finish(&mut self) {
447            self.current = self.total;
448            self.draw();
449            println!();
450        }
451
452        /// 绘制进度条
453        fn draw(&self) {
454            let percentage = if self.total > 0 { (self.current as f64 / self.total as f64) * 100.0 } else { 0.0 };
455
456            let filled = (percentage / 100.0 * self.width as f64) as u16;
457            let empty = self.width - filled;
458
459            print!("\r[");
460            print!("{}", "=".repeat(filled as usize));
461            print!("{}", " ".repeat(empty as usize));
462            print!("}}] {:.1}%", percentage);
463            io::stdout().flush().unwrap();
464        }
465    }
466
467    /// 交互式选择菜单
468    pub fn select<T>(items: &[T], prompt: &str) -> Option<usize>
469    where
470        T: std::fmt::Display,
471    {
472        println!("{}", prompt);
473        for (i, item) in items.iter().enumerate() {
474            println!("{}: {}", i + 1, item);
475        }
476
477        loop {
478            print!("请选择 (1-{}): ", items.len());
479            io::stdout().flush().unwrap();
480
481            let mut input = String::new();
482            io::stdin().read_line(&mut input).unwrap();
483
484            match input.trim().parse::<usize>() {
485                Ok(choice) if choice >= 1 && choice <= items.len() => {
486                    return Some(choice - 1);
487                }
488                _ => {
489                    println!("无效的选择,请重新输入");
490                }
491            }
492        }
493    }
494
495    /// 显示加载动画
496    pub fn loading_animation(duration: Duration, message: &str) {
497        let chars = ["|", "/", "-", "\\"];
498        let start = std::time::Instant::now();
499
500        print!("{}", message);
501        io::stdout().flush().unwrap();
502
503        let mut i = 0;
504        while start.elapsed() < duration {
505            print!("\r{}{}", message, chars[i % chars.len()]);
506            io::stdout().flush().unwrap();
507            thread::sleep(Duration::from_millis(100));
508            i += 1;
509        }
510
511        print!("\r{}{}", message, " ");
512        println!();
513    }
514}
515
516/// 执行命令
517pub fn execute_command(cli: Cli) -> Result<(), Box<dyn Error>> {
518    match cli.command {
519        Command::Init { name } => {
520            info!("{}", t_with_args("messages.initializing_project", &[("name", &name)]));
521            // 测试进度条
522            let mut pb = tui::ProgressBar::new(100, 50);
523            for i in 0..=100 {
524                pb.update(i);
525                std::thread::sleep(std::time::Duration::from_millis(50));
526            }
527            pb.finish();
528            Ok(())
529        }
530        Command::Build => {
531            info!("{}", t("messages.building_project"));
532            // 测试加载动画
533            tui::loading_animation(std::time::Duration::from_secs(3), "构建中...");
534            Ok(())
535        }
536        Command::Dev => {
537            info!("{}", t("messages.starting_dev_server"));
538            // 测试交互式选择菜单
539            let items = ["选项 1", "选项 2", "选项 3"];
540            if let Some(choice) = tui::select(&items, "请选择一个选项:") {
541                info!("选择了: {}", items[choice]);
542            }
543            Ok(())
544        }
545        Command::Check => {
546            info!("{}", t("messages.checking_project"));
547            // 测试进度条
548            let mut pb = tui::ProgressBar::new(50, 30);
549            for i in 0..=50 {
550                pb.update(i);
551                std::thread::sleep(std::time::Duration::from_millis(100));
552            }
553            pb.finish();
554            Ok(())
555        }
556    }
557}
558
559/// 测试宏功能
560pub mod test_macro;
561pub use test_macro::test_macro;