use crate::commands::scrape::beliefs::verification::VerificationQuery;
pub fn parse_verification_blocks(content: &str) -> Vec<VerificationQuery> {
let mut queries = Vec::new();
let mut in_verification_section = false;
let mut in_verify_block = false;
let mut current_attrs: Option<(String, String, String)> = None; let mut current_body = String::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("## ") {
in_verification_section = trimmed.starts_with("## Verification");
continue;
}
if !in_verification_section {
continue;
}
if in_verify_block {
if trimmed.starts_with("```") {
if let Some((query_type, label, expect)) = current_attrs.take() {
let query_text = current_body.trim().to_string();
if !query_text.is_empty() {
queries.push(VerificationQuery {
query_type,
label,
expect,
query_text,
});
}
}
in_verify_block = false;
current_body.clear();
} else {
current_body.push_str(line);
current_body.push('\n');
}
} else if let Some(info_string) = trimmed.strip_prefix("```verify") {
if let Some(attrs) = parse_verify_attributes(info_string) {
current_attrs = Some(attrs);
in_verify_block = true;
current_body.clear();
}
}
}
queries
}
fn parse_verify_attributes(info: &str) -> Option<(String, String, String)> {
let mut query_type = None;
let mut label = None;
let mut expect = None;
let mut chars = info.chars().peekable();
while chars.peek().is_some() {
while chars.peek().is_some_and(|c| c.is_whitespace()) {
chars.next();
}
let key: String = chars
.by_ref()
.take_while(|&c| c != '=')
.collect::<String>()
.trim()
.to_string();
if key.is_empty() {
break;
}
if chars.peek() != Some(&'"') {
while chars.peek().is_some_and(|c| !c.is_whitespace()) {
chars.next();
}
continue;
}
chars.next();
let value: String = chars.by_ref().take_while(|&c| c != '"').collect();
match key.as_str() {
"type" => query_type = Some(value),
"label" => label = Some(value),
"expect" => expect = Some(value),
_ => {} }
}
match (query_type, label, expect) {
(Some(t), Some(l), Some(e)) => Some((t, l, e)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_verification_blocks() {
let content = r#"---
type: belief
id: test-belief
---
# test-belief
Some statement.
## Verification
```verify type="sql" label="No async functions" expect="= 0"
SELECT COUNT(*) FROM function_facts WHERE is_async = 1
```
```verify type="sql" label="No tokio imports" expect="= 0"
SELECT COUNT(*) FROM import_facts WHERE import_path LIKE '%tokio%'
```
## Evidence
- Some evidence here
"#;
let queries = parse_verification_blocks(content);
assert_eq!(queries.len(), 2);
assert_eq!(queries[0].query_type, "sql");
assert_eq!(queries[0].label, "No async functions");
assert_eq!(queries[0].expect, "= 0");
assert!(queries[0]
.query_text
.contains("SELECT COUNT(*) FROM function_facts"));
assert_eq!(queries[1].label, "No tokio imports");
}
#[test]
fn test_parse_empty_verification() {
let content = "## Statement\nSome belief.\n## Evidence\n- something\n";
let queries = parse_verification_blocks(content);
assert!(queries.is_empty());
}
#[test]
fn test_parse_verification_stops_at_next_section() {
let content = r#"## Verification
```verify type="sql" label="Test" expect="= 0"
SELECT 1
```
## Evidence
```verify type="sql" label="Should not be parsed" expect="= 0"
SELECT 2
```
"#;
let queries = parse_verification_blocks(content);
assert_eq!(queries.len(), 1);
assert_eq!(queries[0].label, "Test");
}
#[test]
fn test_parse_verify_attributes() {
let attrs = parse_verify_attributes(r#" type="sql" label="Test query" expect=">= 5""#);
assert!(attrs.is_some());
let (t, l, e) = attrs.unwrap();
assert_eq!(t, "sql");
assert_eq!(l, "Test query");
assert_eq!(e, ">= 5");
}
#[test]
fn test_parse_verify_attributes_missing_field() {
let attrs = parse_verify_attributes(r#" type="sql" label="Test""#);
assert!(attrs.is_none()); }
#[test]
fn test_parse_verification_blocks_mixed_types() {
let content = r#"## Verification
```verify type="sql" label="caller count" expect=">= 20"
SELECT COUNT(*) FROM call_graph WHERE callee LIKE '%insert_event%'
```
```verify type="assay" label="callers across files" expect=">= 5"
callers --pattern "insert_event" | count(distinct file)
```
```verify type="temporal" label="commit frequency" expect=">= 1000"
derive-moments | summary.total_commits
```
"#;
let queries = parse_verification_blocks(content);
assert_eq!(queries.len(), 3);
assert_eq!(queries[0].query_type, "sql");
assert_eq!(queries[1].query_type, "assay");
assert_eq!(
queries[1].query_text.trim(),
r#"callers --pattern "insert_event" | count(distinct file)"#
);
assert_eq!(queries[2].query_type, "temporal");
assert_eq!(
queries[2].query_text.trim(),
"derive-moments | summary.total_commits"
);
}
}