use super::ContextDocument;
pub(super) fn render(documents: &[ContextDocument]) -> Option<String> {
if documents.is_empty() {
return None;
}
let sections = documents
.iter()
.map(|document| {
format!(
"<context path=\"{}\" scope=\"{}\">\n{}\n</context>",
document.path.display(),
document.scope.label(),
document.content.trim_end(),
)
})
.collect::<Vec<_>>()
.join("\n\n");
Some(format!(
"The following instructions come from the workspace. Later blocks are \
more specific and take precedence over earlier ones.\n\n{sections}"
))
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{super::ContextScope, *};
fn document(path: &str, scope: ContextScope, content: &str) -> ContextDocument {
ContextDocument {
path: PathBuf::from(path),
scope,
content: content.to_string(),
}
}
#[test]
fn nothing_renders_to_none() {
assert_eq!(render(&[]), None);
}
#[test]
fn each_document_carries_its_path_and_scope() {
let rendered = render(&[document(
"/repo/AGENTS.md",
ContextScope::Workspace,
"rule one",
)])
.expect("renders");
assert!(rendered.contains("path=\"/repo/AGENTS.md\""));
assert!(rendered.contains("scope=\"workspace\""));
assert!(rendered.contains("rule one"));
}
#[test]
fn ancestor_scope_records_its_distance() {
let rendered = render(&[document(
"/a/AGENTS.md",
ContextScope::Ancestor { depth: 3 },
"far",
)])
.expect("renders");
assert!(rendered.contains("scope=\"ancestor:3\""));
}
#[test]
fn documents_render_in_the_order_given() {
let rendered = render(&[
document("/a/AGENTS.md", ContextScope::Global, "FIRST"),
document("/b/AGENTS.md", ContextScope::Workspace, "SECOND"),
])
.expect("renders");
assert!(rendered.find("FIRST") < rendered.find("SECOND"));
}
#[test]
fn trailing_whitespace_does_not_reach_the_prompt() {
let rendered = render(&[document(
"/repo/AGENTS.md",
ContextScope::Workspace,
"body\n\n\n",
)])
.expect("renders");
assert!(rendered.contains("body\n</context>"));
}
}