Passed
Push — develop ( 5bfd4a...779a2f )
by Christophe
02:55 queued 13s
created

pandoc_latex_admonition._main.define_environment()   A

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