Completed
Push — master ( a0bdd5...38c5b4 )
by Christophe
31s
created

tip()   C

Complexity

Conditions 7

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
c 1
b 0
f 0
dl 0
loc 21
rs 6.4705
1
#!/usr/bin/env python
2
3
"""
4
Pandoc filter for adding tip in LaTeX
5
"""
6
7
from pandocfilters import RawInline, RawBlock, Span, Div, stringify
8
9
import io
10
import os
11
import sys
12
import codecs
13
import json
14
import re
15
16
try:
17
    FileNotFoundError
18
except NameError:
19
    #py2
20
    FileNotFoundError = IOError
21
22
def toJSONFilters(actions):
23
    """Converts a list of actions into a filter that reads a JSON-formatted
24
    pandoc document from stdin, transforms it by walking the tree
25
    with the actions, and returns a new JSON-formatted pandoc document
26
    to stdout.  The argument is a list of functions action(key, value, format, meta),
27
    where key is the type of the pandoc object (e.g. 'Str', 'Para'),
28
    value is the contents of the object (e.g. a string for 'Str',
29
    a list of inline elements for 'Para'), format is the target
30
    output format (which will be taken for the first command line
31
    argument if present), and meta is the document's metadata.
32
    If the function returns None, the object to which it applies
33
    will remain unchanged.  If it returns an object, the object will
34
    be replaced.    If it returns a list, the list will be spliced in to
35
    the list to which the target object belongs.    (So, returning an
36
    empty list deletes the object.)
37
    """
38
    try:
39
        input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
40
    except AttributeError:
41
        # Python 2 does not have sys.stdin.buffer.
42
        # REF: http://stackoverflow.com/questions/2467928/python-unicodeencodeerror-when-reading-from-stdin
43
        input_stream = codecs.getreader("utf-8")(sys.stdin)
44
45
    doc = json.loads(input_stream.read())
46
    if len(sys.argv) > 1:
47
        format = sys.argv[1]
48
    else:
49
        format = ""
50
51
    if 'meta' in doc:
52
        meta = doc['meta']
53
    elif doc[0]:  # old API
54
        meta = doc[0]['unMeta']
55
    else:
56
        meta = {}
57
58
    from functools import reduce
59
    altered = reduce(lambda x, action: walk(x, action, format, meta), actions, doc)
60
    json.dump(altered, sys.stdout)
61
62
def walk(x, action, format, meta):
63
    """Walk a tree, applying an action to every object.
64
    Returns a modified tree.
65
    """
66
    if isinstance(x, list):
67
        array = []
68
        for item in x:
69
            if isinstance(item, dict) and 't' in item:
70
                res = action(item['t'], item['c'] if 'c' in item else None, format, meta)
71
                if res is None:
72
                    array.append(walk(item, action, format, meta))
73
                elif isinstance(res, list):
74
                    for z in res:
75
                        array.append(walk(z, action, format, meta))
76
                else:
77
                    array.append(walk(res, action, format, meta))
78
            else:
79
                array.append(walk(item, action, format, meta))
80
        return array
81
    elif isinstance(x, dict):
82
        for k in x:
83
            x[k] = walk(x[k], action, format, meta)
84
        return x
85
    else:
86
        return x
87
88
def tip(key, value, format, meta):
89
    # Is it a Span and the right format?
90
    if key in ['Span', 'Div'] and format == 'latex':
91
92
        # Get the attributes
93
        [[id, classes, properties], content] = value
94
95
        # Use the Span classes as a set
96
        currentClasses = set(classes)
97
98
        # Loop on all tip definition
99
        for elt in getDefined(meta):
100
101
            # Is the classes correct?
102
            if currentClasses >= elt['classes']:
103
104
                # Prepend a tex block for inserting images
105
                if key == 'Span':
106
                    return [RawInline('tex', elt['latex']), Span([id, classes, properties], content)]
107
                elif key == 'Div':
108
                    return [RawBlock('tex', elt['latex']), Div([id, classes, properties], content)]
109
110
def getIconFont():
111
    if not hasattr(getIconFont, 'value'):
112
        import icon_font_to_png
113
        getIconFont.value = icon_font_to_png.IconFont(
114
            os.path.dirname(os.path.realpath(__file__)) + '/pandoc_latex_tip-data/font-awesome.css',
115
            os.path.dirname(os.path.realpath(__file__)) + '/pandoc_latex_tip-data/fontawesome-webfont.ttf'
116
        )
117
    return getIconFont.value
118
119
def getDefined(meta):
120
    if not hasattr(getDefined, 'value'):
121
        # Prepare the values
122
        getDefined.value = []
123
124
        # Get the meta data
125
        if 'pandoc-latex-tip' in meta and meta['pandoc-latex-tip']['t'] == 'MetaList':
126
            tipMeta = meta['pandoc-latex-tip']['c']
127
128
            # Loop on all definitions
129
            for definition in tipMeta:
130
131
                # Verify the definition type
132
                if definition['t'] == 'MetaMap':
133
134
                     # Get the classes
135
                    classes = []
136
                    if 'classes' in definition['c'] and definition['c']['classes']['t'] == 'MetaList':
137
                        for klass in definition['c']['classes']['c']:
138
                            classes.append(stringify(klass))
139
140
                    # Get the icons
141
                    icons = [{'name': 'exclamation-circle', 'color': 'black'}]
142
143
                    # Test the icons definition
144
                    if 'icons' in definition['c'] and definition['c']['icons']['t'] == 'MetaList':
145
                        icons = []
146
                        for icon in definition['c']['icons']['c']:
147
                            if icon['t'] == 'MetaInlines':
148
                                # Simple icon
149
                                color = 'black'
150
                                name = stringify(icon['c'])
151
                            elif icon['t'] == 'MetaMap' and 'color' in icon['c'] and 'name' in icon['c']:
152
                                # Complex icon with name and color
153
                                color = stringify(icon['c']['color'])
154
                                name = stringify(icon['c']['name'])
155
                            else:
156
                                # Bad formed icon
157
                                break
158
159
                            # Lower the color
160
                            lowerColor = color.lower()
161
162
                            # Convert the color to black if unexisting
163
                            from PIL import ImageColor
164
                            if lowerColor not in ImageColor.colormap:
165
                                lowerColor = 'black'
166
167
                            # Is the icon correct?
168
                            try:
169
                                if name in getIconFont().css_icons:
170
                                    icons.append({'name': name, 'color': lowerColor})
171
                            except FileNotFoundError:
172
                                pass
173
174
                    # Add a definition if correct
175
                    if bool(classes) and bool(icons):
176
177
                        # Generate the LaTeX image code
178
                        images = []
179
180
                       # Get the size
181
                        size = '18'
182
                        if 'size' in definition['c'] and definition['c']['size']['t'] == 'MetaString':
183
                            try:
184
                                intValue = int(definition['c']['size']['c'])
185
                                if intValue > 0:
186
                                    size = str(intValue)
187
                            except ValueError:
188
                                pass
189
190
                        for icon in icons:
191
192
                            # Get the apps dirs
193
                            from pkg_resources import get_distribution
194
                            from appdirs import AppDirs
195
                            dirs = AppDirs('pandoc_latex_tip', version = get_distribution('pandoc_latex_tip').version)
196
197
                            # Get the image from the App cache folder
198
                            image = dirs.user_cache_dir + '/' + icon['color'] + '/' + icon['name'] + '.png'
199
200
                            # Create the image if not existing in the cache
201
                            try:
202
                                if not os.path.isfile(image):
203
204
                                    # Create the image in the cache
205
                                    getIconFont().export_icon(
206
                                        icon['name'],
207
                                        size = 512,
208
                                        color = icon['color'],
209
                                        export_dir = dirs.user_cache_dir + '/' + icon['color']
210
                                    )
211
212
                                # Add the LaTeX image
213
                                images.append('\\includegraphics[width=' + size + 'pt]{' + image + '}')
214
                            except FileNotFoundError:
215
                                pass
216
217
                        # Get the prefix
218
                        prefix = '\\reversemarginpar'
219
220
                        if 'position' in definition['c'] and\
221
                            definition['c']['position']['t'] == 'MetaInlines' and\
222
                            stringify(definition['c']['position']['c']) == 'right':
223
224
                            prefix = '\\normalmarginpar'
225
226
                        latex = [
227
                            '{',
228
                            '\\makeatletter',
229
                            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
230
                            '\\patchcmd{\\@mn@margintest}{\\@tempswafalse}{\\@tempswatrue}{}{}',
231
                            '\\makeatother',
232
                            prefix,
233
                            '\\marginnote{'
234
                        ] + images + [
235
                            '}[0pt]',
236
                            '}',
237
                        ]
238
239
                        getDefined.value.append({'classes' : set(classes), 'latex': ''.join(latex)})
240
241
        if 'header-includes' not in meta:
242
            meta['header-includes'] = {u'c': [], u't': u'MetaList'}
243
244
        meta['header-includes']['c'].append({
245
            'c': [{'t': 'RawInline', 'c': ['tex', '\\usepackage{graphicx,grffile}']}],
246
            't': 'MetaInlines'
247
        })
248
        meta['header-includes']['c'].append({
249
            'c': [{'t': 'RawInline', 'c': ['tex', '\\usepackage{marginnote}']}],
250
            't': 'MetaInlines'
251
        })
252
        meta['header-includes']['c'].append({
253
            'c': [{'t': 'RawInline', 'c': ['tex', '\\usepackage{etoolbox}']}],
254
            't': 'MetaInlines'
255
        })
256
257
    return getDefined.value
258
259
def main():
260
    toJSONFilters([tip])
261
262
if __name__ == '__main__':
263
    main()
264
265