1use crate::cli::Args;
2use anyhow::{Context, Result};
3use regex::Regex;
4use std::collections::HashMap;
5use std::path::PathBuf;
6use termcolor::Color;
7
8#[derive(Debug, Clone)]
9pub struct Config {
10 pub files: Vec<PathBuf>,
11 pub patterns: Vec<String>,
12 pub regex_patterns: Vec<Regex>,
13 pub case_insensitive: bool,
14 pub color_mappings: HashMap<String, Color>,
15 pub notify_enabled: bool,
16 pub notify_patterns: Vec<String>,
17 pub notify_throttle: u32,
18 pub dry_run: bool,
19 pub quiet: bool,
20 pub no_color: bool,
21 pub prefix_files: bool,
22 pub poll_interval: u64,
23 pub buffer_size: usize,
24}
25
26impl Config {
27 pub fn from_args(args: &Args) -> Result<Self> {
28 let patterns = args.patterns();
29 let notify_patterns = args.notify_patterns();
30
31 let regex_patterns = if args.regex {
33 Self::compile_regex_patterns(&patterns, args.case_insensitive)?
34 } else {
35 vec![]
36 };
37
38 let color_mappings = Self::parse_color_mappings(&args.color_mappings())?;
40
41 Ok(Config {
42 files: args.files().to_vec(),
43 patterns,
44 regex_patterns,
45 case_insensitive: args.case_insensitive,
46 color_mappings,
47 notify_enabled: args.notify,
48 notify_patterns,
49 notify_throttle: args.notify_throttle,
50 dry_run: args.dry_run,
51 quiet: args.quiet,
52 no_color: args.no_color,
53 prefix_files: args.should_prefix_files(),
54 poll_interval: args.poll_interval,
55 buffer_size: args.buffer_size,
56 })
57 }
58
59 fn compile_regex_patterns(patterns: &[String], case_insensitive: bool) -> Result<Vec<Regex>> {
60 let mut compiled = Vec::new();
61
62 for pattern in patterns {
63 let mut regex_builder = regex::RegexBuilder::new(pattern);
64 regex_builder.case_insensitive(case_insensitive);
65
66 let regex = regex_builder
67 .build()
68 .with_context(|| format!("Invalid regex pattern: {}", pattern))?;
69
70 compiled.push(regex);
71 }
72
73 Ok(compiled)
74 }
75
76 fn parse_color_mappings(mappings: &[(String, String)]) -> Result<HashMap<String, Color>> {
77 let mut color_map = HashMap::new();
78
79 for (pattern, color_name) in mappings {
80 let color = Self::parse_color(color_name)?;
81 color_map.insert(pattern.clone(), color);
82 }
83
84 Self::add_default_color_mappings(&mut color_map);
86
87 Ok(color_map)
88 }
89
90 fn parse_color(color_name: &str) -> Result<Color> {
91 match color_name.to_lowercase().as_str() {
92 "black" => Ok(Color::Black),
93 "red" => Ok(Color::Red),
94 "green" => Ok(Color::Green),
95 "yellow" => Ok(Color::Yellow),
96 "blue" => Ok(Color::Blue),
97 "magenta" => Ok(Color::Magenta),
98 "cyan" => Ok(Color::Cyan),
99 "white" => Ok(Color::White),
100 _ => Err(anyhow::anyhow!("Unknown color: {}", color_name)),
101 }
102 }
103
104 fn add_default_color_mappings(color_map: &mut HashMap<String, Color>) {
105 let defaults = [
106 ("ERROR", Color::Red),
107 ("WARN", Color::Yellow),
108 ("WARNING", Color::Yellow),
109 ("INFO", Color::Green),
110 ("DEBUG", Color::Cyan),
111 ("TRACE", Color::Magenta),
112 ("FATAL", Color::Red),
113 ("CRITICAL", Color::Red),
114 ];
115
116 for (pattern, color) in defaults {
117 color_map.entry(pattern.to_string()).or_insert(color);
118 }
119 }
120
121 pub fn should_notify_for_pattern(&self, pattern: &str) -> bool {
123 self.notify_enabled && self.notify_patterns.contains(&pattern.to_string())
124 }
125
126 pub fn get_color_for_pattern(&self, pattern: &str) -> Option<Color> {
128 self.color_mappings.get(pattern).copied()
129 }
130}