log_watcher/
config.rs

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        // Validate and compile regex patterns if needed
32        let regex_patterns = if args.regex {
33            Self::compile_regex_patterns(&patterns, args.case_insensitive)?
34        } else {
35            vec![]
36        };
37
38        // Parse color mappings
39        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        // Add default color mappings if not specified
85        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    /// Check if a pattern should trigger notifications
122    pub fn should_notify_for_pattern(&self, pattern: &str) -> bool {
123        self.notify_enabled && self.notify_patterns.contains(&pattern.to_string())
124    }
125
126    /// Get color for a pattern
127    pub fn get_color_for_pattern(&self, pattern: &str) -> Option<Color> {
128        self.color_mappings.get(pattern).copied()
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_all_color_parsing() {
138        // Test all color mappings to cover the uncovered lines
139        assert_eq!(Config::parse_color("yellow").unwrap(), Color::Yellow);
140        assert_eq!(Config::parse_color("magenta").unwrap(), Color::Magenta);
141        assert_eq!(Config::parse_color("cyan").unwrap(), Color::Cyan);
142        assert_eq!(Config::parse_color("white").unwrap(), Color::White);
143    }
144
145    #[test]
146    fn test_parse_color_unknown_coverage_line_100() {
147        // Test unknown color to cover line 100 (_ => Err(...))
148        let result = Config::parse_color("unknown_color");
149        assert!(result.is_err());
150        assert!(result
151            .unwrap_err()
152            .to_string()
153            .contains("Unknown color: unknown_color"));
154    }
155
156    #[test]
157    fn test_get_color_for_pattern() {
158        let args = Args {
159            files: vec![PathBuf::from("test.log")],
160            patterns: "ERROR".to_string(),
161            regex: false,
162            case_insensitive: false,
163            color_map: None,
164            notify: false,
165            notify_patterns: None,
166            quiet: false,
167            dry_run: false,
168            prefix_file: Some(false),
169            poll_interval: 1000,
170            buffer_size: 8192,
171            no_color: false,
172            notify_throttle: 0,
173        };
174
175        let config = Config::from_args(&args).unwrap();
176
177        // Test that default color mappings work
178        assert_eq!(config.get_color_for_pattern("ERROR"), Some(Color::Red));
179        assert_eq!(config.get_color_for_pattern("WARN"), Some(Color::Yellow));
180        assert_eq!(config.get_color_for_pattern("INFO"), Some(Color::Green));
181        assert_eq!(config.get_color_for_pattern("DEBUG"), Some(Color::Cyan));
182        assert_eq!(config.get_color_for_pattern("UNKNOWN"), None);
183    }
184}