Passed
Push — main ( f857c7...785dfc )
by Christophe
02:36 queued 01:09
created

pandoc_numbering._main   F

Complexity

Total Complexity 191

Size/Duplication

Total Lines 1665
Duplicated Lines 3.72 %

Importance

Changes 0
Metric Value
eloc 814
dl 62
loc 1665
rs 1.786
c 0
b 0
f 0
wmc 191

36 Functions

Rating   Name   Duplication   Size   Complexity  
A replace_description() 0 17 1
A replace_page_number() 0 14 1
A replace_local_number() 0 13 1
A replace_global_number() 0 12 1
A replace_section_number() 0 12 1
A replace_count() 0 12 1
A replace_title() 0 17 1
C meta_listing() 0 44 9
F finalize() 0 123 13
A referencing() 0 23 5
A lowering() 0 11 2
B referencing_cite() 0 38 7
F meta_levels() 62 93 17
A meta_format() 0 28 3
A to_latex() 0 19 1
A meta_cite() 0 24 3
B replacing() 0 41 7
A update_header_aliases() 0 14 2
A remove_useless_latex() 0 46 2
A main() 0 10 1
A prepare() 0 25 5
A numbering() 0 18 4
A meta_format_entry() 0 19 1
A meta_format_caption() 0 24 4
A meta_format_text() 0 19 1
A update_header_numbers() 0 15 3
A meta_entry_tab() 0 18 1
A meta_entry_space() 0 18 1
B referencing_link() 0 39 5
A link_color() 0 19 2
A meta_format_link() 0 19 1
A meta_classes() 0 20 3
B add_definition() 0 39 5
A define() 0 41 2
B meta_entry() 0 47 7
A table_other() 0 25 2

33 Methods

Rating   Name   Duplication   Size   Complexity  
A Numbered._get_content() 0 6 3
C Numbered._compute_data() 0 109 7
A Numbered._set_content() 0 5 3
A Numbered._compute_category() 0 8 2
A Numbered._compute_tag() 0 12 3
A Numbered._compute_description() 0 5 1
A Numbered._compute_number() 0 2 1
A Numbered._compute_alias() 0 27 4
A Numbered._compute_global_number() 0 6 2
A Numbered._compute_basic_category() 0 9 3
A Numbered._replace_double_sharp() 0 3 1
A Numbered._compute_leading() 0 6 2
A Numbered._compute_section_alias() 0 6 3
A Numbered._replace_marker() 0 15 1
A Numbered._compute_local_number() 0 7 1
A Numbered._compute_section_number() 0 3 1
B Numbered._compute_title() 0 16 6
A Numbered._compute_levels() 0 12 2
A Numbered.link() 0 10 1
A Numbered.alias() 0 10 1
A Numbered.section_alias() 0 10 1
A Numbered._remove_accents() 0 5 1
A Numbered.tag() 0 10 1
B Numbered.__init__() 0 27 5
A Numbered.global_number() 0 10 1
A Numbered.entry() 0 10 1
A Numbered._identifier() 0 9 1
A Numbered.caption() 0 10 1
A Numbered.category() 0 10 1
A Numbered.description() 0 10 1
A Numbered.title() 0 10 1
A Numbered.section_number() 0 10 1
A Numbered.local_number() 0 10 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like pandoc_numbering._main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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