Passed
Push — develop ( 501945...90faec )
by Christophe
02:24
created

pandoc_numbering.Numbered._compute_data()   C

Complexity

Conditions 7

Size

Total Lines 109
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 74
nop 1
dl 0
loc 109
rs 6.4509
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#!/usr/bin/env python
2
3
# pylint: disable=too-many-lines
4
5
"""Pandoc filter to number all kinds of things."""
6
7
import copy
8
import re
9
import unicodedata
10
from functools import partial
11
12
from panflute import (
13
    BlockQuote,
14
    BulletList,
15
    Citation,
16
    Cite,
17
    CodeBlock,
18
    Definition,
19
    DefinitionItem,
20
    DefinitionList,
21
    Div,
22
    Emph,
23
    Header,
24
    HorizontalRule,
25
    Image,
26
    LineBlock,
27
    LineBreak,
28
    LineItem,
29
    Link,
30
    ListItem,
31
    MetaBool,
32
    MetaInlines,
33
    MetaList,
34
    MetaMap,
35
    MetaString,
36
    Note,
37
    Para,
38
    Plain,
39
    RawBlock,
40
    RawInline,
41
    SoftBreak,
42
    Space,
43
    Span,
44
    Str,
45
    Strong,
46
    Table,
47
    TableCell,
48
    TableRow,
49
    convert_text,
50
    debug,
51
    run_filters,
52
    stringify,
53
)
54
55
56
# pylint: disable=bad-option-value,useless-object-inheritance
57
class Numbered:
58
    """
59
    Numbered elements.
60
    """
61
62
    # pylint: disable=too-many-instance-attributes
63
    __slots__ = [
64
        "_elem",
65
        "_doc",
66
        "_match",
67
        "_tag",
68
        "_entry",
69
        "_link",
70
        "_caption",
71
        "_title",
72
        "_description",
73
        "_category",
74
        "_basic_category",
75
        "_first_section_level",
76
        "_last_section_level",
77
        "_leading",
78
        "_number",
79
        "_global_number",
80
        "_section_number",
81
        "_local_number",
82
        "_section_alias",
83
        "_alias",
84
    ]
85
86
    @property
87
    def tag(self):
88
        """
89
        Get the tag property.
90
91
        Returns
92
        -------
93
            The tag property.
94
        """
95
        return self._tag
96
97
    @property
98
    def entry(self):
99
        """
100
        Get the entry property.
101
102
        Returns
103
        -------
104
            The entry property.
105
        """
106
        return self._entry
107
108
    @property
109
    def link(self):
110
        """
111
        Get the link property.
112
113
        Returns
114
        -------
115
            The link property.
116
        """
117
        return self._link
118
119
    @property
120
    def title(self):
121
        """
122
        Get the title property.
123
124
        Returns
125
        -------
126
            The title property.
127
        """
128
        return self._title
129
130
    @property
131
    def description(self):
132
        """
133
        Get the description property.
134
135
        Returns
136
        -------
137
            The description property.
138
        """
139
        return self._description
140
141
    @property
142
    def global_number(self):
143
        """
144
        Get the global_number property.
145
146
        Returns
147
        -------
148
            The global_number property.
149
        """
150
        return self._global_number
151
152
    @property
153
    def section_number(self):
154
        """
155
        Get the section_number property.
156
157
        Returns
158
        -------
159
            The section_number property.
160
        """
161
        return self._section_number
162
163
    @property
164
    def section_alias(self):
165
        """
166
        Get the section_alias property.
167
168
        Returns
169
        -------
170
            The section_alias property.
171
        """
172
        return self._section_alias
173
174
    @property
175
    def alias(self):
176
        """
177
        Get the alias property.
178
179
        Returns
180
        -------
181
            The alias property.
182
        """
183
        return self._alias
184
185
    @property
186
    def local_number(self):
187
        """
188
        Get the local_number property.
189
190
        Returns
191
        -------
192
            The local_number property.
193
        """
194
        return self._local_number
195
196
    @property
197
    def category(self):
198
        """
199
        Get the category property.
200
201
        Returns
202
        -------
203
            The category property.
204
        """
205
        return self._category
206
207
    @property
208
    def caption(self):
209
        """
210
        Get the caption property.
211
212
        Returns
213
        -------
214
            The caption property.
215
        """
216
        return self._caption
217
218
    number_regex = "#((?P<prefix>[a-zA-Z][\\w.-]*):)?(?P<name>[a-zA-Z][\\w:.-]*)?"
219
    _regex = "(?P<header>(?P<hidden>(-\\.)*)(\\+\\.)*)"
220
    header_regex = "^" + _regex + "$"
221
    marker_regex = "^" + _regex + number_regex + "$"
222
    double_sharp_regex = "^" + _regex + "#" + number_regex + "$"
223
224
    @staticmethod
225
    def _remove_accents(string):
226
        nfkd_form = unicodedata.normalize("NFKD", string)
227
        # pylint: disable=redundant-u-string-prefix
228
        return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
229
230
    @staticmethod
231
    def _identifier(string):
232
        # replace invalid characters by dash
233
        string = re.sub(
234
            "[^0-9a-zA-Z_-]+", "-", Numbered._remove_accents(string.lower())
235
        )
236
237
        # Remove leading digits
238
        return re.sub("^[^a-zA-Z]+", "", string)
239
240
    def __init__(self, elem, doc):
241
        """
242
        Initialise an instance.
243
244
        Arguments
245
        ---------
246
        elem
247
            An element.
248
        doc
249
            The document.
250
        """
251
        self._elem = elem
252
        self._doc = doc
253
        self._tag = None
254
        self._entry = Span(classes=["pandoc-numbering-entry"])
255
        self._link = Span(classes=["pandoc-numbering-link"])
256
        self._caption = None
257
        self._title = None
258
        self._description = None
259
        self._category = None
260
        self._basic_category = None
261
        self._first_section_level = None
262
        self._last_section_level = None
263
        self._leading = None
264
        self._number = None
265
        self._global_number = None
266
        self._section_number = None
267
        self._local_number = None
268
        self._section_alias = None
269
        self._alias = None
270
271
        if self._get_content() and isinstance(self._get_content()[-1], Str):
272
            self._match = re.match(Numbered.marker_regex, self._get_content()[-1].text)
273
            if self._match:
274
                self._replace_marker()
275
            elif re.match(Numbered.double_sharp_regex, self._get_content()[-1].text):
276
                self._replace_double_sharp()
277
278
    def _set_content(self, content):
279
        if isinstance(self._elem, Para):
280
            self._elem.content = content
281
        elif isinstance(self._elem, DefinitionItem):
282
            self._elem.term = content
283
284
    def _get_content(self):
285
        if isinstance(self._elem, Para):
286
            return self._elem.content
287
        if isinstance(self._elem, DefinitionItem):
288
            return self._elem.term
289
        return None
290
291
    def _replace_double_sharp(self):
292
        self._get_content()[-1].text = self._get_content()[-1].text.replace(
293
            "##", "#", 1
294
        )
295
296
    def _replace_marker(self):
297
        self._compute_title()
298
        self._compute_description()
299
        self._compute_basic_category()
300
        self._compute_levels()
301
        self._compute_section_number()
302
        self._compute_section_alias()
303
        self._compute_leading()
304
        self._compute_category()
305
        self._compute_number()
306
        self._compute_tag()
307
        self._compute_alias()
308
        self._compute_local_number()
309
        self._compute_global_number()
310
        self._compute_data()
311
312
    def _compute_title(self):
313
        self._title = []
314
        if (
315
            isinstance(self._get_content()[-3], Str)
316
            and self._get_content()[-3].text[-1:] == ")"
317
        ):
318
            for i, item in enumerate(self._get_content()):
319
                if isinstance(item, Str) and item.text[0] == "(":
320
                    self._title = self._get_content()[i:-2]
321
                    # Detach from original parent
322
                    self._title.parent = None
323
                    self._title[0].text = self._title[0].text[1:]
324
                    self._title[-1].text = self._title[-1].text[:-1]
325
                    del self._get_content()[i - 1 : -2]
326
                    break
327
        self._title = list(self._title)
328
329
    def _compute_description(self):
330
        self._description = self._get_content()[:-2]
331
        # Detach from original parent
332
        self._description.parent = None
333
        self._description = list(self._description)
334
335
    def _compute_basic_category(self):
336
        if self._match.group("prefix") is None:
337
            self._basic_category = Numbered._identifier(
338
                "".join(map(stringify, self._description))
339
            )
340
        else:
341
            self._basic_category = self._match.group("prefix")
342
        if self._basic_category not in self._doc.defined:
343
            define(self._basic_category, self._doc)
344
345
    def _compute_levels(self):
346
        # Compute the first and last section level values
347
        self._first_section_level = len(self._match.group("hidden")) // 2
348
        self._last_section_level = len(self._match.group("header")) // 2
349
350
        # Get the default first and last section level
351
        if self._first_section_level == self._last_section_level == 0:
352
            self._first_section_level = self._doc.defined[self._basic_category][
353
                "first-section-level"
354
            ]
355
            self._last_section_level = self._doc.defined[self._basic_category][
356
                "last-section-level"
357
            ]
358
359
    def _compute_section_number(self):
360
        self._section_number = ".".join(
361
            map(str, self._doc.headers[: self._last_section_level])
362
        )
363
364
    def _compute_section_alias(self):
365
        strings = list(map(str, self._doc.aliases[: self._last_section_level]))
366
        for index, string in enumerate(strings):
367
            if string == "":
368
                strings[index] = "0"
369
        self._section_alias = ".".join(strings)
370
371
    def _compute_leading(self):
372
        # Compute the leading (composed of the section numbering and a dot)
373
        if self._last_section_level != 0:
374
            self._leading = self._section_number + "."
375
        else:
376
            self._leading = ""
377
378
    def _compute_category(self):
379
        self._category = self._basic_category + ":" + self._leading
380
381
        # Is it a new category?
382
        if self._category not in self._doc.count:
383
            self._doc.count[self._category] = 0
384
385
        self._doc.count[self._category] = self._doc.count[self._category] + 1
386
387
    def _compute_number(self):
388
        self._number = str(self._doc.count[self._category])
389
390
    def _compute_tag(self):
391
        # Determine the final tag
392
        if self._match.group("name") is None:
393
            self._tag = self._category + self._number
394
        else:
395
            self._tag = self._basic_category + ":" + self._match.group("name")
396
397
        # Compute collections
398
        if self._basic_category not in self._doc.collections:
399
            self._doc.collections[self._basic_category] = []
400
401
        self._doc.collections[self._basic_category].append(self._tag)
402
403
    def _compute_alias(self):
404
        # Determine the final alias
405
        if not self._title:
406
            if self._section_alias:
407
                self._alias = (
408
                    self._basic_category
409
                    + ":"
410
                    + self._section_alias
411
                    + "."
412
                    + self._number
413
                )
414
            else:
415
                self._alias = self._basic_category + ":" + self._number
416
        else:
417
            if self._section_alias:
418
                self._alias = (
419
                    self._basic_category
420
                    + ":"
421
                    + self._section_alias
422
                    + "."
423
                    + Numbered._identifier(stringify(Span(*self._title)))
424
                )
425
            else:
426
                self._alias = (
427
                    self._basic_category
428
                    + ":"
429
                    + Numbered._identifier(stringify(Span(*self._title)))
430
                )
431
432
    def _compute_local_number(self):
433
        # Replace the '-.-.+.+...#' by the category count (omitting the hidden part)
434
        self._local_number = ".".join(
435
            map(
436
                str,
437
                self._doc.headers[self._first_section_level : self._last_section_level]
438
                + [self._number],
439
            )
440
        )
441
442
    def _compute_global_number(self):
443
        # Compute the global number
444
        if self._section_number:
445
            self._global_number = self._section_number + "." + self._number
446
        else:
447
            self._global_number = self._number
448
449
    def _compute_data(self):
450
        # pylint: disable=too-many-statements,no-member
451
        classes = self._doc.defined[self._basic_category]["classes"]
452
        if self._alias == self._tag:
453
            self._set_content(
454
                [
455
                    Span(),
456
                    Span(
457
                        identifier=self._tag,
458
                        classes=["pandoc-numbering-text"] + classes,
459
                    ),
460
                ]
461
            )
462
        else:
463
            self._set_content(
464
                [
465
                    Span(identifier=self._alias),
466
                    Span(
467
                        identifier=self._tag,
468
                        classes=["pandoc-numbering-text"] + classes,
469
                    ),
470
                ]
471
            )
472
        self._link.classes = self._link.classes + classes
473
        self._entry.classes = self._entry.classes + classes
474
475
        # Prepare the final data
476
        if self._title:
477
            self._get_content()[1].content = copy.deepcopy(
478
                self._doc.defined[self._basic_category]["format-text-title"]
479
            )
480
            self._link.content = copy.deepcopy(
481
                self._doc.defined[self._basic_category]["format-link-title"]
482
            )
483
            self._entry.content = copy.deepcopy(
484
                self._doc.defined[self._basic_category]["format-entry-title"]
485
            )
486
            self._caption = self._doc.defined[self._basic_category][
487
                "format-caption-title"
488
            ]
489
        else:
490
            self._get_content()[1].content = copy.deepcopy(
491
                self._doc.defined[self._basic_category]["format-text-classic"]
492
            )
493
            self._link.content = copy.deepcopy(
494
                self._doc.defined[self._basic_category]["format-link-classic"]
495
            )
496
            self._entry.content = copy.deepcopy(
497
                self._doc.defined[self._basic_category]["format-entry-classic"]
498
            )
499
            self._caption = self._doc.defined[self._basic_category][
500
                "format-caption-classic"
501
            ]
502
503
        # Compute caption (delay replacing %c at the end)
504
        title = stringify(Span(*self._title))
505
        description = stringify(Span(*self._description))
506
        self._caption = self._caption.replace("%t", title.lower())
507
        self._caption = self._caption.replace("%T", title)
508
        self._caption = self._caption.replace("%d", description.lower())
509
        self._caption = self._caption.replace("%D", description)
510
        self._caption = self._caption.replace("%s", self._section_number)
511
        self._caption = self._caption.replace("%g", self._global_number)
512
        self._caption = self._caption.replace("%n", self._local_number)
513
        self._caption = self._caption.replace("#", self._local_number)
514
        if self._doc.format in {"tex", "latex"}:
515
            self._caption = self._caption.replace("%p", "\\pageref{" + self._tag + "}")
516
517
        # Compute content
518
        if isinstance(self._elem, DefinitionItem):
519
            replace_description(Plain(*self._elem.term), self._description)
520
            replace_title(Plain(*self._elem.term), self._title)
521
            replace_global_number(Plain(*self._elem.term), self._global_number)
522
            replace_section_number(Plain(*self._elem.term), self._section_number)
523
            replace_local_number(Plain(*self._elem.term), self._local_number)
524
        else:
525
            replace_description(self._elem, self._description)
526
            replace_title(self._elem, self._title)
527
            replace_global_number(self._elem, self._global_number)
528
            replace_section_number(self._elem, self._section_number)
529
            replace_local_number(self._elem, self._local_number)
530
531
        # Compute link
532
        replace_description(self._link, self._description)
533
        replace_title(self._link, self._title)
534
        replace_global_number(self._link, self._global_number)
535
        replace_section_number(self._link, self._section_number)
536
        replace_local_number(self._link, self._local_number)
537
        if self._doc.format in {"tex", "latex"}:
538
            replace_page_number(self._link, self._tag)
539
540
        # Compute entry
541
        replace_description(self._entry, self._description)
542
        replace_title(self._entry, self._title)
543
        replace_global_number(self._entry, self._global_number)
544
        replace_section_number(self._entry, self._section_number)
545
        replace_local_number(self._entry, self._local_number)
546
547
        # Finalize the content
548
        if self._doc.format in {"tex", "latex"}:
549
            latex_category = re.sub("[^a-z]+", "", self._basic_category)
550
            latex = (
551
                "\\phantomsection"
552
                f"\\addcontentsline{{{latex_category}}}{{{latex_category}}}"
553
                f"{{\\protect\\numberline {{{self._leading + self._number}}}"
554
                f"{{\\ignorespaces {to_latex(self._entry)}"
555
                "}}"
556
            )
557
            self._get_content().insert(0, RawInline(latex, "tex"))
558
559
560
def replace_description(where, description):
561
    """
562
    Replace description in where.
563
564
    Arguments
565
    ---------
566
    where
567
        where to replace
568
    description
569
        replace %D and %d by description
570
    """
571
    where.walk(partial(replacing, search="%D", replace=copy.deepcopy(description)))
572
    where.walk(
573
        partial(
574
            replacing,
575
            search="%d",
576
            replace=[item.walk(lowering) for item in copy.deepcopy(description)],
577
        )
578
    )
579
580
581
def replace_title(where, title):
582
    """
583
    Replace title in where.
584
585
    Arguments
586
    ---------
587
    where
588
        where to replace
589
    title
590
        replace %T and %t by title
591
    """
592
    where.walk(partial(replacing, search="%T", replace=copy.deepcopy(title)))
593
    where.walk(
594
        partial(
595
            replacing,
596
            search="%t",
597
            replace=[item.walk(lowering) for item in copy.deepcopy(title)],
598
        )
599
    )
600
601
602
def replace_section_number(where, section_number):
603
    """
604
    Replace section number in where.
605
606
    Arguments
607
    ---------
608
    where
609
        where to replace
610
    section_number
611
        replace %s by section_number
612
    """
613
    where.walk(partial(replacing, search="%s", replace=[Str(section_number)]))
614
615
616
def replace_global_number(where, global_number):
617
    """
618
    Replace global number in where.
619
620
    Arguments
621
    ---------
622
    where
623
        where to replace
624
    global_number
625
        replace %g by global_number
626
    """
627
    where.walk(partial(replacing, search="%g", replace=[Str(global_number)]))
628
629
630
def replace_local_number(where, local_number):
631
    """
632
    Replace local number in where.
633
634
    Arguments
635
    ---------
636
    where
637
        where to replace
638
    local_number
639
        replace %n and # by local_number
640
    """
641
    where.walk(partial(replacing, search="%n", replace=[Str(local_number)]))
642
    where.walk(partial(replacing, search="#", replace=[Str(local_number)]))
643
644
645
def replace_page_number(where, tag):
646
    """
647
    Replace page number in where.
648
649
    Arguments
650
    ---------
651
    where
652
        where to replace
653
    tag
654
        replace %p by tag
655
    """
656
    where.walk(
657
        partial(
658
            replacing, search="%p", replace=[RawInline("\\pageref{" + tag + "}", "tex")]
659
        )
660
    )
661
662
663
def replace_count(where, count):
664
    """
665
    Replace count in where.
666
667
    Arguments
668
    ---------
669
    where
670
        where to replace
671
    count
672
        replace %c by count
673
    """
674
    where.walk(partial(replacing, search="%c", replace=[Str(count)]))
675
676
677
def remove_useless_latex(elem, _):
678
    """
679
    Clean up LaTeX element for entries.
680
681
    Arguments
682
    ---------
683
    elem
684
        elem to scan
685
686
    Returns
687
    -------
688
        []: if elem is an instance to remove
689
        None: otherwise
690
    """
691
    if isinstance(
692
        elem,
693
        (
694
            BlockQuote,
695
            BulletList,
696
            Citation,
697
            Cite,
698
            CodeBlock,
699
            Definition,
700
            DefinitionItem,
701
            DefinitionList,
702
            Div,
703
            Header,
704
            HorizontalRule,
705
            Image,
706
            LineBlock,
707
            LineBreak,
708
            LineItem,
709
            ListItem,
710
            Note,
711
            Para,
712
            RawBlock,
713
            RawInline,
714
            SoftBreak,
715
            Table,
716
            TableCell,
717
            TableRow,
718
        ),
719
    ):
720
        return []
721
    return None
722
723
724
def to_latex(elem):
725
    """
726
    Convert element to LaTeX.
727
728
    Arguments
729
    ---------
730
    elem
731
        elem to convert
732
733
    Returns
734
    -------
735
        LaTex string
736
    """
737
    return convert_text(
738
        run_filters([remove_useless_latex], doc=Plain(elem)),
739
        input_format="panflute",
740
        output_format="latex",
741
        extra_args=["--no-highlight"],
742
    )
743
744
745
def define(category, doc):
746
    """
747
    Define a category in document.
748
749
    Arguments
750
    ---------
751
    category
752
        category to define
753
    doc
754
        pandoc document
755
    """
756
    # pylint: disable=line-too-long
757
    doc.defined[category] = {
758
        "first-section-level": 0,
759
        "last-section-level": 0,
760
        "format-text-classic": [Strong(Str("%D"), Space(), Str("%n"))],
761
        "format-text-title": [
762
            Strong(Str("%D"), Space(), Str("%n")),
763
            Space(),
764
            Emph(Str("(%T)")),
765
        ],
766
        "format-link-classic": [Str("%D"), Space(), Str("%n")],
767
        "format-link-title": [Str("%D"), Space(), Str("%n"), Space(), Str("(%T)")],
768
        "format-caption-classic": "%D %n",
769
        "format-caption-title": "%D %n (%T)",
770
        "format-entry-title": [Str("%T")],
771
        "classes": [category],
772
        "cite-shortcut": True,
773
        "listing-title": None,
774
        "listing-unnumbered": True,
775
        "listing-unlisted": True,
776
        "listing-identifier": True,
777
        "entry-tab": 1.5,
778
        "entry-space": 2.3,
779
    }
780
    if doc.format == "latex":
781
        doc.defined[category]["format-entry-classic"] = [Str("%D")]
782
        doc.defined[category]["entry-tab"] = 1.5
783
        doc.defined[category]["entry-space"] = 2.3
784
    else:
785
        doc.defined[category]["format-entry-classic"] = [Str("%D"), Space(), Str("%g")]
786
787
788
def lowering(elem, _):
789
    """
790
    Lower element.
791
792
    Arguments
793
    ---------
794
    elem
795
        element to lower
796
    """
797
    if isinstance(elem, Str):
798
        elem.text = elem.text.lower()
799
800
801
def replacing(elem, _, search=None, replace=None):
802
    """
803
    Replace an element.
804
805
    Arguments
806
    ---------
807
    elem
808
        element to scan
809
    search
810
        string to search
811
    replace
812
        string to replace
813
814
    Returns
815
    -------
816
        The modified elements.
817
    """
818
    if isinstance(elem, Str):
819
        search_splitted = elem.text.split(search)
820
        if len(search_splitted) > 1:
821
            text = []
822
823
            if search_splitted[0] != "":
824
                text.append(Str(search_splitted[0]))
825
826
            for string in search_splitted[1:]:
827
                text.extend(replace)
828
                if string != "":
829
                    text.append(Str(string))
830
831
            return text
832
833
    return [elem]
834
835
836
def numbering(elem, doc):
837
    """
838
    Add the numbering of an element.
839
840
    Arguments
841
    ---------
842
    elem
843
        element to number
844
    doc
845
        pandoc document
846
    """
847
    if isinstance(elem, Header):
848
        update_header_numbers(elem, doc)
849
        update_header_aliases(elem, doc)
850
    elif isinstance(elem, (Para, DefinitionItem)):
851
        numbered = Numbered(elem, doc)
852
        if numbered.tag is not None:
853
            doc.information[numbered.tag] = numbered
854
855
856
def referencing(elem, doc):
857
    """
858
    Add a reference for an element.
859
860
    Arguments
861
    ---------
862
    elem
863
        element to reference
864
    doc
865
        pandoc document
866
867
    Returns
868
    -------
869
        A Link or None
870
    """
871
    if isinstance(elem, Link):
872
        return referencing_link(elem, doc)
873
    if isinstance(elem, Cite):
874
        return referencing_cite(elem, doc)
875
    if isinstance(elem, Span) and elem.identifier in doc.information:
876
        replace_count(elem, str(doc.count[doc.information[elem.identifier].category]))
877
    return None
878
879
880
def referencing_link(elem, doc):
881
    """
882
    Add a eference link.
883
884
    Arguments
885
    ---------
886
    elem
887
        element to reference
888
    doc
889
        pandoc document
890
    """
891
    match = re.match("^#(?P<tag>([a-zA-Z][\\w:.-]*))$", elem.url)
892
    if match:
893
        tag = match.group("tag")
894
        if tag in doc.information:
895
            replace_title(elem, doc.information[tag].title)
896
            replace_description(elem, doc.information[tag].description)
897
            replace_global_number(elem, doc.information[tag].global_number)
898
            replace_section_number(elem, doc.information[tag].section_number)
899
            replace_local_number(elem, doc.information[tag].local_number)
900
            replace_count(elem, str(doc.count[doc.information[tag].category]))
901
            if doc.format in {"tex", "latex"}:
902
                replace_page_number(elem, tag)
903
904
            title = stringify(Span(*doc.information[tag].title))
905
            description = stringify(Span(*doc.information[tag].description))
906
            elem.title = elem.title.replace("%t", title.lower())
907
            elem.title = elem.title.replace("%T", title)
908
            elem.title = elem.title.replace("%d", description.lower())
909
            elem.title = elem.title.replace("%D", description)
910
            elem.title = elem.title.replace("%s", doc.information[tag].section_number)
911
            elem.title = elem.title.replace("%g", doc.information[tag].global_number)
912
            elem.title = elem.title.replace("%n", doc.information[tag].local_number)
913
            elem.title = elem.title.replace("#", doc.information[tag].local_number)
914
            elem.title = elem.title.replace(
915
                "%c", str(doc.count[doc.information[tag].category])
916
            )
917
            if doc.format in {"tex", "latex"}:
918
                elem.title = elem.title.replace("%p", "\\pageref{" + tag + "}")
919
920
921
def referencing_cite(elem, doc):
922
    """
923
    Cite reference.
924
925
    Arguments
926
    ---------
927
    elem
928
        element to reference
929
    doc
930
        pandoc document
931
932
    Returns
933
    -------
934
        A Link or None
935
    """
936
    if len(elem.content) == 1 and isinstance(elem.content[0], Str):
937
        match = re.match(
938
            "^(@(?P<tag>(?P<category>[a-zA-Z][\\w.-]*):"
939
            "(([a-zA-Z][\\w.-]*)|(\\d*(\\.\\d*)*))))$",
940
            elem.content[0].text,
941
        )
942
        if match:
943
            category = match.group("category")
944
            if category in doc.defined and doc.defined[category]["cite-shortcut"]:
945
                # Deal with @prefix:name shortcut
946
                tag = match.group("tag")
947
                if tag in doc.information:
948
                    ret = Link(
949
                        doc.information[tag].link,
950
                        url="#" + tag,
951
                        title=doc.information[tag].caption.replace(
952
                            "%c", str(doc.count[doc.information[tag].category])
953
                        ),
954
                    )
955
                    replace_count(ret, str(doc.count[doc.information[tag].category]))
956
                    return ret
957
    return None
958
959
960
def update_header_numbers(elem, doc):
961
    """
962
    Update header numbers.
963
964
    Arguments
965
    ---------
966
    elem
967
        element to update
968
    doc
969
        pandoc document
970
    """
971
    if "unnumbered" not in elem.classes:
972
        doc.headers[elem.level - 1] = doc.headers[elem.level - 1] + 1
973
        for index in range(elem.level, 6):
974
            doc.headers[index] = 0
975
976
977
def update_header_aliases(elem, doc):
978
    """
979
    Update header aliases.
980
981
    Arguments
982
    ---------
983
    elem
984
        element to update
985
    doc
986
        pandoc document
987
    """
988
    doc.aliases[elem.level - 1] = elem.identifier
989
    for index in range(elem.level, 6):
990
        doc.aliases[index] = ""
991
992
993
def prepare(doc):
994
    """
995
    Prepare document.
996
997
    Arguments
998
    ---------
999
    doc
1000
        pandoc document
1001
    """
1002
    doc.headers = [0, 0, 0, 0, 0, 0]
1003
    doc.aliases = ["", "", "", "", "", ""]
1004
    doc.information = {}
1005
    doc.defined = {}
1006
1007
    if "pandoc-numbering" in doc.metadata.content and isinstance(
1008
        doc.metadata.content["pandoc-numbering"], MetaMap
1009
    ):
1010
        for category, definition in doc.metadata.content[
1011
            "pandoc-numbering"
1012
        ].content.items():
1013
            if isinstance(definition, MetaMap):
1014
                add_definition(category, definition, doc)
1015
1016
    doc.count = {}
1017
    doc.collections = {}
1018
1019
1020
def add_definition(category, definition, doc):
1021
    """
1022
    Add definition for a category.
1023
1024
    Arguments
1025
    ---------
1026
    category
1027
        The category
1028
    definition
1029
        The definition
1030
    doc
1031
        The pandoc document
1032
    """
1033
    # Create the category with options by default
1034
    define(category, doc)
1035
1036
    # Detect general options
1037
    if "general" in definition:
1038
        meta_cite(category, definition["general"], doc.defined)
1039
        meta_listing(category, definition["general"], doc.defined)
1040
        meta_levels(category, definition["general"], doc.defined)
1041
        meta_classes(category, definition["general"], doc.defined)
1042
1043
    # Detect LaTeX options
1044
    if doc.format in {"tex", "latex"}:
1045
        if "latex" in definition:
1046
            meta_format_text(category, definition["latex"], doc.defined)
1047
            meta_format_link(category, definition["latex"], doc.defined)
1048
            meta_format_caption(category, definition["latex"], doc.defined)
1049
            meta_format_entry(category, definition["latex"], doc.defined)
1050
            meta_entry_tab(category, definition["latex"], doc.defined)
1051
            meta_entry_space(category, definition["latex"], doc.defined)
1052
    # Detect standard options
1053
    else:
1054
        if "standard" in definition:
1055
            meta_format_text(category, definition["standard"], doc.defined)
1056
            meta_format_link(category, definition["standard"], doc.defined)
1057
            meta_format_caption(category, definition["standard"], doc.defined)
1058
            meta_format_entry(category, definition["standard"], doc.defined)
1059
1060
1061
def meta_cite(category, definition, defined):
1062
    """
1063
    Compute cite for a category.
1064
1065
    Arguments
1066
    ---------
1067
    category
1068
        The category
1069
    definition
1070
        The definition
1071
    defined
1072
        The defined parameter
1073
    """
1074
    if "cite-shortcut" in definition:
1075
        if isinstance(definition["cite-shortcut"], MetaBool):
1076
            defined[category]["cite-shortcut"] = definition["cite-shortcut"].boolean
1077
        else:
1078
            debug(
1079
                "[WARNING] pandoc-numbering: cite-shortcut is not correct for category "
1080
                + category
1081
            )
1082
1083
1084
# pylint:disable=too-many-branches
1085
def meta_listing(category, definition, defined):
1086
    """
1087
    Compute listing for a category.
1088
1089
    Arguments
1090
    ---------
1091
    category
1092
        The category
1093
    definition
1094
        The definition
1095
    defined
1096
        The defined parameter
1097
    """
1098
    if "listing-title" in definition:
1099
        if isinstance(definition["listing-title"], MetaInlines):
1100
            defined[category]["listing-title"] = definition["listing-title"].content
1101
            # Detach from original parent
1102
            defined[category]["listing-title"].parent = None
1103
        else:
1104
            debug(
1105
                "[WARNING] pandoc-numbering: listing-title is not correct for category "
1106
                + category
1107
            )
1108 View Code Duplication
    if "listing-unnumbered" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1109
        if isinstance(definition["listing-unnumbered"], MetaBool):
1110
            defined[category]["listing-unnumbered"] = definition[
1111
                "listing-unnumbered"
1112
            ].boolean
1113
        else:
1114
            debug(
1115
                "[WARNING] pandoc-numbering: "
1116
                "listing-unnumbered is not correct for category " + category
1117
            )
1118 View Code Duplication
    if "listing-unlisted" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1119
        if isinstance(definition["listing-unlisted"], MetaBool):
1120
            defined[category]["listing-unlisted"] = definition[
1121
                "listing-unlisted"
1122
            ].boolean
1123
        else:
1124
            debug(
1125
                "[WARNING] pandoc-numbering: "
1126
                "listing-unlisted is not correct for category " + category
1127
            )
1128
    if "listing-identifier" in definition:
1129
        if isinstance(definition["listing-identifier"], MetaBool):
1130
            defined[category]["listing-identifier"] = definition[
1131
                "listing-identifier"
1132
            ].boolean
1133
        elif (
1134
            isinstance(definition["listing-identifier"], MetaInlines)
1135
            and len(definition["listing-identifier"].content) == 1
1136
            and isinstance(definition["listing-identifier"].content[0], Str)
1137
        ):
1138
            defined[category]["listing-identifier"] = (
1139
                definition["listing-identifier"].content[0].text
1140
            )
1141
        else:
1142
            debug(
1143
                "[WARNING] pandoc-numbering: "
1144
                "listing-identifier is not correct for category " + category
1145
            )
1146
1147
1148 View Code Duplication
def meta_format_text(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1149
    """
1150
    Compute format text for a category.
1151
1152
    Arguments
1153
    ---------
1154
    category
1155
        The category
1156
    definition
1157
        The definition
1158
    defined
1159
        The defined parameter
1160
    """
1161
    if "format-text-classic" in definition:
1162
        if isinstance(definition["format-text-classic"], MetaInlines):
1163
            defined[category]["format-text-classic"] = definition[
1164
                "format-text-classic"
1165
            ].content
1166
            # Detach from original parent
1167
            defined[category]["format-text-classic"].parent = None
1168
        else:
1169
            debug(
1170
                "[WARNING] pandoc-numbering: "
1171
                "format-text-classic is not correct for category " + category
1172
            )
1173
1174
    if "format-text-title" in definition:
1175
        if isinstance(definition["format-text-title"], MetaInlines):
1176
            defined[category]["format-text-title"] = definition[
1177
                "format-text-title"
1178
            ].content
1179
            # Detach from original parent
1180
            defined[category]["format-text-title"].parent = None
1181
        else:
1182
            debug(
1183
                "[WARNING] pandoc-numbering: "
1184
                "format-text-title is not correct for category " + category
1185
            )
1186
1187
1188 View Code Duplication
def meta_format_link(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1189
    """
1190
    Compute format link for a category.
1191
1192
    Arguments
1193
    ---------
1194
    category
1195
        The category
1196
    definition
1197
        The definition
1198
    defined
1199
        The defined parameter
1200
    """
1201
    if "format-link-classic" in definition:
1202
        if isinstance(definition["format-link-classic"], MetaInlines):
1203
            defined[category]["format-link-classic"] = definition[
1204
                "format-link-classic"
1205
            ].content
1206
            # Detach from original parent
1207
            defined[category]["format-link-classic"].parent = None
1208
        else:
1209
            debug(
1210
                "[WARNING] pandoc-numbering: "
1211
                "format-link-classic is not correct for category " + category
1212
            )
1213
1214
    if "format-link-title" in definition:
1215
        if isinstance(definition["format-link-title"], MetaInlines):
1216
            defined[category]["format-link-title"] = definition[
1217
                "format-link-title"
1218
            ].content
1219
            # Detach from original parent
1220
            defined[category]["format-link-title"].parent = None
1221
        else:
1222
            debug(
1223
                "[WARNING] pandoc-numbering: "
1224
                "format-link-title is not correct for category " + category
1225
            )
1226
1227
1228
def meta_format_caption(category, definition, defined):
1229
    """
1230
    Compute format caption for a category.
1231
1232
    Arguments
1233
    ---------
1234
    category
1235
        The category
1236
    definition
1237
        The definition
1238
    defined
1239
        The defined parameter
1240
    """
1241 View Code Duplication
    if "format-caption-classic" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1242
        if isinstance(definition["format-caption-classic"], MetaInlines):
1243
            defined[category]["format-caption-classic"] = stringify(
1244
                definition["format-caption-classic"]
1245
            )
1246
        else:
1247
            debug(
1248
                "[WARNING] pandoc-numbering: "
1249
                "format-caption-classic is not correct for category " + category
1250
            )
1251
1252 View Code Duplication
    if "format-caption-title" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1253
        if isinstance(definition["format-caption-title"], MetaInlines):
1254
            defined[category]["format-caption-title"] = stringify(
1255
                definition["format-caption-title"]
1256
            )
1257
        else:
1258
            debug(
1259
                "[WARNING] pandoc-numbering: "
1260
                "format-caption-title is not correct for category " + category
1261
            )
1262
1263
1264 View Code Duplication
def meta_format_entry(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1265
    """
1266
    Compute format entry for a category.
1267
1268
    Arguments
1269
    ---------
1270
    category
1271
        The category
1272
    definition
1273
        The definition
1274
    defined
1275
        The defined parameter
1276
    """
1277
    if "format-entry-classic" in definition:
1278
        if isinstance(definition["format-entry-classic"], MetaInlines):
1279
            defined[category]["format-entry-classic"] = definition[
1280
                "format-entry-classic"
1281
            ].content
1282
            # Detach from original parent
1283
            defined[category]["format-entry-classic"].parent = None
1284
        else:
1285
            debug(
1286
                "[WARNING] pandoc-numbering: "
1287
                "format-entry-classic is not correct for category " + category
1288
            )
1289
1290
    if "format-entry-title" in definition:
1291
        if isinstance(definition["format-entry-title"], MetaInlines):
1292
            defined[category]["format-entry-title"] = definition[
1293
                "format-entry-title"
1294
            ].content
1295
            # Detach from original parent
1296
            defined[category]["format-entry-title"].parent = None
1297
        else:
1298
            debug(
1299
                "[WARNING] pandoc-numbering: "
1300
                "format-entry-title is not correct for category " + category
1301
            )
1302
1303
1304 View Code Duplication
def meta_entry_tab(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1305
    """
1306
    Compute entry tab for a category.
1307
1308
    Arguments
1309
    ---------
1310
    category
1311
        The category
1312
    definition
1313
        The definition
1314
    defined
1315
        The defined parameter
1316
    """
1317
    if "entry-tab" in definition:
1318
        if isinstance(definition["entry-tab"], MetaString):
1319
            value = definition["entry-tab"].text
1320
        elif (
1321
            isinstance(definition["entry-tab"], MetaInlines)
1322
            and len(definition["entry-tab"].content) == 1
1323
        ):
1324
            value = definition["entry-tab"].content[0].text
1325
        else:
1326
            debug(
1327
                "[WARNING] pandoc-numbering: entry-tab is not correct for category "
1328
                + category
1329
            )
1330
            return
1331
        # Get the tab
1332
        try:
1333
            tab = float(value)
1334
            if tab > 0:
1335
                defined[category]["entry-tab"] = tab
1336
            else:
1337
                debug(
1338
                    "[WARNING] pandoc-numbering: "
1339
                    "entry-tab must be positive for category " + category
1340
                )
1341
        except ValueError:
1342
            debug(
1343
                "[WARNING] pandoc-numbering: entry-tab is not correct for category "
1344
                + category
1345
            )
1346
1347
1348 View Code Duplication
def meta_entry_space(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1349
    """
1350
    Compute entry space for a category.
1351
1352
    Arguments
1353
    ---------
1354
    category
1355
        The category
1356
    definition
1357
        The definition
1358
    defined
1359
        The defined parameter
1360
    """
1361
    if "entry-space" in definition:
1362
        if isinstance(definition["entry-space"], MetaString):
1363
            value = definition["entry-space"].text
1364
        elif (
1365
            isinstance(definition["entry-space"], MetaInlines)
1366
            and len(definition["entry-space"].content) == 1
1367
        ):
1368
            value = definition["entry-space"].content[0].text
1369
        else:
1370
            debug(
1371
                "[WARNING] pandoc-numbering: entry-space is not correct for category "
1372
                + category
1373
            )
1374
            return
1375
        # Get the space
1376
        try:
1377
            space = float(value)
1378
            if space > 0:
1379
                defined[category]["entry-space"] = space
1380
            else:
1381
                debug(
1382
                    "[WARNING] pandoc-numbering: "
1383
                    "entry-space must be positive for category " + category
1384
                )
1385
        except ValueError:
1386
            debug(
1387
                "[WARNING] pandoc-numbering: entry-space is not correct for category "
1388
                + category
1389
            )
1390
1391
1392
def meta_levels(category, definition, defined):
1393
    """
1394
    Compute level for a category.
1395
1396
    Arguments
1397
    ---------
1398
    category
1399
        The category
1400
    definition
1401
        The definition
1402
    defined
1403
        The defined parameter
1404
    """
1405
    if (
1406
        "sectioning-levels" in definition
1407
        and isinstance(definition["sectioning-levels"], MetaInlines)
1408
        and len(definition["sectioning-levels"].content) == 1
1409
    ):
1410
        match = re.match(
1411
            Numbered.header_regex, definition["sectioning-levels"].content[0].text
1412
        )
1413
        if match:
1414
            # Compute the first and last levels section
1415
            defined[category]["first-section-level"] = len(match.group("hidden")) // 2
1416
            defined[category]["last-section-level"] = len(match.group("header")) // 2
1417
    if "first-section-level" in definition:
1418
        if isinstance(definition["first-section-level"], MetaString):
1419
            value = definition["first-section-level"].text
1420
        elif (
1421
            isinstance(definition["first-section-level"], MetaInlines)
1422
            and len(definition["first-section-level"].content) == 1
1423
        ):
1424
            value = definition["first-section-level"].content[0].text
1425
        else:
1426
            debug(
1427
                "[WARNING] pandoc-numbering: "
1428
                "first-section-level is not correct for category " + category
1429
            )
1430
            return
1431
1432
        # Get the level
1433
        try:
1434
            level = int(value) - 1
1435
        except ValueError:
1436
            debug(
1437
                "[WARNING] pandoc-numbering: "
1438
                "first-section-level is not correct for category " + category
1439
            )
1440
1441
        if 0 <= level <= 6:
1442
            defined[category]["first-section-level"] = level
1443
        else:
1444
            # pylint: disable=line-too-long
1445
            debug(
1446
                "[WARNING] pandoc-numbering: "
1447
                "first-section-level must be positive or zero for category " + category
1448
            )
1449
1450
    if "last-section-level" in definition:
1451
        if isinstance(definition["last-section-level"], MetaString):
1452
            value = definition["last-section-level"].text
1453
        elif (
1454
            isinstance(definition["last-section-level"], MetaInlines)
1455
            and len(definition["last-section-level"].content) == 1
1456
        ):
1457
            value = definition["last-section-level"].content[0].text
1458
        else:
1459
            debug(
1460
                "[WARNING] pandoc-numbering: "
1461
                "last-section-level is not correct for category " + category
1462
            )
1463
            return
1464
1465
        # Get the level
1466
        try:
1467
            level = int(value)
1468
        except ValueError:
1469
            debug(
1470
                "[WARNING] pandoc-numbering: "
1471
                "last-section-level is not correct for category " + category
1472
            )
1473
1474
        if 0 <= level <= 6:
1475
            defined[category]["last-section-level"] = level
1476
        else:
1477
            # pylint: disable=line-too-long
1478
            debug(
1479
                "[WARNING] pandoc-numbering: "
1480
                "last-section-level must be positive or zero for category " + category
1481
            )
1482
1483
1484
def meta_classes(category, definition, defined):
1485
    """
1486
    Compute classes for a category.
1487
1488
    Arguments
1489
    ---------
1490
    category
1491
        The category
1492
    definition
1493
        The definition
1494
    defined
1495
        The defined parameter
1496
    """
1497
    if "classes" in definition and isinstance(definition["classes"], MetaList):
1498
        defined[category]["classes"] = [
1499
            stringify(elt) for elt in definition["classes"].content
1500
        ]
1501
1502
1503
def finalize(doc):
1504
    """
1505
    Finalize document.
1506
1507
    Arguments
1508
    ---------
1509
    doc
1510
        The pandoc document
1511
    """
1512
    # Loop on all listings definition
1513
1514
    if doc.format in {"tex", "latex"}:
1515
        # Add header-includes if necessary
1516
        if "header-includes" not in doc.metadata:
1517
            doc.metadata["header-includes"] = MetaList()
1518
        # Convert header-includes to MetaList if necessary
1519
        elif not isinstance(doc.metadata["header-includes"], MetaList):
1520
            doc.metadata["header-includes"] = MetaList(doc.metadata["header-includes"])
1521
1522
        doc.metadata["header-includes"].append(
1523
            MetaInlines(RawInline(r"\usepackage{tocloft}", "tex"))
1524
        )
1525
        doc.metadata["header-includes"].append(
1526
            MetaInlines(RawInline(r"\usepackage{etoolbox}", "tex"))
1527
        )
1528
1529
    i = 0
1530
    listof = []
1531
    for category, definition in doc.defined.items():
1532
        if definition["listing-title"] is not None:
1533
            # pylint: disable=consider-using-f-string
1534
            if doc.format in {"tex", "latex"}:
1535
                latex_category = re.sub("[^a-z]+", "", category)
1536
                latex = (
1537
                    r"\newlistof{%s}{%s}{%s}"
1538
                    r"\renewcommand{\cft%stitlefont}{\cfttoctitlefont}"
1539
                    r"\setlength{\cft%snumwidth}{\cftfignumwidth}"
1540
                    r"\setlength{\cft%sindent}{\cftfigindent}"
1541
                    % (
1542
                        latex_category,
1543
                        latex_category,
1544
                        convert_text(
1545
                            Plain(*definition["listing-title"]),
1546
                            input_format="panflute",
1547
                            output_format="latex",
1548
                        ),
1549
                        latex_category,
1550
                        latex_category,
1551
                        latex_category,
1552
                    )
1553
                )
1554
                doc.metadata["header-includes"].append(
1555
                    MetaInlines(RawInline(latex, "tex"))
1556
                )
1557
                listof.append(f"\\listof{latex_category}")
1558
            else:
1559
                classes = ["pandoc-numbering-listing"] + definition["classes"]
1560
1561
                if definition["listing-unnumbered"]:
1562
                    classes.append("unnumbered")
1563
1564
                if definition["listing-unlisted"]:
1565
                    classes.append("unlisted")
1566
1567
                if definition["listing-identifier"] is False:
1568
                    header = Header(
1569
                        *definition["listing-title"], level=1, classes=classes
1570
                    )
1571
                elif definition["listing-identifier"] is True:
1572
                    header = Header(
1573
                        *definition["listing-title"], level=1, classes=classes
1574
                    )
1575
                    header = convert_text(
1576
                        convert_text(
1577
                            header, input_format="panflute", output_format="markdown"
1578
                        ),
1579
                        output_format="panflute",
1580
                    )[0]
1581
                else:
1582
                    header = Header(
1583
                        *definition["listing-title"],
1584
                        level=1,
1585
                        classes=classes,
1586
                        identifier=definition["listing-identifier"],
1587
                    )
1588
1589
                doc.content.insert(i, header)
1590
                i = i + 1
1591
1592
                table = table_other(doc, category, definition)
1593
1594
                if table:
1595
                    doc.content.insert(i, table)
1596
                    i = i + 1
1597
1598
    if doc.format in {"tex", "latex"}:
1599
        header = (
1600
            r"\ifdef{\mainmatter}"
1601
            r"{\let\oldmainmatter\mainmatter"
1602
            r"\renewcommand{\mainmatter}[0]{%s\oldmainmatter}}"
1603
            r"{}"
1604
        )
1605
        doc.metadata["header-includes"].append(
1606
            MetaInlines(RawInline(header % "\n".join(listof), "tex"))
1607
        )
1608
1609
        latex = r"\ifdef{\mainmatter}{}{%s}"
1610
        doc.content.insert(0, RawBlock(latex % "\n".join(listof), "tex"))
1611
1612
1613
def table_other(doc, category, _):
1614
    """
1615
    Compute other code for table.
1616
1617
    Arguments
1618
    ---------
1619
    doc
1620
        pandoc document
1621
    category
1622
        category numbered
1623
1624
    Returns
1625
    -------
1626
        A BulletList or None
1627
    """
1628
    if category in doc.collections:
1629
        # Return a bullet list
1630
        return BulletList(
1631
            *(
1632
                ListItem(Plain(Link(doc.information[tag].entry, url="#" + tag)))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable tag does not seem to be defined.
Loading history...
1633
                for tag in doc.collections[category]
1634
            )
1635
        )
1636
    return None
1637
1638
1639
def link_color(doc):
1640
    """
1641
    Compute LaTeX code for toc.
1642
1643
    Arguments
1644
    ---------
1645
    doc
1646
        pandoc document
1647
1648
    Returns
1649
    -------
1650
        LaTeX code for links.
1651
    """
1652
    # Get the link color
1653
    metadata = doc.get_metadata()
1654
    if "toccolor" in metadata:
1655
        return "\\hypersetup{linkcolor=" + str(metadata["toccolor"]) + "}"
1656
    return "\\hypersetup{linkcolor=black}"
1657
1658
1659
def main(doc=None) -> None:
1660
    """
1661
    Produce the final document.
1662
1663
    Parameters
1664
    ----------
1665
    doc
1666
        pandoc document
1667
    """
1668
    run_filters([numbering, referencing], prepare=prepare, doc=doc, finalize=finalize)
1669
1670
1671
if __name__ == "__main__":
1672
    main()
1673