Completed
Push — master ( eeb092...8fcac8 )
by Christophe
26s
created

add_latex()   D

Complexity

Conditions 10

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
dl 0
loc 23
rs 4.0396
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
B extract_images() 0 5 5
A note() 0 9 4

How to fix   Complexity   

Complexity

Complex classes like add_latex() 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
"""
4
Pandoc filter for adding admonition in LaTeX
5
"""
6
7
from panflute import *
8
import uuid
9
10
def default_environment():
11
    return {
12
        'env': 'env-' + str(uuid.uuid4()),
13
        'color': 'black',
14
        'position': 'left',
15
        'linewidth': 2,
16
        'margin': -4,
17
        'innermargin': 5,
18
        'localfootnotes': False
19
    }
20
21
def x11colors():
22
    # See https://www.w3.org/TR/css-color-3/#svg-color
23
    return {
24
        'aliceblue': 'F0F8FF',
25
        'antiquewhite': 'FAEBD7',
26
        'aqua': '00FFFF',
27
        'aquamarine': '7FFFD4',
28
        'azure': 'F0FFFF',
29
        'beige': 'F5F5DC',
30
        'bisque': 'FFE4C4',
31
        'black': '000000',
32
        'blanchedalmond': 'FFEBCD',
33
        'blue': '0000FF',
34
        'blueviolet': '8A2BE2',
35
        'brown': 'A52A2A',
36
        'burlywood': 'DEB887',
37
        'cadetblue': '5F9EA0',
38
        'chartreuse': '7FFF00',
39
        'chocolate': 'D2691E',
40
        'coral': 'FF7F50',
41
        'cornflowerblue': '6495ED',
42
        'cornsilk': 'FFF8DC',
43
        'crimson': 'DC143C',
44
        'cyan': '00FFFF',
45
        'darkblue': '00008B',
46
        'darkcyan': '008B8B',
47
        'darkgoldenrod': 'B8860B',
48
        'darkgray': 'A9A9A9',
49
        'darkgreen': '006400',
50
        'darkgrey': 'A9A9A9',
51
        'darkkhaki': 'BDB76B',
52
        'darkmagenta': '8B008B',
53
        'darkolivegreen': '556B2F',
54
        'darkorange': 'FF8C00',
55
        'darkorchid': '9932CC',
56
        'darkred': '8B0000',
57
        'darksalmon': 'E9967A',
58
        'darkseagreen': '8FBC8F',
59
        'darkslateblue': '483D8B',
60
        'darkslategray': '2F4F4F',
61
        'darkslategrey': '2F4F4F',
62
        'darkturquoise': '00CED1',
63
        'darkviolet': '9400D3',
64
        'deeppink': 'FF1493',
65
        'deepskyblue': '00BFFF',
66
        'dimgray': '696969',
67
        'dimgrey': '696969',
68
        'dodgerblue': '1E90FF',
69
        'firebrick': 'B22222',
70
        'floralwhite': 'FFFAF0',
71
        'forestgreen': '228B22',
72
        'fuchsia': 'FF00FF',
73
        'gainsboro': 'DCDCDC',
74
        'ghostwhite': 'F8F8FF',
75
        'gold': 'FFD700',
76
        'goldenrod': 'DAA520',
77
        'gray': '808080',
78
        'green': '008000',
79
        'greenyellow': 'ADFF2F',
80
        'grey': '808080',
81
        'honeydew': 'F0FFF0',
82
        'hotpink': 'FF69B4',
83
        'indianred': 'CD5C5C',
84
        'indigo': '4B0082',
85
        'ivory': 'FFFFF0',
86
        'khaki': 'F0E68C',
87
        'lavender': 'E6E6FA',
88
        'lavenderblush': 'FFF0F5',
89
        'lawngreen': '7CFC00',
90
        'lemonchiffon': 'FFFACD',
91
        'lightblue': 'ADD8E6',
92
        'lightcoral': 'F08080',
93
        'lightcyan': 'E0FFFF',
94
        'lightgoldenrodyellow': 'FAFAD2',
95
        'lightgray': 'D3D3D3',
96
        'lightgreen': '90EE90',
97
        'lightgrey': 'D3D3D3',
98
        'lightpink': 'FFB6C1',
99
        'lightsalmon': 'FFA07A',
100
        'lightseagreen': '20B2AA',
101
        'lightskyblue': '87CEFA',
102
        'lightslategray': '778899',
103
        'lightslategrey': '778899',
104
        'lightsteelblue': 'B0C4DE',
105
        'lightyellow': 'FFFFE0',
106
        'lime': '00FF00',
107
        'limegreen': '32CD32',
108
        'linen': 'FAF0E6',
109
        'magenta': 'FF00FF',
110
        'maroon': '800000',
111
        'mediumaquamarine': '66CDAA',
112
        'mediumblue': '0000CD',
113
        'mediumorchid': 'BA55D3',
114
        'mediumpurple': '9370DB',
115
        'mediumseagreen': '3CB371',
116
        'mediumslateblue': '7B68EE',
117
        'mediumspringgreen': '00FA9A',
118
        'mediumturquoise': '48D1CC',
119
        'mediumvioletred': 'C71585',
120
        'midnightblue': '191970',
121
        'mintcream': 'F5FFFA',
122
        'mistyrose': 'FFE4E1',
123
        'moccasin': 'FFE4B5',
124
        'navajowhite': 'FFDEAD',
125
        'navy': '000080',
126
        'oldlace': 'FDF5E6',
127
        'olive': '808000',
128
        'olivedrab': '6B8E23',
129
        'orange': 'FFA500',
130
        'orangered': 'FF4500',
131
        'orchid': 'DA70D6',
132
        'palegoldenrod': 'EEE8AA',
133
        'palegreen': '98FB98',
134
        'paleturquoise': 'AFEEEE',
135
        'palevioletred': 'DB7093',
136
        'papayawhip': 'FFEFD5',
137
        'peachpuff': 'FFDAB9',
138
        'peru': 'CD853F',
139
        'pink': 'FFC0CB',
140
        'plum': 'DDA0DD',
141
        'powderblue': 'B0E0E6',
142
        'purple': '800080',
143
        'red': 'FF0000',
144
        'rosybrown': 'BC8F8F',
145
        'royalblue': '4169E1',
146
        'saddlebrown': '8B4513',
147
        'salmon': 'FA8072',
148
        'sandybrown': 'F4A460',
149
        'seagreen': '2E8B57',
150
        'seashell': 'FFF5EE',
151
        'sienna': 'A0522D',
152
        'silver': 'C0C0C0',
153
        'skyblue': '87CEEB',
154
        'slateblue': '6A5ACD',
155
        'slategray': '708090',
156
        'slategrey': '708090',
157
        'snow': 'FFFAFA',
158
        'springgreen': '00FF7F',
159
        'steelblue': '4682B4',
160
        'tan': 'D2B48C',
161
        'teal': '008080',
162
        'thistle': 'D8BFD8',
163
        'tomato': 'FF6347',
164
        'turquoise': '40E0D0',
165
        'violet': 'EE82EE',
166
        'wheat': 'F5DEB3',
167
        'white': 'FFFFFF',
168
        'whitesmoke': 'F5F5F5',
169
        'yellow': 'FFFF00',
170
        'yellowgreen': '9ACD32'
171
    }
172
173
def admonition(elem, doc):
174
    # Is it in the right format and is it Div or a CodeBlock?
175
    if doc.format in ['latex', 'beamer'] and elem.tag in ['Div', 'CodeBlock']:
176
177
        # Is there a latex-admonition-color attribute?
178
        if 'latex-admonition-color' in elem.attributes:
179
            environment = define_environment(
180
                doc,
181
                elem.attributes,
182
                'latex-admonition-color',
183
                'latex-admonition-position',
184
                'latex-admonition-linewidth',
185
                'latex-admonition-margin',
186
                'latex-admonition-innermargin',
187
                'latex-admonition-localfootnotes'
188
            )
189
            doc.added.append(environment)
190
            return add_latex(elem, environment)
191
        else:
192
            # Get the classes
193
            classes = set(elem.classes)
194
195
            # Loop on all fontsize definition
196
            for environment in doc.defined:
197
198
                # Are the classes correct?
199
                if classes >= environment['classes']:
200
                    return add_latex(elem,  environment)
201
202
def add_latex(elem, environment):
203
    def note(elem, doc):
204
        if isinstance(elem, Note) and doc.format == 'beamer' and not environment['localfootnotes']:
205
            return RawInline(
206
                ''.join([
207
                    '\\footnote<.->[frame]{',
208
                    convert_text(elem.content, input_format='panflute', output_format='latex'),
209
                    '}',
210
                ]),
211
                'tex')
212
213
    images = []
214
    def extract_images(elem, doc):
215
        # Extract image which is alone with a title
216
        if isinstance(elem, Para) and len(elem.content) == 1 and isinstance(elem.content[0], Image) and bool(elem.content[0].content):
217
            images.append(elem)
218
            return []
219
    # The images need to be placed after the framed environment
220
    return [
221
        RawBlock('\\begin{' + environment['env'] + '}', 'tex'),
222
        elem.walk(extract_images).walk(note),
223
        RawBlock('\\end{' + environment['env'] + '}', 'tex')
224
    ] + images
225
226
def prepare(doc):
227
    doc.x11colors = x11colors()
228
229
   # Prepare the definitions
230
    doc.defined = []
231
    doc.added = []
232
233
    # Get the meta data
234
    meta = doc.get_metadata('pandoc-latex-admonition')
235
236
    if isinstance(meta, list):
237
238
        # Loop on all definitions
239
        for definition in meta:
240
241
            # Verify the definition
242
            if isinstance(definition, dict) and 'classes' in definition and isinstance(definition['classes'], list):
243
                environment = define_environment(doc, definition, 'color', 'position', 'linewidth', 'margin', 'innermargin', 'localfootnotes')
244
                environment['classes'] = set(definition['classes'])
245
                doc.defined.append(environment)
246
247
def define_environment(doc, definition, key_color, key_position, key_linewidth, key_margin, key_innermargin, key_localfootnotes):
248
    # Get the default environment
249
    environment = default_environment()
250
    define_color(doc, environment, definition, key_color)
251
    define_position(doc, environment, definition, key_position)
252
    define_linewidth(doc, environment, definition, key_linewidth)
253
    define_margin(doc, environment, definition, key_margin)
254
    define_innermargin(doc, environment, definition, key_innermargin)
255
    define_localfootnotes(doc, environment, definition, key_localfootnotes)
256
    return environment
257
258
def define_color(doc, environment, definition, key_color):
259
    # Get the color
260
    if key_color in definition:
261
        color = str(definition[key_color]).lower()
262
        if color in doc.x11colors:
263
            environment['color'] = color
264
        else:
265
            # color must be a valid x11 color (https://www.w3.org/TR/css-color-3/#svg-color)
266
            debug('[WARNING] pandoc-latex-admonition: ' + color + ' is not a valid x11 color; using ' + environment['color'])
267
268
def define_position(doc, environment, definition, key_position):
269
    # Get the position
270
    if key_position in definition:
271
        environment['position'] = str(definition[key_position])
272
273
def define_linewidth(doc, environment, definition, key_linewidth):
274
    # Get the line width
275
    if key_linewidth in definition:
276
        try:
277
            linewidth = int(str(definition[key_linewidth]))
278
            if linewidth <= 0:
279
                debug('[WARNING] pandoc-latex-admonition: linewidth must be a positivie integer; using ' + str(environment['linewidth']))
280
            else:
281
                environment['linewidth'] = linewidth
282
        except ValueError:
283
            debug('[WARNING] pandoc-latex-admonition: linewidth is not a valid; using ' + str(environment['linewidth']))
284
285
def define_margin(doc, environment, definition, key_margin):
286
    # Get the margin
287
    if key_margin in definition:
288
        try:
289
            environment['margin'] = int(str(definition[key_margin]))
290
        except ValueError:
291
            debug('[WARNING] pandoc-latex-admonition: margin is not a valid; using ' + str(environment['margin']))
292
293
def define_innermargin(doc, environment, definition, key_innermargin):
294
    # Get the inner margin
295
    if key_innermargin in definition:
296
        try:
297
            environment['innermargin'] = int(str(definition[key_innermargin]))
298
        except ValueError:
299
            debug('[WARNING] pandoc-latex-admonition: innermargin is not a valid; using ' + str(environment['innermargin']))
300
301
def define_localfootnotes(doc, environment, definition, key_localfootnotes):
302
    # Get the local footnotes
303
    if key_localfootnotes in definition:
304
        environment['localfootnotes'] = str(definition[key_localfootnotes]) == 'true'
305
306
def environment_option(position, linewidth, innermargin, margin, color):
307
    if position == 'right':
308
       pos = 'right'
309
       inv = 'left'
310
    else:
311
       pos = 'left'
312
       inv = 'right'
313
314
    properties = [
315
        'topline=false',
316
        'bottomline=false',
317
        inv + 'line=false',
318
        'linewidth=' + str(linewidth) + 'pt',
319
        'inner' + pos + 'margin=' + str(innermargin) +'pt',
320
        pos + 'margin=' + str(margin) +'pt',
321
        'inner' + inv + 'margin=0pt',
322
        'linecolor=' + color,
323
        'skipabove=\\topskip'
324
    ]
325
    return '[' + ','.join(properties) + ']'
326
327
def new_environment(doc, environment):
328
    if environment['localfootnotes'] or doc.format == 'beamer':
329
        return '\n'.join([
330
            '\\newenvironment{' + environment['env'] + '}',
331
            '{',
332
            '    \\begin{mdframed}' + environment_option(environment['position'], environment['linewidth'], environment['innermargin'], environment['margin'], environment['color']),
333
            '}',
334
            '{',
335
            '    \\end{mdframed}',
336
            '}'
337
        ])
338
    else:
339
        return '\n'.join([
340
            '\\newenvironment{' + environment['env'] + '}',
341
            '{',
342
            '    \\savenotes',
343
            '    \\begin{mdframed}' + environment_option(environment['position'], environment['linewidth'], environment['innermargin'], environment['margin'], environment['color']),
344
            '    \\let\\thempfootnote=\\thefootnote',
345
            '    \\let\\oldfootnote\\footnote',
346
            '    \\renewcommand{\\footnote}[1]{\\stepcounter{footnote}\\oldfootnote{##1}}',
347
            '}',
348
            '{',
349
            '    \\let\\footnote\\oldfootnote',
350
            '    \\end{mdframed}',
351
            '    \\spewnotes',
352
            '}'
353
        ])
354
355
def finalize(doc):
356
    # Add header-includes if necessary
357
    if 'header-includes' not in doc.metadata:
358
        doc.metadata['header-includes'] = MetaList()
359
    # Convert header-includes to MetaList if necessary
360
    elif not isinstance(doc.metadata['header-includes'], MetaList):
361
        doc.metadata['header-includes'] = MetaList(doc.metadata['header-includes'])
362
363
    # Add usefull LaTexPackage
364
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{mdframed}', 'tex')))
365
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{xcolor}', 'tex')))
366
    if doc.format == 'latex':
367
        doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{footnote}', 'tex')))
368
369
    # Define x11 colors
370
    tex = []
371
    for name, color in doc.x11colors.items():
372
        tex.append('\\definecolor{' + name.lower() + '}{HTML}{' + color + '}')
373
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\n'.join(tex), 'tex')))
374
375
    # Define specific environments
376
    for environment in doc.defined + doc.added:
377
        doc.metadata['header-includes'].append(MetaInlines(RawInline(new_environment(doc, environment), 'tex')))
378
379
def main(doc = None):
380
    return run_filter(admonition, prepare = prepare, finalize = finalize, doc = doc)
381
382
if __name__ == '__main__':
383
    main()
384
385