use crate::symgraph::emitter::{EmitError, LayoutRules, assign_file, topological_sort};
use crate::symgraph::registry::{SymbolId, SymbolKind, SymbolRegistry};
use std::collections::HashMap;
use std::path::PathBuf;
pub trait EmitStrategy {
fn partition(
&self,
registry: &SymbolRegistry,
rules: &LayoutRules,
) -> std::result::Result<HashMap<PathBuf, Vec<SymbolId>>, EmitError>;
fn order_within_file(
&self,
ids: &[SymbolId],
registry: &SymbolRegistry,
) -> std::result::Result<Vec<SymbolId>, EmitError>;
fn name(&self) -> &'static str;
}
pub struct ModulePathStrategy {
pub src_root: String,
}
impl Default for ModulePathStrategy {
fn default() -> Self {
Self {
src_root: "src".to_string(),
}
}
}
impl EmitStrategy for ModulePathStrategy {
fn partition(
&self,
registry: &SymbolRegistry,
rules: &LayoutRules,
) -> std::result::Result<HashMap<PathBuf, Vec<SymbolId>>, EmitError> {
let mut file_symbols: HashMap<PathBuf, Vec<SymbolId>> = HashMap::new();
for (id, entry) in registry.iter() {
let file = entry
.assigned_file
.clone()
.unwrap_or_else(|| assign_file(id, &rules.src_root));
file_symbols.entry(file).or_default().push(id.clone());
}
Ok(file_symbols)
}
fn order_within_file(
&self,
ids: &[SymbolId],
registry: &SymbolRegistry,
) -> std::result::Result<Vec<SymbolId>, EmitError> {
let content_ids: Vec<SymbolId> = ids
.iter()
.filter(|id| {
registry
.get(id)
.map(|e| e.kind != SymbolKind::Import)
.unwrap_or(true)
})
.cloned()
.collect();
topological_sort(&content_ids, registry)
}
fn name(&self) -> &'static str {
"module-path"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symgraph::registry::{SymbolEntry, SymbolId, SymbolKind, SymbolRegistry};
use std::path::PathBuf;
fn make_registry() -> SymbolRegistry {
let mut reg = SymbolRegistry::new(PathBuf::from("/tmp"));
reg.insert(SymbolEntry::new(
SymbolId::new("utils", "helper"),
SymbolKind::Function,
"fn helper() {}".into(),
"rust",
));
reg.insert(SymbolEntry::new(
SymbolId::new("api::handlers", "process"),
SymbolKind::Function,
"fn process() {}".into(),
"rust",
));
reg
}
#[test]
fn partition_uses_assign_file() {
let strategy = ModulePathStrategy::default();
let reg = make_registry();
let rules = LayoutRules::default();
let result = strategy.partition(®, &rules).unwrap();
assert!(result.contains_key(&PathBuf::from("src/utils.rs")));
assert!(result.contains_key(&PathBuf::from("src/api/handlers.rs")));
}
#[test]
fn partition_respects_assigned_file_override() {
let strategy = ModulePathStrategy::default();
let mut reg = SymbolRegistry::new(PathBuf::from("/tmp"));
let mut entry = SymbolEntry::new(
SymbolId::new("utils", "helper"),
SymbolKind::Function,
"fn helper() {}".into(),
"rust",
);
entry.assigned_file = Some(PathBuf::from("custom/path.rs"));
reg.insert(entry);
let rules = LayoutRules::default();
let result = strategy.partition(®, &rules).unwrap();
assert!(result.contains_key(&PathBuf::from("custom/path.rs")));
assert!(!result.contains_key(&PathBuf::from("src/utils.rs")));
}
#[test]
fn order_filters_imports() {
let strategy = ModulePathStrategy::default();
let mut reg = SymbolRegistry::new(PathBuf::from("/tmp"));
reg.insert(SymbolEntry::new(
SymbolId::new("utils", "helper"),
SymbolKind::Function,
"fn helper() {}".into(),
"rust",
));
reg.insert(SymbolEntry::new(
SymbolId::new("utils", "imp"),
SymbolKind::Import,
"use std::io;".into(),
"rust",
));
let ids: Vec<SymbolId> = reg.iter().map(|(id, _)| id.clone()).collect();
let ordered = strategy.order_within_file(&ids, ®).unwrap();
assert_eq!(ordered.len(), 1);
assert_eq!(ordered[0].as_str(), "utils::helper");
}
#[test]
fn name_returns_module_path() {
let strategy = ModulePathStrategy::default();
assert_eq!(strategy.name(), "module-path");
}
}