1use std::borrow::Cow;
25use std::collections::{BTreeMap, BTreeSet};
26
27use crate::doctree::{kinds, AttrValue, Doctree, Node};
28use crate::env::std_domain::PropagatedIds;
29use crate::env::toctree::VIRTUAL_DOC_NAMES;
30use crate::env::BuildEnvironment;
31
32pub type DoctreeLoader<'a> = dyn Fn(&str) -> Option<Cow<'a, Doctree>> + 'a;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct NumberingWarning {
52 pub docname: String,
54 pub toctree_index: usize,
57 pub message: String,
59 pub category: Option<String>,
62}
63
64#[derive(Debug, Clone, Default, PartialEq, Eq)]
66pub struct SectionNumbering {
67 pub changed: Vec<String>,
70 pub warnings: Vec<NumberingWarning>,
71}
72
73pub fn assign_section_numbers<'d>(
85 env: &mut BuildEnvironment,
86 load_doctree: &DoctreeLoader<'d>,
87) -> SectionNumbering {
88 let old = std::mem::take(&mut env.toc_secnumbers);
89 let mut walker = SecnumWalker {
90 env,
91 load_doctree,
92 old,
93 assigned: BTreeSet::new(),
94 numstack: vec![0],
95 out: SectionNumbering::default(),
96 };
97
98 let containers: Vec<String> = walker.env.numbered_toctrees.iter().cloned().collect();
104 for docname in containers {
105 walker.assigned.insert(docname.clone());
106 let Some(doctree) = (walker.load_doctree)(&docname) else {
107 continue;
108 };
109 for (index, toctree) in toctree_nodes(&doctree.root).into_iter().enumerate() {
110 let depth = numbered_depth(toctree);
111 if depth != 0 {
112 walker.numstack = vec![0];
114 walker.walk_toctree(&docname, index, toctree, depth);
115 }
116 }
117 }
118
119 walker.out
120}
121
122struct SecnumWalker<'e, 'l, 'd> {
123 env: &'e mut BuildEnvironment,
124 load_doctree: &'l DoctreeLoader<'d>,
125 old: BTreeMap<String, BTreeMap<String, Vec<u32>>>,
127 assigned: BTreeSet<String>,
128 numstack: Vec<u32>,
129 out: SectionNumbering,
130}
131
132impl SecnumWalker<'_, '_, '_> {
133 fn walk_toctree(
138 &mut self,
139 location_docname: &str,
140 location_index: usize,
141 toctree: &Node,
142 depth: i64,
143 ) {
144 if depth == 0 {
145 return;
146 }
147 for reference in toctree_includefiles(toctree).to_vec() {
152 if self.assigned.contains(&reference) {
153 self.out.warnings.push(NumberingWarning {
154 docname: location_docname.to_string(),
155 toctree_index: location_index,
156 message: format!(
157 "{reference} is already assigned section numbers \
158 (nested numbered toctree?)"
159 ),
160 category: Some("toc.secnum".to_string()),
161 });
162 continue;
163 }
164 if !self.env.tocs.contains_key(&reference) {
165 continue;
166 }
167 self.assigned.insert(reference.clone());
168
169 let mut toc = self
173 .env
174 .tocs
175 .remove(&reference)
176 .expect("presence checked above");
177 let mut titlenode = self.env.titles.remove(&reference);
178 let mut secnums: BTreeMap<String, Vec<u32>> = BTreeMap::new();
179 let mut toctree_index = 0;
180 self.walk_toc(
181 &reference,
182 &mut toctree_index,
183 &mut toc.children,
184 &mut secnums,
185 depth,
186 titlenode.as_mut(),
187 );
188 self.env.tocs.insert(reference.clone(), toc);
189 if let Some(title) = titlenode {
190 self.env.titles.insert(reference.clone(), title);
191 }
192
193 if self.old.get(&reference) != Some(&secnums) {
194 self.out.changed.push(reference.clone());
195 }
196 self.env.toc_secnumbers.insert(reference, secnums);
200 }
201 }
202
203 fn walk_toc(
210 &mut self,
211 docname: &str,
212 toctree_index: &mut usize,
213 children: &mut [Node],
214 secnums: &mut BTreeMap<String, Vec<u32>>,
215 depth: i64,
216 mut titlenode: Option<&mut Node>,
217 ) {
218 for subnode in children.iter_mut() {
219 match subnode.kind {
220 kinds::BULLET_LIST => {
221 self.numstack.push(0);
222 self.walk_toc(
223 docname,
224 toctree_index,
225 &mut subnode.children,
226 secnums,
227 depth - 1,
228 titlenode.take(),
229 );
230 self.numstack.pop();
231 }
232 kinds::LIST_ITEM | kinds::ONLY => {
236 self.walk_toc(
237 docname,
238 toctree_index,
239 &mut subnode.children,
240 secnums,
241 depth,
242 titlenode.take(),
243 );
244 }
245 kinds::COMPACT_PARAGRAPH => {
246 if subnode.get("skip_section_number").is_some() {
248 continue;
249 }
250 if let Some(last) = self.numstack.last_mut() {
251 *last += 1;
252 }
253 let number = if depth > 0 {
254 Some(self.numstack.clone())
255 } else {
256 None
257 };
258 let Some(reference) = subnode.children.first_mut() else {
259 continue;
260 };
261 let anchorname = match reference.get("anchorname") {
262 Some(AttrValue::Str(anchor)) => anchor.clone(),
263 _ => String::new(),
264 };
265 secnums.insert(anchorname, number.clone().unwrap_or_default());
266 reference.set("secnumber", secnumber_attr(number.as_deref()));
267 if let Some(title) = titlenode.take() {
268 title.set("secnumber", secnumber_attr(number.as_deref()));
269 }
270 }
271 kinds::TOCTREE => {
272 let index = *toctree_index;
273 *toctree_index += 1;
274 self.walk_toctree(docname, index, subnode, depth);
275 }
276 _ => {}
277 }
278 }
279 }
280}
281
282fn secnumber_attr(number: Option<&[u32]>) -> AttrValue {
288 match number {
289 Some(number) => AttrValue::List(number.iter().map(u32::to_string).collect()),
290 None => AttrValue::Str("True".to_string()),
291 }
292}
293
294pub fn assign_figure_numbers<'d>(
306 env: &mut BuildEnvironment,
307 numfig: bool,
308 numfig_secnum_depth: u32,
309 load_doctree: &DoctreeLoader<'d>,
310) -> Vec<String> {
311 let old = std::mem::take(&mut env.toc_fignumbers);
312 if !numfig {
313 return Vec::new();
314 }
315
316 let root_doc = env.root_doc.clone();
317 let mut walker = FignumWalker {
318 env,
319 load_doctree,
320 secnum_depth: numfig_secnum_depth as usize,
321 assigned: BTreeSet::new(),
322 counters: BTreeMap::new(),
323 };
324 walker.walk_doc(&root_doc, &[]);
325
326 env.toc_fignumbers
327 .iter()
328 .filter(|(docname, fignums)| old.get(*docname) != Some(*fignums))
329 .map(|(docname, _)| docname.clone())
330 .collect()
331}
332
333struct FignumWalker<'e, 'l, 'd> {
334 env: &'e mut BuildEnvironment,
335 load_doctree: &'l DoctreeLoader<'d>,
336 secnum_depth: usize,
337 assigned: BTreeSet<String>,
338 counters: BTreeMap<String, BTreeMap<Vec<u32>, u32>>,
340}
341
342impl FignumWalker<'_, '_, '_> {
343 fn walk_doc(&mut self, docname: &str, secnum: &[u32]) {
345 if !self.assigned.insert(docname.to_string()) {
346 return;
347 }
348 let Some(doctree) = (self.load_doctree)(docname) else {
351 return;
352 };
353 let propagated = PropagatedIds::of(&doctree);
358 self.walk_doctree(docname, &doctree.root.children, secnum, &propagated);
359 }
360
361 fn walk_doctree(
363 &mut self,
364 docname: &str,
365 children: &[Node],
366 secnum: &[u32],
367 propagated: &PropagatedIds,
368 ) {
369 for subnode in children {
370 if subnode.kind == kinds::TEXT {
373 continue;
374 }
375 if subnode.kind == kinds::SECTION {
376 let next = self.section_number(docname, subnode);
377 let inherited = if next.is_empty() { secnum } else { &next };
378 self.walk_doctree(docname, &subnode.children, inherited, propagated);
379 } else if subnode.kind == kinds::TOCTREE {
380 for subdocname in toctree_includefiles(subnode).to_vec() {
385 if VIRTUAL_DOC_NAMES.contains(&subdocname.as_str()) {
386 continue;
387 }
388 self.walk_doc(&subdocname, secnum);
389 }
390 } else {
391 if let Some(figtype) = figtype_of(subnode) {
392 if let Some(figure_id) = propagated.effective_ids(subnode).first() {
394 let figure_id = figure_id.clone();
395 self.register_fignumber(docname, secnum, figtype, &figure_id);
396 }
397 }
398 self.walk_doctree(docname, &subnode.children, secnum, propagated);
399 }
400 }
401 }
402
403 fn section_number(&self, docname: &str, section: &Node) -> Vec<u32> {
406 let anchorname = format!(
407 "#{}",
408 section.attrs.ids.first().map(String::as_str).unwrap_or("")
409 );
410 let Some(secnumbers) = self.env.toc_secnumbers.get(docname) else {
411 return Vec::new();
412 };
413 secnumbers
414 .get(&anchorname)
415 .or_else(|| secnumbers.get(""))
416 .cloned()
417 .unwrap_or_default()
418 }
419
420 fn register_fignumber(
423 &mut self,
424 docname: &str,
425 secnum: &[u32],
426 figtype: &str,
427 figure_id: &str,
428 ) {
429 let truncated = &secnum[..secnum.len().min(self.secnum_depth)];
430 let counter = self
431 .counters
432 .entry(figtype.to_string())
433 .or_default()
434 .entry(truncated.to_vec())
435 .or_insert(0);
436 *counter += 1;
437
438 let mut number = truncated.to_vec();
439 number.push(*counter);
440 self.env
441 .toc_fignumbers
442 .entry(docname.to_string())
443 .or_default()
444 .entry(figtype.to_string())
445 .or_default()
446 .insert(figure_id.to_string(), number);
447 }
448}
449
450fn figtype_of(node: &Node) -> Option<&'static str> {
462 if let Some(figtype) = math_enumerable_node_type(node) {
463 return Some(figtype);
464 }
465 let figtype = std_enumerable_node_type(node);
466 if clean_astext(std_numfig_title(node)?).is_empty() {
467 return None;
468 }
469 figtype
470}
471
472fn math_enumerable_node_type(node: &Node) -> Option<&'static str> {
478 (node.kind == "math_block").then_some("displaymath")
479}
480
481fn std_enumerable_node_type(node: &Node) -> Option<&'static str> {
484 match node.kind {
485 kinds::SECTION => Some("section"),
486 "figure" => Some("figure"),
487 kinds::TABLE => Some("table"),
488 "container" => Some("code-block"),
494 _ => None,
495 }
496}
497
498pub(crate) fn std_numfig_title(node: &Node) -> Option<&Node> {
503 if !matches!(node.kind, "figure" | "container" | kinds::TABLE) {
504 return None;
505 }
506 node.children
507 .iter()
508 .find(|child| child.kind == "caption" || child.kind == kinds::TITLE)
509}
510
511pub(crate) fn clean_astext(node: &Node) -> String {
515 fn walk(node: &Node, out: &mut String) {
516 if node.kind == "raw" {
517 return;
518 }
519 match &node.text {
520 Some(text) => out.push_str(text),
521 None => {
522 for child in &node.children {
523 walk(child, out);
524 }
525 }
526 }
527 }
528 let mut out = String::new();
529 walk(node, &mut out);
530 out
531}
532
533fn numbered_depth(toctree: &Node) -> i64 {
535 match toctree.get("numbered") {
536 Some(AttrValue::Int(depth)) => *depth,
537 _ => 0,
538 }
539}
540
541fn toctree_includefiles(toctree: &Node) -> &[String] {
544 match toctree.get("includefiles") {
545 Some(AttrValue::List(files)) => files,
546 _ => &[],
547 }
548}
549
550fn toctree_nodes(node: &Node) -> Vec<&Node> {
553 fn walk<'a>(node: &'a Node, out: &mut Vec<&'a Node>) {
554 if node.kind == kinds::TOCTREE {
555 out.push(node);
556 }
557 for child in &node.children {
558 walk(child, out);
559 }
560 }
561 let mut out = Vec::new();
562 walk(node, &mut out);
563 out
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use crate::doctree::Span;
570 use crate::env::toctree::{build_toc, document_title, note_toctree, toctree_copies};
571 use crate::rst::{parse_rst, ParseOptions};
572
573 fn read(sources: &[(&str, &str)]) -> (BuildEnvironment, BTreeMap<String, Doctree>) {
576 let found: BTreeSet<String> = sources.iter().map(|(name, _)| name.to_string()).collect();
577 let found = std::sync::Arc::new(found);
578
579 let mut env = BuildEnvironment {
580 root_doc: "index".to_string(),
581 ..Default::default()
582 };
583 let mut doctrees = BTreeMap::new();
584 for (docname, body) in sources {
585 let doctree = parse_rst(
586 body,
587 &ParseOptions {
588 source_path: format!("{docname}.rst"),
589 sphinx: true,
590 docname: (*docname).to_string(),
591 exclude_patterns: Vec::new(),
592 py: Default::default(),
593 srcdir: None,
594 found_docs: Some(std::sync::Arc::clone(&found)),
595 ..Default::default()
596 },
597 );
598 env.all_docs.insert((*docname).to_string(), 0);
599 env.titles
600 .insert((*docname).to_string(), document_title(&doctree));
601 let (toc, entries) = build_toc(&doctree, docname);
602 for toctree in toctree_copies(&toc) {
603 note_toctree(&mut env, docname, toctree);
604 }
605 env.tocs.insert((*docname).to_string(), toc);
606 env.toc_num_entries.insert((*docname).to_string(), entries);
607 doctrees.insert((*docname).to_string(), doctree);
608 }
609 (env, doctrees)
610 }
611
612 fn loader<'a>(
613 doctrees: &'a BTreeMap<String, Doctree>,
614 ) -> impl Fn(&str) -> Option<Cow<'a, Doctree>> + 'a {
615 move |docname: &str| doctrees.get(docname).map(Cow::Borrowed)
616 }
617
618 fn secnums(env: &BuildEnvironment, docname: &str) -> Vec<(String, Vec<u32>)> {
619 env.toc_secnumbers
620 .get(docname)
621 .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
622 .unwrap_or_default()
623 }
624
625 #[test]
626 fn numbered_toctree_numbers_every_reachable_document() {
627 let (mut env, doctrees) = read(&[
628 (
629 "index",
630 "Index\n=====\n\n.. toctree::\n :numbered:\n\n a\n b\n",
631 ),
632 ("a", "A\n=\n\nSub\n---\n\nText.\n"),
633 ("b", "B\n=\n\nLeaf.\n"),
634 ]);
635 let load = loader(&doctrees);
636 let out = assign_section_numbers(&mut env, &load);
637
638 assert_eq!(
639 secnums(&env, "a"),
640 vec![(String::new(), vec![1]), ("#sub".to_string(), vec![1, 1]),]
641 );
642 assert_eq!(secnums(&env, "b"), vec![(String::new(), vec![2])]);
643 assert!(!env.toc_secnumbers.contains_key("index"));
645 assert_eq!(out.changed, vec!["a".to_string(), "b".to_string()]);
646 assert!(out.warnings.is_empty());
647 }
648
649 #[test]
650 fn secnumbers_are_stamped_onto_the_toc_references_and_the_title() {
651 let (mut env, doctrees) = read(&[
652 (
653 "index",
654 "Index\n=====\n\n.. toctree::\n :numbered:\n\n a\n",
655 ),
656 ("a", "A\n=\n\nSub\n---\n\nText.\n"),
657 ]);
658 let load = loader(&doctrees);
659 assign_section_numbers(&mut env, &load);
660
661 let toc = env.tocs["a"].pformat();
662 assert!(
663 toc.contains(r#"anchorname="" internal="1" refuri="a" secnumber="1""#),
664 "{toc}"
665 );
666 assert!(
667 toc.contains(r##"anchorname="#sub" internal="1" refuri="a" secnumber="1 1""##),
668 "{toc}"
669 );
670 assert_eq!(
673 env.titles["a"].get("secnumber"),
674 Some(&AttrValue::List(vec!["1".to_string()]))
675 );
676 }
677
678 #[test]
679 fn entries_below_the_numbered_depth_get_no_number() {
680 let (mut env, doctrees) = read(&[
681 (
682 "index",
683 "Index\n=====\n\n.. toctree::\n :numbered: 1\n\n a\n",
684 ),
685 ("a", "A\n=\n\nSub\n---\n\nText.\n"),
686 ]);
687 let load = loader(&doctrees);
688 assign_section_numbers(&mut env, &load);
689
690 assert_eq!(
691 secnums(&env, "a"),
692 vec![(String::new(), vec![1]), ("#sub".to_string(), vec![])]
693 );
694 assert!(
696 env.tocs["a"]
697 .pformat()
698 .contains(r##"anchorname="#sub" internal="1" refuri="a" secnumber="True""##),
699 "{}",
700 env.tocs["a"].pformat()
701 );
702 }
703
704 #[test]
705 fn a_document_two_numbered_toctrees_claim_warns_once() {
706 let (mut env, doctrees) = read(&[
707 (
708 "index",
709 "Index\n=====\n\n.. toctree::\n :numbered:\n\n a\n\n.. toctree::\n :numbered:\n\n a\n",
710 ),
711 ("a", "A\n=\n\nText.\n"),
712 ]);
713 let load = loader(&doctrees);
714 let out = assign_section_numbers(&mut env, &load);
715
716 assert_eq!(
717 out.warnings,
718 vec![NumberingWarning {
719 docname: "index".to_string(),
720 toctree_index: 1,
721 message: "a is already assigned section numbers (nested numbered toctree?)"
722 .to_string(),
723 category: Some("toc.secnum".to_string()),
724 }]
725 );
726 assert_eq!(secnums(&env, "a"), vec![(String::new(), vec![1])]);
728 }
729
730 #[test]
731 fn a_document_that_is_not_numbered_keeps_no_secnumbers() {
732 let (mut env, doctrees) = read(&[
733 ("index", "Index\n=====\n\n.. toctree::\n\n a\n"),
734 ("a", "A\n=\n\nText.\n"),
735 ]);
736 let load = loader(&doctrees);
737 let out = assign_section_numbers(&mut env, &load);
738
739 assert!(env.toc_secnumbers.is_empty());
740 assert!(out.changed.is_empty());
741 assert!(!env.tocs["a"].pformat().contains("secnumber="));
742 }
743
744 #[test]
745 fn unchanged_numbers_are_not_reported_as_changed() {
746 let (mut env, doctrees) = read(&[
747 (
748 "index",
749 "Index\n=====\n\n.. toctree::\n :numbered:\n\n a\n",
750 ),
751 ("a", "A\n=\n\nText.\n"),
752 ]);
753 let load = loader(&doctrees);
754 assert_eq!(assign_section_numbers(&mut env, &load).changed, ["a"]);
755 assert!(assign_section_numbers(&mut env, &load).changed.is_empty());
757 }
758
759 #[test]
760 fn figure_numbers_are_scoped_by_the_secnum_depth() {
761 let sources = [
762 (
763 "index",
764 "Index\n=====\n\n.. toctree::\n :numbered:\n\n a\n b\n",
765 ),
766 (
767 "a",
768 "A\n=\n\n.. figure:: one.png\n :name: fig-one\n\n One.\n",
769 ),
770 (
771 "b",
772 "B\n=\n\n.. figure:: two.png\n :name: fig-two\n\n Two.\n",
773 ),
774 ];
775 let (mut env, doctrees) = read(&sources);
777 let load = loader(&doctrees);
778 assign_section_numbers(&mut env, &load);
779 assign_figure_numbers(&mut env, true, 1, &load);
780 assert_eq!(env.toc_fignumbers["a"]["figure"]["fig-one"], vec![1, 1]);
781 assert_eq!(env.toc_fignumbers["b"]["figure"]["fig-two"], vec![2, 1]);
782
783 let (mut env, doctrees) = read(&sources);
785 let load = loader(&doctrees);
786 assign_section_numbers(&mut env, &load);
787 assign_figure_numbers(&mut env, true, 0, &load);
788 assert_eq!(env.toc_fignumbers["a"]["figure"]["fig-one"], vec![1]);
789 assert_eq!(env.toc_fignumbers["b"]["figure"]["fig-two"], vec![2]);
790 }
791
792 #[test]
793 fn uncaptioned_enumerables_are_skipped() {
794 let (mut env, doctrees) = read(&[
795 ("index", "Index\n=====\n\n.. toctree::\n\n a\n"),
796 (
797 "a",
798 "A\n=\n\n.. figure:: none.png\n :name: fig-bare\n\n.. figure:: cap.png\n :name: fig-cap\n\n Caption.\n",
799 ),
800 ]);
801 let load = loader(&doctrees);
802 assign_figure_numbers(&mut env, true, 1, &load);
803
804 let figures = &env.toc_fignumbers["a"]["figure"];
805 assert!(!figures.contains_key("fig-bare"), "{figures:?}");
806 assert_eq!(figures["fig-cap"], vec![1]);
807 }
808
809 #[test]
810 fn numfig_off_clears_every_figure_number() {
811 let (mut env, doctrees) = read(&[
812 ("index", "Index\n=====\n\n.. toctree::\n\n a\n"),
813 (
814 "a",
815 "A\n=\n\n.. figure:: cap.png\n :name: fig-cap\n\n Caption.\n",
816 ),
817 ]);
818 let load = loader(&doctrees);
819 assign_figure_numbers(&mut env, true, 1, &load);
820 assert!(!env.toc_fignumbers.is_empty());
821
822 assert!(assign_figure_numbers(&mut env, false, 1, &load).is_empty());
825 assert!(env.toc_fignumbers.is_empty());
826 }
827
828 #[test]
829 fn a_document_reached_twice_is_numbered_once() {
830 let (mut env, doctrees) = read(&[
831 ("index", "Index\n=====\n\n.. toctree::\n\n a\n b\n"),
832 (
833 "a",
834 "A\n=\n\n.. figure:: cap.png\n :name: fig-a\n\n Caption.\n\n.. toctree::\n\n b\n",
835 ),
836 (
837 "b",
838 "B\n=\n\n.. figure:: cap.png\n :name: fig-b\n\n Caption.\n",
839 ),
840 ]);
841 let load = loader(&doctrees);
842 assign_figure_numbers(&mut env, true, 1, &load);
843
844 assert_eq!(env.toc_fignumbers["a"]["figure"]["fig-a"], vec![1]);
847 assert_eq!(env.toc_fignumbers["b"]["figure"]["fig-b"], vec![2]);
848 }
849
850 #[test]
851 fn tables_and_captioned_code_blocks_get_their_own_counters() {
852 let (mut env, doctrees) = read(&[
853 ("index", "Index\n=====\n\n.. toctree::\n\n a\n"),
854 (
855 "a",
856 "A\n=\n\n.. list-table:: The Table\n :name: tab-a\n\n * - x\n\n.. code-block:: python\n :name: code-a\n :caption: The Listing\n\n x = 1\n",
857 ),
858 ]);
859 let load = loader(&doctrees);
860 assign_figure_numbers(&mut env, true, 1, &load);
861
862 assert_eq!(env.toc_fignumbers["a"]["table"]["tab-a"], vec![1]);
863 assert_eq!(env.toc_fignumbers["a"]["code-block"]["code-a"], vec![1]);
864 }
865
866 #[test]
872 fn a_label_written_above_an_enumerable_node_numbers_it() {
873 let (mut env, doctrees) = read(&[
874 ("index", "Index\n=====\n\n.. toctree::\n\n a\n"),
875 (
876 "a",
877 "A\n=\n\n.. figure:: cap.png\n :name: fig-named\n\n Caption.\n\n\
878 .. _fig-labelled:\n\n.. figure:: cap.png\n\n Another caption.\n\n\
879 .. _tab-labelled:\n\n.. list-table:: The Table\n\n * - x\n\n\
880 .. _code-labelled:\n\n.. code-block:: python\n :caption: The Listing\n\n x = 1\n",
881 ),
882 ]);
883 let load = loader(&doctrees);
884 assign_figure_numbers(&mut env, true, 1, &load);
885
886 let figures = &env.toc_fignumbers["a"]["figure"];
887 assert_eq!(figures["fig-named"], vec![1]);
888 assert_eq!(
889 figures["fig-labelled"],
890 vec![2],
891 "a labelled figure still advances the counter: {figures:?}"
892 );
893 assert_eq!(env.toc_fignumbers["a"]["table"]["tab-labelled"], vec![1]);
894 assert_eq!(
895 env.toc_fignumbers["a"]["code-block"]["code-labelled"],
896 vec![1]
897 );
898 }
899
900 #[test]
905 fn labelled_display_math_is_numbered_as_displaymath() {
906 let (mut env, doctrees) = read(&[
907 ("index", "Index\n=====\n\n.. toctree::\n\n a\n"),
908 (
909 "a",
910 "A\n=\n\n.. math::\n :label: eq-one\n\n x = 1\n\n.. math::\n\n y = 2\n",
911 ),
912 ]);
913 let load = loader(&doctrees);
914 assign_figure_numbers(&mut env, true, 1, &load);
915
916 let equations = &env.toc_fignumbers["a"]["displaymath"];
917 assert_eq!(equations["equation-eq-one"], vec![1]);
918 assert_eq!(equations.len(), 1, "{equations:?}");
919 }
920
921 #[test]
922 fn a_missing_doctree_is_skipped_rather_than_panicking() {
923 let mut env = BuildEnvironment {
924 root_doc: "index".to_string(),
925 ..Default::default()
926 };
927 env.numbered_toctrees.insert("index".to_string());
928 let empty: BTreeMap<String, Doctree> = BTreeMap::new();
929 let load = loader(&empty);
930
931 assert!(assign_section_numbers(&mut env, &load).changed.is_empty());
932 assert!(assign_figure_numbers(&mut env, true, 1, &load).is_empty());
933 }
934
935 #[test]
936 fn secnumber_attribute_renders_the_way_docutils_renders_it() {
937 let mut node = Node::elem(kinds::REFERENCE, Span::ZERO);
938 node.set("secnumber", secnumber_attr(Some(&[1, 2, 3])));
939 assert_eq!(node.pformat(), "<reference secnumber=\"1 2 3\">\n");
940
941 let mut node = Node::elem(kinds::REFERENCE, Span::ZERO);
942 node.set("secnumber", secnumber_attr(None));
943 assert_eq!(node.pformat(), "<reference secnumber=\"True\">\n");
944 }
945}