1use std::path::PathBuf;
10
11use crate::inline::parse_inline;
12use crate::nodes::*;
13
14#[derive(Debug, Clone)]
17struct Line {
18 text: String,
19 indent: usize,
20 num: usize,
21}
22
23fn expand_tabs(s: &str) -> String {
24 let mut out = String::with_capacity(s.len());
25 let mut col = 0;
26 for ch in s.chars() {
27 if ch == '\t' {
28 let n = 8 - (col % 8);
29 for _ in 0..n {
30 out.push(' ');
31 }
32 col += n;
33 } else {
34 out.push(ch);
35 col += 1;
36 }
37 }
38 out
39}
40
41fn split_lines(text: &str) -> Vec<Line> {
42 text.lines()
43 .enumerate()
44 .map(|(i, raw)| {
45 let expanded = expand_tabs(raw);
46 let trimmed_end = expanded.trim_end().to_string();
47 let indent = trimmed_end.len() - trimmed_end.trim_start().len();
48 Line {
49 text: trimmed_end,
50 indent,
51 num: i + 1,
52 }
53 })
54 .collect()
55}
56
57pub fn parse(text: &str) -> Document {
59 Parser::new(text, None).run()
60}
61
62pub struct Parser {
63 lines: Vec<Line>,
64 pos: usize,
65 adorns: Vec<(char, bool)>,
67 messages: Vec<SystemMessage>,
68 default_role: Option<String>,
69 title_override: Option<Vec<Inline>>,
70 sectnum: bool,
71 sectnum_depth: Option<u32>,
72 meta: Vec<(String, String)>,
73 base_dir: Option<PathBuf>,
74 pending_classes: Vec<String>,
76 pending_blocks: Vec<Block>,
80 msg_blocks: Vec<Block>,
83 target_names: std::collections::HashSet<String>,
85 next_id: usize,
87}
88
89impl Parser {
90 pub fn new(text: &str, base_dir: Option<PathBuf>) -> Self {
91 Self {
92 lines: split_lines(text),
93 pos: 0,
94 adorns: Vec::new(),
95 messages: Vec::new(),
96 default_role: None,
97 title_override: None,
98 sectnum: false,
99 sectnum_depth: None,
100 meta: Vec::new(),
101 base_dir,
102 pending_classes: Vec::new(),
103 pending_blocks: Vec::new(),
104 msg_blocks: Vec::new(),
105 target_names: std::collections::HashSet::new(),
106 next_id: 1,
107 }
108 }
109
110 pub fn parse_with_base(text: &str, base_dir: PathBuf) -> Document {
111 Self::new(text, Some(base_dir)).run()
112 }
113
114 pub fn run(mut self) -> Document {
115 let children = self.parse_container(0, 0);
116 let mut doc = Document {
117 children,
118 title: None,
119 subtitle: None,
120 header: Vec::new(),
121 footer: Vec::new(),
122 next_id: self.next_id,
123 messages: std::mem::take(&mut self.messages),
124 meta: std::mem::take(&mut self.meta),
125 };
126 hoist_decoration(&mut doc);
129 promote_doc_title(&mut doc);
130 if let Some(t) = self.title_override.take() {
132 doc.title = Some(t);
133 }
134 if self.sectnum {
135 apply_sectnum(&mut doc, self.sectnum_depth);
136 }
137 doc
138 }
139
140 fn error(&mut self, text: impl Into<String>, line: Option<usize>) {
144 self.msg(3, text, line);
145 }
146
147 fn warn(&mut self, text: impl Into<String>, line: Option<usize>) {
149 self.msg(2, text, line);
150 }
151
152 fn info(&mut self, text: impl Into<String>, line: Option<usize>) {
154 self.msg(1, text, line);
155 }
156
157 fn msg(&mut self, level: u8, text: impl Into<String>, line: Option<usize>) {
158 let text = text.into();
159 self.messages.push(SystemMessage {
160 level,
161 text: text.clone(),
162 line,
163 id: None,
164 });
165 self.msg_blocks.push(Block::SystemMessage(SystemMessage {
166 level,
167 text,
168 line,
169 id: None,
170 }));
171 }
172
173 fn msg_before(&mut self, level: u8, text: impl Into<String>, line: Option<usize>) {
176 let text = text.into();
177 self.messages.push(SystemMessage {
178 level,
179 text: text.clone(),
180 line,
181 id: None,
182 });
183 self.pending_blocks
184 .push(Block::SystemMessage(SystemMessage {
185 level,
186 text,
187 line,
188 id: None,
189 }));
190 }
191
192 fn record(&mut self, level: u8, text: impl Into<String>, line: Option<usize>) {
195 self.messages.push(SystemMessage {
196 level,
197 text: text.into(),
198 line,
199 id: None,
200 });
201 }
202
203 fn drain_pending(&mut self, out: &mut Vec<Block>) {
204 if !self.pending_blocks.is_empty() {
205 out.extend(std::mem::take(&mut self.pending_blocks));
206 }
207 }
208
209 fn drain_messages(&mut self, out: &mut Vec<Block>) {
210 if !self.msg_blocks.is_empty() {
211 out.extend(std::mem::take(&mut self.msg_blocks));
212 }
213 }
214
215 fn inline(&mut self, text: &str, line: usize) -> Vec<Inline> {
216 let (mut nodes, errs) = parse_inline(text);
217 let mut err_ids: Vec<Option<usize>> = Vec::with_capacity(errs.len());
221 for e in &errs {
222 if e.level != 1 {
223 let n = self.next_id;
224 self.next_id += 1;
225 err_ids.push(Some(n));
226 } else {
227 err_ids.push(None);
228 }
229 }
230 {
231 let mut pairing = err_ids.iter().filter_map(|id| *id);
232 fn link(nodes: &mut [Inline], pairing: &mut impl Iterator<Item = usize>) {
233 for n in nodes.iter_mut() {
234 match n {
235 Inline::Problematic(p) => {
236 if p.id.is_none() {
237 p.id = pairing.next();
238 }
239 }
240 Inline::Emphasis(c) | Inline::Strong(c) | Inline::Cite(c) => {
241 link(c, pairing);
242 }
243 Inline::Reference(_, c) => link(c, pairing),
244 Inline::PendingRef(p) => link(&mut p.text, pairing),
245 _ => {}
246 }
247 }
248 }
249 link(&mut nodes, &mut pairing);
250 }
251 for (e, id) in errs.into_iter().zip(err_ids) {
252 self.messages.push(SystemMessage {
253 level: e.level,
254 text: e.message.clone(),
255 line: Some(line),
256 id,
257 });
258 self.msg_blocks.push(Block::SystemMessage(SystemMessage {
259 level: e.level,
260 text: e.message,
261 line: Some(line),
262 id,
263 }));
264 }
265 match &self.default_role {
268 None => nodes,
269 Some(role) => nodes
270 .into_iter()
271 .map(|n| match n {
272 Inline::Cite(inner) => match role.as_str() {
273 "emphasis" => Inline::Emphasis(inner),
274 "strong" => Inline::Strong(inner),
275 "literal" => Inline::Literal(inline_text(&inner)),
276 "code" => Inline::Code(inline_text(&inner)),
277 _ => Inline::Cite(inner),
278 },
279 other => other,
280 })
281 .collect(),
282 }
283 }
284
285 fn skip_blanks(&mut self) {
286 while self.pos < self.lines.len() && self.lines[self.pos].text.trim().is_empty() {
287 self.pos += 1;
288 }
289 }
290
291 fn peek(&mut self) -> Option<Line> {
292 self.skip_blanks();
293 self.lines.get(self.pos).cloned()
294 }
295
296 fn parse_container(&mut self, min_indent: usize, level: u8) -> Vec<Block> {
299 let mut out = Vec::new();
300 loop {
301 let line = match self.peek() {
302 None => break,
303 Some(l) => l,
304 };
305 if line.indent < min_indent {
306 break;
307 }
308 if line.indent == min_indent {
310 let save = self.pos;
311 if let Some(h) = self.try_section_header(min_indent) {
312 let (title_text, hlevel, tline) = h;
313 if hlevel <= level {
314 self.pos = save;
315 break; }
317 let hlevel = if hlevel > level + 1 {
318 self.msg_before(
319 3,
320 format!(
321 "title level inconsistent (jump to level {hlevel}); treated as {}",
322 level + 1
323 ),
324 Some(tline),
325 );
326 level + 1
327 } else {
328 hlevel
329 };
330 let title = self.inline(&title_text, tline);
331 let children = self.parse_container(min_indent, hlevel);
334 let ids = vec![slugify(&inline_text(&title))];
335 self.drain_pending(&mut out);
336 out.push(Block::Section(Section {
337 title,
338 level: hlevel,
339 ids,
340 children,
341 classes: Vec::new(),
342 }));
343 self.drain_messages(&mut out);
344 continue;
345 }
346 }
347 match self.parse_block(min_indent, level) {
348 Some(b) => {
349 self.drain_pending(&mut out);
350 if !self.pending_classes.is_empty() && !matches!(b, Block::SystemMessage(_)) {
351 let classes = std::mem::take(&mut self.pending_classes);
352 out.push(Block::Container(Container {
353 classes,
354 children: vec![b],
355 }));
356 } else {
357 out.push(b);
358 }
359 self.drain_messages(&mut out);
360 }
361 None => break,
362 }
363 }
364 self.drain_pending(&mut out);
365 self.drain_messages(&mut out);
366 out
367 }
368
369 fn try_section_header(&mut self, min_indent: usize) -> Option<(String, u8, usize)> {
372 if self.pos + 2 < self.lines.len()
374 && is_adornment(&self.lines[self.pos].text)
375 && !self.lines[self.pos + 1].text.trim().is_empty()
376 && is_adornment(&self.lines[self.pos + 2].text)
377 && self.lines[self.pos].indent == min_indent
378 && self.lines[self.pos + 1].indent == min_indent
379 && self.lines[self.pos + 2].indent == min_indent
380 {
381 let (a, b, c) = (
382 self.lines[self.pos].clone(),
383 self.lines[self.pos + 1].clone(),
384 self.lines[self.pos + 2].clone(),
385 );
386 if adorn_char(&a.text) != adorn_char(&c.text) {
387 self.pos += 3;
389 self.msg_before(3, "Title overline & underline mismatch.", Some(b.num));
390 return None;
391 }
392 if b.text.trim().len() > a.text.trim().len() {
393 if a.text.trim().len() < 4 {
394 self.msg_before(
395 1,
396 "Possible incomplete section title.\nTreating the overline as ordinary text because it's so short.",
397 Some(a.num),
398 );
399 return None;
400 }
401 self.msg_before(2, "Title overline too short.", Some(b.num));
402 }
403 let ch = adorn_char(&a.text);
404 let title = b.text.trim().to_string();
405 let tline = b.num;
406 self.pos += 3;
407 let level = self.adorn_level(ch, true);
408 return Some((title, level, tline));
409 }
410 if self.pos + 1 < self.lines.len() {
412 let (a, b) = (
413 self.lines[self.pos].clone(),
414 self.lines[self.pos + 1].clone(),
415 );
416 let ok = !a.text.trim().is_empty()
417 && is_adornment(&b.text)
418 && a.indent == min_indent
419 && b.indent == min_indent
420 && !is_list_marker(a.text.trim_start());
421 if ok {
422 if b.text.trim().len() < a.text.trim().len() {
426 if b.text.trim().len() < 4 {
427 self.msg_before(
428 1,
429 "Possible title underline, too short for the title.\nTreating it as ordinary text because it's so short.",
430 Some(b.num),
431 );
432 return None;
433 }
434 self.msg_before(2, "Title underline too short.", Some(b.num));
435 }
436 let ch = adorn_char(&b.text);
437 let tline = a.num;
438 let title = a.text.trim().to_string();
439 self.pos += 2;
440 let level = self.adorn_level(ch, false);
441 return Some((title, level, tline));
442 }
443 }
444 None
445 }
446
447 fn adorn_level(&mut self, ch: char, over: bool) -> u8 {
448 if let Some(i) = self.adorns.iter().position(|&(c, o)| c == ch && o == over) {
449 return (i + 1) as u8;
450 }
451 self.adorns.push((ch, over));
452 self.adorns.len() as u8
453 }
454
455 fn parse_block(&mut self, min_indent: usize, level: u8) -> Option<Block> {
459 let line = self.peek()?;
460 if line.indent < min_indent {
461 return None;
462 }
463 let text = line.text.clone();
464 let stripped: String = text.trim_start().to_string();
465
466 if stripped.starts_with("..")
468 && (stripped.len() == 2 || stripped[2..].starts_with([' ', '\t']))
469 {
470 return Some(self.parse_explicit(min_indent, level));
473 }
474 if is_transition(&stripped) && line.indent == min_indent {
477 self.pos += 1;
478 return Some(Block::Transition);
479 }
480 if is_grid_border(&stripped) {
482 return Some(self.parse_grid_table(line.num));
483 }
484 if is_simple_header(&stripped) && self.pos + 1 < self.lines.len() {
486 return Some(self.parse_simple_table());
487 }
488 if stripped.starts_with('|') && (stripped.len() == 1 || stripped[1..].starts_with(' ')) {
490 return Some(self.parse_line_block(min_indent));
491 }
492 if let Some(marker) = bullet_marker(&stripped) {
494 let _ = marker;
495 return Some(self.parse_bullet_list(min_indent, level));
496 }
497 if enum_marker(&stripped).is_some() {
499 return Some(self.parse_enum_list(min_indent, level));
500 }
501 if stripped.starts_with(">>> ") || stripped == ">>>" {
503 return Some(self.parse_doctest_block(min_indent));
504 }
505 if field_start(&stripped).is_some() {
507 return Some(self.parse_field_list(min_indent, level));
508 }
509 if option_start(&stripped).is_some() {
511 return Some(self.parse_option_list(min_indent, level));
512 }
513 if line.indent > min_indent {
515 return Some(self.parse_blockquote(min_indent, level, line.num));
516 }
517 Some(self.parse_para_or_deflist_or_literal(min_indent, level))
518 }
519
520 fn gather_para_lines(&mut self, indent: usize) -> Vec<Line> {
523 let mut lines = Vec::new();
524 while self.pos < self.lines.len() {
525 let l = &self.lines[self.pos];
526 if l.text.trim().is_empty() || l.indent != indent {
527 break;
528 }
529 let s = l.text.trim_start();
530 let is_first = lines.is_empty();
531 if !is_first
532 && (s.starts_with("..") && (s.len() == 2 || s[2..].starts_with([' ', '\t']))
533 || s.starts_with('|') && (s.len() == 1 || s[1..].starts_with(' '))
534 || s.starts_with(">>> ")
535 || bullet_marker(s).is_some()
536 || enum_marker(s).is_some()
537 || field_start(s).is_some()
538 || option_start(s).is_some()
539 || is_grid_border(s)
540 || is_transition(s))
541 {
542 break;
543 }
544 if self.pos + 1 < self.lines.len() {
548 let n = &self.lines[self.pos + 1];
549 if !n.text.trim().is_empty()
550 && is_adornment(&n.text)
551 && n.indent == indent
552 && n.text.trim().len() >= 4
553 {
554 break;
555 }
556 }
557 lines.push(l.clone());
558 self.pos += 1;
559 }
560 lines
561 }
562
563 fn parse_para_or_deflist_or_literal(&mut self, _min_indent: usize, level: u8) -> Block {
564 let first = self.lines[self.pos].clone();
565 let indent = first.indent;
566 let para = self.gather_para_lines(indent);
567 if para.is_empty() {
568 let l = self.lines[self.pos].clone();
571 self.pos += 1;
572 return Block::Paragraph(self.inline(l.text.trim(), l.num));
573 }
574 let last_text = para.last().unwrap().text.clone();
575 let ends_literal = last_text.trim_end().ends_with("::");
576 let mut j = self.pos;
578 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
579 j += 1;
580 }
581 let next_indented = j < self.lines.len() && self.lines[j].indent > indent;
582
583 let mut ptext: Vec<String> = para.iter().map(|l| l.text.clone()).collect();
586 if ends_literal {
587 strip_literal_marker(&mut ptext);
588 }
589 if ends_literal && next_indented {
590 let lit_indent = self.lines[j].indent;
591 let lit_lines = self.take_indented(lit_indent);
592 let dedented = dedent(&lit_lines, lit_indent);
593 let para_block = if ptext.iter().any(|l| !l.trim().is_empty()) {
594 let joined = ptext.join("\n");
595 Some(Block::Paragraph(self.inline(joined.trim(), para[0].num)))
596 } else {
597 None
598 };
599 let lit = Block::LiteralBlock(LiteralBlock {
600 lines: dedented,
601 language: None,
602 });
603 if let Some(p) = para_block {
605 self.pending_blocks.push(p);
606 }
607 return lit;
608 }
609 if ends_literal && !next_indented {
610 let at = self.lines.get(j).map(|l| l.num).unwrap_or(para[0].num);
612 self.warn("Literal block expected; none found.", Some(at));
613 }
614 if para.len() == 1 && next_indented && !ends_literal {
616 return self.parse_deflist_from_term(para.into_iter().next().unwrap(), level);
617 }
618 if next_indented && j == self.pos {
621 self.error("Unexpected indentation.", Some(self.lines[j].num));
622 }
623 let joined = if ends_literal {
625 ptext.join("\n")
626 } else {
627 para.iter()
628 .map(|l| l.text.clone())
629 .collect::<Vec<_>>()
630 .join("\n")
631 };
632 Block::Paragraph(self.inline(joined.trim(), para[0].num))
633 }
634
635 fn parse_deflist_from_term(&mut self, term_line: Line, level: u8) -> Block {
636 let term_indent = term_line.indent;
639 let mut items = Vec::new();
640 let mut pending: Option<Line> = Some(term_line);
641 loop {
642 let tl = match pending.take() {
643 Some(t) => t,
644 None => {
645 self.skip_blanks();
647 if self.pos >= self.lines.len() || self.lines[self.pos].indent != term_indent {
648 break;
649 }
650 let save = self.pos;
651 let cand = self.gather_para_lines(term_indent);
652 let mut k = self.pos;
653 while k < self.lines.len() && self.lines[k].text.trim().is_empty() {
654 k += 1;
655 }
656 let ok = cand.len() == 1
657 && k < self.lines.len()
658 && self.lines[k].indent > term_indent
659 && !cand[0].text.trim_end().ends_with("::")
660 && !is_block_start(cand[0].text.trim_start());
661 if ok {
662 cand.into_iter().next().unwrap()
663 } else {
664 self.pos = save;
665 break;
666 }
667 }
668 };
669 let term = self.inline(tl.text.trim(), tl.num);
670 let mut j = self.pos;
671 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
672 j += 1;
673 }
674 if j >= self.lines.len() || self.lines[j].indent <= term_indent {
675 if items.is_empty() {
676 return Block::Paragraph(term);
678 }
679 self.pos = tl.num.saturating_sub(1).min(self.pos);
686 break;
687 }
688 let def_indent = self.lines[j].indent;
689 let def_lines = self.take_indented(def_indent);
690 let definition = self.parse_sub_lines(def_lines, level);
691 items.push(DefItem { term, definition });
692 }
693 Block::DefinitionList(items)
694 }
695
696 fn parse_blockquote(&mut self, min_indent: usize, level: u8, _ln: usize) -> Block {
697 let indent = self.lines[self.pos].indent;
698 debug_assert!(indent > min_indent);
699 let quote_lines = self.take_indented(indent);
700 let children = self.parse_sub_lines(quote_lines, level);
701 let mut children = children;
703 let mut attribution = None;
704 if let Some(Block::Paragraph(inlines)) = children.last() {
705 let t = inline_text(inlines);
706 let s = t.trim_start();
707 if (s.starts_with("-- ") || s.starts_with("--- ")) && s.len() > 3 {
708 let name = s.trim_start_matches('-').trim().to_string();
709 if let Some(Block::Paragraph(_)) = children.pop() {
710 let (nodes, errs) = parse_inline(&name);
711 for e in errs {
712 self.messages.push(SystemMessage {
713 level: e.level,
714 text: e.message.clone(),
715 line: None,
716 id: None,
717 });
718 self.msg_blocks.push(Block::SystemMessage(SystemMessage {
719 level: e.level,
720 text: e.message,
721 line: None,
722 id: None,
723 }));
724 }
725 attribution = Some(nodes);
726 }
727 }
728 }
729 Block::BlockQuote(BlockQuote {
730 children,
731 attribution,
732 })
733 }
734
735 fn take_indented(&mut self, indent: usize) -> Vec<Line> {
737 let mut out = Vec::new();
738 while self.pos < self.lines.len() {
739 let l = &self.lines[self.pos];
740 if l.text.trim().is_empty() {
741 out.push(l.clone());
742 self.pos += 1;
743 continue;
744 }
745 if l.indent < indent {
746 break;
747 }
748 out.push(l.clone());
749 self.pos += 1;
750 }
751 while out
753 .last()
754 .map(|l| l.text.trim().is_empty())
755 .unwrap_or(false)
756 {
757 out.pop();
758 self.pos -= 1;
759 }
760 out
761 }
762
763 fn parse_bullet_list(&mut self, min_indent: usize, level: u8) -> Block {
766 let bullet = bullet_marker(self.lines[self.pos].text.trim_start()).unwrap();
767 let mut items = Vec::new();
768 loop {
769 self.skip_blanks();
770 if self.pos >= self.lines.len() {
771 break;
772 }
773 let l = &self.lines[self.pos];
774 if l.indent != min_indent || bullet_marker(l.text.trim_start()) != Some(bullet) {
775 break;
776 }
777 let stripped = l.text.trim_start();
779 let after = stripped[1..].trim_start_matches(' ');
780 let content_indent = if after.is_empty() {
781 let mut j = self.pos + 1;
783 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
784 j += 1;
785 }
786 if j < self.lines.len() && self.lines[j].indent > min_indent {
787 self.lines[j].indent
788 } else {
789 min_indent + 2
790 }
791 } else {
792 l.text.len() - l.text.trim_start().len() + (stripped.len() - after.len())
793 };
794 self.pos += 1;
795 let mut item_lines: Vec<Line> = Vec::new();
797 if !after.is_empty() {
798 item_lines.push(Line {
799 text: format!("{}{}", " ".repeat(content_indent), after),
800 indent: content_indent,
801 num: l.num,
802 });
803 }
804 while self.pos < self.lines.len() {
806 let n = &self.lines[self.pos];
807 if n.text.trim().is_empty() {
808 let mut j = self.pos + 1;
810 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
811 j += 1;
812 }
813 if j < self.lines.len() && self.lines[j].indent >= content_indent {
814 item_lines.push(n.clone());
815 self.pos += 1;
816 continue;
817 }
818 break;
819 }
820 if n.indent < content_indent {
821 break;
822 }
823 item_lines.push(n.clone());
824 self.pos += 1;
825 }
826 items.push(self.parse_sub_lines(item_lines, level));
827 }
828 Block::BulletList(BulletList { bullet, items })
829 }
830
831 fn parse_enum_list(&mut self, min_indent: usize, level: u8) -> Block {
832 let first = enum_marker(self.lines[self.pos].text.trim_start()).unwrap();
833 let format = first.0;
834 if first.1 != 1 {
835 let tok: String = self.lines[self.pos]
837 .text
838 .trim_start()
839 .chars()
840 .take_while(|c| !c.is_whitespace())
841 .collect();
842 let core = tok.trim_start_matches('(').trim_end_matches(['.', ')']);
843 self.info(
844 format!(
845 "Enumerated list start value not ordinal-1: \"{core}\" (ordinal {})",
846 first.1
847 ),
848 Some(self.lines[self.pos].num),
849 );
850 }
851 let mut items = Vec::new();
852 let mut expected = first.1;
853 loop {
854 self.skip_blanks();
855 if self.pos >= self.lines.len() {
856 break;
857 }
858 let l = &self.lines[self.pos];
859 if l.indent != min_indent {
860 break;
861 }
862 let m = match enum_marker(l.text.trim_start()) {
863 Some(m) => m,
864 None => break,
865 };
866 if m.0 != format && m.0 != EnumFormat::Auto {
871 let tok: String = l
872 .text
873 .trim_start()
874 .chars()
875 .take_while(|c| !c.is_whitespace())
876 .collect();
877 match enum_value_in_format(enum_core(&tok), format) {
878 Some(v) if v == expected => {}
879 _ => break,
880 }
881 }
882 let stripped = l.text.trim_start();
883 let base = l.text.len() - stripped.len();
884 let token_len = stripped.find(char::is_whitespace).unwrap_or(stripped.len());
885 let after_ws = stripped[token_len..].trim_start_matches([' ', '\t']);
886 let content_indent = if after_ws.trim().is_empty() {
887 min_indent + 2
888 } else {
889 base + (stripped.len() - after_ws.len())
890 };
891 let after = after_ws;
892 let _ = expected;
893 expected += 1;
894 let _ = m;
895 self.pos += 1;
896 let mut item_lines: Vec<Line> = Vec::new();
897 if !after.trim().is_empty() {
898 item_lines.push(Line {
899 text: format!("{}{}", " ".repeat(content_indent), after.trim_start()),
900 indent: content_indent,
901 num: l.num,
902 });
903 }
904 while self.pos < self.lines.len() {
905 let n = &self.lines[self.pos];
906 if n.text.trim().is_empty() {
907 let mut j = self.pos + 1;
908 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
909 j += 1;
910 }
911 if j < self.lines.len() && self.lines[j].indent >= content_indent {
912 item_lines.push(n.clone());
913 self.pos += 1;
914 continue;
915 }
916 break;
917 }
918 if n.indent < content_indent {
919 break;
920 }
921 item_lines.push(n.clone());
922 self.pos += 1;
923 }
924 items.push(self.parse_sub_lines(item_lines, level));
925 }
926 Block::EnumList(EnumList {
927 format,
928 start: first.1,
929 items,
930 })
931 }
932
933 fn parse_sub_lines(&mut self, lines: Vec<Line>, level: u8) -> Vec<Block> {
938 if lines.iter().all(|l| l.text.trim().is_empty()) {
939 return Vec::new();
940 }
941 let min = lines
942 .iter()
943 .filter(|l| !l.text.trim().is_empty())
944 .map(|l| l.indent)
945 .min()
946 .unwrap_or(0);
947 let lines: Vec<Line> = lines
950 .into_iter()
951 .map(|mut l| {
952 if !l.text.trim().is_empty() && l.indent >= min {
953 l.text = l.text.get(min..).unwrap_or(l.text.trim_start()).to_string();
954 l.indent -= min;
955 }
956 l
957 })
958 .collect();
959 let saved_lines = std::mem::take(&mut self.lines);
960 let saved_pos = self.pos;
961 let saved_pending = std::mem::take(&mut self.pending_blocks);
964 let saved_msgs = std::mem::take(&mut self.msg_blocks);
965 self.lines = lines;
966 self.pos = 0;
967 let mut blocks = self.parse_container(0, level);
968 let mut leftover_p = std::mem::take(&mut self.pending_blocks);
970 let mut leftover_m = std::mem::take(&mut self.msg_blocks);
971 if !leftover_p.is_empty() || !leftover_m.is_empty() {
972 leftover_p.append(&mut leftover_m);
973 leftover_p.append(&mut blocks);
974 blocks = leftover_p;
975 }
976 self.pending_blocks = saved_pending;
977 self.msg_blocks = saved_msgs;
978 self.lines = saved_lines;
979 self.pos = saved_pos;
980 blocks
981 }
982
983 fn parse_field_list(&mut self, min_indent: usize, level: u8) -> Block {
984 let mut fields = Vec::new();
985 loop {
986 self.skip_blanks();
987 if self.pos >= self.lines.len() {
988 break;
989 }
990 let l = &self.lines[self.pos];
991 if l.indent != min_indent {
992 break;
993 }
994 let (name, rest) = match field_start(l.text.trim_start()) {
995 Some(x) => x,
996 None => break,
997 };
998 let lnum = l.num;
999 self.pos += 1;
1000 let has_rest = !rest.trim().is_empty();
1005 let mut body_lines: Vec<Line> = Vec::new();
1006 if has_rest {
1007 body_lines.push(Line {
1008 text: format!("{}{}", " ".repeat(l.indent), rest.trim_start()),
1009 indent: l.indent,
1010 num: lnum,
1011 });
1012 }
1013 loop {
1014 if self.pos >= self.lines.len() {
1015 break;
1016 }
1017 let n = &self.lines[self.pos];
1018 if n.text.trim().is_empty() {
1019 let mut j = self.pos + 1;
1020 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
1021 j += 1;
1022 }
1023 if j < self.lines.len() && self.lines[j].indent > min_indent {
1024 body_lines.push(n.clone());
1025 self.pos += 1;
1026 continue;
1027 }
1028 break;
1029 }
1030 if n.indent <= min_indent {
1031 break;
1032 }
1033 body_lines.push(n.clone());
1034 self.pos += 1;
1035 }
1036 let skip = usize::from(has_rest);
1039 let base = body_lines[skip..]
1040 .iter()
1041 .filter(|l| !l.text.trim().is_empty())
1042 .map(|l| l.indent)
1043 .min();
1044 if let Some(base) = base {
1045 for l in body_lines.iter_mut().skip(skip) {
1046 if l.text.trim().is_empty() {
1047 continue;
1048 }
1049 let cut = base.min(l.indent).min(leading_spaces(&l.text));
1050 l.text = l.text[cut..].to_string();
1051 l.indent -= cut;
1052 }
1053 }
1054 let name_inlines = self.inline(&name, lnum);
1055 let body = if body_lines.iter().all(|l| l.text.trim().is_empty()) {
1056 Vec::new()
1057 } else {
1058 self.parse_sub_lines(body_lines, level)
1059 };
1060 fields.push(Field {
1061 name: name_inlines,
1062 body,
1063 });
1064 }
1065 Block::FieldList(fields)
1066 }
1067 fn parse_option_list(&mut self, min_indent: usize, level: u8) -> Block {
1068 let mut groups = Vec::new();
1069 loop {
1070 self.skip_blanks();
1071 if self.pos >= self.lines.len() {
1072 break;
1073 }
1074 let l = &self.lines[self.pos];
1075 if l.indent != min_indent {
1076 break;
1077 }
1078 let (opts, rest) = match option_start(l.text.trim_start()) {
1079 Some(x) => x,
1080 None => break,
1081 };
1082 let lnum = l.num;
1083 self.pos += 1;
1084 let mut body_lines: Vec<Line> = Vec::new();
1086 if !rest.trim().is_empty() {
1087 body_lines.push(Line {
1088 text: format!("{}{}", " ".repeat(l.indent + 1), rest.trim_start()),
1089 indent: l.indent + 1,
1090 num: lnum,
1091 });
1092 }
1093 loop {
1094 if self.pos >= self.lines.len() {
1095 break;
1096 }
1097 let n = &self.lines[self.pos];
1098 if n.text.trim().is_empty() {
1099 let mut j = self.pos + 1;
1100 while j < self.lines.len() && self.lines[j].text.trim().is_empty() {
1101 j += 1;
1102 }
1103 if j < self.lines.len() && self.lines[j].indent > min_indent {
1104 body_lines.push(n.clone());
1105 self.pos += 1;
1106 continue;
1107 }
1108 break;
1109 }
1110 if n.indent <= min_indent {
1111 break;
1112 }
1113 body_lines.push(n.clone());
1114 self.pos += 1;
1115 }
1116 let body = if body_lines.iter().all(|l| l.text.trim().is_empty()) {
1117 Vec::new()
1118 } else {
1119 self.parse_sub_lines(body_lines, level)
1120 };
1121 groups.push(OptGroup { opts, body });
1122 }
1123 Block::OptionList(groups)
1124 }
1125
1126 fn parse_line_block(&mut self, min_indent: usize) -> Block {
1127 let mut raw: Vec<(usize, String, usize)> = Vec::new();
1130 loop {
1131 if self.pos >= self.lines.len() || self.lines[self.pos].text.trim().is_empty() {
1132 break;
1133 }
1134 let l = self.lines[self.pos].clone();
1135 if l.indent != min_indent {
1136 break;
1137 }
1138 let s = l.text.trim_start().to_string();
1139 if !(s.starts_with('|') && (s.len() == 1 || s[1..].starts_with(' '))) {
1140 break;
1141 }
1142 let after = &s[1..];
1143 let cindent = after.len() - after.trim_start_matches(' ').len();
1144 raw.push((cindent, after.trim_start().to_string(), l.num));
1145 self.pos += 1;
1146 }
1147 if raw.is_empty() {
1148 return Block::LineBlock(Vec::new());
1149 }
1150 let base = raw[0].0;
1151 let (mut rows, mut i) = self.build_line_rows(&raw, 0, base);
1152 while i < raw.len() {
1155 let (_, text, lnum) = &raw[i];
1156 rows.push(LineRow::Line(self.inline(text, *lnum)));
1157 i += 1;
1158 }
1159 Block::LineBlock(rows)
1160 }
1161
1162 fn build_line_rows(
1165 &mut self,
1166 raw: &[(usize, String, usize)],
1167 start: usize,
1168 base: usize,
1169 ) -> (Vec<LineRow>, usize) {
1170 let mut rows = Vec::new();
1171 let mut i = start;
1172 while i < raw.len() {
1173 let (ci, text, lnum) = &raw[i];
1174 if *ci < base {
1175 break;
1176 }
1177 if *ci == base {
1178 rows.push(LineRow::Line(self.inline(text, *lnum)));
1179 i += 1;
1180 } else {
1181 let (nested, next) = self.build_line_rows(raw, i, *ci);
1182 rows.push(LineRow::Nested(nested));
1183 i = next;
1184 }
1185 }
1186 (rows, i)
1187 }
1188
1189 fn parse_doctest_block(&mut self, min_indent: usize) -> Block {
1190 let mut out = Vec::new();
1191 while self.pos < self.lines.len() {
1192 let l = &self.lines[self.pos];
1193 if l.text.trim().is_empty() || l.indent < min_indent {
1194 break;
1195 }
1196 out.push(l.text.clone());
1197 self.pos += 1;
1198 }
1199 Block::LiteralBlock(LiteralBlock {
1201 lines: dedent(
1202 &out.iter()
1203 .map(|t| Line {
1204 text: t.clone(),
1205 indent: min_indent,
1206 num: 0,
1207 })
1208 .collect::<Vec<_>>(),
1209 min_indent,
1210 ),
1211 language: None,
1212 })
1213 }
1214
1215 fn parse_grid_table(&mut self, _ln: usize) -> Block {
1218 let mut raw: Vec<String> = Vec::new();
1220 while self.pos < self.lines.len() {
1221 let t = self.lines[self.pos].text.trim().to_string();
1222 if t.is_empty() {
1223 break;
1224 }
1225 if !(t.starts_with('+') || t.starts_with('|')) {
1226 break;
1227 }
1228 raw.push(t);
1229 self.pos += 1;
1230 }
1231 build_grid_table(raw, &mut self.messages, &mut self.pending_blocks)
1232 }
1233
1234 fn parse_simple_table(&mut self) -> Block {
1235 let header = self.lines[self.pos].text.trim().to_string();
1236 let cols = simple_columns(&header);
1237 if cols.is_empty() {
1238 let l = self.lines[self.pos].clone();
1239 self.pos += 1;
1240 return Block::Paragraph(self.inline(l.text.trim(), l.num));
1241 }
1242 let lnum = self.lines[self.pos].num;
1243 self.pos += 1;
1244 let mut pre: Vec<Vec<String>> = Vec::new();
1247 let mut post: Vec<Vec<String>> = Vec::new();
1248 let mut saw_mid = false;
1249 let mut ended_clean = false;
1250 while self.pos < self.lines.len() {
1251 let t = self.lines[self.pos].text.trim_end().to_string();
1252 if t.trim().is_empty() {
1253 self.pos += 1;
1254 ended_clean = true;
1255 break;
1256 }
1257 if is_simple_header(&t) {
1258 if saw_mid {
1259 self.pos += 1;
1261 ended_clean = true;
1262 break;
1263 }
1264 saw_mid = true;
1265 self.pos += 1;
1266 continue;
1267 }
1268 if !simple_row_fits(&t, &cols) {
1269 ended_clean = true;
1271 break;
1272 }
1273 let cells = slice_simple_row(&t, &cols);
1274 if saw_mid {
1275 post.push(cells);
1276 } else {
1277 pre.push(cells);
1278 }
1279 self.pos += 1;
1280 }
1281 if !ended_clean && self.pos >= self.lines.len() {
1282 self.error(
1284 "Malformed table.\nNo bottom table border found or no blank line after table.",
1285 Some(lnum),
1286 );
1287 self.warn("Blank line required after table.", Some(lnum));
1288 return Block::Comment(String::new());
1289 }
1290 let (head_rows, body_rows) = if saw_mid {
1291 (pre, post)
1292 } else {
1293 (Vec::new(), pre)
1294 };
1295 build_simple_table(cols.len(), head_rows, body_rows, &mut self.messages)
1296 }
1297
1298 fn parse_explicit(&mut self, _min_indent: usize, level: u8) -> Block {
1301 let line = self.lines[self.pos].clone();
1302 let indent = line.indent;
1303 let after = line.text[indent + 2..].trim_start().to_string();
1304 let lnum = line.num;
1305 self.pos += 1;
1306
1307 if after.is_empty() {
1309 let extra = self.take_indented(indent + 1);
1310 let mut text = String::new();
1311 for l in extra {
1312 text.push_str(l.text.trim());
1313 text.push('\n');
1314 }
1315 return Block::Comment(text);
1316 }
1317 if after.starts_with("__:") || after == "__" {
1319 let uri = after["__".len()..]
1320 .trim_start_matches(':')
1321 .trim()
1322 .to_string();
1323 return Block::HyperlinkTarget(HyperlinkTarget {
1324 name: String::new(),
1325 uri,
1326 anonymous: true,
1327 });
1328 }
1329 if let Some(rest) = after.strip_prefix('_') {
1331 if let Some((name, uri)) = split_target(rest) {
1332 let norm = norm_name(&name);
1333 if !self.target_names.insert(norm.clone()) {
1334 self.warn(
1335 format!("Duplicate explicit target name: \"{norm}\"."),
1336 Some(lnum),
1337 );
1338 }
1339 return Block::HyperlinkTarget(HyperlinkTarget {
1340 name: norm,
1341 uri: uri.trim().to_string(),
1342 anonymous: false,
1343 });
1344 }
1345 let extra = self.take_indented(indent + 1);
1347 let mut text = after;
1348 for l in extra {
1349 text.push('\n');
1350 text.push_str(&l.text);
1351 }
1352 return Block::Comment(text);
1353 }
1354 if let Some(b) = self.parse_bracket_def(&after, indent, lnum, level) {
1356 return b;
1357 }
1358 if let Some(b) = self.parse_substitution_def(&after, indent, lnum, level) {
1360 return b;
1361 }
1362 if let Some((dname, args)) = split_directive(&after) {
1364 let (children, node) = self.parse_directive(&dname, &args, indent, lnum, level, false);
1365 if let Some(n) = node {
1366 return n;
1367 }
1368 self.pending_blocks.extend(children);
1371 return Block::Comment(String::new());
1372 }
1373 let extra = self.take_indented(indent + 1);
1375 let mut text = after;
1376 for l in extra {
1377 if !l.text.trim().is_empty() {
1378 text.push('\n');
1379 text.push_str(l.text.trim());
1380 }
1381 }
1382 Block::Comment(text)
1383 }
1384
1385 fn parse_bracket_def(
1388 &mut self,
1389 after: &str,
1390 indent: usize,
1391 lnum: usize,
1392 level: u8,
1393 ) -> Option<Block> {
1394 let bracket = after.strip_prefix('[')?;
1395 let end = bracket.find(']')?;
1396 let label = bracket[..end].trim().to_string();
1397 let rest = bracket[end + 1..].trim().to_string();
1398 let body_indent = indent + 3; let mut body_lines: Vec<Line> = Vec::new();
1400 if !rest.is_empty() {
1401 body_lines.push(Line {
1402 text: format!("{}{}", " ".repeat(body_indent), rest),
1403 indent: body_indent,
1404 num: lnum,
1405 });
1406 }
1407 let extra = self.take_indented(indent + 1);
1408 body_lines.extend(extra);
1409 let children = if body_lines.iter().all(|l| l.text.trim().is_empty()) {
1410 Vec::new()
1411 } else {
1412 self.parse_sub_lines(body_lines, level)
1413 };
1414 if label == "*" || label == "#" || label.starts_with('#') || is_digits_str(&label) {
1415 Some(Block::Footnote(Footnote {
1416 name: label.to_lowercase(),
1417 auto_numbered: label == "#" || label.starts_with('#') || label == "*",
1418 symbol: label == "*",
1419 number: None,
1420 children,
1421 }))
1422 } else {
1423 Some(Block::Citation(Citation {
1424 name: label.to_lowercase(),
1425 label,
1426 children,
1427 }))
1428 }
1429 }
1430
1431 fn parse_substitution_def(
1434 &mut self,
1435 after: &str,
1436 indent: usize,
1437 lnum: usize,
1438 level: u8,
1439 ) -> Option<Block> {
1440 let rest = after.strip_prefix('|')?;
1441 let end = rest.find('|')?;
1442 let name = rest[..end].trim().to_lowercase();
1443 let tail = rest[end + 1..].trim_start().to_string();
1444 if let Some((dname, args)) = split_directive(&tail) {
1446 let (children, node) = self.parse_directive(&dname, &args, indent, lnum, level, true);
1447 let replacement = match node {
1449 Some(n) => vec![n],
1450 None => children,
1451 };
1452 return Some(Block::SubstitutionDef(SubstitutionDef {
1453 name,
1454 replacement,
1455 }));
1456 }
1457 self.record(
1458 3,
1459 format!("malformed substitution definition |{name}|"),
1460 Some(lnum),
1461 );
1462 let _ = self.take_indented(indent + 1);
1463 Some(Block::SystemMessage(SystemMessage {
1464 level: 3,
1465 text: format!("malformed substitution definition |{name}|"),
1466 line: Some(lnum),
1467 id: None,
1468 }))
1469 }
1470
1471 fn parse_directive(
1474 &mut self,
1475 name: &str,
1476 args: &str,
1477 indent: usize,
1478 lnum: usize,
1479 level: u8,
1480 in_substitution: bool,
1481 ) -> (Vec<Block>, Option<Block>) {
1482 let mut options: Vec<(String, String)> = Vec::new();
1484 loop {
1485 self.skip_blanks();
1486 if self.pos >= self.lines.len() {
1487 break;
1488 }
1489 let l = &self.lines[self.pos];
1490 if l.indent <= indent {
1491 break;
1492 }
1493 match field_start(l.text.trim_start()) {
1494 Some((k, v)) => {
1495 options.push((k.to_lowercase(), v.trim().to_string()));
1496 self.pos += 1;
1497 }
1498 None => break,
1499 }
1500 }
1501 self.skip_blanks();
1503 let body_indent = if self.pos < self.lines.len() && self.lines[self.pos].indent > indent {
1504 self.lines[self.pos].indent
1505 } else {
1506 indent + 1
1507 };
1508 let body_lines = self.take_indented(body_indent);
1509 let has_body = body_lines.iter().any(|l| !l.text.trim().is_empty());
1510 let opt = |k: &str| {
1511 options
1512 .iter()
1513 .find(|(ok, _)| ok == k)
1514 .map(|(_, v)| v.clone())
1515 };
1516 let _ = level;
1517
1518 if let Some(kind) = AdmonKind::from_directive(name) {
1520 let mut children = Vec::new();
1525 let title = if name == "admonition" {
1526 if args.trim().is_empty() {
1527 self.error("admonition requires a title argument", Some(lnum));
1528 None
1529 } else {
1530 Some(self.inline(args.trim(), lnum))
1531 }
1532 } else {
1533 if !args.trim().is_empty() {
1534 children.push(Block::Paragraph(self.inline(args.trim(), lnum)));
1535 }
1536 None
1537 };
1538 children.extend(self.parse_sub_lines(body_lines, 0));
1539 return (
1540 vec![],
1541 Some(Block::Admonition(Admonition {
1542 kind,
1543 title,
1544 children,
1545 })),
1546 );
1547 }
1548 match name {
1549 "code" => {
1550 let ded = dedent(&body_lines, body_indent);
1551 let language = if args.trim().is_empty() {
1552 None
1553 } else {
1554 Some(args.trim().to_string())
1555 };
1556 (
1557 vec![],
1558 Some(Block::LiteralBlock(LiteralBlock {
1559 lines: ded,
1560 language,
1561 })),
1562 )
1563 }
1564 "contents" => {
1565 let depth = opt("depth").and_then(|d| d.parse().ok());
1566 let title = if args.trim().is_empty() {
1567 None
1568 } else {
1569 Some(self.inline(args.trim(), lnum))
1570 };
1571 (vec![], Some(Block::PendingContents { depth, title }))
1572 }
1573 "topic" => {
1574 let title = self.inline(args.trim(), lnum);
1575 let children = self.parse_sub_lines(body_lines, 0);
1576 (
1577 vec![],
1578 Some(Block::Topic(Topic {
1579 title,
1580 children,
1581 classes: vec!["topic".into()],
1582 })),
1583 )
1584 }
1585 "sidebar" => {
1586 let title = self.inline(args.trim(), lnum);
1587 let children = self.parse_sub_lines(body_lines, 0);
1588 (
1589 vec![],
1590 Some(Block::Topic(Topic {
1591 title,
1592 children,
1593 classes: vec!["sidebar".into()],
1594 })),
1595 )
1596 }
1597 "rubric" => (vec![], Some(Block::Rubric(self.inline(args.trim(), lnum)))),
1598 "compound" => {
1599 let children = self.parse_sub_lines(body_lines, 0);
1600 (
1601 vec![],
1602 Some(Block::Container(Container {
1603 classes: vec!["compound".into()],
1604 children,
1605 })),
1606 )
1607 }
1608 "container" => {
1609 let classes: Vec<String> = args.split_whitespace().map(|s| s.to_string()).collect();
1610 let children = self.parse_sub_lines(body_lines, 0);
1611 (
1612 vec![],
1613 Some(Block::Container(Container { classes, children })),
1614 )
1615 }
1616 "class" => {
1617 let classes: Vec<String> = args.split_whitespace().map(|s| s.to_string()).collect();
1618 self.pending_classes = classes;
1619 (vec![], None)
1621 }
1622 "image" => {
1623 let uri = args.trim().to_string();
1624 if uri.is_empty() {
1625 self.error("image directive requires a URI argument", Some(lnum));
1626 }
1627 (
1628 vec![],
1629 Some(Block::Image(Image {
1630 uri,
1631 alt: opt("alt"),
1632 })),
1633 )
1634 }
1635 "figure" => {
1636 let uri = args.trim().to_string();
1637 let caption = self.parse_sub_lines(body_lines, 0);
1638 (
1639 vec![],
1640 Some(Block::Figure(Figure {
1641 uri,
1642 alt: opt("alt"),
1643 caption,
1644 })),
1645 )
1646 }
1647 "raw" => {
1648 let text = dedent(&body_lines, body_indent).join("\n");
1649 (
1650 vec![],
1651 Some(Block::Raw(Raw {
1652 format: args.trim().to_lowercase(),
1653 text,
1654 })),
1655 )
1656 }
1657 "include" => {
1658 let path = args.trim();
1659 let content = self.read_include(path, lnum);
1660 match content {
1661 Some(text) => {
1662 let sub = Self::new(&text, self.base_dir.clone());
1663 let mut sub = sub;
1666 let blocks = sub.parse_container(0, 0);
1667 self.messages.extend(sub.messages);
1668 (blocks, None)
1669 }
1670 None => (vec![], None),
1671 }
1672 }
1673 "replace" => {
1674 if in_substitution {
1675 let inlines = self.inline(args, lnum);
1676 (vec![Block::Paragraph(inlines)], None)
1677 } else {
1678 self.msg_before(
1681 3,
1682 format!(
1683 "Invalid context: the \"replace\" directive can only be used within a substitution definition.\n\n.. replace:: {args}"
1684 ),
1685 Some(lnum),
1686 );
1687 (vec![], None)
1688 }
1689 }
1690 "unicode" => {
1691 if in_substitution {
1692 let text = parse_unicode_spec(args);
1693 (vec![Block::Paragraph(vec![Inline::Text(text)])], None)
1694 } else {
1695 self.msg_before(
1696 3,
1697 format!(
1698 "Invalid context: the \"unicode\" directive can only be used within a substitution definition.\n\n.. unicode:: {args}"
1699 ),
1700 Some(lnum),
1701 );
1702 (vec![], None)
1703 }
1704 }
1705 "date" => {
1706 if in_substitution {
1707 let text = format_date(args.trim());
1708 (vec![Block::Paragraph(vec![Inline::Text(text)])], None)
1709 } else {
1710 self.msg_before(
1711 3,
1712 format!(
1713 "Invalid context: the \"date\" directive can only be used within a substitution definition.\n\n.. date:: {args}"
1714 ),
1715 Some(lnum),
1716 );
1717 (vec![], None)
1718 }
1719 }
1720 "header" | "footer" => {
1721 let mut children = Vec::new();
1723 if !args.trim().is_empty() {
1724 children.push(Block::Paragraph(self.inline(args.trim(), lnum)));
1725 }
1726 children.extend(self.parse_sub_lines(body_lines, 0));
1727 (
1728 vec![],
1729 Some(Block::Topic(Topic {
1730 title: vec![],
1731 children,
1732 classes: vec![name.to_string()],
1733 })),
1734 )
1735 }
1736 "parsed-literal" => {
1737 let ded = dedent(&body_lines, body_indent);
1738 let mut rows = Vec::new();
1739 for l in ded {
1740 rows.push(self.inline(&l, lnum));
1741 }
1742 (vec![], Some(Block::ParsedLiteral(rows)))
1743 }
1744 "line-block" => {
1745 let ded = dedent(&body_lines, body_indent);
1748 let mut rows = Vec::new();
1749 for l in ded {
1750 rows.push(LineRow::Line(self.inline(l.trim(), lnum)));
1751 }
1752 (vec![], Some(Block::LineBlock(rows)))
1753 }
1754 "list-table" => {
1755 let node = self.build_list_table(args, body_lines, body_indent, &options, lnum);
1756 (vec![], node)
1757 }
1758 "table" => {
1759 let text = dedent(&body_lines, body_indent).join("\n");
1760 let mut sub = Self::new(&text, self.base_dir.clone());
1761 let inner = sub.parse_container(0, 0);
1762 self.messages.extend(sub.messages);
1763 let table = inner.into_iter().find_map(|b| match b {
1764 Block::Table(t) => Some(t),
1765 _ => None,
1766 });
1767 match table {
1768 Some(mut t) => {
1769 if !args.trim().is_empty() {
1770 t.caption = self.inline(args.trim(), lnum);
1771 }
1772 (vec![], Some(Block::Table(t)))
1773 }
1774 None => {
1775 self.error("table directive body contains no table", Some(lnum));
1776 (vec![], None)
1777 }
1778 }
1779 }
1780 "csv-table" => {
1781 let node = self.build_csv_table(args, body_lines, body_indent, &options, lnum);
1782 (vec![], node)
1783 }
1784 "target-notes" => {
1785 (
1788 vec![],
1789 Some(Block::Topic(Topic {
1790 title: vec![],
1791 children: vec![],
1792 classes: vec!["__target_notes__".into()],
1793 })),
1794 )
1795 }
1796 "math" => {
1797 let ded = dedent(&body_lines, body_indent);
1798 (
1799 vec![],
1800 Some(Block::LiteralBlock(LiteralBlock {
1801 lines: ded,
1802 language: None,
1803 })),
1804 )
1805 }
1806 "meta" => {
1807 for (k, v) in options {
1808 self.meta.push((k, v));
1810 }
1811 if has_body {
1812 self.error("meta directive takes no body; body ignored", Some(lnum));
1813 }
1814 (vec![], None)
1815 }
1816 "title" => {
1817 self.title_override = Some(self.inline(args.trim(), lnum));
1818 (vec![], None)
1819 }
1820 "sectnum" => {
1821 self.sectnum = true;
1822 self.sectnum_depth = opt("depth").and_then(|d| d.parse().ok());
1823 (vec![], None)
1824 }
1825 "role" => {
1826 (vec![], None)
1829 }
1830 "default-role" => {
1831 let r = args.trim().to_lowercase();
1832 match r.as_str() {
1833 "emphasis" | "strong" | "literal" | "code" | "title-reference" => {
1834 self.default_role = Some(r);
1835 }
1836 _ => self.error(format!("unknown role for default-role: {r}"), Some(lnum)),
1837 }
1838 (vec![], None)
1839 }
1840 _ => {
1841 self.msg_before(
1842 1,
1843 format!(
1844 "No directive entry for \"{name}\" in module \"docutils.parsers.rst.languages.en\".\nTrying \"{name}\" as canonical directive name."
1845 ),
1846 Some(lnum),
1847 );
1848 let text = format!("Unknown directive type \"{name}\".\n\n.. {name}:: {args}");
1849 self.record(3, text.clone(), Some(lnum));
1850 (
1851 vec![],
1852 Some(Block::SystemMessage(SystemMessage {
1853 level: 3,
1854 text,
1855 line: Some(lnum),
1856 id: None,
1857 })),
1858 )
1859 }
1860 }
1861 }
1862
1863 fn read_include(&mut self, path: &str, lnum: usize) -> Option<String> {
1864 let base = match &self.base_dir {
1865 Some(b) => b.clone(),
1866 None => {
1867 self.error(
1868 "include directive needs a base directory (none given)",
1869 Some(lnum),
1870 );
1871 return None;
1872 }
1873 };
1874 let full = base.join(path);
1875 match std::fs::read_to_string(&full) {
1876 Ok(s) => Some(s),
1877 Err(e) => {
1878 self.error(format!("include file not found: {path} ({e})"), Some(lnum));
1879 None
1880 }
1881 }
1882 }
1883
1884 fn build_list_table(
1885 &mut self,
1886 args: &str,
1887 body_lines: Vec<Line>,
1888 body_indent: usize,
1889 options: &[(String, String)],
1890 lnum: usize,
1891 ) -> Option<Block> {
1892 let text = dedent(&body_lines, body_indent).join("\n");
1893 let mut sub = Self::new(&text, self.base_dir.clone());
1894 let inner = sub.parse_container(0, 0);
1895 self.messages.extend(sub.messages);
1896 let rows_list = inner.into_iter().find_map(|b| match b {
1897 Block::BulletList(l) => Some(l.items),
1898 _ => None,
1899 });
1900 let rows_list = match rows_list {
1901 Some(r) => r,
1902 None => {
1903 self.error("list-table body must be a bullet list of rows", Some(lnum));
1904 return None;
1905 }
1906 };
1907 let header_rows: usize = options
1908 .iter()
1909 .find(|(k, _)| k == "header-rows")
1910 .and_then(|(_, v)| v.parse().ok())
1911 .unwrap_or(0);
1912 let mut head = Vec::new();
1913 let mut body = Vec::new();
1914 for (ri, row_blocks) in rows_list.into_iter().enumerate() {
1915 let cells: Vec<TableCell> = match row_blocks.as_slice() {
1917 [Block::BulletList(cells)] => cells
1918 .items
1919 .iter()
1920 .map(|c| TableCell {
1921 blocks: c.clone(),
1922 colspan: 1,
1923 })
1924 .collect(),
1925 other => other
1926 .iter()
1927 .map(|b| TableCell {
1928 blocks: vec![b.clone()],
1929 colspan: 1,
1930 })
1931 .collect(),
1932 };
1933 if ri < header_rows {
1934 head.push(cells);
1935 } else {
1936 body.push(cells);
1937 }
1938 }
1939 let widths = body.first().map(|r| vec![0; r.len()]).unwrap_or_default();
1940 let caption = if args.trim().is_empty() {
1941 vec![]
1942 } else {
1943 self.inline(args.trim(), lnum)
1944 };
1945 Some(Block::Table(Table {
1946 head,
1947 body,
1948 widths,
1949 caption,
1950 }))
1951 }
1952
1953 fn build_csv_table(
1954 &mut self,
1955 args: &str,
1956 body_lines: Vec<Line>,
1957 body_indent: usize,
1958 options: &[(String, String)],
1959 lnum: usize,
1960 ) -> Option<Block> {
1961 let header_opt = options
1962 .iter()
1963 .find(|(k, _)| k == "header")
1964 .map(|(_, v)| v.clone());
1965 let mut header_rows: usize = options
1966 .iter()
1967 .find(|(k, _)| k == "header-rows")
1968 .and_then(|(_, v)| v.parse().ok())
1969 .unwrap_or(0);
1970 if header_opt.is_some() {
1972 header_rows = header_rows.max(1);
1973 }
1974 let mut rows: Vec<Vec<String>> = Vec::new();
1975 if let Some(h) = header_opt {
1976 rows.push(parse_csv_line(&h));
1977 }
1978 for l in dedent(&body_lines, body_indent) {
1979 if l.trim().is_empty() {
1980 continue;
1981 }
1982 rows.push(parse_csv_line(&l));
1983 }
1984 if rows.is_empty() {
1985 self.error("csv-table has no rows", Some(lnum));
1986 return None;
1987 }
1988 let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
1989 let mut head = Vec::new();
1990 let mut body = Vec::new();
1991 for (ri, row) in rows.into_iter().enumerate() {
1992 let cells: Vec<TableCell> = row
1993 .into_iter()
1994 .map(|c| {
1995 let (nodes, errs) = parse_inline(c.trim());
1996 for e in errs {
1997 self.messages.push(SystemMessage {
1998 level: e.level,
1999 text: e.message.clone(),
2000 line: Some(lnum),
2001 id: None,
2002 });
2003 self.msg_blocks.push(Block::SystemMessage(SystemMessage {
2004 level: e.level,
2005 text: e.message,
2006 line: Some(lnum),
2007 id: None,
2008 }));
2009 }
2010 TableCell {
2011 blocks: vec![Block::Paragraph(nodes)],
2012 colspan: 1,
2013 }
2014 })
2015 .collect();
2016 let mut cells = cells;
2018 while cells.len() < ncols {
2019 cells.push(TableCell {
2020 blocks: vec![],
2021 colspan: 1,
2022 });
2023 }
2024 if ri < header_rows {
2025 head.push(cells);
2026 } else {
2027 body.push(cells);
2028 }
2029 }
2030 let caption = if args.trim().is_empty() {
2031 vec![]
2032 } else {
2033 self.inline(args.trim(), lnum)
2034 };
2035 Some(Block::Table(Table {
2036 head,
2037 body,
2038 widths: vec![0; ncols],
2039 caption,
2040 }))
2041 }
2042}
2043
2044fn is_nonalphanum7bit(c: char) -> bool {
2049 matches!(c, '!'..='/' | ':'..='@' | '['..='`' | '{'..='~')
2050}
2051
2052fn is_adornment(s: &str) -> bool {
2053 let t = s.trim();
2054 if t.len() < 2 {
2055 return false;
2056 }
2057 let mut chars = t.chars();
2058 let first = chars.next().unwrap();
2059 if !is_nonalphanum7bit(first) {
2060 return false;
2061 }
2062 chars.all(|c| c == first)
2063}
2064
2065fn adorn_char(s: &str) -> char {
2066 s.trim().chars().next().unwrap_or('=')
2067}
2068
2069fn is_transition(s: &str) -> bool {
2070 let t = s.trim();
2071 t.len() >= 4 && is_adornment(t)
2072}
2073
2074fn is_list_marker(s: &str) -> bool {
2075 bullet_marker(s).is_some() || enum_marker(s).is_some()
2076}
2077fn is_block_start(s: &str) -> bool {
2079 (s.starts_with("..") && (s.len() == 2 || s[2..].starts_with([' ', '\t'])))
2080 || (s.starts_with('|') && (s.len() == 1 || s[1..].starts_with(' ')))
2081 || s.starts_with(">>> ")
2082 || bullet_marker(s).is_some()
2083 || enum_marker(s).is_some()
2084 || field_start(s).is_some()
2085 || option_start(s).is_some()
2086 || is_grid_border(s)
2087 || is_transition(s)
2088 || is_simple_header(s)
2089}
2090
2091fn bullet_marker(s: &str) -> Option<char> {
2092 let mut chars = s.chars();
2093 let m = chars.next()?;
2094 if !"-*+".contains(m) {
2095 return None;
2096 }
2097 match chars.next() {
2098 Some(c) if c == ' ' || c == '\t' => Some(m),
2099 None => Some(m),
2100 _ => None,
2101 }
2102}
2103
2104fn enum_marker(s: &str) -> Option<(EnumFormat, u32)> {
2106 let token: String = s.chars().take_while(|c| !c.is_whitespace()).collect();
2107 if token.is_empty() {
2108 return None;
2109 }
2110 if token.starts_with('(') && token.ends_with(')') && token.len() > 2 {
2112 return enum_value(&token[1..token.len() - 1]);
2113 }
2114 let core = token.strip_suffix(['.', ')'])?;
2116 if core.is_empty() {
2117 return None;
2118 }
2119 enum_value(core)
2120}
2121
2122fn enum_value(core: &str) -> Option<(EnumFormat, u32)> {
2123 if core == "#" {
2124 return Some((EnumFormat::Auto, 1));
2125 }
2126 if core.chars().all(|c| c.is_ascii_digit()) {
2127 return core.parse::<u32>().ok().map(|v| (EnumFormat::Arabic, v));
2128 }
2129 if core.len() == 1 {
2130 let c = core.chars().next().unwrap();
2131 if c == 'i' {
2133 return Some((EnumFormat::LowerRoman, 1));
2134 }
2135 if c == 'I' {
2136 return Some((EnumFormat::UpperRoman, 1));
2137 }
2138 if c.is_ascii_lowercase() {
2139 return Some((EnumFormat::LowerAlpha, (c as u32) - ('a' as u32) + 1));
2140 }
2141 if c.is_ascii_uppercase() {
2142 return Some((EnumFormat::UpperAlpha, (c as u32) - ('A' as u32) + 1));
2143 }
2144 }
2145 if let Some(v) = roman_value(&core.to_uppercase()) {
2146 let fmt = if core.chars().next().unwrap().is_lowercase() {
2147 EnumFormat::LowerRoman
2148 } else {
2149 EnumFormat::UpperRoman
2150 };
2151 return Some((fmt, v));
2152 }
2153 None
2154}
2155
2156fn enum_core(token: &str) -> &str {
2158 let t = token.strip_prefix('(').unwrap_or(token);
2159 let t = t.strip_suffix(')').unwrap_or(t);
2160 t.strip_suffix('.').unwrap_or(t)
2161}
2162
2163fn enum_value_in_format(core: &str, format: EnumFormat) -> Option<u32> {
2166 match format {
2167 EnumFormat::Arabic => {
2168 if core.chars().all(|c| c.is_ascii_digit()) {
2169 core.parse::<u32>().ok()
2170 } else {
2171 None
2172 }
2173 }
2174 EnumFormat::LowerAlpha => {
2175 if core.len() == 1 {
2176 let c = core.chars().next().unwrap();
2177 if c.is_ascii_lowercase() && c != 'i' {
2178 Some((c as u32) - ('a' as u32) + 1)
2179 } else {
2180 None
2181 }
2182 } else {
2183 None
2184 }
2185 }
2186 EnumFormat::UpperAlpha => {
2187 if core.len() == 1 {
2188 let c = core.chars().next().unwrap();
2189 if c.is_ascii_uppercase() && c != 'I' {
2190 Some((c as u32) - ('A' as u32) + 1)
2191 } else {
2192 None
2193 }
2194 } else {
2195 None
2196 }
2197 }
2198 EnumFormat::LowerRoman | EnumFormat::UpperRoman => roman_value(&core.to_uppercase()),
2199 EnumFormat::Auto => None,
2200 }
2201}
2202
2203fn roman_value(s: &str) -> Option<u32> {
2204 if s.is_empty() || !s.chars().all(|c| "IVXLCDM".contains(c)) {
2205 return None;
2206 }
2207 let val = |c: char| match c {
2208 'I' => 1,
2209 'V' => 5,
2210 'X' => 10,
2211 'L' => 50,
2212 'C' => 100,
2213 'D' => 500,
2214 'M' => 1000,
2215 _ => 0,
2216 };
2217 let chars: Vec<char> = s.chars().collect();
2218 let mut total = 0;
2219 let mut prev = 0;
2220 for &c in chars.iter().rev() {
2221 let v = val(c);
2222 if v < prev {
2223 total -= v;
2224 } else {
2225 total += v;
2226 }
2227 prev = v;
2228 }
2229 if total == 0 || total > 3999 {
2231 return None;
2232 }
2233 if roman_canonical(total) == s {
2234 Some(total)
2235 } else {
2236 None
2237 }
2238}
2239
2240fn roman_canonical(mut v: u32) -> String {
2241 let table = [
2242 (1000, "M"),
2243 (900, "CM"),
2244 (500, "D"),
2245 (400, "CD"),
2246 (100, "C"),
2247 (90, "XC"),
2248 (50, "L"),
2249 (40, "XL"),
2250 (10, "X"),
2251 (9, "IX"),
2252 (5, "V"),
2253 (4, "IV"),
2254 (1, "I"),
2255 ];
2256 let mut out = String::new();
2257 for (n, sym) in table {
2258 while v >= n {
2259 out.push_str(sym);
2260 v -= n;
2261 }
2262 }
2263 out
2264}
2265
2266fn field_start(s: &str) -> Option<(String, String)> {
2270 if !s.starts_with(':') || s[1..].starts_with([':', ' ']) {
2271 return None;
2272 }
2273 let bytes = s.as_bytes();
2275 let mut i = 1;
2276 while i < bytes.len() {
2277 if bytes[i] == b':' {
2278 let after = &s[i + 1..];
2279 if after.is_empty() || after.starts_with([' ', '\t']) {
2280 let name = s[1..i].trim();
2281 if name.is_empty() {
2282 return None;
2283 }
2284 return Some((name.to_string(), after.to_string()));
2285 }
2286 }
2287 i += 1;
2288 }
2289 None
2290}
2291fn option_start(s: &str) -> Option<(Vec<OptName>, String)> {
2295 let mut rest = s;
2296 let mut opts = Vec::new();
2297 loop {
2298 let (opt, tail) = option_one(rest)?;
2299 opts.push(opt);
2300 if let Some(comma) = tail.strip_prefix(", ") {
2302 rest = comma;
2303 continue;
2304 }
2305 if tail.is_empty() {
2306 return Some((opts, String::new()));
2307 }
2308 let spaces = tail.chars().take_while(|c| *c == ' ' || *c == '\t').count();
2310 if spaces >= 2 || (spaces == 1 && tail[1..].trim_start().is_empty()) {
2311 return Some((opts, tail[spaces..].to_string()));
2312 }
2313 if tail.trim().is_empty() {
2315 return Some((opts, String::new()));
2316 }
2317 return None;
2318 }
2319}
2320
2321fn option_one(s: &str) -> Option<(OptName, &str)> {
2323 let (prefix_len, long) = if s.starts_with("--") {
2324 (2, true)
2325 } else if s.starts_with('/') {
2326 (1, true)
2327 } else if s.starts_with('-') || s.starts_with('+') {
2328 (1, false)
2329 } else {
2330 return None;
2331 };
2332 let after_prefix = &s[prefix_len..];
2333 let name_len: usize = if long {
2334 after_prefix
2335 .chars()
2336 .take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '-' || *c == '.')
2337 .map(|c| c.len_utf8())
2338 .sum()
2339 } else {
2340 after_prefix
2341 .chars()
2342 .next()
2343 .filter(|c| c.is_alphanumeric())
2344 .map(|c| c.len_utf8())
2345 .unwrap_or(0)
2346 };
2347 if name_len == 0 {
2348 return None;
2349 }
2350 let name = format!("{}{}", &s[..prefix_len], &after_prefix[..name_len]);
2351 let mut tail = &s[prefix_len + name_len..];
2352 let mut arg = None;
2354 let mut sep = None;
2355 if let Some(eq) = tail.strip_prefix('=') {
2356 let n: usize = eq
2357 .chars()
2358 .take_while(|c| !c.is_whitespace() && *c != ',')
2359 .map(|c| c.len_utf8())
2360 .sum();
2361 if n == 0 {
2362 return None;
2363 }
2364 arg = Some(eq[..n].to_string());
2365 sep = Some('=');
2366 tail = &eq[n..];
2367 } else if let Some(sp) = tail.strip_prefix(' ') {
2368 let n: usize = sp
2371 .chars()
2372 .take_while(|c| !c.is_whitespace() && *c != ',')
2373 .map(|c| c.len_utf8())
2374 .sum();
2375 let after_arg = &sp[n..];
2376 if n > 0 && (after_arg.is_empty() || after_arg.starts_with([' ', '\t'])) {
2377 arg = Some(sp[..n].to_string());
2378 sep = Some(' ');
2379 tail = after_arg;
2380 }
2381 }
2382 Some((OptName { name, arg, sep }, tail))
2383}
2384
2385fn is_grid_border(s: &str) -> bool {
2388 let t = s.trim();
2389 t.len() >= 2 && t.starts_with('+') && t.chars().all(|c| "+-=".contains(c))
2390}
2391
2392fn build_grid_table(
2393 raw: Vec<String>,
2394 messages: &mut Vec<SystemMessage>,
2395 pending: &mut Vec<Block>,
2396) -> Block {
2397 if raw.len() < 3 {
2398 let text = "malformed grid table (fewer than 3 lines)".to_string();
2399 messages.push(SystemMessage {
2400 level: 3,
2401 text: text.clone(),
2402 line: None,
2403 id: None,
2404 });
2405 pending.push(Block::SystemMessage(SystemMessage {
2406 level: 3,
2407 text,
2408 line: None,
2409 id: None,
2410 }));
2411 return Block::Paragraph(vec![Inline::Text(raw.join("\n"))]);
2412 }
2413 let first = &raw[0];
2415 let bounds: Vec<usize> = first
2416 .chars()
2417 .enumerate()
2418 .filter(|(_, c)| *c == '+')
2419 .map(|(i, _)| i)
2420 .collect();
2421 if bounds.len() < 2 {
2422 return Block::Paragraph(vec![Inline::Text(raw.join("\n"))]);
2423 }
2424 let ncols = bounds.len() - 1;
2425 let mut head: Vec<Vec<TableCell>> = Vec::new();
2426 let mut body: Vec<Vec<TableCell>> = Vec::new();
2427 let mut pending: Vec<Vec<TableCell>> = Vec::new();
2428 let mut current: Vec<Vec<String>> = vec![vec![String::new()]; ncols];
2429 let mut dirty = false;
2430 let mut head_sep_seen = false;
2431 for line in raw.iter().skip(1) {
2432 if is_grid_border(line) {
2433 if dirty {
2434 pending.push(mkrow(¤t, messages));
2435 current = vec![vec![String::new()]; ncols];
2436 dirty = false;
2437 }
2438 if line.contains('=') && !head_sep_seen {
2440 head_sep_seen = true;
2441 head.append(&mut pending);
2442 }
2443 continue;
2444 }
2445 let chars: Vec<char> = line.chars().collect();
2447 for ci in 0..ncols {
2448 let (a, b) = (bounds[ci], bounds[ci + 1]);
2449 let slice: String = chars
2450 .get(a + 1..b.min(chars.len()))
2451 .unwrap_or(&[])
2452 .iter()
2453 .collect();
2454 current[ci].push(slice.trim_end().to_string());
2455 }
2456 dirty = true;
2457 }
2458 if dirty {
2460 pending.push(mkrow(¤t, messages));
2461 }
2462 body.append(&mut pending);
2463 let widths: Vec<u32> = bounds
2464 .windows(2)
2465 .map(|w| (w[1] - w[0]).saturating_sub(1) as u32)
2466 .collect();
2467 Block::Table(Table {
2468 head,
2469 body,
2470 widths,
2471 caption: Vec::new(),
2472 })
2473}
2474
2475fn mkrow(current: &[Vec<String>], messages: &mut Vec<SystemMessage>) -> Vec<TableCell> {
2476 current
2477 .iter()
2478 .map(|cell_lines| {
2479 let text = cell_lines.join("\n");
2480 if text.trim().is_empty() {
2481 return TableCell {
2482 blocks: vec![],
2483 colspan: 1,
2484 };
2485 }
2486 let (blocks, mut sub_messages) = Parser::new(text.trim(), None).parse_cell();
2487 messages.append(&mut sub_messages);
2488 TableCell { blocks, colspan: 1 }
2489 })
2490 .collect()
2491}
2492
2493fn is_simple_header(s: &str) -> bool {
2494 let t = s.trim();
2495 if t.len() < 3 || !t.chars().all(|c| c == '=' || c == ' ') {
2496 return false;
2497 }
2498 t.split(' ').filter(|w| !w.is_empty()).count() >= 2
2500 && t.split(' ')
2501 .filter(|w| !w.is_empty())
2502 .all(|w| w.chars().all(|c| c == '='))
2503}
2504
2505fn simple_columns(header: &str) -> Vec<(usize, usize)> {
2507 let bytes = header.as_bytes();
2508 let mut cols = Vec::new();
2509 let mut i = 0;
2510 while i < bytes.len() {
2511 if bytes[i] == b'=' {
2512 let start = i;
2513 while i < bytes.len() && bytes[i] == b'=' {
2514 i += 1;
2515 }
2516 cols.push((start, i));
2517 } else {
2518 i += 1;
2519 }
2520 }
2521 cols
2522}
2523fn simple_row_fits(line: &str, cols: &[(usize, usize)]) -> bool {
2525 let last_end = cols.last().map(|(_, e)| *e).unwrap_or(0);
2526 line.chars().skip(last_end).all(|c| c.is_whitespace())
2527}
2528
2529fn slice_simple_row(line: &str, cols: &[(usize, usize)]) -> Vec<String> {
2530 cols.iter()
2531 .map(|&(a, b)| {
2532 line.get(a..b.min(line.len().max(a)))
2533 .unwrap_or("")
2534 .trim()
2535 .to_string()
2536 })
2537 .collect()
2538}
2539
2540fn build_simple_table(
2541 ncols: usize,
2542 head_rows: Vec<Vec<String>>,
2543 body_rows: Vec<Vec<String>>,
2544 messages: &mut Vec<SystemMessage>,
2545) -> Block {
2546 let mk = |rows: Vec<Vec<String>>, messages: &mut Vec<SystemMessage>| -> Vec<Vec<TableCell>> {
2547 rows.into_iter()
2548 .map(|r| {
2549 let mut cells: Vec<TableCell> = r
2550 .into_iter()
2551 .map(|c| {
2552 if c.trim().is_empty() {
2553 return TableCell {
2554 blocks: vec![],
2555 colspan: 1,
2556 };
2557 }
2558 let (blocks, mut sub) = Parser::new(&c, None).parse_cell();
2559 messages.append(&mut sub);
2560 TableCell { blocks, colspan: 1 }
2561 })
2562 .collect();
2563 while cells.len() < ncols {
2564 cells.push(TableCell {
2565 blocks: vec![],
2566 colspan: 1,
2567 });
2568 }
2569 cells.truncate(ncols);
2570 cells
2571 })
2572 .collect()
2573 };
2574 Block::Table(Table {
2575 head: mk(head_rows, messages),
2576 body: mk(body_rows, messages),
2577 widths: vec![0; ncols],
2578 caption: Vec::new(),
2579 })
2580}
2581
2582fn leading_spaces(s: &str) -> usize {
2586 s.len() - s.trim_start_matches(' ').len()
2587}
2588fn dedent(lines: &[Line], indent: usize) -> Vec<String> {
2589 lines
2590 .iter()
2591 .map(|l| {
2592 if l.text.trim().is_empty() {
2593 String::new()
2594 } else {
2595 l.text
2596 .get(indent..)
2597 .unwrap_or(l.text.trim_start())
2598 .to_string()
2599 }
2600 })
2601 .collect()
2602}
2603fn strip_literal_marker(ptext: &mut Vec<String>) {
2606 let Some(last) = ptext.last() else { return };
2607 if last.trim() == "::" {
2608 ptext.pop();
2609 return;
2610 }
2611 let t = last.trim_end();
2612 if t.ends_with("::") {
2613 let lead: String = last.chars().take_while(|c| c.is_whitespace()).collect();
2614 let stripped = t[..t.len() - 1].to_string();
2615 *ptext.last_mut().unwrap() = format!("{lead}{stripped}");
2616 }
2617}
2618
2619fn split_target(rest: &str) -> Option<(String, String)> {
2621 let rest = rest.trim_start();
2622 if let Some(q) = rest.strip_prefix('`') {
2623 let end = q.find('`')?;
2624 let name = q[..end].to_string();
2625 let tail = q[end + 1..].trim_start();
2626 let uri = tail.strip_prefix(':')?.trim().to_string();
2627 return Some((name, uri));
2628 }
2629 let end = rest.find(':')?;
2630 let name = rest[..end].trim().to_string();
2631 if name.is_empty() || name.contains(char::is_whitespace) && name.split_whitespace().count() > 6
2632 {
2633 return None;
2634 }
2635 let uri = rest[end + 1..].trim().to_string();
2636 Some((name, uri))
2637}
2638
2639fn split_directive(s: &str) -> Option<(String, String)> {
2641 let idx = s.find("::")?;
2642 let name = s[..idx].trim().to_lowercase();
2643 if name.is_empty() || name.contains(char::is_whitespace) {
2644 return None;
2645 }
2646 Some((name, s[idx + 2..].trim().to_string()))
2647}
2648
2649pub fn norm_name(s: &str) -> String {
2651 s.split_whitespace()
2652 .collect::<Vec<_>>()
2653 .join(" ")
2654 .to_lowercase()
2655}
2656
2657fn is_digits_str(s: &str) -> bool {
2658 !s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
2659}
2660
2661fn parse_unicode_spec(args: &str) -> String {
2663 let mut out = String::new();
2664 for token in args.split_whitespace() {
2665 if token.eq_ignore_ascii_case(":trim:")
2666 || token.eq_ignore_ascii_case(":ltrim:")
2667 || token.eq_ignore_ascii_case(":rtrim:")
2668 {
2669 continue;
2670 }
2671 let hex = token
2672 .strip_prefix("U+")
2673 .or_else(|| token.strip_prefix("u+"))
2674 .unwrap_or(token);
2675 if let Ok(cp) = u32::from_str_radix(hex.trim_end_matches(','), 16)
2676 && let Some(ch) = char::from_u32(cp)
2677 {
2678 out.push(ch);
2679 continue;
2680 }
2681 out.push_str(token);
2682 out.push(' ');
2683 }
2684 out
2685}
2686
2687fn parse_csv_line(line: &str) -> Vec<String> {
2688 let mut fields = Vec::new();
2689 let mut cur = String::new();
2690 let mut chars = line.chars().peekable();
2691 let mut in_quotes = false;
2692 while let Some(c) = chars.next() {
2693 if in_quotes {
2694 if c == '"' {
2695 if chars.peek() == Some(&'"') {
2696 cur.push('"');
2697 chars.next();
2698 } else {
2699 in_quotes = false;
2700 }
2701 } else {
2702 cur.push(c);
2703 }
2704 } else if c == '"' && cur.trim().is_empty() {
2705 cur.clear();
2706 in_quotes = true;
2707 } else if c == ',' {
2708 fields.push(std::mem::take(&mut cur));
2709 } else {
2710 cur.push(c);
2711 }
2712 }
2713 fields.push(cur);
2714 fields
2715}
2716
2717fn today_ymd() -> (i32, u32, u32) {
2720 use std::time::{SystemTime, UNIX_EPOCH};
2721 let days = SystemTime::now()
2722 .duration_since(UNIX_EPOCH)
2723 .map(|d| (d.as_secs() / 86400) as i64)
2724 .unwrap_or(0);
2725 let z = days + 719468;
2727 let era = z.div_euclid(146097);
2728 let doe = (z - era * 146097) as u64;
2729 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2730 let y = yoe as i64 + era * 400;
2731 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2732 let mp = (5 * doy + 2) / 153;
2733 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
2734 let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
2735 ((if m <= 2 { y + 1 } else { y }) as i32, m, d)
2736}
2737
2738fn format_date(fmt: &str) -> String {
2739 let (y, m, d) = today_ymd();
2740 const MONTHS: [&str; 12] = [
2741 "January",
2742 "February",
2743 "March",
2744 "April",
2745 "May",
2746 "June",
2747 "July",
2748 "August",
2749 "September",
2750 "October",
2751 "November",
2752 "December",
2753 ];
2754 let fmt = if fmt.is_empty() { "%Y-%m-%d" } else { fmt };
2755 let mut out = String::new();
2756 let mut chars = fmt.chars();
2757 while let Some(c) = chars.next() {
2758 if c != '%' {
2759 out.push(c);
2760 continue;
2761 }
2762 match chars.next() {
2763 Some('Y') => out.push_str(&format!("{y:04}")),
2764 Some('y') => out.push_str(&format!("{:02}", y % 100)),
2765 Some('m') => out.push_str(&format!("{m:02}")),
2766 Some('d') => out.push_str(&format!("{d:02}")),
2767 Some('B') => out.push_str(MONTHS[(m - 1) as usize]),
2768 Some('b') => out.push_str(&MONTHS[(m - 1) as usize][..3]),
2769 Some('%') => out.push('%'),
2770 Some(other) => {
2771 out.push('%');
2772 out.push(other);
2773 }
2774 None => out.push('%'),
2775 }
2776 }
2777 out
2778}
2779fn hoist_decoration(doc: &mut Document) {
2785 fn walk(blocks: &mut Vec<Block>, header: &mut Vec<Block>, footer: &mut Vec<Block>) {
2786 let mut rest = Vec::with_capacity(blocks.len());
2787 for b in blocks.drain(..) {
2788 match b {
2789 Block::Topic(mut t) if t.classes.iter().any(|c| c == "header") => {
2790 header.append(&mut t.children);
2791 }
2792 Block::Topic(mut t) if t.classes.iter().any(|c| c == "footer") => {
2793 footer.append(&mut t.children);
2794 }
2795 Block::Section(mut s) => {
2796 walk(&mut s.children, header, footer);
2797 rest.push(Block::Section(s));
2798 }
2799 Block::Container(mut c) => {
2800 walk(&mut c.children, header, footer);
2801 rest.push(Block::Container(c));
2802 }
2803 other => rest.push(other),
2804 }
2805 }
2806 *blocks = rest;
2807 }
2808 let mut header = Vec::new();
2809 let mut footer = Vec::new();
2810 walk(&mut doc.children, &mut header, &mut footer);
2811 doc.header = header;
2812 doc.footer = footer;
2813}
2814
2815fn promote_doc_title(doc: &mut Document) {
2820 let first_is_l1 = matches!(doc.children.first(), Some(Block::Section(s)) if s.level == 1);
2821 if !first_is_l1 {
2822 return;
2823 }
2824 let s = match doc.children.remove(0) {
2825 Block::Section(s) => s,
2826 _ => unreachable!("leading block checked above"),
2827 };
2828 let title_had_children = !s.children.is_empty();
2829 let mut rest = s.children;
2830 renumber_sections(&mut rest, 1);
2833 rest.append(&mut doc.children);
2834 doc.children = rest;
2835 doc.title = Some(s.title);
2836 if !title_had_children {
2839 let sub_is_l1 = matches!(doc.children.first(), Some(Block::Section(x)) if x.level == 1);
2840 if sub_is_l1 {
2841 let s2 = match doc.children.remove(0) {
2842 Block::Section(s) => s,
2843 _ => unreachable!("leading block checked above"),
2844 };
2845 let mut rest2 = s2.children;
2846 renumber_sections(&mut rest2, 1);
2847 rest2.append(&mut doc.children);
2848 doc.children = rest2;
2849 doc.subtitle = Some(s2.title);
2850 }
2851 }
2852}
2853
2854fn renumber_sections(blocks: &mut [Block], by: u8) {
2856 for b in blocks {
2857 match b {
2858 Block::Section(s) => {
2859 s.level = s.level.saturating_sub(by).max(1);
2860 renumber_sections(&mut s.children, by);
2861 }
2862 Block::Topic(t) => renumber_sections(&mut t.children, by),
2863 Block::Container(c) => renumber_sections(&mut c.children, by),
2864 _ => {}
2865 }
2866 }
2867}
2868
2869fn apply_sectnum(doc: &mut Document, depth: Option<u32>) {
2871 let mut counters: Vec<u32> = Vec::new();
2872 fn walk(blocks: &mut [Block], level: u32, counters: &mut Vec<u32>, depth: Option<u32>) {
2873 for b in blocks {
2874 if let Block::Section(s) = b {
2875 while counters.len() < level as usize {
2876 counters.push(0);
2877 }
2878 counters.truncate(level as usize);
2879 if let Some(last) = counters.last_mut() {
2880 *last += 1;
2881 }
2882 if depth.map(|d| level <= d).unwrap_or(true) {
2883 let num = counters
2884 .iter()
2885 .map(|n| n.to_string())
2886 .collect::<Vec<_>>()
2887 .join(".");
2888 s.title
2890 .insert(0, Inline::Text(format!("{num}\u{a0}\u{a0}\u{a0}")));
2891 }
2892 walk(&mut s.children, level + 1, counters, depth);
2893 } else {
2894 match b {
2896 Block::Topic(t) => walk(&mut t.children, level, counters, depth),
2897 Block::Container(c) => walk(&mut c.children, level, counters, depth),
2898 _ => {}
2899 }
2900 }
2901 }
2902 }
2903 walk(&mut doc.children, 1, &mut counters, depth);
2904}
2905
2906impl Parser {
2909 fn parse_cell(mut self) -> (Vec<Block>, Vec<SystemMessage>) {
2911 self.adorns = Vec::new();
2912 let blocks = self.parse_container(0, 0);
2913 let messages = std::mem::take(&mut self.messages);
2914 (blocks, messages)
2915 }
2916}
2917
2918#[cfg(test)]
2919mod tests {
2920 use super::*;
2921
2922 fn para_text(doc: &Document) -> String {
2923 doc.astext()
2924 }
2925
2926 #[test]
2927 fn title_and_sections() {
2928 let doc = parse("Title\n=====\n\nSection\n-------\n\nBody text.\n");
2929 assert_eq!(
2931 doc.title.as_ref().map(|t| inline_text(t)),
2932 Some("Title".into())
2933 );
2934 assert!(matches!(doc.children[0], Block::Section(_)));
2935 assert!(para_text(&doc).contains("Body text."));
2936 }
2937
2938 #[test]
2939 fn bullet_list() {
2940 let doc = parse("- a\n- b\n\nAfter.\n");
2941 assert!(matches!(doc.children[0], Block::BulletList(_)));
2942 }
2943
2944 #[test]
2945 fn literal_block() {
2946 let doc = parse("Intro::\n\n code here\n");
2947 assert!(matches!(doc.children[1], Block::LiteralBlock(_)));
2948 }
2949
2950 #[test]
2951 fn grid_table() {
2952 let doc = parse("+---+---+\n| a | b |\n+===+===+\n| c | d |\n+---+---+\n");
2953 assert!(matches!(doc.children[0], Block::Table(_)));
2954 }
2955
2956 #[test]
2957 fn unknown_directive_errors() {
2958 let doc = parse(".. frobnicate:: x\n");
2959 assert!(!doc.messages.is_empty());
2960 }
2961
2962 #[test]
2963 fn field_body_dedents_to_paragraphs() {
2964 let doc = parse(":Multi: first line\n\n continuation line\n");
2965 let Block::FieldList(fields) = &doc.children[0] else {
2966 panic!("expected field list: {:?}", doc.children);
2967 };
2968 assert!(
2969 fields[0]
2970 .body
2971 .iter()
2972 .all(|b| matches!(b, Block::Paragraph(_)))
2973 );
2974 assert_eq!(fields[0].body.len(), 2);
2975 }
2976
2977 #[test]
2978 fn line_block_nests_extra_indent() {
2979 let doc = parse("| one\n| nested\n");
2980 let Block::LineBlock(rows) = &doc.children[0] else {
2981 panic!("expected line block: {:?}", doc.children);
2982 };
2983 assert!(matches!(rows[0], LineRow::Line(_)));
2984 assert!(matches!(rows[1], LineRow::Nested(_)));
2985 }
2986
2987 #[test]
2988 fn short_underline_stays_one_paragraph() {
2989 let doc = parse("Bad\n--\n");
2990 let paras: Vec<_> = doc
2991 .children
2992 .iter()
2993 .filter(|b| matches!(b, Block::Paragraph(_)))
2994 .collect();
2995 assert_eq!(paras.len(), 1);
2996 assert!(doc.messages.iter().any(|m| m.level == 1));
2997 }
2998}