1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8#[serde(rename_all = "lowercase")]
9pub enum OutputFormat {
10 #[default]
11 Yaml,
12 Json,
13}
14
15impl OutputFormat {
16 pub fn from_string(s: &str) -> Self {
18 match s {
19 "json" => Self::Json,
20 _ => Self::Yaml,
21 }
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ScanConfig {
28 pub path: PathBuf,
30
31 pub format: OutputFormat,
33
34 #[serde(default = "default_include_patterns")]
36 pub include: Vec<String>,
37
38 #[serde(default = "default_exclude_patterns")]
40 pub exclude: Vec<String>,
41
42 #[serde(default = "default_parallel")]
44 pub parallel: bool,
45
46 #[serde(default)]
48 pub max_threads: Option<usize>,
49
50 #[serde(default)]
52 pub incremental: bool,
53
54 #[serde(default = "default_cache_dir")]
56 pub cache_dir: PathBuf,
57
58 #[serde(default)]
60 pub verbose: bool,
61}
62
63fn default_include_patterns() -> Vec<String> {
64 vec!["**/*.py".to_string()]
65}
66
67fn default_exclude_patterns() -> Vec<String> {
68 vec![
69 "**/node_modules/**".to_string(),
70 "**/.venv/**".to_string(),
71 "**/__pycache__/**".to_string(),
72 "**/dist/**".to_string(),
73 "**/build/**".to_string(),
74 "**/.git/**".to_string(),
75 ]
76}
77
78fn default_parallel() -> bool {
79 true
80}
81
82fn default_cache_dir() -> PathBuf {
83 PathBuf::from(".raxit/cache")
84}
85
86impl Default for ScanConfig {
87 fn default() -> Self {
88 Self {
89 path: PathBuf::from("."),
90 format: OutputFormat::Yaml,
91 include: default_include_patterns(),
92 exclude: default_exclude_patterns(),
93 parallel: default_parallel(),
94 max_threads: None,
95 incremental: false,
96 cache_dir: default_cache_dir(),
97 verbose: false,
98 }
99 }
100}
101
102impl ScanConfig {
103 pub fn new(path: impl Into<PathBuf>) -> Self {
105 Self {
106 path: path.into(),
107 ..Default::default()
108 }
109 }
110
111 pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
113 self.path = path.into();
114 self
115 }
116
117 pub fn with_format(mut self, format: impl AsRef<str>) -> Self {
119 self.format = match format.as_ref() {
120 "json" => OutputFormat::Json,
121 _ => OutputFormat::Yaml,
122 };
123 self
124 }
125
126 pub fn with_include(mut self, pattern: impl Into<String>) -> Self {
128 self.include.push(pattern.into());
129 self
130 }
131
132 pub fn with_exclude(mut self, pattern: impl Into<String>) -> Self {
134 self.exclude.push(pattern.into());
135 self
136 }
137
138 pub fn with_parallel(mut self, parallel: bool) -> Self {
140 self.parallel = parallel;
141 self
142 }
143
144 pub fn with_max_threads(mut self, threads: usize) -> Self {
146 self.max_threads = Some(threads);
147 self
148 }
149
150 pub fn with_incremental(mut self, incremental: bool) -> Self {
152 self.incremental = incremental;
153 self
154 }
155
156 pub fn with_verbose(mut self, verbose: bool) -> Self {
158 self.verbose = verbose;
159 self
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_default_config() {
169 let config = ScanConfig::default();
170 assert_eq!(config.path, PathBuf::from("."));
171 assert_eq!(config.format, OutputFormat::Yaml);
172 assert!(config.parallel);
173 }
174
175 #[test]
176 fn test_builder_pattern() {
177 let config = ScanConfig::default()
178 .with_path("/my/project")
179 .with_format("json")
180 .with_parallel(false);
181
182 assert_eq!(config.path, PathBuf::from("/my/project"));
183 assert_eq!(config.format, OutputFormat::Json);
184 assert!(!config.parallel);
185 }
186}