#[derive(Debug, Clone)]
pub struct Fence {
pub lang: String,
pub body: String,
pub line: usize,
}
pub fn fences(markdown: &str) -> Vec<Fence> {
let mut out = Vec::new();
let mut lines = markdown.lines().enumerate();
while let Some((i, line)) = lines.next() {
let t = line.trim_end();
if !t.starts_with("```") {
continue;
}
let lang = t.trim_start_matches('`').trim().to_string();
let mut body = String::new();
for (_, l) in lines.by_ref() {
if l.trim_end().starts_with("```") {
break;
}
body.push_str(l);
body.push('\n');
}
out.push(Fence { lang, body, line: i + 1 });
}
out
}
pub const DECLARATION_HEADS: &[&str] = &[
"type", "axonstore", "axonendpoint", "axpoint", "flow", "shield", "socket",
"tool", "fabric", "resource", "manifest", "mandate", "persona", "context",
"daemon", "corpus", "agent", "session", "channel", "effect", "observable",
"scope", "memory", "anchor",
];
pub fn declares(body: &str) -> bool {
body.lines().any(|l| {
let l = l.trim_start();
DECLARATION_HEADS.iter().any(|h| {
let Some(rest) = l.strip_prefix(h) else { return false };
let trimmed = rest.trim_start();
if trimmed.len() == rest.len() {
return false; }
let Some(first) = trimmed.chars().next() else { return false };
if !(first.is_ascii_alphabetic() || first == '_' || first == '<') {
return false;
}
let tail: String = trimmed
.chars()
.skip_while(|c| c.is_alphanumeric() || *c == '_' || *c == '<' || *c == '>')
.collect();
let tail = tail.trim_start();
tail.starts_with('{') || tail.starts_with('(') || tail.is_empty()
})
})
}
pub fn normalise_comments(body: &str) -> String {
let mut out = String::with_capacity(body.len());
for line in body.lines() {
match hash_outside_a_string(line) {
Some(j) => {
out.push_str(&line[..j]);
out.push_str("//");
out.push_str(&line[j + 1..]);
}
None => out.push_str(line),
}
out.push('\n');
}
out
}
pub fn hash_outside_a_string(line: &str) -> Option<usize> {
let mut in_string = false;
for (j, ch) in line.char_indices() {
match ch {
'"' => in_string = !in_string,
'#' if !in_string => return Some(j),
_ => {}
}
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExcerptKind {
Sketch,
Template,
Elision,
Fragment,
CounterExample,
MultiFile,
}
impl ExcerptKind {
pub fn slug(self) -> &'static str {
match self {
ExcerptKind::Sketch => "sketch",
ExcerptKind::Template => "template",
ExcerptKind::Elision => "elision",
ExcerptKind::Fragment => "fragment",
ExcerptKind::CounterExample => "counter_example",
ExcerptKind::MultiFile => "multi_file",
}
}
}
pub fn excerpt_kind(body: &str, diagnostic: &str) -> Option<ExcerptKind> {
if body.contains("── File:") {
return Some(ExcerptKind::MultiFile);
}
if body.contains("{{") {
return Some(ExcerptKind::Template);
}
if has_placeholder(body) {
return Some(ExcerptKind::Sketch);
}
if body.contains('…') || body.contains("...") {
return Some(ExcerptKind::Elision);
}
let low = diagnostic.to_ascii_lowercase();
if low.contains("axon-t1214") || low.contains("is not a regulatory class") {
return Some(ExcerptKind::CounterExample);
}
if ["undefined", "not declared", "does not resolve", "unknown", "not found"]
.iter()
.any(|w| low.contains(w))
{
return Some(ExcerptKind::Fragment);
}
None
}
fn has_placeholder(body: &str) -> bool {
let bytes: Vec<char> = body.chars().collect();
let mut i = 0;
while i < bytes.len() {
if bytes[i] != '<' {
i += 1;
continue;
}
let Some(close) = bytes[i + 1..].iter().position(|c| *c == '>') else { break };
let inner: String = bytes[i + 1..i + 1 + close].iter().collect();
i += close + 2;
if inner.is_empty() {
continue;
}
let looks_like_a_type = inner
.chars()
.next()
.is_some_and(|c| c.is_ascii_uppercase())
&& inner.chars().all(|c| c.is_alphanumeric() || c == '_');
if !looks_like_a_type {
return true;
}
}
body.lines().any(|l| {
let l = l.trim();
l.contains('|') && !l.starts_with("//") && !l.starts_with('|')
})
}