Completed
Push — master ( b27104...a73377 )
by Christophe
32s
created

add_latex()   C

Complexity

Conditions 10

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
dl 0
loc 19
rs 6
c 0
b 0
f 0

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 tip in LaTeX
5
"""
6
7
from panflute import *
8
import os
9
10
try:
11
    FileNotFoundError
12
except NameError:
13
    #py2
14
    FileNotFoundError = IOError
15
16
def tip(elem, doc):
17
    # Is it in the right format and is it a Span, Div?
18
    if doc.format == 'latex' and elem.tag in ['Span', 'Div', 'Code', 'CodeBlock']:
19
20
        # Is there a latex-tip-icon attribute?
21
        if 'latex-tip-icon' in elem.attributes:
22
            return add_latex(
23
                elem,
24
                latex_code(
25
                    doc,
26
                    elem.attributes,
27
                    'latex-tip-icon',
28
                    'latex-tip-position',
29
                    'latex-tip-size',
30
                    'latex-tip-color'
31
                )
32
            )
33
        else:
34
            # Get the classes
35
            classes = set(elem.classes)
36
37
            # Loop on all fontsize definition
38
            for definition in doc.defined:
39
40
                # Are the classes correct?
41
                if classes >= definition['classes']:
42
                    return add_latex(elem, definition['latex'])
43
44
def add_latex(elem, latex):
45
    if bool(latex):
46
        # Is it a Span or a Code?
47
        if isinstance(elem, Span) or isinstance(elem, Code):
48
            return [RawInline(latex, 'tex'), elem]
49
50
        # It is a CodeBlock: create a minipage to ensure the tip to be on the same page as the codeblock
51
        elif isinstance(elem, CodeBlock):
52
            return [RawBlock('\\begin{minipage}{\\textwidth}' + latex, 'tex'), elem, RawBlock('\\end{minipage}', 'tex')]
53
        # It is a Div: try to insert an inline raw before the first inline element
54
        else:
55
            inserted = [False]
56
            def insert(elem, doc):
57
                if not inserted[0] and isinstance(elem, Inline) and not isinstance(elem.parent, Inline):
58
                    inserted[0] = True
59
                    return [RawInline(latex, 'tex'), elem]
60
            elem.walk(insert)
61
            if not inserted[0]:
62
                return [RawBlock(latex, 'tex'), elem]
63
64
def latex_code(doc, definition, key_icon, key_position, key_size, key_color):
65
    # Get the default color
66
    color = get_color(doc, definition, key_color)
67
68
    # Get the size
69
    size = get_size(doc, definition, key_size)
70
71
    # Get the prefix
72
    prefix = get_prefix(doc, definition, key_position)
73
74
    # Get the icons
75
    icons = get_icons(doc, definition, key_icon, color)
76
77
    # Get the images
78
    images = create_images(doc, icons, size)
79
80
    if bool(images):
81
        # Prepare LaTeX code
82
        latex = [
83
            '{',
84
            '\\makeatletter',
85
            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
86
            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
87
            '\\makeatother',
88
            prefix,
89
            '\\marginnote{'
90
        ] + images + [
91
            '}[0pt]',
92
            '\\vspace{0cm}',
93
            '}',
94
        ]
95
96
        # Return LaTeX code
97
        return ''.join(latex)
98
    else:
99
        return ''
100
101
102
def get_icons(doc, definition, key_icons, color):
103
    icons = [{'name': 'exclamation-circle', 'color': color}]
104
105
    # Test the icons definition
106
    if key_icons in definition:
107
        icons = []
108
        if isinstance(definition[key_icons], str) or isinstance(definition[key_icons], unicode):
109
            check_icon(doc, icons, definition[key_icons], color)
110
        elif isinstance(definition[key_icons], list):
111
            for icon in definition[key_icons]:
112
                check_icon(doc, icons, icon, color)
113
114
    return icons
115
116
# Fix unicode for python3
117
try:
118
    unicode = unicode
119
except (NameError):
120
    unicode = str
121
122
def check_icon(doc, icons, icon, color):
123
    if isinstance(icon, str) or isinstance(icon, unicode):
124
        # Simple icon
125
        name = icon
126
    elif isinstance(icon, dict) and 'color' in icon and 'name' in icon:
127
        # Complex icon with name and color
128
        color = str(icon['color'])
129
        name = str(icon['name'])
130
    else:
131
        # Bad formed icon
132
        debug('[WARNING] pandoc-latex-tip: Bad formed icon')
133
        return
134
135
    add_icon(doc, icons, color, name)
136
137
def add_icon(doc, icons, color, name):
138
    # Lower the color
139
    lowerColor = color.lower()
140
141
    # Convert the color to black if unexisting
142
    from PIL import ImageColor
143
    if lowerColor not in ImageColor.colormap:
144
        debug('[WARNING] pandoc-latex-tip: ' + lowerColor + ' is not a correct color name; using black')
145
        lowerColor = 'black'
146
147
    # Is the icon correct?
148
    try:
149
        if name in doc.getIconFont.css_icons:
150
            icons.append({'name': name, 'color': lowerColor})
151
        else:
152
            debug('[WARNING] pandoc-latex-tip: ' + name + ' is not a correct icon name')
153
    except FileNotFoundError:
154
        debug('[WARNING] pandoc-latex-tip: error in accessing to icons definition')
155
156
def get_color(doc, definition, key):
157
    if key in definition:
158
        return str(definition[key])
159
    else:
160
        return 'black'
161
162
def get_prefix(doc, definition, key):
163
    if key in definition:
164
        if definition[key] == 'right':
165
            return '\\normalmarginpar'
166
        elif definition[key] == 'left':
167
            return '\\reversemarginpar'
168
        else:
169
            debug('[WARNING] pandoc-latex-tip: ' + str(definition[key]) + ' is not a correct position; using left')
170
            return '\\reversemarginpar'
171
    return '\\reversemarginpar'
172
173
def get_size(doc, definition, key):
174
   # Get the size
175
    size = '18'
176
    if key in definition:
177
        try:
178
            intValue = int(definition[key])
179
            if intValue > 0:
180
                size = str(intValue)
181
            else:
182
                debug('[WARNING] pandoc-latex-tip: size must be greater than 0; using ' + size)
183
        except ValueError:
184
            debug('[WARNING] pandoc-latex-tip: size must be a number; using ' + size)
185
    return size
186
187
def create_images(doc, icons, size):
188
    # Generate the LaTeX image code
189
    images = []
190
191
    for icon in icons:
192
193
        # Get the apps dirs
194
        from pkg_resources import get_distribution
195
        from appdirs import AppDirs
196
        dirs = AppDirs('pandoc_latex_tip', version = get_distribution('pandoc_latex_tip').version)
197
198
        # Get the image from the App cache folder
199
        image = dirs.user_cache_dir + '/' + icon['color'] + '/' + icon['name'] + '.png'
200
201
        # Create the image if not existing in the cache
202
        try:
203
            if not os.path.isfile(image):
204
205
                # Create the image in the cache
206
                doc.getIconFont.export_icon(
207
                    icon['name'],
208
                    size = 512,
209
                    color = icon['color'],
210
                    export_dir = dirs.user_cache_dir + '/' + icon['color']
211
                )
212
213
            # Add the LaTeX image
214
            images.append('\\includegraphics[width=' + size + 'pt]{' + image + '}')
215
        except FileNotFoundError:
216
            debug('[WARNING] pandoc-latex-tip: error in generating image')
217
218
    return images
219
220
def add_definition(doc, definition):
221
    # Get the classes
222
    classes = definition['classes']
223
224
    # Add a definition if correct
225
    if bool(classes):
226
        latex = latex_code(doc, definition, 'icons', 'position', 'size', 'color')
227
        if latex:
228
            doc.defined.append({'classes' : set(classes), 'latex': latex})
229
230
def prepare(doc):
231
    # Add getIconFont library to doc
232
    import icon_font_to_png
233
    from pkg_resources import get_distribution
234
    from appdirs import AppDirs
235
    dirs = AppDirs('pandoc_latex_tip', version = get_distribution('pandoc_latex_tip').version)
236
    doc.getIconFont = icon_font_to_png.IconFont(
237
        dirs.user_data_dir + '/font-awesome.css',
238
        dirs.user_data_dir + '/fontawesome-webfont.ttf'
239
    )
240
241
    # Prepare the definitions
242
    doc.defined = []
243
244
    # Get the meta data
245
    meta = doc.get_metadata('pandoc-latex-tip')
246
247
    if isinstance(meta, list):
248
249
        # Loop on all definitions
250
        for definition in meta:
251
252
            # Verify the definition
253
            if isinstance(definition, dict) and 'classes' in definition and isinstance(definition['classes'], list):
254
                add_definition(doc, definition)
255
256
def finalize(doc):
257
    if 'header-includes' not in doc.metadata:
258
        doc.metadata['header-includes'] = []
259
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{graphicx,grffile}', 'tex')))
260
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{marginnote}', 'tex')))
261
    doc.metadata['header-includes'].append(MetaInlines(RawInline('\\usepackage{etoolbox}', 'tex')))
262
263
def main(doc = None):
264
    run_filter(tip, prepare = prepare, finalize = finalize, doc = doc)
265
266
if __name__ == '__main__':
267
    main()
268
269