1use serde::{Deserialize, Serialize};
2use std::{env, path::PathBuf};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct Config {
6 pub target_directory: PathBuf,
7 pub ignore_patterns: Vec<String>,
8 pub file_extensions: Vec<String>,
9 pub max_file_size: usize,
10 pub llm: LLMConfig,
11 pub analysis: AnalysisConfig,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct LLMConfig {
16 pub provider: LLMProvider,
17 pub api_key: Option<String>,
18 pub base_url: Option<String>,
19 pub model: String,
20 pub max_tokens: usize,
21 pub temperature: f32,
22 pub timeout_seconds: u64,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub enum LLMProvider {
27 OpenAI,
28 Ollama,
29 Anthropic,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct AnalysisConfig {
34 pub include_dependencies: bool,
35 pub include_function_calls: bool,
36 pub include_architecture_patterns: bool,
37 pub include_security_analysis: bool,
38 pub max_depth: usize,
39}
40
41impl Default for Config {
42 fn default() -> Self {
43 Self {
44 target_directory: PathBuf::from("."),
45 ignore_patterns: vec![
46 "node_modules".to_string(),
47 ".git".to_string(),
48 "target".to_string(),
49 "build".to_string(),
50 "dist".to_string(),
51 "*.log".to_string(),
52 ".env".to_string(),
53 ".env.*".to_string(),
54 "*.min.js".to_string(),
55 "*.map".to_string(),
56 "test-*".to_string(),
57 "test_*".to_string(),
58 ],
59 file_extensions: vec![
60 "rs".to_string(),
61 "js".to_string(),
62 "ts".to_string(),
63 "tsx".to_string(),
64 "jsx".to_string(),
65 "py".to_string(),
66 "java".to_string(),
67 "go".to_string(),
68 "cpp".to_string(),
69 "c".to_string(),
70 "h".to_string(),
71 "md".to_string(),
72 "txt".to_string(),
73 "toml".to_string(),
74 "yaml".to_string(),
75 "yml".to_string(),
76 "json".to_string(),
77 "html".to_string(),
78 "css".to_string(),
79 ],
80 max_file_size: 1024 * 1024, llm: LLMConfig {
82 provider: LLMProvider::OpenAI,
83 api_key: None,
84 base_url: None,
85 model: "gpt-4".to_string(),
86 max_tokens: 4000,
87 temperature: 0.1,
88 timeout_seconds: 300,
89 },
90 analysis: AnalysisConfig {
91 include_dependencies: true,
92 include_function_calls: true,
93 include_architecture_patterns: true,
94 include_security_analysis: false,
95 max_depth: 10,
96 },
97 }
98 }
99}
100
101impl Config {
102 pub fn default_config_path() -> crate::Result<PathBuf> {
104 let home_dir = env::var("HOME")
105 .or_else(|_| env::var("USERPROFILE"))
106 .map_err(|_| anyhow::anyhow!("Could not determine home directory"))?;
107 Ok(PathBuf::from(home_dir).join(".project-examer.toml"))
108 }
109
110 pub fn load() -> crate::Result<Self> {
112 let config_path = Self::default_config_path()?;
113
114 let mut config = if config_path.exists() {
115 println!("đ Loading configuration from: {}", config_path.display());
116 Self::from_file(&config_path)?
117 } else {
118 println!("âšī¸ No config file found at {}, using defaults", config_path.display());
119 println!("đĄ Run 'project-examer config' to create a default configuration file");
120 Self::default()
121 };
122
123 if config.llm.api_key.is_none() {
125 config.llm.api_key = match config.llm.provider {
126 LLMProvider::OpenAI => env::var("OPENAI_API_KEY").ok(),
127 LLMProvider::Anthropic => env::var("ANTHROPIC_API_KEY").ok(),
128 LLMProvider::Ollama => None, };
130 }
131
132 Ok(config)
133 }
134
135 pub fn from_file(path: &PathBuf) -> crate::Result<Self> {
137 let content = std::fs::read_to_string(path)?;
138 let config: Config = toml::from_str(&content)?;
139 Ok(config)
140 }
141
142 pub fn to_file(&self, path: &PathBuf) -> crate::Result<()> {
144 if let Some(parent) = path.parent() {
146 std::fs::create_dir_all(parent)?;
147 }
148
149 let content = toml::to_string_pretty(self)?;
150 std::fs::write(path, content)?;
151 Ok(())
152 }
153
154 pub fn save_default(&self) -> crate::Result<()> {
156 let config_path = Self::default_config_path()?;
157 self.to_file(&config_path)
158 }
159
160 pub fn create_documented_config() -> String {
162 format!(r#"# Project Examer Configuration File
163# This file configures how project-examer analyzes your codebase
164
165# Target directory to analyze (defaults to current directory)
166target_directory = "."
167
168# Patterns to ignore during file discovery
169ignore_patterns = [
170 "node_modules",
171 ".git",
172 "target",
173 "build",
174 "dist",
175 "*.log",
176 ".env",
177 ".env.*",
178 "*.min.js",
179 "*.map"
180]
181
182# File extensions to include in analysis
183file_extensions = [
184 "rs", "js", "ts", "tsx", "jsx", "py", "java", "go",
185 "cpp", "c", "h", "php", "rb", "cs", "swift", "kt",
186 "scala", "clj", "hs", "ml", "elm", "ex", "erl", "dart",
187 "lua", "r", "pl", "sh", "sql", "html", "css", "scss"
188]
189
190# Maximum file size to analyze (in bytes, default 1MB)
191max_file_size = 1048576
192
193[llm]
194# LLM Provider: "OpenAI", "Ollama", or "Anthropic"
195provider = "OpenAI"
196
197# API key for the provider (can also be set via environment variables)
198# OpenAI: OPENAI_API_KEY
199# Anthropic: ANTHROPIC_API_KEY
200# api_key = "your-api-key-here"
201
202# Base URL (mainly for Ollama local instances)
203# base_url = "http://localhost:11434"
204
205# Model to use
206model = "gpt-4"
207
208# Maximum tokens for LLM responses
209max_tokens = 4000
210
211# Temperature for LLM responses (0.0 = deterministic, 1.0 = creative)
212temperature = 0.1
213
214# Request timeout in seconds (default: 300 seconds / 5 minutes)
215timeout_seconds = 300
216
217[analysis]
218# Include dependency analysis
219include_dependencies = true
220
221# Include function call analysis
222include_function_calls = true
223
224# Include architecture pattern detection
225include_architecture_patterns = true
226
227# Include security vulnerability analysis
228include_security_analysis = false
229
230# Maximum depth for dependency traversal
231max_depth = 10
232"#)
233 }
234}