Skip to main content

itools_config/
lib.rs

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