use proc_macro2::TokenStream;
use std::{fs, io};
pub fn include_asciidoc(item: TokenStream) -> syn::Result<TokenStream> {
super::include_file(item, collect::<fs::File>)
}
fn collect<R: io::Read>(
name: &str,
iter: io::Lines<io::BufReader<R>>,
) -> io::Result<(u32, Vec<String>)> {
let mut lines = Vec::new();
let mut in_block = false;
let mut delimiter_checked = false;
let mut use_delimiters = false;
let mut start_line: u32 = 0;
for (line_idx, line) in iter.enumerate() {
let line = line?;
let line_num = (line_idx + 1) as u32;
if !in_block {
let trimmed = line.trim();
if trimmed.starts_with("[source,rust") || trimmed.starts_with("[,rust") {
if has_matching_id(trimmed, name) {
in_block = true;
start_line = line_num + 1; }
}
} else if !delimiter_checked {
delimiter_checked = true;
if line.trim() == "----" {
use_delimiters = true;
start_line = line_num + 1; continue; } else {
if line.trim().is_empty() || line.trim() == "----" {
break;
}
lines.push(line);
}
} else if use_delimiters {
if line.trim() == "----" {
break;
}
lines.push(line);
} else {
if line.trim().is_empty() || line.trim() == "----" {
break;
}
lines.push(line);
}
}
Ok((start_line, lines))
}
fn has_matching_id(line: &str, name: &str) -> bool {
if let Some(id_pos) = line.find("id=") {
let after_id = &line[id_pos + 3..];
if let Some(after_quote) = after_id.strip_prefix('"') {
if let Some(end_quote) = after_quote.find('"') {
let id_value = &after_quote[..end_quote];
return id_value == name;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::collect;
use crate::extract;
use std::io;
#[test]
fn extract_no_source_blocks() {
let content = r#"This is an AsciiDoc file
with no source blocks at all.
Just plain text."#;
let cursor = io::Cursor::new(content);
let result = extract(cursor, "example", collect);
assert!(matches!(result, Err(err) if err.kind() == io::ErrorKind::NotFound));
}
#[test]
fn extract_no_matching_id() {
let content = r#"Some text here.
[source,rust]
----
fn main() {
println!("Hello");
}
----
More text."#;
let cursor = io::Cursor::new(content);
let result = extract(cursor, "example", collect);
assert!(matches!(result, Err(err) if err.kind() == io::ErrorKind::NotFound));
}
#[test]
fn extract_source_rust_with_delimiters() {
let content = r#"Some introduction text.
[source,rust,id="example"]
----
fn main() {
println!("Hello, world!");
}
----
Text after the block."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"fn main() {
println!("Hello, world!");
}"#
);
}
#[test]
fn extract_shorthand_rust_with_delimiters() {
let content = r#"Some introduction text.
[,rust,id="example"]
----
fn test() {
assert_eq!(2 + 2, 4);
}
----
Text after the block."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"fn test() {
assert_eq!(2 + 2, 4);
}"#
);
}
#[test]
fn extract_source_rust_without_delimiters() {
let content = r#"Some introduction text.
[source,rust,id="example"]
let x = 42;
let y = x + 1;
This text should not be included.
More text here."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"let x = 42;
let y = x + 1;"#
);
}
#[test]
fn extract_shorthand_rust_without_delimiters() {
let content = r#"Some introduction text.
[,rust,id="example"]
fn inline() {
println!("No delimiters");
}
This text after the blank line should not be included.
Neither should this."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"fn inline() {
println!("No delimiters");
}"#
);
}
#[test]
fn extract_multiple_blocks_one_match() {
let content = r#"Here's the first block:
[source,python,id="other"]
----
print("Not this one")
----
And here's the one we want:
[,rust,id="example"]
----
fn main() {
println!("This is the one!");
}
----
And another one:
[source,java]
----
System.out.println("Also not this one");
----"#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"fn main() {
println!("This is the one!");
}"#
);
}
#[test]
fn extract_nested_delimiters() {
let content = r#"Outer content:
[source,rust,id="example"]
----
// Comment with ---- in it
fn nested() {
let s = "----";
println!("{}", s);
}
----
After the block."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"// Comment with ---- in it
fn nested() {
let s = "----";
println!("{}", s);
}"#
);
}
#[test]
fn extract_id_in_middle() {
let content = r#"Text before.
[,rust,id="example",role="highlight"]
----
fn with_attributes() {}
----
Text after."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(result, "fn with_attributes() {}");
}
#[test]
fn extract_empty_lines_within_delimiters() {
let content = r#"Text before.
[,rust,id="example"]
----
fn first() {}
fn second() {}
----
Text after."#;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r#"fn first() {}
fn second() {}"#
);
}
#[test]
fn extract_within_outer_code_block() {
let content = r####"Text before.
[,asciidoc]
----
[,rust,id="example"]
let m = example()?;
assert_eq!(format!("{m:?}"), r#"Model { name: "example" }"#);
----
Text after."####;
let cursor = io::Cursor::new(content);
let (_, result) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(
result,
r###"let m = example()?;
assert_eq!(format!("{m:?}"), r#"Model { name: "example" }"#);"###
);
}
#[test]
fn extract_start_line_with_delimiters() {
let content = "Text.\n\n[,rust,id=\"example\"]\n----\nlet x = 1;\n----\n";
let cursor = io::Cursor::new(content);
let (start_line, _) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(start_line, 5);
}
#[test]
fn extract_start_line_without_delimiters() {
let content = "Text.\n\n[,rust,id=\"example\"]\nlet x = 1;\n";
let cursor = io::Cursor::new(content);
let (start_line, _) = extract(cursor, "example", collect).expect("expected content");
assert_eq!(start_line, 4);
}
}