-
-
Notifications
You must be signed in to change notification settings - Fork 394
/
preprocessors.py
207 lines (168 loc) · 7.24 KB
/
preprocessors.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
"""
Prototype demo:
python holoviews/ipython/convert.py Conversion_Example.ipynb | python
"""
import ast
from nbconvert.preprocessors import Preprocessor
def comment_out_magics(source):
"""
Utility used to make sure AST parser does not choke on unrecognized
magics.
"""
filtered = []
for line in source.splitlines():
if line.strip().startswith('%'):
filtered.append('# ' + line)
else:
filtered.append(line)
return '\n'.join(filtered)
def wrap_cell_expression(source, template='{expr}'):
"""
If a cell ends in an expression that could be displaying a HoloViews
object (as determined using the AST), wrap it with a given prefix
and suffix string.
If the cell doesn't end in an expression, return the source unchanged.
"""
cell_output_types = (ast.IfExp, ast.BoolOp, ast.BinOp, ast.Call,
ast.Name, ast.Attribute)
try:
node = ast.parse(comment_out_magics(source))
except SyntaxError:
return source
filtered = source.splitlines()
if node.body != []:
last_expr = node.body[-1]
if not isinstance(last_expr, ast.Expr):
pass # Not an expression
elif isinstance(last_expr.value, cell_output_types):
# CAREFUL WITH UTF8!
expr_end_slice = filtered[last_expr.lineno-1][:last_expr.col_offset]
expr_start_slice = filtered[last_expr.lineno-1][last_expr.col_offset:]
start = '\n'.join(filtered[:last_expr.lineno-1]
+ ([expr_end_slice] if expr_end_slice else []))
ending = '\n'.join(([expr_start_slice] if expr_start_slice else [])
+ filtered[last_expr.lineno:])
# BUG!! Adds newline for 'foo'; <expr>
return start + '\n' + template.format(expr=ending)
return source
def filter_magic(source, magic, strip=True):
"""
Given the source of a cell, filter out the given magic and collect
the lines using the magic into a list.
If strip is True, the IPython syntax part of the magic (e.g. %magic
or %%magic) is stripped from the returned lines.
"""
filtered, magic_lines=[],[]
for line in source.splitlines():
if line.strip().startswith(magic):
magic_lines.append(line)
else:
filtered.append(line)
if strip:
magic_lines = [el.replace(magic,'') for el in magic_lines]
return '\n'.join(filtered), magic_lines
def strip_magics(source):
"""
Given the source of a cell, filter out all cell and line magics.
"""
filtered=[]
for line in source.splitlines():
if not line.startswith('%'):
filtered.append(line)
return '\n'.join(filtered)
def replace_line_magic(source, magic, template='{line}'):
"""
Given a cell's source, replace line magics using a formatting
template, where {line} is the string that follows the magic.
"""
filtered = []
for line in source.splitlines():
if line.strip().startswith(magic):
substitution = template.format(line=line.replace(magic, ''))
filtered.append(substitution)
else:
filtered.append(line)
return '\n'.join(filtered)
class OptsMagicProcessor(Preprocessor):
"""
Preprocessor to convert notebooks to Python source to convert use of
opts magic to use the util.opts utility instead.
"""
def preprocess_cell(self, cell, resources, index):
if cell['cell_type'] == 'code':
source = replace_line_magic(cell['source'], '%opts',
template='hv.util.opts({line!r})')
source, opts_lines = filter_magic(source, '%%opts')
if opts_lines:
# Escape braces (e.g. normalization options) as they pass through format
template = 'hv.util.opts({options!r}, {{expr}})'.format(
options=' '.join(opts_lines).replace('{','{{').replace('}','}}'))
source = wrap_cell_expression(source, template)
cell['source'] = source
return cell, resources
def __call__(self, nb, resources): return self.preprocess(nb,resources)
class OutputMagicProcessor(Preprocessor):
"""
Preprocessor to convert notebooks to Python source to convert use of
output magic to use the util.output utility instead.
"""
def preprocess_cell(self, cell, resources, index):
if cell['cell_type'] == 'code':
source = replace_line_magic(cell['source'], '%output',
template='hv.util.output({line!r})')
source, output_lines = filter_magic(source, '%%output')
if output_lines:
template = f'hv.util.output({output_lines[-1]!r}, {{expr}})'
source = wrap_cell_expression(source, template)
cell['source'] = source
return cell, resources
def __call__(self, nb, resources): return self.preprocess(nb,resources)
class StripMagicsProcessor(Preprocessor):
"""
Preprocessor to convert notebooks to Python source to strips out all
magics. To be applied after the preprocessors that can handle
holoviews magics appropriately.
"""
def preprocess_cell(self, cell, resources, index):
if cell['cell_type'] == 'code':
cell['source'] = strip_magics(cell['source'])
return cell, resources
def __call__(self, nb, resources): return self.preprocess(nb,resources)
class Substitute(Preprocessor):
"""
An nbconvert preprocessor that substitutes one set of HTML data
output for another, adding annotation to the output as required.
The constructor accepts the notebook format version and a
substitutions dictionary:
{source_html:(target_html, annotation)}
Where the annotation may be None (i.e. no annotation).
"""
annotation = '<center><b>%s</b></center>'
def __init__(self, version, substitutions, **kw):
self.nbversion = version
self.substitutions = substitutions
super(Preprocessor, self).__init__(**kw)
def __call__(self, nb, resources): # Temporary hack around 'enabled' flag
return self.preprocess(nb,resources)
def replace(self, src):
"Given some source html substitute and annotated as applicable"
for html in self.substitutions.keys():
if src == html:
annotation = self.annotation % self.substitutions[src][1]
return annotation + self.substitutions[src][0]
return src
def preprocess_cell(self, cell, resources, index):
v4 = (self.nbversion[0] == 4)
if cell['cell_type'] == 'code':
for outputs in cell['outputs']:
output_key = ('execute_result' if v4 else 'pyout')
if outputs['output_type'] == output_key:
# V1-3
if not v4 and 'html' in outputs:
outputs['html'] = self.replace(outputs['html'])
# V4
for data in outputs.get('data',[]):
if v4 and data == 'text/html':
substitution = self.replace(outputs['data']['text/html'])
outputs['data']['text/html'] = substitution
return cell, resources