pandoc_numbering._main   F
last analyzed

Complexity

Total Complexity 193

Size/Duplication

Total Lines 1709
Duplicated Lines 3.63 %

Importance

Changes 0
Metric Value
wmc 193
eloc 826
dl 62
loc 1709
rs 1.774
c 0
b 0
f 0

36 Functions

Rating   Name   Duplication   Size   Complexity  
C meta_listing() 0 44 9
A replace_description() 0 17 1
F finalize() 0 135 15
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 replace_page_number() 0 14 1
A to_latex() 0 19 1
A meta_cite() 0 24 3
A replace_local_number() 0 13 1
B replacing() 0 41 7
A replace_global_number() 0 12 1
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 replace_section_number() 0 12 1
A meta_entry_tab() 0 18 1
A meta_entry_space() 0 18 1
B referencing_link() 0 39 5
A replace_count() 0 12 1
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 replace_title() 0 17 1
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.link() 0 11 1
A Numbered.alias() 0 11 1
A Numbered._get_content() 0 6 3
C Numbered._compute_data() 0 109 7
A Numbered.section_alias() 0 11 1
A Numbered._remove_accents() 0 5 1
A Numbered._set_content() 0 5 3
A Numbered.tag() 0 11 1
B Numbered.__init__() 0 28 5
A Numbered.global_number() 0 11 1
A Numbered._compute_category() 0 8 2
A Numbered.entry() 0 11 1
A Numbered._compute_tag() 0 17 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.caption() 0 11 1
A Numbered._compute_basic_category() 0 9 3
A Numbered._replace_double_sharp() 0 3 1
A Numbered.category() 0 11 1
A Numbered.identifier() 0 22 1
A Numbered._compute_leading() 0 6 2
A Numbered.description() 0 11 1
A Numbered.title() 0 11 1
A Numbered.section_number() 0 11 1
A Numbered.local_number() 0 11 1
A Numbered._compute_section_alias() 0 6 3
A Numbered._replace_marker() 0 15 1
A Numbered._compute_local_number() 0 7 1
B Numbered._compute_title() 0 16 6
A Numbered._compute_section_number() 0 3 1
A Numbered._compute_levels() 0 12 2

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