Parse all top-level directives out of a markdown body. Prose outside any directive is ignored by the lift (it stays as unlifted narrative).
(body: &str)
| 42 | /// Parse all top-level directives out of a markdown body. Prose outside any |
| 43 | /// directive is ignored by the lift (it stays as unlifted narrative). |
| 44 | pub fn parse(body: &str) -> Result<Vec<Directive>> { |
| 45 | let lines: Vec<&str> = body.lines().collect(); |
| 46 | let mut out = Vec::new(); |
| 47 | let mut i = 0; |
| 48 | while i < lines.len() { |
| 49 | let trimmed = lines[i].trim_start(); |
| 50 | if let Some(rest) = trimmed.strip_prefix(":::") { |
| 51 | // Container open (a bare "):::" close is handled inside). |
| 52 | let (name, attrs_src) = split_name_attrs(rest); |
| 53 | if name.is_empty() { |
| 54 | // stray close or empty — skip |
| 55 | i += 1; |
| 56 | continue; |
| 57 | } |
| 58 | check_known(&name)?; |
| 59 | let (id, attrs) = parse_attrs(attrs_src)?; |
| 60 | // Gather body lines until a line that is exactly ":::". |
| 61 | let mut body_lines: Vec<&str> = Vec::new(); |
| 62 | let mut children = Vec::new(); |
| 63 | i += 1; |
| 64 | let mut closed = false; |
| 65 | while i < lines.len() { |
| 66 | let lt = lines[i].trim(); |
| 67 | if lt == ":::" { |
| 68 | closed = true; |
| 69 | i += 1; |
| 70 | break; |
| 71 | } |
| 72 | // A leaf directive nested in the container body. |
| 73 | if lt.starts_with("::") && !lt.starts_with(":::") { |
| 74 | if let Some(leaf) = parse_leaf(lt)? { |
| 75 | children.push(leaf); |
| 76 | i += 1; |
| 77 | continue; |
| 78 | } |
| 79 | } |
| 80 | body_lines.push(lines[i]); |
| 81 | i += 1; |
| 82 | } |
| 83 | if !closed { |
| 84 | return Err(CanonicalError::Directive(format!( |
| 85 | "unterminated container directive ':::{name}' (missing closing ':::')" |
| 86 | ))); |
| 87 | } |
| 88 | let body = body_lines.join("\n").trim().to_string(); |
| 89 | // Inline directives inside the prose surface as children; the |
| 90 | // prose keeps them verbatim (store the edge, render resolves it). |
| 91 | children.extend(parse_inline(&body)?); |
| 92 | out.push(Directive { |
| 93 | name, |
| 94 | id, |
| 95 | attrs, |
| 96 | body, |
| 97 | label: None, |
| 98 | children, |
| 99 | }); |
| 100 | } else if trimmed.starts_with("::") { |
| 101 | if let Some(leaf) = parse_leaf(trimmed)? { |