use std::fmt::Write;
use crate::search::{SearchDomain, describe_domains};
#[derive(Debug, Clone, Default)]
pub struct FrontmatterConfig {
pub enabled: bool,
pub target: Option<String>,
pub search: Option<FrontmatterSearch>,
pub filter: Option<String>,
pub binary_target: Option<FrontmatterBinaryTarget>,
}
impl FrontmatterConfig {
pub fn for_target(target: impl Into<String>) -> Self {
Self {
enabled: true,
target: Some(target.into()),
search: None,
filter: None,
binary_target: None,
}
}
pub fn disabled() -> Self {
Self {
enabled: false,
..Self::default()
}
}
pub fn with_filter(mut self, filter: Option<String>) -> Self {
self.filter = filter;
self
}
pub fn with_binary_target(mut self, binary_target: FrontmatterBinaryTarget) -> Self {
self.binary_target = Some(binary_target);
self
}
pub fn with_search(mut self, search: FrontmatterSearch) -> Self {
self.search = Some(search);
self
}
pub fn render(
&self,
include_private: bool,
render_auto_impls: bool,
render_blanket_impls: bool,
) -> Option<String> {
if !self.enabled {
return None;
}
let mut output = String::new();
output.push_str(
"// Ruskel skeleton - syntactically valid Rust with implementation omitted.\n",
);
let mut private_note_written = false;
if let Some(binary_target) = &self.binary_target {
if binary_target.is_bin_only {
if include_private {
writeln!(
output,
"// Note: binary crate; rendering bin target \"{}\"; showing private API.",
binary_target.name
)
.expect("write frontmatter binary note");
private_note_written = true;
} else {
writeln!(
output,
"// Note: binary crate; rendering bin target \"{}\".",
binary_target.name
)
.expect("write frontmatter binary note");
}
} else if include_private {
writeln!(
output,
"// Note: rendering bin target \"{}\"; showing private API.",
binary_target.name
)
.expect("write frontmatter binary note");
private_note_written = true;
} else {
writeln!(
output,
"// Note: rendering bin target \"{}\".",
binary_target.name
)
.expect("write frontmatter binary note");
}
}
if include_private && !private_note_written {
writeln!(output, "// Note: showing private API.")
.expect("write frontmatter private note");
}
let mut settings = Vec::new();
if let Some(target) = &self.target {
settings.push(format!("target={target}"));
}
if let Some(filter) = &self.filter
&& !filter.is_empty()
{
settings.push(format!("path={filter}"));
}
let visibility = if include_private { "private" } else { "public" };
settings.push(format!("visibility={visibility}"));
settings.push(format!("auto_impls={render_auto_impls}"));
settings.push(format!("blanket_impls={render_blanket_impls}"));
writeln!(output, "// settings: {}", settings.join(", "))
.expect("write frontmatter settings");
if let Some(search) = &self.search {
output.push('\n');
write_search_section(&mut output, search);
}
output.push('\n');
Some(output)
}
}
#[derive(Debug, Clone)]
pub struct FrontmatterBinaryTarget {
pub name: String,
pub is_bin_only: bool,
}
impl FrontmatterBinaryTarget {
pub fn new(name: impl Into<String>, is_bin_only: bool) -> Self {
Self {
name: name.into(),
is_bin_only,
}
}
}
#[cfg(test)]
mod tests {
use super::{FrontmatterBinaryTarget, FrontmatterConfig};
#[test]
fn frontmatter_inserts_binary_admonition_after_header() {
let frontmatter = FrontmatterConfig::for_target("bincrate")
.with_binary_target(FrontmatterBinaryTarget::new("bincrate", true));
let rendered = frontmatter
.render(true, false, false)
.expect("frontmatter output");
let mut lines = rendered.lines();
assert_eq!(
lines.next().unwrap(),
"// Ruskel skeleton - syntactically valid Rust with implementation omitted."
);
assert_eq!(
lines.next().unwrap(),
"// Note: binary crate; rendering bin target \"bincrate\"; showing private API."
);
}
#[test]
fn frontmatter_inserts_private_note_when_enabled() {
let frontmatter = FrontmatterConfig::for_target("libcrate");
let rendered = frontmatter
.render(true, false, false)
.expect("frontmatter output");
let mut lines = rendered.lines();
assert_eq!(
lines.next().unwrap(),
"// Ruskel skeleton - syntactically valid Rust with implementation omitted."
);
assert_eq!(lines.next().unwrap(), "// Note: showing private API.");
}
}
#[derive(Debug, Clone)]
pub struct FrontmatterSearch {
pub query: String,
pub domains: SearchDomain,
pub case_sensitive: bool,
pub expand_containers: bool,
pub hits: Vec<FrontmatterHit>,
}
#[derive(Debug, Clone)]
pub struct FrontmatterHit {
pub path: String,
pub domains: SearchDomain,
}
fn write_search_section(buffer: &mut String, search: &FrontmatterSearch) {
let domains = describe_domains(search.domains);
let mut details = String::new();
if search.case_sensitive {
details.push_str("case_sensitive=true");
} else {
details.push_str("case_sensitive=false");
}
if !domains.is_empty() {
if !details.is_empty() {
details.push_str("; ");
}
details.push_str("domains=");
details.push_str(&domains.join(", "));
}
if !details.is_empty() {
details.push_str("; ");
}
details.push_str("expand_containers=");
details.push_str(if search.expand_containers {
"true"
} else {
"false"
});
writeln!(
buffer,
"// search: query=\"{}\"{}",
search.query,
if details.is_empty() {
String::new()
} else {
format!("; {}", details)
}
)
.expect("write frontmatter search");
if search.hits.is_empty() {
return;
}
writeln!(buffer, "// hits ({}):", search.hits.len()).expect("write frontmatter hit count");
for hit in &search.hits {
let labels = describe_domains(hit.domains);
if labels.is_empty() {
writeln!(buffer, "// - {}", hit.path).expect("write frontmatter hit path");
} else {
writeln!(buffer, "// - {} [{}]", hit.path, labels.join(", "))
.expect("write frontmatter hit labels");
}
}
}