use super::{
CBehavior, CParser, CSharpBehavior, CSharpParser, ClojureBehavior, ClojureParser, CppBehavior,
CppParser, GdscriptBehavior, GdscriptParser, GoBehavior, GoParser, JavaBehavior, JavaParser,
JavaScriptBehavior, JavaScriptParser, KotlinBehavior, KotlinParser, Language, LanguageBehavior,
LanguageId, LanguageParser, LuaBehavior, LuaParser, PhpBehavior, PhpParser, PythonBehavior,
PythonParser, RustBehavior, RustParser, SwiftBehavior, SwiftParser, TypeScriptBehavior,
TypeScriptParser, get_registry,
};
use crate::{IndexError, IndexResult, Settings};
use std::sync::Arc;
pub struct ParserWithBehavior {
pub parser: Box<dyn LanguageParser>,
pub behavior: Box<dyn LanguageBehavior>,
}
#[derive(Debug)]
pub struct ParserFactory {
settings: Arc<Settings>,
}
impl ParserFactory {
pub fn new(settings: Arc<Settings>) -> Self {
Self { settings }
}
#[must_use = "Parser creation may fail and should be handled"]
pub fn create_parser_from_registry(
&self,
language_id: LanguageId,
) -> IndexResult<Box<dyn LanguageParser>> {
let registry = get_registry();
let registry = registry
.lock()
.map_err(|e| IndexError::General(format!("Failed to acquire registry lock: {e}")))?;
registry
.create_parser(language_id, &self.settings)
.map_err(|e| IndexError::General(e.to_string()))
}
pub fn create_parser_with_behavior_from_registry(
&self,
language_id: LanguageId,
) -> IndexResult<ParserWithBehavior> {
let registry = get_registry();
let registry = registry
.lock()
.map_err(|e| IndexError::General(format!("Failed to acquire registry lock: {e}")))?;
let (parser, behavior) = registry
.create_parser_with_behavior(language_id, &self.settings)
.map_err(|e| IndexError::General(e.to_string()))?;
Ok(ParserWithBehavior { parser, behavior })
}
pub fn is_language_enabled_in_registry(&self, language_id: LanguageId) -> bool {
let registry = get_registry();
if let Ok(registry) = registry.lock() {
registry.is_enabled(language_id, &self.settings)
} else {
false
}
}
pub fn get_language_for_extension(&self, extension: &str) -> Option<LanguageId> {
let registry = get_registry();
if let Ok(registry) = registry.lock() {
registry
.get_by_extension(extension)
.filter(|def| def.is_enabled(&self.settings))
.map(|def| def.id())
} else {
None
}
}
#[must_use = "Parser creation may fail and should be handled"]
pub fn create_parser(&self, language: Language) -> IndexResult<Box<dyn LanguageParser>> {
let lang_key = language.config_key();
if let Some(config) = self.settings.languages.get(lang_key) {
if !config.enabled {
return Err(IndexError::ConfigError {
reason: format!(
"Language {} is disabled in configuration. Enable it in your settings to use.",
language.name()
),
});
}
}
match language {
Language::Rust => {
let parser = RustParser::new().map_err(IndexError::General)?;
Ok(Box::new(parser))
}
Language::Python => {
let parser = PythonParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::JavaScript => {
Err(IndexError::General(format!(
"{} parser not yet implemented. Currently only Rust is supported.",
language.name()
)))
}
Language::TypeScript => {
Err(IndexError::General(format!(
"{} parser not yet implemented. Currently only Rust is supported.",
language.name()
)))
}
Language::Php => {
let parser = PhpParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Go => {
let parser = GoParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::C => {
let parser = CParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Cpp => {
let parser = CppParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::CSharp => {
let parser = CSharpParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Gdscript => {
let parser = GdscriptParser::new().map_err(IndexError::General)?;
Ok(Box::new(parser))
}
Language::Java => {
let parser = JavaParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Kotlin => {
let parser = KotlinParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Clojure => {
let parser =
ClojureParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Lua => {
let parser = LuaParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
Language::Swift => {
let parser = SwiftParser::new().map_err(|e| IndexError::General(e.to_string()))?;
Ok(Box::new(parser))
}
}
}
pub fn is_language_enabled(&self, language: Language) -> bool {
let lang_key = language.config_key();
self.settings
.languages
.get(lang_key)
.map(|config| config.enabled)
.unwrap_or(false)
}
pub fn create_parser_with_behavior(
&self,
language: Language,
) -> IndexResult<ParserWithBehavior> {
let lang_key = language.config_key();
if let Some(config) = self.settings.languages.get(lang_key) {
if !config.enabled {
return Err(IndexError::ConfigError {
reason: format!(
"Language {} is disabled in configuration. Enable it in your settings to use.",
language.name()
),
});
}
}
let result = match language {
Language::Rust => {
let parser = RustParser::new().map_err(IndexError::General)?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(RustBehavior::new()),
}
}
Language::Python => {
let parser = PythonParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(PythonBehavior::new()),
}
}
Language::Php => {
let parser = PhpParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(PhpBehavior::new()),
}
}
Language::TypeScript => {
let parser =
TypeScriptParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(TypeScriptBehavior::new()),
}
}
Language::JavaScript => {
let parser =
JavaScriptParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(JavaScriptBehavior::new()),
}
}
Language::Go => {
let parser = GoParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(GoBehavior::new()),
}
}
Language::C => {
let parser = CParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(CBehavior::new()),
}
}
Language::Cpp => {
let parser = CppParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(CppBehavior::new()),
}
}
Language::CSharp => {
let parser = CSharpParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(CSharpBehavior::new()),
}
}
Language::Gdscript => {
let parser = GdscriptParser::new().map_err(IndexError::General)?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(GdscriptBehavior::new()),
}
}
Language::Java => {
let parser = JavaParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(JavaBehavior::new()),
}
}
Language::Kotlin => {
let parser = KotlinParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(KotlinBehavior::new()),
}
}
Language::Clojure => {
let parser =
ClojureParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(ClojureBehavior::new()),
}
}
Language::Lua => {
let parser = LuaParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(LuaBehavior::new()),
}
}
Language::Swift => {
let parser = SwiftParser::new().map_err(|e| IndexError::General(e.to_string()))?;
ParserWithBehavior {
parser: Box::new(parser),
behavior: Box::new(SwiftBehavior::new()),
}
}
};
Ok(result)
}
pub fn create_behavior_from_registry(
&self,
language_id: LanguageId,
) -> Box<dyn LanguageBehavior> {
let registry = get_registry();
let registry = registry.lock().unwrap();
if let Some(definition) = registry.get(language_id) {
definition.create_behavior()
} else {
Box::new(RustBehavior::new())
}
}
pub fn enabled_languages(&self) -> Vec<Language> {
vec![
Language::C,
Language::Clojure,
Language::Cpp,
Language::CSharp,
Language::Gdscript,
Language::Go,
Language::Java,
Language::JavaScript,
Language::Kotlin,
Language::Lua,
Language::Php,
Language::Python,
Language::Rust,
Language::Swift,
Language::TypeScript,
]
.into_iter()
.filter(|&lang| self.is_language_enabled(lang))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_based_parser_creation() {
let mut settings = Settings::default();
for (_, config) in settings.languages.iter_mut() {
config.enabled = true;
}
let settings = Arc::new(settings);
let factory = ParserFactory::new(settings.clone());
let rust_id = LanguageId::new("rust");
let parser = factory.create_parser_from_registry(rust_id);
assert!(parser.is_ok(), "Should create Rust parser from registry");
let python_id = LanguageId::new("python");
let parser_with_behavior = factory.create_parser_with_behavior_from_registry(python_id);
let go_id = LanguageId::new("go");
if let Err(e) = &parser_with_behavior {
eprintln!("Failed to create Python parser with behavior: {e}");
}
assert!(
parser_with_behavior.is_ok(),
"Should create Python parser with behavior from registry"
);
assert!(factory.is_language_enabled_in_registry(rust_id));
assert!(factory.is_language_enabled_in_registry(python_id));
assert_eq!(factory.get_language_for_extension("rs"), Some(rust_id));
assert_eq!(factory.get_language_for_extension("py"), Some(python_id));
assert_eq!(
factory.get_language_for_extension("php"),
Some(LanguageId::new("php"))
);
assert_eq!(factory.get_language_for_extension("go"), Some(go_id));
assert_eq!(factory.get_language_for_extension("unknown"), None);
}
#[test]
fn test_language_to_language_id_conversion() {
assert_eq!(Language::Rust.to_language_id(), LanguageId::new("rust"));
assert_eq!(Language::Python.to_language_id(), LanguageId::new("python"));
assert_eq!(Language::Php.to_language_id(), LanguageId::new("php"));
assert_eq!(
Language::JavaScript.to_language_id(),
LanguageId::new("javascript")
);
assert_eq!(
Language::TypeScript.to_language_id(),
LanguageId::new("typescript")
);
}
#[test]
fn test_registry_legacy_parity() {
let settings = Arc::new(Settings::default());
let factory = ParserFactory::new(settings.clone());
let language = Language::Rust;
let language_id = language.to_language_id();
assert_eq!(
factory.is_language_enabled(language),
factory.is_language_enabled_in_registry(language_id),
"Registry and legacy should agree on enabled status"
);
let legacy_result = factory.create_parser(language);
let registry_result = factory.create_parser_from_registry(language_id);
assert!(legacy_result.is_ok());
assert!(registry_result.is_ok());
}
#[test]
fn test_create_rust_parser() {
let settings = Arc::new(Settings::default());
let factory = ParserFactory::new(settings);
let parser = factory.create_parser(Language::Rust);
assert!(parser.is_ok());
let parser = parser.unwrap();
assert_eq!(parser.language(), Language::Rust);
}
#[test]
fn test_create_parser_with_behavior() {
use crate::config::LanguageConfig;
use indexmap::IndexMap;
use std::collections::HashMap;
let mut settings = Settings::default();
let mut languages = IndexMap::new();
languages.insert(
"rust".to_string(),
LanguageConfig {
enabled: true,
extensions: vec!["rs".to_string()],
parser_options: HashMap::new(),
config_files: Vec::new(),
projects: Vec::new(),
},
);
languages.insert(
"python".to_string(),
LanguageConfig {
enabled: true,
extensions: vec!["py".to_string()],
parser_options: HashMap::new(),
config_files: Vec::new(),
projects: Vec::new(),
},
);
languages.insert(
"php".to_string(),
LanguageConfig {
enabled: true,
extensions: vec!["php".to_string()],
parser_options: HashMap::new(),
config_files: Vec::new(),
projects: Vec::new(),
},
);
languages.insert(
"gdscript".to_string(),
LanguageConfig {
enabled: true,
extensions: vec!["gd".to_string()],
parser_options: HashMap::new(),
config_files: Vec::new(),
projects: Vec::new(),
},
);
settings.languages = languages;
let factory = ParserFactory::new(Arc::new(settings));
let result = factory.create_parser_with_behavior(Language::Rust);
assert!(result.is_ok());
let rust_pair = result.unwrap();
assert_eq!(rust_pair.parser.language(), Language::Rust);
assert_eq!(rust_pair.behavior.module_separator(), "::");
let result = factory.create_parser_with_behavior(Language::Python);
assert!(result.is_ok());
let python_pair = result.unwrap();
assert_eq!(python_pair.parser.language(), Language::Python);
assert_eq!(python_pair.behavior.module_separator(), ".");
let result = factory.create_parser_with_behavior(Language::Php);
assert!(result.is_ok());
let php_pair = result.unwrap();
assert_eq!(php_pair.parser.language(), Language::Php);
assert_eq!(php_pair.behavior.module_separator(), "\\");
let result = factory.create_parser_with_behavior(Language::Gdscript);
assert!(result.is_ok());
let gd_pair = result.unwrap();
assert_eq!(gd_pair.parser.language(), Language::Gdscript);
assert_eq!(gd_pair.behavior.module_separator(), "/");
}
#[test]
fn test_create_parser_with_behavior_disabled_language() {
let mut settings = Settings::default();
if let Some(rust_config) = settings.languages.get_mut("rust") {
rust_config.enabled = false;
}
let factory = ParserFactory::new(Arc::new(settings));
let result = factory.create_parser_with_behavior(Language::Rust);
assert!(result.is_err());
if let Err(err) = result {
assert!(
matches!(err, IndexError::ConfigError { reason } if reason.contains("disabled"))
);
}
}
#[test]
fn test_disabled_language() {
let mut settings = Settings::default();
if let Some(rust_config) = settings.languages.get_mut("rust") {
rust_config.enabled = false;
}
let factory = ParserFactory::new(Arc::new(settings));
let result = factory.create_parser(Language::Rust);
assert!(result.is_err());
if let Err(err) = result {
assert!(
matches!(err, IndexError::ConfigError { reason } if reason.contains("disabled"))
);
}
}
#[test]
fn test_enabled_languages() {
let settings = Arc::new(Settings::default());
let factory = ParserFactory::new(settings);
let enabled = factory.enabled_languages();
assert!(
!enabled.is_empty(),
"Should have at least one enabled language"
);
assert!(
enabled.contains(&Language::Rust),
"Rust should be enabled by default"
);
assert!(
enabled.contains(&Language::Python),
"Python should be enabled by default"
);
println!("Currently {} languages enabled by default", enabled.len());
}
#[test]
fn test_create_python_parser() {
use crate::config::LanguageConfig;
use indexmap::IndexMap;
use std::collections::HashMap;
let mut settings = Settings::default();
let mut languages = IndexMap::new();
languages.insert(
"python".to_string(),
LanguageConfig {
enabled: true,
extensions: vec!["py".to_string()],
parser_options: HashMap::new(),
config_files: Vec::new(),
projects: Vec::new(),
},
);
settings.languages = languages;
let factory = ParserFactory::new(Arc::new(settings));
let parser = factory.create_parser(Language::Python);
assert!(parser.is_ok());
let parser = parser.unwrap();
assert_eq!(parser.language(), Language::Python);
}
}