##// END OF EJS Templates
refactored nbconvert, nosetest pass...
Paul Ivanov -
Show More
@@ -0,0 +1,19 b''
1 from nbconvert import ConverterRST
2 import nose.tools as nt
3
4 import os
5 import glob
6
7 def clean_dir():
8 "Remove .rst files created during conversion"
9 map(os.remove, glob.glob("*.rst"))
10 map(os.remove, glob.glob("*.png"))
11
12
13 @nt.with_setup(clean_dir, clean_dir)
14 def test_simple():
15 fname = 'test.ipynb'
16 c = ConverterRST(fname)
17 f = c.render()
18 nt.assert_true('rst' in f, 'changed file extension to rst')
19
@@ -1,198 +1,222 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A really simple notebook to rst/html exporter.
2 """A really simple notebook to rst/html exporter.
3
3
4 Usage
4 Usage
5
5
6 ./nb2html.py file.ipynb
6 ./nb2html.py file.ipynb
7
7
8 Produces 'file.rst' and 'file.html', along with auto-generated figure files
8 Produces 'file.rst' and 'file.html', along with auto-generated figure files
9 called nb_figure_NN.png.
9 called nb_figure_NN.png.
10
10
11 """
11 """
12
12
13 import os
13 import os
14 import subprocess
14 import subprocess
15 import sys
15 import sys
16
16
17 from IPython.nbformat import current as nbformat
17 from IPython.nbformat import current as nbformat
18 from IPython.utils.text import wrap_paragraphs, indent
18 from IPython.utils.text import wrap_paragraphs, indent
19
19
20
20
21 # Cell converters
21 # Cell converters
22
22
23 def unknown_cell(cell):
23 def unknown_cell(cell):
24 """Default converter for cells of unknown type.
24 """Default converter for cells of unknown type.
25 """
25 """
26
26
27 return rst_directive('.. warning:: Unknown cell') + \
27 return rst_directive('.. warning:: Unknown cell') + \
28 [repr(cell)]
28 [repr(cell)]
29
29
30 def heading_cell(cell):
31 """convert a heading cell to rst
32
33 Returns list."""
34 heading_level = {1:'=', 2:'-', 3:'`', 4:'\'', 5:'.',6:'~'}
35 marker = heading_level[cell.level]
36 return ['{0}\n{1}\n'.format(cell.source, marker*len(cell.source))]
37
38 def markdown_cell(cell):
39 """convert a markdown cell to rst
40
41 Returns list."""
42 return [cell.source]
43
44
30
45 def rst_directive(directive, text=''):
31 def rst_directive(directive, text=''):
46 out = [directive, '']
32 out = [directive, '']
47 if text:
33 if text:
48 out.extend([indent(text), ''])
34 out.extend([indent(text), ''])
49 return out
35 return out
50
36
51 def code_cell(cell):
52 """Convert a code cell to rst
53
54 Returns list."""
55
56 if not cell.input:
57 return []
58
59 lines = ['In[%s]:' % cell.prompt_number, '']
60 lines.extend(rst_directive('.. code:: python', cell.input))
61
62 for output in cell.outputs:
63 conv = converters.get(output.output_type, unknown_cell)
64 lines.extend(conv(output))
65
66 return lines
67
68 # Converters for parts of a cell.
37 # Converters for parts of a cell.
69 figures_counter = 1
38 figures_counter = 1
70
39
71 def out_display(output):
40 class ConversionException(Exception):
72 """convert display data from the output of a code cell to rst.
41 pass
42
43 class Converter(object):
44 default_encoding = 'utf-8'
45 def __init__(self, fname):
46 self.fname = fname
47
48 @property
49 def extension(self):
50 raise ConversionException("""extension must be defined in Converter
51 subclass""")
52
53 def dispatch(self,cell_type):
54 """return cell_type dependent render method, for example render_code
55 """
56 return getattr(self, 'render_'+cell_type, unknown_cell)
57
58 def convert(self):
59 lines = []
60 for cell in self.nb.worksheets[0].cells:
61 conv_fn = self.dispatch(cell.cell_type)
62 lines.extend(conv_fn(cell))
63 lines.append('')
64 return '\n'.join(lines)
65
66 def render(self):
67 "read, convert, and save self.fname"
68 self.read()
69 self.output = self.convert()
70 return self.save()
71
72 def read(self):
73 "read and parse notebook into NotebookNode called self.nb"
74 with open(self.fname) as f:
75 self.nb = nbformat.read(f, 'json')
76
77 def save(self,fname=None, encoding=None):
78 "read and parse notebook into self.nb"
79 if fname is None:
80 fname = os.path.splitext(self.fname)[0] + '.' + self.extension
81 if encoding is None:
82 encoding = self.default_encoding
83 with open(fname, 'w') as f:
84 f.write(self.output.encode(encoding))
85 return fname
73
86
74 Returns list.
87 def render_heading(self,cell):
75 """
88 raise NotImplementedError
76 global figures_counter
89 def render_code(self,cell):
90 raise NotImplementedError
91 def render_markdown(self,cell):
92 raise NotImplementedError
93 def render_pyout(self,cell):
94 raise NotImplementedError
95 def render_display_data(self,cell):
96 raise NotImplementedError
77
97
78 lines = []
98 class ConverterRST(Converter):
99 extension = 'rst'
100 def render_heading(self,cell):
101 """convert a heading cell to rst
79
102
80 if 'png' in output:
103 Returns list."""
81 fname = 'nb_figure_%s.png' % figures_counter
104 heading_level = {1:'=', 2:'-', 3:'`', 4:'\'', 5:'.',6:'~'}
82 with open(fname, 'w') as f:
105 marker = heading_level[cell.level]
83 f.write(output.png.decode('base64'))
106 return ['{0}\n{1}\n'.format(cell.source, marker*len(cell.source))]
84
107
85 figures_counter += 1
108 def render_code(self,cell):
86 lines.append('.. image:: %s' % fname)
109 """Convert a code cell to rst
87 lines.append('')
88
89 return lines
90
110
91
111 Returns list."""
92 def out_pyout(output):
93 """convert pyout part of a code cell to rst
94
112
95 Returns list."""
113 if not cell.input:
114 return []
96
115
97 lines = ['Out[%s]:' % output.prompt_number, '']
116 lines = ['In[%s]:' % cell.prompt_number, '']
98
117 lines.extend(rst_directive('.. code:: python', cell.input))
99 if 'latex' in output:
100 lines.extend(rst_directive('.. math::', output.latex))
101
118
102 if 'text' in output:
119 for output in cell.outputs:
103 lines.extend(rst_directive('.. parsed-literal::', output.text))
120 conv_fn = self.dispatch(output.output_type)
121 lines.extend(conv_fn(output))
104
122
105 return lines
123 return lines
106
124
125 def render_markdown(self,cell):
126 """convert a markdown cell to rst
107
127
108 converters = dict(heading = heading_cell,
128 Returns list."""
109 code = code_cell,
129 return [cell.source]
110 markdown = markdown_cell,
111 pyout = out_pyout,
112 display_data = out_display,
113 )
114
130
131 def render_pyout(self,output):
132 """convert pyout part of a code cell to rst
115
133
134 Returns list."""
116
135
117 def convert_notebook(nb):
136 lines = ['Out[%s]:' % output.prompt_number, '']
118 lines = []
137
119 for cell in nb.worksheets[0].cells:
138 if 'latex' in output:
120 conv = converters.get(cell.cell_type, unknown_cell)
139 lines.extend(rst_directive('.. math::', output.latex))
121 lines.extend(conv(cell))
122 lines.append('')
123
124 return '\n'.join(lines)
125
140
141 if 'text' in output:
142 lines.extend(rst_directive('.. parsed-literal::', output.text))
126
143
127 def nb2rst(fname):
144 return lines
128 "Convert notebook to rst"
129
130 with open(fname) as f:
131 nb = nbformat.read(f, 'json')
132
145
133 rst = convert_notebook(nb)
146 def render_display_data(self,output):
147 """convert display data from the output of a code cell to rst.
134
148
135 newfname = os.path.splitext(fname)[0] + '.rst'
149 Returns list.
136 with open(newfname, 'w') as f:
150 """
137 f.write(rst.encode('utf8'))
151 global figures_counter
138
152
139 return newfname
153 lines = []
154
155 if 'png' in output:
156 fname = 'nb_figure_%s.png' % figures_counter
157 with open(fname, 'w') as f:
158 f.write(output.png.decode('base64'))
140
159
160 figures_counter += 1
161 lines.append('.. image:: %s' % fname)
162 lines.append('')
163
164 return lines
141
165
142 def rst2simplehtml(fname):
166 def rst2simplehtml(fname):
143 """Convert a rst file to simplified html suitable for blogger.
167 """Convert a rst file to simplified html suitable for blogger.
144
168
145 This just runs rst2html with certain parameters to produce really simple
169 This just runs rst2html with certain parameters to produce really simple
146 html and strips the document header, so the resulting file can be easily
170 html and strips the document header, so the resulting file can be easily
147 pasted into a blogger edit window.
171 pasted into a blogger edit window.
148 """
172 """
149
173
150 # This is the template for the rst2html call that produces the cleanest,
174 # This is the template for the rst2html call that produces the cleanest,
151 # simplest html I could find. This should help in making it easier to
175 # simplest html I could find. This should help in making it easier to
152 # paste into the blogspot html window, though I'm still having problems
176 # paste into the blogspot html window, though I'm still having problems
153 # with linebreaks there...
177 # with linebreaks there...
154 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
178 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
155 "--no-generator --no-datestamp --no-source-link "
179 "--no-generator --no-datestamp --no-source-link "
156 "--no-toc-backlinks --no-section-numbering "
180 "--no-toc-backlinks --no-section-numbering "
157 "--strip-comments ")
181 "--strip-comments ")
158
182
159 cmd = "%s %s" % (cmd_template, fname)
183 cmd = "%s %s" % (cmd_template, fname)
160 proc = subprocess.Popen(cmd,
184 proc = subprocess.Popen(cmd,
161 stdout=subprocess.PIPE,
185 stdout=subprocess.PIPE,
162 stderr=subprocess.PIPE,
186 stderr=subprocess.PIPE,
163 shell=True)
187 shell=True)
164 html, stderr = proc.communicate()
188 html, stderr = proc.communicate()
165 if stderr:
189 if stderr:
166 raise IOError(stderr)
190 raise IOError(stderr)
167
191
168 # Make an iterator so breaking out holds state. Our implementation of
192 # Make an iterator so breaking out holds state. Our implementation of
169 # searching for the html body below is basically a trivial little state
193 # searching for the html body below is basically a trivial little state
170 # machine, so we need this.
194 # machine, so we need this.
171 walker = iter(html.splitlines())
195 walker = iter(html.splitlines())
172
196
173 # Find start of main text, break out to then print until we find end /div.
197 # Find start of main text, break out to then print until we find end /div.
174 # This may only work if there's a real title defined so we get a 'div class'
198 # This may only work if there's a real title defined so we get a 'div class'
175 # tag, I haven't really tried.
199 # tag, I haven't really tried.
176 for line in walker:
200 for line in walker:
177 if line.startswith('<body>'):
201 if line.startswith('<body>'):
178 break
202 break
179
203
180 newfname = os.path.splitext(fname)[0] + '.html'
204 newfname = os.path.splitext(fname)[0] + '.html'
181 with open(newfname, 'w') as f:
205 with open(newfname, 'w') as f:
182 for line in walker:
206 for line in walker:
183 if line.startswith('</body>'):
207 if line.startswith('</body>'):
184 break
208 break
185 f.write(line)
209 f.write(line)
186 f.write('\n')
210 f.write('\n')
187
211
188 return newfname
212 return newfname
189
213
190
214
191 def main(fname):
215 def main(fname):
192 """Convert a notebook to html in one step"""
216 """Convert a notebook to html in one step"""
193 newfname = nb2rst(fname)
217 newfname = nb2rst(fname)
194 rst2simplehtml(newfname)
218 #rst2simplehtml(newfname)
195
219
196
220
197 if __name__ == '__main__':
221 if __name__ == '__main__':
198 main(sys.argv[1])
222 main(sys.argv[1])
1 NO CONTENT: file renamed from test.ipynb to tests/test.ipynb
NO CONTENT: file renamed from test.ipynb to tests/test.ipynb
General Comments 0
You need to be logged in to leave comments. Login now