1#![warn(missing_docs)]
2
3use 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
12pub use itools_localization::{t, t_with_args};
14
15pub trait Parse {
17 fn parse(args: &mut Vec<String>) -> Self;
19}
20
21impl 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
31impl 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
41impl 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
51impl 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#[derive(Debug, Deserialize, Serialize, Clone)]
63pub struct ConfigFragment {
64 pub data: JsonValueNode,
66 pub priority: u32,
68}
69
70#[derive(Debug)]
72pub struct ConfigManager {
73 fragments: Vec<ConfigFragment>,
75 merged_config: JsonValueNode,
77}
78
79impl ConfigManager {
80 pub fn new() -> Self {
82 Self { fragments: Vec::new(), merged_config: JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() }) }
83 }
84
85 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 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 pub fn validate_config(&self, config: &JsonValueNode) -> Result<(), Box<dyn Error>> {
139 if let oak_json::JsonValueNode::Object(_map) = config {
144 }
146
147 Ok(())
148 }
149
150 pub fn validate_merged_config(&self) -> Result<(), Box<dyn Error>> {
152 self.validate_config(&self.merged_config)
153 }
154
155 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 fn merge_configs(&mut self) {
181 self.fragments.sort_by(|a, b| a.priority.cmp(&b.priority));
183
184 self.merged_config = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
186
187 for fragment in &self.fragments {
189 self.merged_config = self.merge_values(self.merged_config.clone(), fragment.data.clone());
190 }
191 }
192
193 fn merge_values(&self, target: JsonValueNode, source: JsonValueNode) -> JsonValueNode {
195 match (target, source) {
196 (JsonValueNode::Object(target_obj), JsonValueNode::Object(source_obj)) => {
197 let mut target_fields: HashMap<String, JsonValueNode> = target_obj.fields.into_iter()
199 .map(|field| (field.name.value, field.value))
200 .collect();
201
202 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 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 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, }
232 }
233
234 pub fn get_config(&self) -> &JsonValueNode {
236 &self.merged_config
237 }
238
239 pub fn override_from_args(&mut self, args: &[String]) {
241 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 pub fn override_from_env(&mut self) {
256 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 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 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 let mut current = &mut config;
279 for (i, key) in keys.iter().enumerate() {
280 if i == keys.len() - 1 {
281 *current = self.parse_value(value);
283 }
284 else {
285 if !matches!(current, JsonValueNode::Object(_)) {
287 *current = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
288 }
289
290 match current {
292 JsonValueNode::Object(current_obj) => {
293 let field_index = current_obj.fields.iter()
295 .position(|field| field.name.value == *key);
296
297 if let Some(index) = field_index {
298 if !matches!(¤t_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 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 = &mut current_obj.fields.last_mut().unwrap().value;
313 }
314 }
315 _ => {
316 *current = JsonValueNode::Object(JsonObject { fields: Vec::new(), span: (0..0).into() });
318 if let JsonValueNode::Object(current_obj) = current {
319 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 = &mut current_obj.fields.last_mut().unwrap().value;
327 }
328 }
329 }
330 }
331 }
332
333 self.merged_config = config;
334 }
335
336 fn parse_value(&self, value: &str) -> JsonValueNode {
338 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#[derive(Debug)]
359pub struct Cli {
360 pub command: Command,
362}
363
364#[derive(Debug)]
366pub enum Command {
367 Init {
369 name: String,
371 },
372 Build,
374 Dev,
376 Check,
378}
379
380impl Parse for Cli {
382 fn parse(args: &mut Vec<String>) -> Self {
383 Self { command: Command::parse(args) }
384 }
385}
386
387impl 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
404pub fn init_cli() {
406 tracing_subscriber::fmt().with_env_filter(tracing_subscriber::EnvFilter::from_default_env()).init();
408
409 info!("{}", t("messages.cli_initialized"));
410}
411
412pub mod tui {
414 use std::{
415 io::{self, Write},
416 thread,
417 time::Duration,
418 };
419
420 pub struct ProgressBar {
422 total: u64,
423 current: u64,
424 width: u16,
425 }
426
427 impl ProgressBar {
428 pub fn new(total: u64, width: u16) -> Self {
430 Self { total, current: 0, width }
431 }
432
433 pub fn update(&mut self, current: u64) {
435 self.current = current;
436 self.draw();
437 }
438
439 pub fn inc(&mut self, delta: u64) {
441 self.current += delta;
442 self.draw();
443 }
444
445 pub fn finish(&mut self) {
447 self.current = self.total;
448 self.draw();
449 println!();
450 }
451
452 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 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 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
516pub 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 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 tui::loading_animation(std::time::Duration::from_secs(3), "构建中...");
534 Ok(())
535 }
536 Command::Dev => {
537 info!("{}", t("messages.starting_dev_server"));
538 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 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
559pub mod test_macro;
561pub use test_macro::test_macro;