Passed
Push — develop ( 1f58bc...7245dd )
by Christophe
01:01
created

_main   F

Complexity

Total Complexity 192

Size/Duplication

Total Lines 1614
Duplicated Lines 4.89 %

Importance

Changes 0
Metric Value
eloc 775
dl 79
loc 1614
rs 1.825
c 0
b 0
f 0
wmc 192

33 Methods

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

36 Functions

Rating   Name   Duplication   Size   Complexity  
F finalize() 0 123 13
C meta_listing() 9 49 11
A table_other() 0 24 2
A meta_entry_space() 0 14 1
A meta_cite() 0 20 3
A remove_useless_latex() 0 45 2
A to_latex() 0 18 1
A meta_classes() 0 16 3
A replace_count() 0 12 1
B meta_entry() 0 42 7
A referencing() 0 22 5
B add_definition() 0 39 5
A meta_format_link() 0 15 1
A lowering() 0 11 2
A replace_section_number() 0 12 1
B replacing() 0 33 6
A replace_page_number() 0 14 1
A numbering() 0 18 4
A replace_description() 0 17 1
A main() 0 10 1
A link_color() 0 18 2
A meta_format() 8 23 3
A meta_format_caption() 0 20 4
A meta_entry_tab() 0 14 1
B referencing_cite() 0 37 7
A replace_global_number() 0 12 1
A meta_format_entry() 0 15 1
A prepare() 0 25 5
A replace_title() 0 17 1
B referencing_link() 0 39 5
A update_header_aliases() 0 14 2
A update_header_numbers() 0 15 3
A replace_local_number() 0 13 1
A meta_format_text() 0 15 1
F meta_levels() 62 89 17
A define() 0 41 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 _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
13
from panflute import (
14
    BlockQuote,
15
    BulletList,
16
    Citation,
17
    Cite,
18
    CodeBlock,
19
    Definition,
20
    DefinitionItem,
21
    DefinitionList,
22
    Div,
23
    Emph,
24
    Header,
25
    HorizontalRule,
26
    Image,
27
    LineBlock,
28
    LineBreak,
29
    LineItem,
30
    Link,
31
    ListItem,
32
    MetaBool,
33
    MetaInlines,
34
    MetaList,
35
    MetaMap,
36
    MetaString,
37
    Note,
38
    Para,
39
    Plain,
40
    RawBlock,
41
    RawInline,
42
    SoftBreak,
43
    Space,
44
    Span,
45
    Str,
46
    Strong,
47
    Table,
48
    TableCell,
49
    TableRow,
50
    convert_text,
51
    debug,
52
    run_filters,
53
    stringify,
54
)
55
56
57
# pylint: disable=bad-option-value,useless-object-inheritance
58
class Numbered:
59
    """
60
    Numbered elements.
61
    """
62
63
    # pylint: disable=too-many-instance-attributes
64
    __slots__ = [
65
        "_elem",
66
        "_doc",
67
        "_match",
68
        "_tag",
69
        "_entry",
70
        "_link",
71
        "_caption",
72
        "_title",
73
        "_description",
74
        "_category",
75
        "_basic_category",
76
        "_first_section_level",
77
        "_last_section_level",
78
        "_leading",
79
        "_number",
80
        "_global_number",
81
        "_section_number",
82
        "_local_number",
83
        "_section_alias",
84
        "_alias",
85
    ]
86
87
    @property
88
    def tag(self):
89
        """
90
        Get the tag property.
91
92
        Returns
93
        -------
94
            The tag property.
95
        """
96
        return self._tag
97
98
    @property
99
    def entry(self):
100
        """
101
        Get the entry property.
102
103
        Returns
104
        -------
105
            The entry property.
106
        """
107
        return self._entry
108
109
    @property
110
    def link(self):
111
        """
112
        Get the link property.
113
114
        Returns
115
        -------
116
            The link property.
117
        """
118
        return self._link
119
120
    @property
121
    def title(self):
122
        """
123
        Get the title property.
124
125
        Returns
126
        -------
127
            The title property.
128
        """
129
        return self._title
130
131
    @property
132
    def description(self):
133
        """
134
        Get the description property.
135
136
        Returns
137
        -------
138
            The description property.
139
        """
140
        return self._description
141
142
    @property
143
    def global_number(self):
144
        """
145
        Get the global_number property.
146
147
        Returns
148
        -------
149
            The global_number property.
150
        """
151
        return self._global_number
152
153
    @property
154
    def section_number(self):
155
        """
156
        Get the section_number property.
157
158
        Returns
159
        -------
160
            The section_number property.
161
        """
162
        return self._section_number
163
164
    @property
165
    def section_alias(self):
166
        """
167
        Get the section_alias property.
168
169
        Returns
170
        -------
171
            The section_alias property.
172
        """
173
        return self._section_alias
174
175
    @property
176
    def alias(self):
177
        """
178
        Get the alias property.
179
180
        Returns
181
        -------
182
            The alias property.
183
        """
184
        return self._alias
185
186
    @property
187
    def local_number(self):
188
        """
189
        Get the local_number property.
190
191
        Returns
192
        -------
193
            The local_number property.
194
        """
195
        return self._local_number
196
197
    @property
198
    def category(self):
199
        """
200
        Get the category property.
201
202
        Returns
203
        -------
204
            The category property.
205
        """
206
        return self._category
207
208
    @property
209
    def caption(self):
210
        """
211
        Get the caption property.
212
213
        Returns
214
        -------
215
            The caption property.
216
        """
217
        return self._caption
218
219
    number_regex = "#((?P<prefix>[a-zA-Z][\\w.-]*):)?(?P<name>[a-zA-Z][\\w:.-]*)?"
220
    _regex = "(?P<header>(?P<hidden>(-\\.)*)(\\+\\.)*)"
221
    header_regex = "^" + _regex + "$"
222
    marker_regex = "^" + _regex + number_regex + "$"
223
    double_sharp_regex = "^" + _regex + "#" + number_regex + "$"
224
225
    @staticmethod
226
    def _remove_accents(string):
227
        nfkd_form = unicodedata.normalize("NFKD", string)
228
        # pylint: disable=redundant-u-string-prefix
229
        return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
230
231
    @staticmethod
232
    def _identifier(string):
233
        # replace invalid characters by dash
234
        string = re.sub(
235
            "[^0-9a-zA-Z_-]+", "-", Numbered._remove_accents(string.lower())
236
        )
237
238
        # Remove leading digits
239
        return re.sub("^[^a-zA-Z]+", "", string)
240
241
    def __init__(self, elem, doc):
242
        """
243
        Initialise an instance.
244
245
        Arguments
246
        ---------
247
        elem
248
            An element.
249
        doc
250
            The document.
251
        """
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, description):
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, title):
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, section_number):
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, global_number):
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, local_number):
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, tag):
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, count):
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, _):
679
    """
680
    Clean up LaTeX element for entries.
681
682
    Arguments
683
    ---------
684
    elem
685
        elem to scan
686
687
    Returns
688
    -------
689
        []: if elem is an instance to remove
690
        None: otherwise
691
    """
692
    if isinstance(
693
        elem,
694
        (
695
            BlockQuote,
696
            BulletList,
697
            Citation,
698
            Cite,
699
            CodeBlock,
700
            Definition,
701
            DefinitionItem,
702
            DefinitionList,
703
            Div,
704
            Header,
705
            HorizontalRule,
706
            Image,
707
            LineBlock,
708
            LineBreak,
709
            LineItem,
710
            ListItem,
711
            Note,
712
            Para,
713
            RawBlock,
714
            RawInline,
715
            SoftBreak,
716
            Table,
717
            TableCell,
718
            TableRow,
719
        ),
720
    ):
721
        return []
722
    return None
723
724
725
def to_latex(elem):
726
    """
727
    Convert element to LaTeX.
728
729
    Arguments
730
    ---------
731
    elem
732
        elem to convert
733
734
    Returns
735
    -------
736
        LaTex string
737
    """
738
    return convert_text(
739
        run_filters([remove_useless_latex], doc=Plain(elem)),
740
        input_format="panflute",
741
        output_format="latex",
742
        extra_args=["--no-highlight"],
743
    )
744
745
746
def define(category, doc):
747
    """
748
    Define a category in document.
749
750
    Arguments
751
    ---------
752
    category
753
        category to define
754
    doc
755
        pandoc document
756
    """
757
    # pylint: disable=line-too-long
758
    doc.defined[category] = {
759
        "first-section-level": 0,
760
        "last-section-level": 0,
761
        "format-text-classic": [Strong(Str("%D"), Space(), Str("%n"))],
762
        "format-text-title": [
763
            Strong(Str("%D"), Space(), Str("%n")),
764
            Space(),
765
            Emph(Str("(%T)")),
766
        ],
767
        "format-link-classic": [Str("%D"), Space(), Str("%n")],
768
        "format-link-title": [Str("%D"), Space(), Str("%n"), Space(), Str("(%T)")],
769
        "format-caption-classic": "%D %n",
770
        "format-caption-title": "%D %n (%T)",
771
        "format-entry-title": [Str("%T")],
772
        "classes": [category],
773
        "cite-shortcut": True,
774
        "listing-title": None,
775
        "listing-unnumbered": True,
776
        "listing-unlisted": True,
777
        "listing-identifier": True,
778
        "entry-tab": 1.5,
779
        "entry-space": 2.3,
780
    }
781
    if doc.format == "latex":
782
        doc.defined[category]["format-entry-classic"] = [Str("%D")]
783
        doc.defined[category]["entry-tab"] = 1.5
784
        doc.defined[category]["entry-space"] = 2.3
785
    else:
786
        doc.defined[category]["format-entry-classic"] = [Str("%D"), Space(), Str("%g")]
787
788
789
def lowering(elem, _):
790
    """
791
    Lower element.
792
793
    Arguments
794
    ---------
795
    elem
796
        element to lower
797
    """
798
    if isinstance(elem, Str):
799
        elem.text = elem.text.lower()
800
801
802
def replacing(elem, _, search=None, replace=None):
803
    """
804
    Replace an element.
805
806
    Arguments
807
    ---------
808
    elem
809
        element to scan
810
    search
811
        string to search
812
    replace
813
        string to replace
814
815
    Returns
816
    -------
817
        The modified elements.
818
    """
819
    if isinstance(elem, Str):
820
        search_splitted = elem.text.split(search)
821
        if len(search_splitted) > 1:
822
            text = []
823
824
            if search_splitted[0] != "":
825
                text.append(Str(search_splitted[0]))
826
827
            for string in search_splitted[1:]:
828
                text.extend(replace)
829
                if string != "":
830
                    text.append(Str(string))
831
832
            return text
833
834
    return [elem]
835
836
837
def numbering(elem, doc):
838
    """
839
    Add the numbering of an element.
840
841
    Arguments
842
    ---------
843
    elem
844
        element to number
845
    doc
846
        pandoc document
847
    """
848
    if isinstance(elem, Header):
849
        update_header_numbers(elem, doc)
850
        update_header_aliases(elem, doc)
851
    elif isinstance(elem, (Para, DefinitionItem)):
852
        numbered = Numbered(elem, doc)
853
        if numbered.tag is not None:
854
            doc.information[numbered.tag] = numbered
855
856
857
def referencing(elem, doc):
858
    """
859
    Add a reference for an element.
860
861
    Arguments
862
    ---------
863
    elem
864
        element to reference
865
    doc
866
        pandoc document
867
868
    Returns
869
    -------
870
        A Link or None
871
    """
872
    if isinstance(elem, Link):
873
        return referencing_link(elem, doc)
874
    if isinstance(elem, Cite):
875
        return referencing_cite(elem, doc)
876
    if isinstance(elem, Span) and elem.identifier in doc.information:
877
        replace_count(elem, str(doc.count[doc.information[elem.identifier].category]))
878
    return None
879
880
881
def referencing_link(elem, doc):
882
    """
883
    Add a eference link.
884
885
    Arguments
886
    ---------
887
    elem
888
        element to reference
889
    doc
890
        pandoc document
891
    """
892
    match = re.match("^#(?P<tag>([a-zA-Z][\\w:.-]*))$", elem.url)
893
    if match:
894
        tag = match.group("tag")
895
        if tag in doc.information:
896
            replace_title(elem, doc.information[tag].title)
897
            replace_description(elem, doc.information[tag].description)
898
            replace_global_number(elem, doc.information[tag].global_number)
899
            replace_section_number(elem, doc.information[tag].section_number)
900
            replace_local_number(elem, doc.information[tag].local_number)
901
            replace_count(elem, str(doc.count[doc.information[tag].category]))
902
            if doc.format in {"tex", "latex"}:
903
                replace_page_number(elem, tag)
904
905
            title = stringify(Span(*doc.information[tag].title))
906
            description = stringify(Span(*doc.information[tag].description))
907
            elem.title = elem.title.replace("%t", title.lower())
908
            elem.title = elem.title.replace("%T", title)
909
            elem.title = elem.title.replace("%d", description.lower())
910
            elem.title = elem.title.replace("%D", description)
911
            elem.title = elem.title.replace("%s", doc.information[tag].section_number)
912
            elem.title = elem.title.replace("%g", doc.information[tag].global_number)
913
            elem.title = elem.title.replace("%n", doc.information[tag].local_number)
914
            elem.title = elem.title.replace("#", doc.information[tag].local_number)
915
            elem.title = elem.title.replace(
916
                "%c", str(doc.count[doc.information[tag].category])
917
            )
918
            if doc.format in {"tex", "latex"}:
919
                elem.title = elem.title.replace("%p", "\\pageref{" + tag + "}")
920
921
922
def referencing_cite(elem, doc):
923
    """
924
    Cite reference.
925
926
    Arguments
927
    ---------
928
    elem
929
        element to reference
930
    doc
931
        pandoc document
932
933
    Returns
934
    -------
935
        A Link or None
936
    """
937
    if len(elem.content) == 1 and isinstance(elem.content[0], Str):
938
        match = re.match(
939
            "^(@(?P<tag>(?P<category>[a-zA-Z][\\w.-]*):"
940
            "(([a-zA-Z][\\w.-]*)|(\\d*(\\.\\d*)*))))$",
941
            elem.content[0].text,
942
        )
943
        if match:
944
            category = match.group("category")
945
            if category in doc.defined and doc.defined[category]["cite-shortcut"]:
946
                # Deal with @prefix:name shortcut
947
                tag = match.group("tag")
948
                if tag in doc.information:
949
                    ret = Link(
950
                        doc.information[tag].link,
951
                        url="#" + tag,
952
                        title=doc.information[tag].caption.replace(
953
                            "%c", str(doc.count[doc.information[tag].category])
954
                        ),
955
                    )
956
                    replace_count(ret, str(doc.count[doc.information[tag].category]))
957
                    return ret
958
    return None
959
960
961
def update_header_numbers(elem, doc):
962
    """
963
    Update header numbers.
964
965
    Arguments
966
    ---------
967
    elem
968
        element to update
969
    doc
970
        pandoc document
971
    """
972
    if "unnumbered" not in elem.classes:
973
        doc.headers[elem.level - 1] = doc.headers[elem.level - 1] + 1
974
        for index in range(elem.level, 6):
975
            doc.headers[index] = 0
976
977
978
def update_header_aliases(elem, doc):
979
    """
980
    Update header aliases.
981
982
    Arguments
983
    ---------
984
    elem
985
        element to update
986
    doc
987
        pandoc document
988
    """
989
    doc.aliases[elem.level - 1] = elem.identifier
990
    for index in range(elem.level, 6):
991
        doc.aliases[index] = ""
992
993
994
def prepare(doc):
995
    """
996
    Prepare document.
997
998
    Arguments
999
    ---------
1000
    doc
1001
        pandoc document
1002
    """
1003
    doc.headers = [0, 0, 0, 0, 0, 0]
1004
    doc.aliases = ["", "", "", "", "", ""]
1005
    doc.information = {}
1006
    doc.defined = {}
1007
1008
    if "pandoc-numbering" in doc.metadata.content and isinstance(
1009
        doc.metadata.content["pandoc-numbering"], MetaMap
1010
    ):
1011
        for category, definition in doc.metadata.content[
1012
            "pandoc-numbering"
1013
        ].content.items():
1014
            if isinstance(definition, MetaMap):
1015
                add_definition(category, definition, doc)
1016
1017
    doc.count = {}
1018
    doc.collections = {}
1019
1020
1021
def add_definition(category, definition, doc):
1022
    """
1023
    Add definition for a category.
1024
1025
    Arguments
1026
    ---------
1027
    category
1028
        The category
1029
    definition
1030
        The definition
1031
    doc
1032
        The pandoc document
1033
    """
1034
    # Create the category with options by default
1035
    define(category, doc)
1036
1037
    # Detect general options
1038
    if "general" in definition:
1039
        meta_cite(category, definition["general"], doc.defined)
1040
        meta_listing(category, definition["general"], doc.defined)
1041
        meta_levels(category, definition["general"], doc.defined)
1042
        meta_classes(category, definition["general"], doc.defined)
1043
1044
    # Detect LaTeX options
1045
    if doc.format in {"tex", "latex"}:
1046
        if "latex" in definition:
1047
            meta_format_text(category, definition["latex"], doc.defined)
1048
            meta_format_link(category, definition["latex"], doc.defined)
1049
            meta_format_caption(category, definition["latex"], doc.defined)
1050
            meta_format_entry(category, definition["latex"], doc.defined)
1051
            meta_entry_tab(category, definition["latex"], doc.defined)
1052
            meta_entry_space(category, definition["latex"], doc.defined)
1053
    # Detect standard options
1054
    else:
1055
        if "standard" in definition:
1056
            meta_format_text(category, definition["standard"], doc.defined)
1057
            meta_format_link(category, definition["standard"], doc.defined)
1058
            meta_format_caption(category, definition["standard"], doc.defined)
1059
            meta_format_entry(category, definition["standard"], doc.defined)
1060
1061
1062
def meta_cite(category, definition, defined):
1063
    """
1064
    Compute cite for a category.
1065
1066
    Arguments
1067
    ---------
1068
    category
1069
        The category
1070
    definition
1071
        The definition
1072
    defined
1073
        The defined parameter
1074
    """
1075
    if "cite-shortcut" in definition:
1076
        if isinstance(definition["cite-shortcut"], MetaBool):
1077
            defined[category]["cite-shortcut"] = definition["cite-shortcut"].boolean
1078
        else:
1079
            debug(
1080
                "[WARNING] pandoc-numbering: cite-shortcut is not correct for category "
1081
                + category
1082
            )
1083
1084
1085
# pylint:disable=too-many-branches
1086
def meta_listing(category, definition, defined):
1087
    """
1088
    Compute listing for a category.
1089
1090
    Arguments
1091
    ---------
1092
    category
1093
        The category
1094
    definition
1095
        The definition
1096
    defined
1097
        The defined parameter
1098
    """
1099 View Code Duplication
    if "listing-title" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1100
        if isinstance(definition["listing-title"], MetaInlines):
1101
            defined[category]["listing-title"] = definition["listing-title"].content
1102
            # Detach from original parent
1103
            defined[category]["listing-title"].parent = None
1104
        else:
1105
            debug(
1106
                "[WARNING] pandoc-numbering: listing-title is not correct for category "
1107
                + category
1108
            )
1109
    for key in ("listing-unnumbered", "listing-unlisted"):
1110
        if key in definition:
1111
            if isinstance(definition[key], MetaBool):
1112
                defined[category][key] = definition[key].boolean
1113
            else:
1114
                debug(
1115
                    f"[WARNING] pandoc-numbering: "
1116
                    f"{key} is not correct for category {category}"
1117
                )
1118
    if "listing-identifier" in definition:
1119
        if isinstance(definition["listing-identifier"], MetaBool):
1120
            defined[category]["listing-identifier"] = definition[
1121
                "listing-identifier"
1122
            ].boolean
1123
        elif (
1124
            isinstance(definition["listing-identifier"], MetaInlines)
1125
            and len(definition["listing-identifier"].content) == 1
1126
            and isinstance(definition["listing-identifier"].content[0], Str)
1127
        ):
1128
            defined[category]["listing-identifier"] = (
1129
                definition["listing-identifier"].content[0].text
1130
            )
1131
        else:
1132
            debug(
1133
                "[WARNING] pandoc-numbering: "
1134
                "listing-identifier is not correct for category " + category
1135
            )
1136
1137
1138
def meta_format(category, definition, defined, tag):
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 View Code Duplication
    if tag in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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
def meta_format_text(category, definition, defined):
1166
    """
1167
    Compute format text for a category.
1168
1169
    Arguments
1170
    ---------
1171
    category
1172
        The category
1173
    definition
1174
        The definition
1175
    defined
1176
        The defined parameter
1177
    """
1178
    meta_format(category, definition, defined, "format-text-classic")
1179
    meta_format(category, definition, defined, "format-text-title")
1180
1181
1182
def meta_format_link(category, definition, defined):
1183
    """
1184
    Compute format link for a category.
1185
1186
    Arguments
1187
    ---------
1188
    category
1189
        The category
1190
    definition
1191
        The definition
1192
    defined
1193
        The defined parameter
1194
    """
1195
    meta_format(category, definition, defined, "format-link-classic")
1196
    meta_format(category, definition, defined, "format-link-title")
1197
1198
1199
def meta_format_entry(category, definition, defined):
1200
    """
1201
    Compute format entry for a category.
1202
1203
    Arguments
1204
    ---------
1205
    category
1206
        The category
1207
    definition
1208
        The definition
1209
    defined
1210
        The defined parameter
1211
    """
1212
    meta_format(category, definition, defined, "format-entry-classic")
1213
    meta_format(category, definition, defined, "format-entry-title")
1214
1215
1216
def meta_format_caption(category, definition, defined):
1217
    """
1218
    Compute format caption for a category.
1219
1220
    Arguments
1221
    ---------
1222
    category
1223
        The category
1224
    definition
1225
        The definition
1226
    defined
1227
        The defined parameter
1228
    """
1229
    for tag in ("format-caption-classic", "format-caption-title"):
1230
        if tag in definition:
1231
            if isinstance(definition[tag], MetaInlines):
1232
                defined[category][tag] = stringify(definition[tag])
1233
            else:
1234
                debug(
1235
                    f"[WARNING] pandoc-numbering: "
1236
                    f"{tag} is not correct for category {category}"
1237
                )
1238
1239
1240
def meta_entry(category, definition, defined, tag):
1241
    """
1242
    Compute entry tab for a category.
1243
1244
    Arguments
1245
    ---------
1246
    category
1247
        The category
1248
    definition
1249
        The definition
1250
    defined
1251
        The defined parameter
1252
    tag
1253
        The tag parameter
1254
    """
1255
    if tag in definition:
1256
        if isinstance(definition[tag], MetaString):
1257
            value = definition[tag].text
1258
        elif (
1259
            isinstance(definition[tag], MetaInlines)
1260
            and len(definition[tag].content) == 1
1261
        ):
1262
            value = definition[tag].content[0].text
1263
        else:
1264
            debug(
1265
                f"[WARNING] pandoc-numbering: "
1266
                f"{tag} is not correct for category {category}"
1267
            )
1268
            return
1269
        # Get the element
1270
        try:
1271
            element = float(value)
1272
            if element > 0:
1273
                defined[category][tag] = element
1274
            else:
1275
                debug(
1276
                    f"[WARNING] pandoc-numbering: "
1277
                    f"{tag} must be positive for category {category}"
1278
                )
1279
        except ValueError:
1280
            debug(
1281
                f"[WARNING] pandoc-numbering: "
1282
                f"{tag} is not correct for category {category}"
1283
            )
1284
1285
1286
def meta_entry_tab(category, definition, defined):
1287
    """
1288
    Compute entry tab for a category.
1289
1290
    Arguments
1291
    ---------
1292
    category
1293
        The category
1294
    definition
1295
        The definition
1296
    defined
1297
        The defined parameter
1298
    """
1299
    meta_entry(category, definition, defined, "entry-tab")
1300
1301
1302
def meta_entry_space(category, definition, defined):
1303
    """
1304
    Compute entry space for a category.
1305
1306
    Arguments
1307
    ---------
1308
    category
1309
        The category
1310
    definition
1311
        The definition
1312
    defined
1313
        The defined parameter
1314
    """
1315
    meta_entry(category, definition, defined, "entry-space")
1316
1317
1318
def meta_levels(category, definition, defined):
1319
    """
1320
    Compute level for a category.
1321
1322
    Arguments
1323
    ---------
1324
    category
1325
        The category
1326
    definition
1327
        The definition
1328
    defined
1329
        The defined parameter
1330
    """
1331
    if (
1332
        "sectioning-levels" in definition
1333
        and isinstance(definition["sectioning-levels"], MetaInlines)
1334
        and len(definition["sectioning-levels"].content) == 1
1335
    ):
1336
        match = re.match(
1337
            Numbered.header_regex, definition["sectioning-levels"].content[0].text
1338
        )
1339
        if match:
1340
            # Compute the first and last levels section
1341
            defined[category]["first-section-level"] = len(match.group("hidden")) // 2
1342
            defined[category]["last-section-level"] = len(match.group("header")) // 2
1343 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...
1344
        if isinstance(definition["first-section-level"], MetaString):
1345
            value = definition["first-section-level"].text
1346
        elif (
1347
            isinstance(definition["first-section-level"], MetaInlines)
1348
            and len(definition["first-section-level"].content) == 1
1349
        ):
1350
            value = definition["first-section-level"].content[0].text
1351
        else:
1352
            debug(
1353
                "[WARNING] pandoc-numbering: "
1354
                "first-section-level is not correct for category " + category
1355
            )
1356
            return
1357
1358
        # Get the level
1359
        try:
1360
            level = int(value) - 1
1361
        except ValueError:
1362
            debug(
1363
                "[WARNING] pandoc-numbering: "
1364
                "first-section-level is not correct for category " + category
1365
            )
1366
1367
        if 0 <= level <= 6:
1368
            defined[category]["first-section-level"] = level
1369
        else:
1370
            # pylint: disable=line-too-long
1371
            debug(
1372
                "[WARNING] pandoc-numbering: "
1373
                "first-section-level must be positive or zero for category " + category
1374
            )
1375
1376 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...
1377
        if isinstance(definition["last-section-level"], MetaString):
1378
            value = definition["last-section-level"].text
1379
        elif (
1380
            isinstance(definition["last-section-level"], MetaInlines)
1381
            and len(definition["last-section-level"].content) == 1
1382
        ):
1383
            value = definition["last-section-level"].content[0].text
1384
        else:
1385
            debug(
1386
                "[WARNING] pandoc-numbering: "
1387
                "last-section-level is not correct for category " + category
1388
            )
1389
            return
1390
1391
        # Get the level
1392
        try:
1393
            level = int(value)
1394
        except ValueError:
1395
            debug(
1396
                "[WARNING] pandoc-numbering: "
1397
                "last-section-level is not correct for category " + category
1398
            )
1399
1400
        if 0 <= level <= 6:
1401
            defined[category]["last-section-level"] = level
1402
        else:
1403
            # pylint: disable=line-too-long
1404
            debug(
1405
                "[WARNING] pandoc-numbering: "
1406
                "last-section-level must be positive or zero for category " + category
1407
            )
1408
1409
1410
def meta_classes(category, definition, defined):
1411
    """
1412
    Compute classes for a category.
1413
1414
    Arguments
1415
    ---------
1416
    category
1417
        The category
1418
    definition
1419
        The definition
1420
    defined
1421
        The defined parameter
1422
    """
1423
    if "classes" in definition and isinstance(definition["classes"], MetaList):
1424
        defined[category]["classes"] = [
1425
            stringify(elt) for elt in definition["classes"].content
1426
        ]
1427
1428
1429
def finalize(doc):
1430
    """
1431
    Finalize document.
1432
1433
    Arguments
1434
    ---------
1435
    doc
1436
        The pandoc document
1437
    """
1438
    # Loop on all listings definition
1439
1440
    if doc.format in {"tex", "latex"}:
1441
        # Add header-includes if necessary
1442
        if "header-includes" not in doc.metadata:
1443
            doc.metadata["header-includes"] = MetaList()
1444
        # Convert header-includes to MetaList if necessary
1445
        elif not isinstance(doc.metadata["header-includes"], MetaList):
1446
            doc.metadata["header-includes"] = MetaList(doc.metadata["header-includes"])
1447
1448
        doc.metadata["header-includes"].append(
1449
            MetaInlines(
1450
                RawInline(
1451
                    dedent(
1452
                        r"""
1453
                        \makeatletter
1454
                        \@ifpackageloaded{subfig}{
1455
                            \usepackage[subfigure]{tocloft}
1456
                        }{
1457
                            \usepackage{tocloft}
1458
                        }
1459
                        \makeatother
1460
                        """
1461
                    ),
1462
                    "tex",
1463
                )
1464
            )
1465
        )
1466
        doc.metadata["header-includes"].append(
1467
            MetaInlines(RawInline(r"\usepackage{etoolbox}", "tex"))
1468
        )
1469
1470
    i = 0
1471
    listof = []
1472
    for category, definition in doc.defined.items():
1473
        if definition["listing-title"] is not None:
1474
            # pylint: disable=consider-using-f-string
1475
            if doc.format in {"tex", "latex"}:
1476
                latex_category = re.sub("[^a-z]+", "", category)
1477
                latex = (
1478
                    r"\newlistof{%s}{%s}{%s}"
1479
                    r"\renewcommand{\cft%stitlefont}{\cfttoctitlefont}"
1480
                    r"\setlength{\cft%snumwidth}{\cftfignumwidth}"
1481
                    r"\setlength{\cft%sindent}{\cftfigindent}"
1482
                    % (
1483
                        latex_category,
1484
                        latex_category,
1485
                        convert_text(
1486
                            Plain(*definition["listing-title"]),
1487
                            input_format="panflute",
1488
                            output_format="latex",
1489
                        ),
1490
                        latex_category,
1491
                        latex_category,
1492
                        latex_category,
1493
                    )
1494
                )
1495
                doc.metadata["header-includes"].append(
1496
                    MetaInlines(RawInline(latex, "tex"))
1497
                )
1498
                listof.append(f"\\listof{latex_category}")
1499
            else:
1500
                classes = ["pandoc-numbering-listing"] + definition["classes"]
1501
1502
                if definition["listing-unnumbered"]:
1503
                    classes.append("unnumbered")
1504
1505
                if definition["listing-unlisted"]:
1506
                    classes.append("unlisted")
1507
1508
                if definition["listing-identifier"] is False:
1509
                    header = Header(
1510
                        *definition["listing-title"], level=1, classes=classes
1511
                    )
1512
                elif definition["listing-identifier"] is True:
1513
                    header = Header(
1514
                        *definition["listing-title"], level=1, classes=classes
1515
                    )
1516
                    header = convert_text(
1517
                        convert_text(
1518
                            header, input_format="panflute", output_format="markdown"
1519
                        ),
1520
                        output_format="panflute",
1521
                    )[0]
1522
                else:
1523
                    header = Header(
1524
                        *definition["listing-title"],
1525
                        level=1,
1526
                        classes=classes,
1527
                        identifier=definition["listing-identifier"],
1528
                    )
1529
1530
                doc.content.insert(i, header)
1531
                i = i + 1
1532
1533
                table = table_other(doc, category, definition)
1534
1535
                if table:
1536
                    doc.content.insert(i, table)
1537
                    i = i + 1
1538
1539
    if doc.format in {"tex", "latex"}:
1540
        header = (
1541
            r"\ifdef{\mainmatter}"
1542
            r"{\let\oldmainmatter\mainmatter"
1543
            r"\renewcommand{\mainmatter}[0]{%s\oldmainmatter}}"
1544
            r"{}"
1545
        )
1546
        doc.metadata["header-includes"].append(
1547
            MetaInlines(RawInline(header % "\n".join(listof), "tex"))
1548
        )
1549
1550
        latex = r"\ifdef{\mainmatter}{}{%s}"
1551
        doc.content.insert(0, RawBlock(latex % "\n".join(listof), "tex"))
1552
1553
1554
def table_other(doc, category, _):
1555
    """
1556
    Compute other code for table.
1557
1558
    Arguments
1559
    ---------
1560
    doc
1561
        pandoc document
1562
    category
1563
        category numbered
1564
1565
    Returns
1566
    -------
1567
        A BulletList or None
1568
    """
1569
    if category in doc.collections:
1570
        # Return a bullet list
1571
        return BulletList(
1572
            *(
1573
                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...
1574
                for tag in doc.collections[category]
1575
            )
1576
        )
1577
    return None
1578
1579
1580
def link_color(doc):
1581
    """
1582
    Compute LaTeX code for toc.
1583
1584
    Arguments
1585
    ---------
1586
    doc
1587
        pandoc document
1588
1589
    Returns
1590
    -------
1591
        LaTeX code for links.
1592
    """
1593
    # Get the link color
1594
    metadata = doc.get_metadata()
1595
    if "toccolor" in metadata:
1596
        return "\\hypersetup{linkcolor=" + str(metadata["toccolor"]) + "}"
1597
    return "\\hypersetup{linkcolor=black}"
1598
1599
1600
def main(doc=None) -> None:
1601
    """
1602
    Produce the final document.
1603
1604
    Parameters
1605
    ----------
1606
    doc
1607
        pandoc document
1608
    """
1609
    run_filters([numbering, referencing], prepare=prepare, doc=doc, finalize=finalize)
1610
1611
1612
if __name__ == "__main__":
1613
    main()
1614