#[cfg(feature = "tree-sitter-support")]
pub mod ast;
pub mod browser;
pub mod cargo_options;
pub mod cargo_options_catalog;
pub mod commands;
mod config_adapter;
pub use config_adapter::ConfigManager;
pub mod context;
pub mod dioxus_validation;
pub mod error;
pub mod file_detection;
pub mod filters;
pub mod framework_detection;
pub mod providers;
pub mod rustc_options;
pub mod test_commands;
pub mod tree_sitter_test_detector;
pub mod universal_command_generator;
#[cfg(feature = "tree-sitter-support")]
pub use ast::{RustAnalyzer, SymbolContext as AstSymbolContext};
pub use cargo_options::*;
pub use commands::*;
pub use context::*;
pub use dioxus_validation::*;
pub use error::*;
pub use file_detection::{
EntryPoint, EntryPointType, ExecutionCapabilities, FileDetector, FileExecutionContext,
FileRole, RustProjectType, SingleFileType,
};
pub use filters::*;
pub use framework_detection::*;
pub use providers::*;
pub use raz_config::{
CommandConfigBuilder, CommandOverride, ConfigBuilder, ConfigError, ConfigTemplates,
ConfigValidator, EffectiveConfig, GlobalConfig, OverrideBuilder, OverrideMode, WorkspaceConfig,
};
pub use universal_command_generator::*;
use anyhow::Result;
use std::path::Path;
use std::sync::Arc;
pub struct RazCore {
config: Arc<ConfigManager>,
providers: Vec<Box<dyn CommandProvider>>,
pub context_analyzer: Arc<ProjectAnalyzer>,
filter_engine: Arc<FilterEngine>,
}
impl RazCore {
pub fn new() -> Result<Self> {
let config = ConfigManager::new()?;
Self::with_config(config)
}
pub fn with_config(config: ConfigManager) -> Result<Self> {
let config = Arc::new(config);
let context_analyzer = Arc::new(ProjectAnalyzer::new());
let filter_engine = Arc::new(FilterEngine::new());
let mut raz = Self {
config,
providers: Vec::new(),
context_analyzer,
filter_engine,
};
raz.register_builtin_providers()?;
Ok(raz)
}
pub async fn analyze_workspace(&self, path: &Path) -> Result<ProjectContext> {
self.context_analyzer
.analyze_project(path)
.await
.map_err(anyhow::Error::new)
}
pub async fn generate_commands(&self, context: &ProjectContext) -> Result<Vec<Command>> {
let mut all_commands = Vec::new();
for provider in &self.providers {
if provider.can_handle(context) {
let commands = provider.commands(context).await?;
all_commands.extend(commands);
}
}
let filtered_commands = self.filter_engine.apply_filters(&all_commands, context)?;
Ok(filtered_commands)
}
pub async fn execute_command(&self, command: &Command) -> Result<ExecutionResult> {
command.execute().await.map_err(anyhow::Error::new)
}
pub async fn execute_command_with_browser(
&self,
command: &Command,
browser: Option<String>,
) -> Result<ExecutionResult> {
command
.execute_with_browser(true, browser)
.await
.map_err(anyhow::Error::new)
}
pub fn register_provider(&mut self, provider: Box<dyn CommandProvider>) {
self.providers.push(provider);
}
pub fn get_config(&self, workspace: &Path) -> EffectiveConfig {
self.config.get_effective_config(workspace)
}
pub fn get_command_override(&self, workspace: &Path, key: &str) -> Option<CommandOverride> {
self.config.get_command_override(workspace, key)
}
pub fn set_command_override(
&mut self,
workspace: &Path,
key: String,
override_config: CommandOverride,
) -> Result<()> {
let mut config_manager = ConfigManager::new()?;
config_manager.set_command_override(workspace, key, override_config)
}
pub async fn generate_universal_commands(
&self,
file_path: &Path,
cursor: Option<Position>,
) -> Result<Vec<Command>> {
self.generate_universal_commands_with_options(file_path, cursor, false)
.await
}
pub async fn generate_universal_commands_with_options(
&self,
file_path: &Path,
cursor: Option<Position>,
force_standalone: bool,
) -> Result<Vec<Command>> {
let context =
FileDetector::detect_context_with_options(file_path, cursor, force_standalone)
.map_err(anyhow::Error::new)?;
let workspace = context.get_workspace_root();
let override_key = Self::build_override_key(file_path, &context, cursor);
UniversalCommandGenerator::generate_commands_with_overrides(
&context,
cursor,
workspace,
override_key.as_deref(),
)
.map_err(anyhow::Error::new)
}
pub async fn generate_universal_commands_with_override(
&self,
file_path: &Path,
cursor: Option<Position>,
override_input: &str,
) -> Result<Vec<Command>> {
let context =
FileDetector::detect_context(file_path, cursor).map_err(anyhow::Error::new)?;
let workspace = context.get_workspace_root();
let override_key = Self::build_override_key(file_path, &context, cursor);
UniversalCommandGenerator::generate_commands_with_runtime_override(
&context,
cursor,
workspace,
override_key.as_deref(),
override_input,
)
.map_err(anyhow::Error::new)
}
pub fn build_override_key(
file_path: &Path,
context: &FileExecutionContext,
cursor: Option<Position>,
) -> Option<String> {
let file_str = file_path.to_string_lossy();
if let Some(pos) = cursor {
if let Some(test_entry) =
UniversalCommandGenerator::find_test_at_cursor(&context.entry_points, pos)
{
return Some(format!("{}:{}", file_str, test_entry.name));
}
for entry in &context.entry_points {
if entry.line_range.0 <= pos.line && pos.line <= entry.line_range.1 {
return Some(format!("{}:{}", file_str, entry.name));
}
}
#[cfg(feature = "tree-sitter-support")]
if let Ok(mut analyzer) = crate::ast::RustAnalyzer::new() {
if let Ok(source) = std::fs::read_to_string(file_path) {
if let Ok(tree) = analyzer.parse(&source) {
if let Ok(Some(symbol)) = analyzer.symbol_at_position(&tree, &source, pos) {
return Some(format!("{}:{}", file_str, symbol.name));
}
}
}
}
for entry in &context.entry_points {
if entry.line_range.0 <= pos.line && pos.line <= entry.line_range.1 {
return Some(format!("{}:{}", file_str, entry.name));
}
}
}
if context.entry_points.iter().any(|e| e.name == "main") {
return Some(format!("{file_str}:main"));
}
Some(file_str.to_string())
}
pub async fn generate_smart_commands(
&self,
workspace: &Path,
file_path: Option<&Path>,
cursor: Option<Position>,
) -> Result<Vec<Command>> {
if let Some(file) = file_path {
self.generate_universal_commands(file, cursor).await
} else {
let project_context = self.analyze_workspace(workspace).await?;
self.generate_commands(&project_context).await
}
}
fn register_builtin_providers(&mut self) -> Result<()> {
self.register_provider(Box::new(providers::CargoProvider::new()));
self.register_provider(Box::new(providers::DocProvider::new()));
self.register_provider(Box::new(providers::LeptosProvider::new()));
self.register_provider(Box::new(providers::DioxusProvider::new()));
self.register_provider(Box::new(providers::BevyProvider::new()));
self.register_provider(Box::new(providers::TauriProvider::new()));
self.register_provider(Box::new(providers::YewProvider::new()));
Ok(())
}
}
impl Default for RazCore {
fn default() -> Self {
Self::new().expect("Failed to create default RAZ instance")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[tokio::test]
async fn test_raz_core_creation() {
let raz = RazCore::new().unwrap();
assert!(!raz.providers.is_empty());
}
#[tokio::test]
async fn test_workspace_analysis() {
let temp_dir = TempDir::new().unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
fs::write(
&cargo_toml,
r#"
[package]
name = "test-project"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
let raz = RazCore::new().unwrap();
let context = raz.analyze_workspace(temp_dir.path()).await.unwrap();
assert_eq!(context.workspace_root, temp_dir.path());
assert_eq!(context.project_type, ProjectType::Binary);
}
#[tokio::test]
async fn test_command_generation() {
let temp_dir = TempDir::new().unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
fs::write(
&cargo_toml,
r#"
[package]
name = "test-project"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
let raz = RazCore::new().unwrap();
let context = raz.analyze_workspace(temp_dir.path()).await.unwrap();
let commands = raz.generate_commands(&context).await.unwrap();
assert!(!commands.is_empty());
assert!(commands.iter().any(|c| c.command == "cargo"));
}
}