use crate::types::{Chunk, ChunkType, Entity};
pub fn build_entities(chunks: &[Chunk], repo: &str) -> Vec<Entity> {
let mut entities = Vec::new();
let mut seen_files: std::collections::HashSet<&str> = std::collections::HashSet::new();
for chunk in chunks.iter().filter(|c| c.start_line > 0) {
if seen_files.insert(chunk.file_path.as_str()) {
let is_doc = chunk.language == "markdown" || chunk.language == "pdf";
entities.push(Entity {
entity_iri: if is_doc {
crate::iri::document_iri(repo, &chunk.file_path)
} else {
crate::iri::code_module_iri(repo, &chunk.file_path)
},
text: chunk.file_path.clone(),
entity_type: if is_doc { "Document" } else { "CodeModule" }.to_string(),
repo: Some(repo.to_string()),
});
}
let Some(name) = chunk.name.as_deref() else {
continue;
};
match chunk.chunk_type {
t if t.is_code_symbol() => {
entities.push(Entity {
entity_iri: crate::iri::symbol_iri(repo, &chunk.file_path, name),
text: format!("{} ({} in {})", name, chunk.chunk_type, chunk.file_path),
entity_type: "CodeSymbol".to_string(),
repo: Some(repo.to_string()),
});
}
ChunkType::Section => {
entities.push(Entity {
entity_iri: crate::iri::section_iri(repo, &chunk.file_path, name),
text: format!("{} ({})", name, chunk.file_path),
entity_type: "Section".to_string(),
repo: Some(repo.to_string()),
});
}
_ => {}
}
}
entities
}
#[cfg(test)]
mod tests {
use super::*;
fn chunk(file: &str, t: ChunkType, name: Option<&str>, start: u32, lang: &str) -> Chunk {
Chunk {
id: format!("{file}:{start}"),
file_path: file.to_string(),
chunk_type: t,
name: name.map(str::to_string),
start_line: start,
end_line: start + 3,
content: "body".to_string(),
language: lang.to_string(),
tags: String::new(),
}
}
#[test]
fn derives_modules_symbols_and_sections() {
let chunks = vec![
chunk("src/lib.rs", ChunkType::Function, Some("parse"), 10, "rust"),
chunk("src/lib.rs", ChunkType::Struct, Some("Config"), 30, "rust"),
chunk(
"docs/a.md",
ChunkType::Section,
Some("Guide > Setup"),
5,
"markdown",
),
];
let entities = build_entities(&chunks, "myrepo");
let types: Vec<&str> = entities.iter().map(|e| e.entity_type.as_str()).collect();
assert_eq!(
types,
vec![
"CodeModule",
"CodeSymbol",
"CodeSymbol",
"Document",
"Section"
]
);
assert_eq!(
entities[1].entity_iri,
"http://aegis.gastown.local/ontology/code/myrepo/src%2Flib.rs::parse"
);
assert_eq!(
entities[4].entity_iri,
"http://aegis.gastown.local/ontology/doc/myrepo/docs%2Fa.md#setup"
);
for e in &entities {
assert!(
!e.entity_iri.starts_with("http://aegis.gastown.local/code/"),
"regressed to the superseded lane: {}",
e.entity_iri
);
}
}
#[test]
fn synthetic_and_unnamed_chunks_are_skipped() {
let chunks = vec![
chunk("beads:rig:1", ChunkType::Issue, Some("x"), 0, "beads"),
chunk("src/a.rs", ChunkType::Other, None, 1, "rust"),
];
let entities = build_entities(&chunks, "r");
assert_eq!(entities.len(), 1);
assert_eq!(entities[0].entity_type, "CodeModule");
}
}