Skip to main content

codelens_core/config/
file.rs

1//! Configuration file loading.
2
3use std::path::Path;
4
5use serde::Deserialize;
6
7use crate::error::{Error, Result};
8
9use super::{Config, FilterConfig, OutputConfig, OutputFormatType, SortBy};
10use crate::walker::WalkerConfig;
11
12/// Configuration file structure.
13#[derive(Debug, Deserialize, Default)]
14#[serde(default)]
15struct ConfigFile {
16    /// Exclude patterns.
17    excludes: Option<String>,
18    /// Include patterns.
19    includes: Option<String>,
20    /// Exclude file regex.
21    exclude_files: Option<String>,
22    /// Include file regex.
23    include_files: Option<String>,
24    /// Exclude directory regex.
25    exclude_dirs: Option<String>,
26    /// Target languages.
27    lang: Option<String>,
28    /// Minimum lines.
29    min_lines: Option<usize>,
30    /// Maximum lines.
31    max_lines: Option<usize>,
32    /// Output format.
33    output: Option<String>,
34    /// Output file.
35    output_file: Option<String>,
36    /// Number of threads.
37    threads: Option<usize>,
38    /// Maximum depth.
39    depth: Option<usize>,
40    /// Sort by.
41    sort: Option<String>,
42    /// Top N results.
43    top: Option<usize>,
44    /// Show git info.
45    git_info: Option<bool>,
46    /// Use gitignore.
47    no_gitignore: Option<bool>,
48    /// Use smart exclude.
49    no_smart_exclude: Option<bool>,
50    /// Parallel processing.
51    parallel: Option<bool>,
52    /// Summary only.
53    summary: Option<bool>,
54    /// Verbose output.
55    verbose: Option<bool>,
56    /// Quiet mode.
57    quiet: Option<bool>,
58}
59
60/// Load configuration from a TOML file.
61pub fn load_config_file(path: &Path) -> Result<Config> {
62    let content = std::fs::read_to_string(path).map_err(|e| Error::FileRead {
63        path: path.to_path_buf(),
64        source: e,
65    })?;
66
67    let file: ConfigFile = toml::from_str(&content).map_err(|e| Error::ConfigParse {
68        path: path.to_path_buf(),
69        source: e,
70    })?;
71
72    Ok(Config {
73        walker: WalkerConfig {
74            threads: file.threads.unwrap_or_else(num_cpus::get),
75            use_gitignore: !file.no_gitignore.unwrap_or(false),
76            max_depth: file.depth,
77            ..Default::default()
78        },
79        filter: FilterConfig {
80            excludes: parse_comma_list(&file.excludes),
81            includes: parse_comma_list(&file.includes),
82            exclude_files: parse_comma_list(&file.exclude_files),
83            include_files: parse_comma_list(&file.include_files),
84            exclude_dirs: parse_comma_list(&file.exclude_dirs),
85            languages: parse_comma_list(&file.lang),
86            min_lines: file.min_lines,
87            max_lines: file.max_lines,
88            smart_exclude: !file.no_smart_exclude.unwrap_or(false),
89            ..Default::default()
90        },
91        output: OutputConfig {
92            format: parse_format(&file.output),
93            file: file.output_file.map(Into::into),
94            sort_by: parse_sort(&file.sort),
95            top_n: file.top,
96            show_git_info: file.git_info.unwrap_or(false),
97            summary_only: file.summary.unwrap_or(false),
98            verbose: file.verbose.unwrap_or(false),
99            quiet: file.quiet.unwrap_or(false),
100        },
101    })
102}
103
104fn parse_comma_list(s: &Option<String>) -> Vec<String> {
105    s.as_ref()
106        .map(|s| s.split(',').map(|p| p.trim().to_string()).collect())
107        .unwrap_or_default()
108}
109
110fn parse_format(s: &Option<String>) -> OutputFormatType {
111    match s.as_deref() {
112        Some("json") => OutputFormatType::Json,
113        Some("csv") => OutputFormatType::Csv,
114        Some("markdown") | Some("md") => OutputFormatType::Markdown,
115        Some("html") => OutputFormatType::Html,
116        _ => OutputFormatType::Console,
117    }
118}
119
120fn parse_sort(s: &Option<String>) -> SortBy {
121    match s.as_deref() {
122        Some("files") => SortBy::Files,
123        Some("code") => SortBy::Code,
124        Some("name") => SortBy::Name,
125        Some("size") => SortBy::Size,
126        _ => SortBy::Lines,
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use std::io::Write;
134    use tempfile::NamedTempFile;
135
136    #[test]
137    fn test_load_config_file() {
138        let mut file = NamedTempFile::new().unwrap();
139        writeln!(
140            file,
141            r#"
142            excludes = "*test*,*mock*"
143            lang = "rust,go"
144            output = "json"
145            threads = 4
146            depth = 5
147            git_info = true
148        "#
149        )
150        .unwrap();
151
152        let config = load_config_file(file.path()).unwrap();
153        assert_eq!(config.filter.excludes, vec!["*test*", "*mock*"]);
154        assert_eq!(config.filter.languages, vec!["rust", "go"]);
155        assert_eq!(config.output.format, OutputFormatType::Json);
156        assert_eq!(config.walker.threads, 4);
157        assert_eq!(config.walker.max_depth, Some(5));
158        assert!(config.output.show_git_info);
159    }
160
161    #[test]
162    fn test_parse_comma_list() {
163        assert_eq!(
164            parse_comma_list(&Some("a, b, c".to_string())),
165            vec!["a", "b", "c"]
166        );
167        assert!(parse_comma_list(&None).is_empty());
168    }
169}