pandoc_latex_admonition._main.define_environment()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 58
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 58
rs 9.45
c 0
b 0
f 0
cc 1
nop 9

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
#!/usr/bin/env python
2
3
"""
4
Pandoc filter for adding admonition in LaTeX.
5
"""
6
7
import uuid
8
from typing import Any
9
10
from panflute import (
11
    Doc,
12
    Element,
13
    Figure,
14
    MetaBool,
15
    MetaInlines,
16
    MetaList,
17
    Note,
18
    RawBlock,
19
    RawInline,
20
    convert_text,
21
    debug,
22
    run_filter,
23
)
24
25
26
def default_environment() -> dict[str, Any]:
27
    """
28
    Get the default environment.
29
30
    Returns
31
    -------
32
    dict[str, Any]
33
        The default environment
34
    """
35
    return {
36
        "env": "env-" + str(uuid.uuid4()),
37
        "color": "black",
38
        "position": "left",
39
        "linewidth": 2,
40
        "margin": -4,
41
        "innermargin": 5,
42
        "localfootnotes": False,
43
        "nobreak": False,
44
    }
45
46
47
def x11colors() -> dict[str, str]:
48
    """
49
    Get the x11 colors.
50
51
    Returns
52
    -------
53
    dict[str, str]
54
        The x11 colors
55
    """
56
    # See https://www.w3.org/TR/css-color-3/#svg-color
57
    return {
58
        "aliceblue": "F0F8FF",
59
        "antiquewhite": "FAEBD7",
60
        "aqua": "00FFFF",
61
        "aquamarine": "7FFFD4",
62
        "azure": "F0FFFF",
63
        "beige": "F5F5DC",
64
        "bisque": "FFE4C4",
65
        "black": "000000",
66
        "blanchedalmond": "FFEBCD",
67
        "blue": "0000FF",
68
        "blueviolet": "8A2BE2",
69
        "brown": "A52A2A",
70
        "burlywood": "DEB887",
71
        "cadetblue": "5F9EA0",
72
        "chartreuse": "7FFF00",
73
        "chocolate": "D2691E",
74
        "coral": "FF7F50",
75
        "cornflowerblue": "6495ED",
76
        "cornsilk": "FFF8DC",
77
        "crimson": "DC143C",
78
        "cyan": "00FFFF",
79
        "darkblue": "00008B",
80
        "darkcyan": "008B8B",
81
        "darkgoldenrod": "B8860B",
82
        "darkgray": "A9A9A9",
83
        "darkgreen": "006400",
84
        "darkgrey": "A9A9A9",
85
        "darkkhaki": "BDB76B",
86
        "darkmagenta": "8B008B",
87
        "darkolivegreen": "556B2F",
88
        "darkorange": "FF8C00",
89
        "darkorchid": "9932CC",
90
        "darkred": "8B0000",
91
        "darksalmon": "E9967A",
92
        "darkseagreen": "8FBC8F",
93
        "darkslateblue": "483D8B",
94
        "darkslategray": "2F4F4F",
95
        "darkslategrey": "2F4F4F",
96
        "darkturquoise": "00CED1",
97
        "darkviolet": "9400D3",
98
        "deeppink": "FF1493",
99
        "deepskyblue": "00BFFF",
100
        "dimgray": "696969",
101
        "dimgrey": "696969",
102
        "dodgerblue": "1E90FF",
103
        "firebrick": "B22222",
104
        "floralwhite": "FFFAF0",
105
        "forestgreen": "228B22",
106
        "fuchsia": "FF00FF",
107
        "gainsboro": "DCDCDC",
108
        "ghostwhite": "F8F8FF",
109
        "gold": "FFD700",
110
        "goldenrod": "DAA520",
111
        "gray": "808080",
112
        "green": "008000",
113
        "greenyellow": "ADFF2F",
114
        "grey": "808080",
115
        "honeydew": "F0FFF0",
116
        "hotpink": "FF69B4",
117
        "indianred": "CD5C5C",
118
        "indigo": "4B0082",
119
        "ivory": "FFFFF0",
120
        "khaki": "F0E68C",
121
        "lavender": "E6E6FA",
122
        "lavenderblush": "FFF0F5",
123
        "lawngreen": "7CFC00",
124
        "lemonchiffon": "FFFACD",
125
        "lightblue": "ADD8E6",
126
        "lightcoral": "F08080",
127
        "lightcyan": "E0FFFF",
128
        "lightgoldenrodyellow": "FAFAD2",
129
        "lightgray": "D3D3D3",
130
        "lightgreen": "90EE90",
131
        "lightgrey": "D3D3D3",
132
        "lightpink": "FFB6C1",
133
        "lightsalmon": "FFA07A",
134
        "lightseagreen": "20B2AA",
135
        "lightskyblue": "87CEFA",
136
        "lightslategray": "778899",
137
        "lightslategrey": "778899",
138
        "lightsteelblue": "B0C4DE",
139
        "lightyellow": "FFFFE0",
140
        "lime": "00FF00",
141
        "limegreen": "32CD32",
142
        "linen": "FAF0E6",
143
        "magenta": "FF00FF",
144
        "maroon": "800000",
145
        "mediumaquamarine": "66CDAA",
146
        "mediumblue": "0000CD",
147
        "mediumorchid": "BA55D3",
148
        "mediumpurple": "9370DB",
149
        "mediumseagreen": "3CB371",
150
        "mediumslateblue": "7B68EE",
151
        "mediumspringgreen": "00FA9A",
152
        "mediumturquoise": "48D1CC",
153
        "mediumvioletred": "C71585",
154
        "midnightblue": "191970",
155
        "mintcream": "F5FFFA",
156
        "mistyrose": "FFE4E1",
157
        "moccasin": "FFE4B5",
158
        "navajowhite": "FFDEAD",
159
        "navy": "000080",
160
        "oldlace": "FDF5E6",
161
        "olive": "808000",
162
        "olivedrab": "6B8E23",
163
        "orange": "FFA500",
164
        "orangered": "FF4500",
165
        "orchid": "DA70D6",
166
        "palegoldenrod": "EEE8AA",
167
        "palegreen": "98FB98",
168
        "paleturquoise": "AFEEEE",
169
        "palevioletred": "DB7093",
170
        "papayawhip": "FFEFD5",
171
        "peachpuff": "FFDAB9",
172
        "peru": "CD853F",
173
        "pink": "FFC0CB",
174
        "plum": "DDA0DD",
175
        "powderblue": "B0E0E6",
176
        "purple": "800080",
177
        "red": "FF0000",
178
        "rosybrown": "BC8F8F",
179
        "royalblue": "4169E1",
180
        "saddlebrown": "8B4513",
181
        "salmon": "FA8072",
182
        "sandybrown": "F4A460",
183
        "seagreen": "2E8B57",
184
        "seashell": "FFF5EE",
185
        "sienna": "A0522D",
186
        "silver": "C0C0C0",
187
        "skyblue": "87CEEB",
188
        "slateblue": "6A5ACD",
189
        "slategray": "708090",
190
        "slategrey": "708090",
191
        "snow": "FFFAFA",
192
        "springgreen": "00FF7F",
193
        "steelblue": "4682B4",
194
        "tan": "D2B48C",
195
        "teal": "008080",
196
        "thistle": "D8BFD8",
197
        "tomato": "FF6347",
198
        "turquoise": "40E0D0",
199
        "violet": "EE82EE",
200
        "wheat": "F5DEB3",
201
        "white": "FFFFFF",
202
        "whitesmoke": "F5F5F5",
203
        "yellow": "FFFF00",
204
        "yellowgreen": "9ACD32",
205
    }
206
207
208
# pylint: disable=inconsistent-return-statements
209
def admonition(elem: Element, doc: Doc) -> Element | None:
210
    """
211
    Add admonition to elem.
212
213
    Arguments
214
    ---------
215
    elem
216
        The current element
217
    doc
218
        The pandoc document
219
220
    Returns
221
    -------
222
    Element | None
223
        The modified element or None
224
    """
225
    # Is it in the right format and is it Div or a CodeBlock?
226
    if doc.format in ("latex", "beamer") and elem.tag in ("Div", "CodeBlock"):
227
        # Is there a latex-admonition-color attribute?
228
        if "latex-admonition-color" in elem.attributes:
229
            environment = define_environment(
230
                doc,
231
                elem.attributes,
232
                "latex-admonition-color",
233
                "latex-admonition-position",
234
                "latex-admonition-linewidth",
235
                "latex-admonition-margin",
236
                "latex-admonition-innermargin",
237
                "latex-admonition-localfootnotes",
238
                "latex-admonition-nobreak",
239
            )
240
            doc.added.append(environment)
241
            return add_latex(elem, environment)
242
        # Get the classes
243
        classes = set(elem.classes)
244
245
        # Loop on all font size definition
246
        for environment in doc.defined:
247
            # Are the classes correct?
248
            if any(classes >= defined for defined in environment["classes"]):
249
                return add_latex(elem, environment)
250
    return None
251
252
253
def add_latex(elem: Element, environment: dict[str, Any]) -> Element | None:
254
    """
255
    Add LaTeX code to the element.
256
257
    Arguments
258
    ---------
259
    elem
260
        The current element
261
262
    environment
263
        The environment to add
264
265
    Returns
266
    -------
267
    Element | None
268
        The modified element
269
    """
270
271
    def note(element, doc):
272
        if (
273
            isinstance(element, Note)
274
            and doc.format == "beamer"
275
            and not environment["localfootnotes"]
276
        ):
277
            return RawInline(
278
                "".join(
279
                    [
280
                        "\\footnote<.->[frame]{",
281
                        convert_text(
282
                            element.content,
283
                            input_format="panflute",
284
                            output_format="latex",
285
                        ),
286
                        "}",
287
                    ]
288
                ),
289
                "tex",
290
            )
291
        return None
292
293
    if (
294
        elem.tag == "Div"
295
        and elem.content
296
        and elem.content[0].tag == "Div"
297
        and "title" in elem.content[0].classes
298
        and elem.content[0].content[0].tag == "Para"
299
    ):
300
        elem.content[0].content[0].content.insert(
301
            0,
302
            RawInline(f"\\textbf{{\\color{{{environment['color']}}}", "tex"),
303
        )
304
        elem.content[0].content[0].content.append(RawInline("}", "tex"))
305
306
    images = []
307
308
    def extract_images(element, _doc):
309
        # Extract image which is alone with a title
310
        if isinstance(element, Figure) and len(element.content) == 1:
311
            images.append(element)
312
            return []
313
        return None
314
315
    # The images need to be placed after the framed environment
316
    return [
317
        RawBlock("\\begin{" + environment["env"] + "}", "tex"),
318
        elem.walk(extract_images).walk(note),
319
        RawBlock("\\end{" + environment["env"] + "}", "tex"),
320
    ] + images
321
322
323
def prepare(doc: Doc) -> None:
324
    """
325
    Prepare the document.
326
327
    Arguments
328
    ---------
329
    doc
330
        The pandoc document
331
    """
332
    doc.x11colors = x11colors()
333
334
    # Prepare the definitions
335
    doc.defined = []
336
    doc.added = []
337
338
    # Get the meta data
339
    meta = doc.get_metadata("pandoc-latex-admonition")
340
341
    # pylint: disable=too-many-nested-blocks
342
    if isinstance(meta, list):
343
        # Loop on all definitions
344
        for definition in meta:
345
            # Verify the definition
346
            if (
347
                isinstance(definition, dict)
348
                and "classes" in definition
349
                and isinstance(definition["classes"], list)
350
            ):
351
                environment = define_environment(
352
                    doc,
353
                    definition,
354
                    "color",
355
                    "position",
356
                    "linewidth",
357
                    "margin",
358
                    "innermargin",
359
                    "localfootnotes",
360
                    "nobreak",
361
                )
362
                classes = []
363
                if all(isinstance(elem, str) for elem in definition["classes"]):
364
                    classes.append(set(definition["classes"]))
365
                else:
366
                    for elem in definition["classes"]:
367
                        if isinstance(elem, str):
368
                            classes.append({elem})
369
                        else:
370
                            classes.append({str(x) for x in elem})
371
                environment["classes"] = classes
372
                doc.defined.append(environment)
373
374
375
# pylint: disable=too-many-arguments,too-many-positional-arguments
376
def define_environment(
377
    doc: Doc,
378
    definition: dict[str, str],
379
    key_color: str,
380
    key_position: str,
381
    key_linewidth: str,
382
    key_margin: str,
383
    key_innermargin: str,
384
    key_localfootnotes: str,
385
    key_nobreak: str,
386
) -> dict[str, Any]:
387
    """
388
    Define a new environment.
389
390
    Arguments
391
    ---------
392
    doc
393
        The pandoc document
394
395
    definition
396
        The definition
397
398
    key_color
399
        The color key
400
401
    key_position
402
        The position key
403
404
    key_linewidth
405
        The linewidth key
406
407
    key_margin
408
        The margin key
409
410
    key_innermargin
411
        The innermargin key
412
413
    key_localfootnotes
414
        The localfootnotes key
415
416
    key_nobreak
417
        The nobreak key
418
419
    Returns
420
    -------
421
    dict[str, Any]
422
        A new environment
423
    """
424
    # Get the default environment
425
    environment = default_environment()
426
    define_color(environment, definition, key_color, doc=doc)
427
    define_position(environment, definition, key_position)
428
    define_linewidth(environment, definition, key_linewidth)
429
    define_margin(environment, definition, key_margin)
430
    define_innermargin(environment, definition, key_innermargin)
431
    define_localfootnotes(environment, definition, key_localfootnotes)
432
    define_nobreak(environment, definition, key_nobreak)
433
    return environment
434
435
436
def define_color(
437
    environment: dict[str, Any], definition: dict[str, str], key_color: str, doc: Doc
438
):
439
    """
440
    Define the color.
441
442
    Arguments
443
    ---------
444
    environment
445
        The environment
446
447
    definition
448
        The definition
449
450
    key_color
451
        The color key
452
453
    doc
454
        The pandoc document
455
    """
456
    if key_color in definition:
457
        color = definition[key_color].lower()
458
        if color in doc.x11colors:
459
            environment["color"] = color
460
        else:
461
            # color must be a valid x11 color
462
            # See https://www.w3.org/TR/css-color-3/#svg-color
463
            debug(
464
                "[WARNING] pandoc-latex-admonition: "
465
                + color
466
                + " is not a valid x11 color; using "
467
                + environment["color"]
468
            )
469
470
471
def define_position(
472
    environment: dict[str, Any], definition: dict[str, str], key_position: str
473
) -> None:
474
    """
475
    Define the position.
476
477
    Arguments
478
    ---------
479
    environment
480
        The environment
481
482
    definition
483
        The definition
484
485
    key_position
486
        The position key
487
    """
488
    if key_position in definition:
489
        environment["position"] = definition[key_position]
490
491
492
def define_linewidth(
493
    environment: dict[str, Any], definition: dict[str, str], key_linewidth: str
494
) -> None:
495
    """
496
    Define the line width.
497
498
    Arguments
499
    ---------
500
    environment
501
        The environment
502
503
    definition
504
        The definition
505
506
    key_linewidth
507
        The linewidth key
508
    """
509
    if key_linewidth in definition:
510
        try:
511
            linewidth = int(definition[key_linewidth])
512
            if linewidth <= 0:
513
                debug(
514
                    "[WARNING] pandoc-latex-admonition: "
515
                    + "linewidth must be a positivie integer; using "
516
                    + str(environment["linewidth"])
517
                )
518
            else:
519
                environment["linewidth"] = linewidth
520
        except ValueError:
521
            debug(
522
                "[WARNING] pandoc-latex-admonition: linewidth is not a valid; using "
523
                + str(environment["linewidth"])
524
            )
525
526
527
def define_margin(
528
    environment: dict[str, Any], definition: dict[str, str], key_margin: str
529
) -> None:
530
    """
531
    Define the margin.
532
533
    Arguments
534
    ---------
535
    environment
536
        The environment
537
538
    definition
539
        The definition
540
541
    key_margin
542
        The margin key
543
    """
544
    if key_margin in definition:
545
        try:
546
            environment["margin"] = int(definition[key_margin])
547
        except ValueError:
548
            debug(
549
                "[WARNING] pandoc-latex-admonition: margin is not a valid; using "
550
                + str(environment["margin"])
551
            )
552
553
554
def define_innermargin(
555
    environment: dict[str, Any], definition: dict[str, str], key_innermargin: str
556
) -> None:
557
    """
558
    Define the inner margin.
559
560
    Arguments
561
    ---------
562
    environment
563
        The environment
564
565
    definition
566
        The definition
567
568
    key_innermargin
569
        The inner margin key
570
    """
571
    if key_innermargin in definition:
572
        try:
573
            environment["innermargin"] = int(definition[key_innermargin])
574
        except ValueError:
575
            debug(
576
                "[WARNING] pandoc-latex-admonition: innermargin is not a valid; using "
577
                + str(environment["innermargin"])
578
            )
579
580
581
def define_localfootnotes(
582
    environment: dict[str, Any], definition: dict[str, str], key_localfootnotes: str
583
) -> None:
584
    """
585
    Define the local footnotes.
586
587
    Arguments
588
    ---------
589
    environment
590
        The environment
591
592
    definition
593
        The definition
594
595
    key_localfootnotes
596
        The localfootnotes key
597
    """
598
    if key_localfootnotes in definition:
599
        environment["localfootnotes"] = definition[key_localfootnotes].lower() == "true"
600
601
602
def define_nobreak(
603
    environment: dict[str, Any], definition: dict[str, str], key_nobreak: str
604
) -> None:
605
    """
606
    Define the nobreak.
607
608
    Arguments
609
    ---------
610
    environment
611
        The environment
612
613
    definition
614
        The definition
615
616
    key_nobreak
617
        The nobreak key
618
    """
619
    if key_nobreak in definition:
620
        environment["nobreak"] = (
621
            str(definition[key_nobreak]).lower() == "true"  # noqa: FURB123
622
        )
623
624
625
def new_environment(doc: Doc, environment: dict[str, Any]) -> str:
626
    """
627
    Create a new environment.
628
629
    Arguments
630
    ---------
631
    doc
632
        The pandoc document
633
634
    environment
635
        The environment
636
637
    Returns
638
    -------
639
    str
640
        The LaTeX environment
641
    """
642
    options = ["blanker"]
643
644
    if not environment["nobreak"]:
645
        options.append("breakable")
646
    if environment["position"] == "left":
647
        options.append(left_bar(environment))
648
    elif environment["position"] == "right":
649
        options.append(right_bar(environment))
650
    elif environment["position"] == "inner":
651
        options.append(
652
            f"if odd page={{{left_bar(environment)}}}{{{right_bar(environment)}}}"
653
        )
654
    elif environment["position"] == "outer":
655
        options.append(
656
            f"if odd page={{{right_bar(environment)}}}{{{left_bar(environment)}}}"
657
        )
658
    else:
659
        options.append(left_bar(environment))
660
661
    if environment["localfootnotes"] or doc.format == "beamer":
662
        return f"""
663
\\newenvironment{{{environment['env']}}}
664
{{
665
    \\tcolorbox[{','.join(options)}]
666
}}
667
{{
668
    \\endtcolorbox
669
}}
670
        """
671
    return f"""
672
\\newenvironment{{{environment['env']}}}
673
{{
674
    \\savenotes\\tcolorbox[{','.join(options)}]
675
    \\setcounter{{mpfootnote}}{{\\value{{footnote}}}}
676
    \\renewcommand\\thempfootnote{{\\arabic{{mpfootnote}}}}
677
}}
678
{{
679
    \\setcounter{{footnote}}{{\\value{{mpfootnote}}}}
680
    \\endtcolorbox\\spewnotes
681
}}
682
        """
683
684
685
def left_bar(environment: dict[str, Any]) -> str:
686
    """
687
    Generate a left bar.
688
689
    Arguments
690
    ---------
691
    environment
692
        The environment
693
694
    Returns
695
    -------
696
    str
697
        The left bar options
698
    """
699
    return bar(environment, "left", "west")
700
701
702
def right_bar(environment: dict[str, Any]) -> str:
703
    """
704
    Generate a right bar.
705
706
    Arguments
707
    ---------
708
    environment
709
        The environment
710
711
    Returns
712
    -------
713
    str
714
        The right bar options
715
    """
716
    return bar(environment, "right", "east")
717
718
719
# pylint: disable=blacklisted-name
720
def bar(environment: dict[str, Any], position: str, localization: str) -> str:
721
    """
722
    Generate a bar.
723
724
    Arguments
725
    ---------
726
    environment
727
        The environment
728
729
    position
730
        left or right
731
732
    localization
733
        east or west
734
735
    Returns
736
    -------
737
    str
738
        The bar options
739
    """
740
    return (
741
        f"{position}={environment['innermargin']:g}pt,borderline "
742
        f"{localization}={{{environment['linewidth']:g}pt}}"
743
        f"{{{environment['margin']:g}pt}}{{{environment['color']}}}"
744
    )
745
746
747
def finalize(doc: Doc) -> None:
748
    """
749
    Finalize the pandoc document.
750
751
    Arguments
752
    ---------
753
    doc
754
        The pandoc document
755
    """
756
    # load 'footnote' or 'footnotehyper' package
757
    if doc.format == "latex":
758
        doc.metadata["tables"] = MetaBool(True)
759
760
    # Add header-includes if necessary
761
    if "header-includes" not in doc.metadata:
762
        doc.metadata["header-includes"] = MetaList()
763
    # Convert header-includes to MetaList if necessary
764
    elif not isinstance(doc.metadata["header-includes"], MetaList):
765
        doc.metadata["header-includes"] = MetaList(doc.metadata["header-includes"])
766
767
    # Add useful LaTexPackage
768
    doc.metadata["header-includes"].append(
769
        MetaInlines(RawInline("\\usepackage{xcolor}", "tex"))
770
    )
771
772
    # Define x11 colors
773
    tex = [
774
        f"\\definecolor{{{name.lower()}}}{{HTML}}{{{color}}}"
775
        for name, color in doc.x11colors.items()
776
    ]
777
    doc.metadata["header-includes"].append(
778
        MetaInlines(RawInline("\n".join(tex), "tex"))
779
    )
780
    doc.metadata["header-includes"].append(
781
        MetaInlines(RawInline("\\usepackage[most]{tcolorbox}", "tex"))
782
    )
783
    doc.metadata["header-includes"].append(
784
        MetaInlines(
785
            RawInline(
786
                r"""
787
\usepackage{ifthen}
788
\provideboolean{admonitiontwoside}
789
\makeatletter%
790
\if@twoside%
791
\setboolean{admonitiontwoside}{true}
792
\else%
793
\setboolean{admonitiontwoside}{false}
794
\fi%
795
\makeatother%
796
""",
797
                "tex",
798
            )
799
        )
800
    )
801
    # Define specific environments
802
    for environment in doc.defined + doc.added:
803
        doc.metadata["header-includes"].append(
804
            MetaInlines(RawInline(new_environment(doc, environment), "tex"))
805
        )
806
807
808
def main(doc: Doc | None = None) -> Doc:
809
    """
810
    Convert the pandoc document.
811
812
    Arguments
813
    ---------
814
    doc
815
        The pandoc document
816
817
    Returns
818
    -------
819
    Doc
820
        The modified pandoc document
821
    """
822
    return run_filter(admonition, prepare=prepare, finalize=finalize, doc=doc)
823
824
825
if __name__ == "__main__":
826
    main()
827