1#[cfg(feature = "tree-sitter-support")]
20pub mod ast;
21pub mod browser;
22pub mod cargo_options;
23pub mod cargo_options_catalog;
24pub mod commands;
25mod config_adapter;
26
27pub use config_adapter::ConfigManager;
28pub mod context;
29pub mod dioxus_validation;
30pub mod error;
31pub mod file_detection;
32pub mod filters;
33pub mod framework_detection;
34pub mod providers;
35pub mod rustc_options;
36pub mod test_commands;
37pub mod tree_sitter_test_detector;
38pub mod universal_command_generator;
39
40#[cfg(feature = "tree-sitter-support")]
42pub use ast::{RustAnalyzer, SymbolContext as AstSymbolContext};
43pub use cargo_options::*;
44pub use commands::*;
45pub use context::*;
47pub use dioxus_validation::*;
48pub use error::*;
49pub use file_detection::{
50 EntryPoint, EntryPointType, ExecutionCapabilities, FileDetector, FileExecutionContext,
51 FileRole, RustProjectType, SingleFileType,
52};
53pub use filters::*;
54pub use framework_detection::*;
55pub use providers::*;
56pub use raz_config::{
57 CommandConfigBuilder, CommandOverride, ConfigBuilder, ConfigError, ConfigTemplates,
58 ConfigValidator, EffectiveConfig, GlobalConfig, OverrideBuilder, OverrideMode, WorkspaceConfig,
59};
60pub use universal_command_generator::*;
61
62use anyhow::Result;
63use std::path::Path;
64use std::sync::Arc;
65
66pub struct RazCore {
68 config: Arc<ConfigManager>,
69 providers: Vec<Box<dyn CommandProvider>>,
70 pub context_analyzer: Arc<ProjectAnalyzer>,
71 filter_engine: Arc<FilterEngine>,
72}
73
74impl RazCore {
75 pub fn new() -> Result<Self> {
77 let config = ConfigManager::new()?;
78 Self::with_config(config)
79 }
80
81 pub fn with_config(config: ConfigManager) -> Result<Self> {
83 let config = Arc::new(config);
84 let context_analyzer = Arc::new(ProjectAnalyzer::new());
85 let filter_engine = Arc::new(FilterEngine::new());
86
87 let mut raz = Self {
88 config,
89 providers: Vec::new(),
90 context_analyzer,
91 filter_engine,
92 };
93
94 raz.register_builtin_providers()?;
96
97 Ok(raz)
98 }
99
100 pub async fn analyze_workspace(&self, path: &Path) -> Result<ProjectContext> {
102 self.context_analyzer
103 .analyze_project(path)
104 .await
105 .map_err(anyhow::Error::new)
106 }
107
108 pub async fn generate_commands(&self, context: &ProjectContext) -> Result<Vec<Command>> {
110 let mut all_commands = Vec::new();
111
112 for provider in &self.providers {
114 if provider.can_handle(context) {
115 let commands = provider.commands(context).await?;
116 all_commands.extend(commands);
117 }
118 }
119
120 let filtered_commands = self.filter_engine.apply_filters(&all_commands, context)?;
122
123 Ok(filtered_commands)
124 }
125
126 pub async fn execute_command(&self, command: &Command) -> Result<ExecutionResult> {
128 command.execute().await.map_err(anyhow::Error::new)
129 }
130
131 pub async fn execute_command_with_browser(
133 &self,
134 command: &Command,
135 browser: Option<String>,
136 ) -> Result<ExecutionResult> {
137 command
138 .execute_with_browser(true, browser)
139 .await
140 .map_err(anyhow::Error::new)
141 }
142
143 pub fn register_provider(&mut self, provider: Box<dyn CommandProvider>) {
145 self.providers.push(provider);
146 }
147
148 pub fn get_config(&self, workspace: &Path) -> EffectiveConfig {
150 self.config.get_effective_config(workspace)
151 }
152
153 pub fn get_command_override(&self, workspace: &Path, key: &str) -> Option<CommandOverride> {
155 self.config.get_command_override(workspace, key)
156 }
157
158 pub fn set_command_override(
160 &mut self,
161 workspace: &Path,
162 key: String,
163 override_config: CommandOverride,
164 ) -> Result<()> {
165 let mut config_manager = ConfigManager::new()?;
168 config_manager.set_command_override(workspace, key, override_config)
169 }
170
171 pub async fn generate_universal_commands(
173 &self,
174 file_path: &Path,
175 cursor: Option<Position>,
176 ) -> Result<Vec<Command>> {
177 self.generate_universal_commands_with_options(file_path, cursor, false)
178 .await
179 }
180
181 pub async fn generate_universal_commands_with_options(
183 &self,
184 file_path: &Path,
185 cursor: Option<Position>,
186 force_standalone: bool,
187 ) -> Result<Vec<Command>> {
188 let context =
189 FileDetector::detect_context_with_options(file_path, cursor, force_standalone)
190 .map_err(anyhow::Error::new)?;
191
192 let workspace = context.get_workspace_root();
194 let override_key = Self::build_override_key(file_path, &context, cursor);
195
196 UniversalCommandGenerator::generate_commands_with_overrides(
197 &context,
198 cursor,
199 workspace,
200 override_key.as_deref(),
201 )
202 .map_err(anyhow::Error::new)
203 }
204
205 pub async fn generate_universal_commands_with_override(
207 &self,
208 file_path: &Path,
209 cursor: Option<Position>,
210 override_input: &str,
211 ) -> Result<Vec<Command>> {
212 let context =
213 FileDetector::detect_context(file_path, cursor).map_err(anyhow::Error::new)?;
214
215 let workspace = context.get_workspace_root();
217 let override_key = Self::build_override_key(file_path, &context, cursor);
218
219 UniversalCommandGenerator::generate_commands_with_runtime_override(
220 &context,
221 cursor,
222 workspace,
223 override_key.as_deref(),
224 override_input,
225 )
226 .map_err(anyhow::Error::new)
227 }
228
229 pub fn build_override_key(
231 file_path: &Path,
232 context: &FileExecutionContext,
233 cursor: Option<Position>,
234 ) -> Option<String> {
235 let file_str = file_path.to_string_lossy();
236
237 if let Some(pos) = cursor {
239 if let Some(test_entry) =
241 UniversalCommandGenerator::find_test_at_cursor(&context.entry_points, pos)
242 {
243 return Some(format!("{}:{}", file_str, test_entry.name));
244 }
245
246 for entry in &context.entry_points {
248 if entry.line_range.0 <= pos.line && pos.line <= entry.line_range.1 {
249 return Some(format!("{}:{}", file_str, entry.name));
250 }
251 }
252
253 #[cfg(feature = "tree-sitter-support")]
255 if let Ok(mut analyzer) = crate::ast::RustAnalyzer::new() {
256 if let Ok(source) = std::fs::read_to_string(file_path) {
258 if let Ok(tree) = analyzer.parse(&source) {
259 if let Ok(Some(symbol)) = analyzer.symbol_at_position(&tree, &source, pos) {
260 return Some(format!("{}:{}", file_str, symbol.name));
261 }
262 }
263 }
264 }
265
266 for entry in &context.entry_points {
269 if entry.line_range.0 <= pos.line && pos.line <= entry.line_range.1 {
270 return Some(format!("{}:{}", file_str, entry.name));
271 }
272 }
273 }
274
275 if context.entry_points.iter().any(|e| e.name == "main") {
277 return Some(format!("{file_str}:main"));
278 }
279
280 Some(file_str.to_string())
282 }
283
284 pub async fn generate_smart_commands(
287 &self,
288 workspace: &Path,
289 file_path: Option<&Path>,
290 cursor: Option<Position>,
291 ) -> Result<Vec<Command>> {
292 if let Some(file) = file_path {
293 self.generate_universal_commands(file, cursor).await
295 } else {
296 let project_context = self.analyze_workspace(workspace).await?;
298 self.generate_commands(&project_context).await
299 }
300 }
301
302 fn register_builtin_providers(&mut self) -> Result<()> {
303 self.register_provider(Box::new(providers::CargoProvider::new()));
305
306 self.register_provider(Box::new(providers::DocProvider::new()));
308
309 self.register_provider(Box::new(providers::LeptosProvider::new()));
311 self.register_provider(Box::new(providers::DioxusProvider::new()));
312 self.register_provider(Box::new(providers::BevyProvider::new()));
313 self.register_provider(Box::new(providers::TauriProvider::new()));
314 self.register_provider(Box::new(providers::YewProvider::new()));
315
316 Ok(())
317 }
318}
319
320impl Default for RazCore {
321 fn default() -> Self {
322 Self::new().expect("Failed to create default RAZ instance")
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use std::fs;
330 use tempfile::TempDir;
331
332 #[tokio::test]
333 async fn test_raz_core_creation() {
334 let raz = RazCore::new().unwrap();
335 assert!(!raz.providers.is_empty());
336 }
337
338 #[tokio::test]
339 async fn test_workspace_analysis() {
340 let temp_dir = TempDir::new().unwrap();
341 let cargo_toml = temp_dir.path().join("Cargo.toml");
342 fs::write(
343 &cargo_toml,
344 r#"
345 [package]
346 name = "test-project"
347 version = "0.1.0"
348 edition = "2021"
349 "#,
350 )
351 .unwrap();
352
353 let raz = RazCore::new().unwrap();
354 let context = raz.analyze_workspace(temp_dir.path()).await.unwrap();
355
356 assert_eq!(context.workspace_root, temp_dir.path());
357 assert_eq!(context.project_type, ProjectType::Binary);
358 }
359
360 #[tokio::test]
361 async fn test_command_generation() {
362 let temp_dir = TempDir::new().unwrap();
363 let cargo_toml = temp_dir.path().join("Cargo.toml");
364 fs::write(
365 &cargo_toml,
366 r#"
367 [package]
368 name = "test-project"
369 version = "0.1.0"
370 edition = "2021"
371 "#,
372 )
373 .unwrap();
374
375 let raz = RazCore::new().unwrap();
376 let context = raz.analyze_workspace(temp_dir.path()).await.unwrap();
377 let commands = raz.generate_commands(&context).await.unwrap();
378
379 assert!(!commands.is_empty());
380 assert!(commands.iter().any(|c| c.command == "cargo"));
381 }
382}