##// END OF EJS Templates
Use @DocInherit to put docstrings on render dispatch functions in base class.
Anton I. Sipos -
Show More
@@ -0,0 +1,68 b''
1 """
2 doc_inherit decorator
3
4 Usage:
5
6 class Foo(object):
7 def foo(self):
8 "Frobber"
9 pass
10
11 class Bar(Foo):
12 @doc_inherit
13 def foo(self):
14 pass
15
16 Now, Bar.foo.__doc__ == Bar().foo.__doc__ == Foo.foo.__doc__ == "Frobber"
17 See: http://stackoverflow.com/questions/2025562/inherit-docstrings-in-python-class-inheritance
18 """
19
20 from functools import wraps
21
22
23 class DocInherit(object):
24 """
25 Docstring inheriting method descriptor
26
27 The class itself is also used as a decorator
28 """
29
30 def __init__(self, mthd):
31 self.mthd = mthd
32 self.name = mthd.__name__
33
34 def __get__(self, obj, cls):
35 if obj:
36 return self.get_with_inst(obj, cls)
37 else:
38 return self.get_no_inst(cls)
39
40 def get_with_inst(self, obj, cls):
41
42 overridden = getattr(super(cls, obj), self.name, None)
43
44 @wraps(self.mthd, assigned=('__name__', '__module__'))
45 def f(*args, **kwargs):
46 return self.mthd(obj, *args, **kwargs)
47
48 return self.use_parent_doc(f, overridden)
49
50 def get_no_inst(self, cls):
51
52 for parent in cls.__mro__[1:]:
53 overridden = getattr(parent, self.name, None)
54 if overridden: break
55
56 @wraps(self.mthd, assigned=('__name__', '__module__'))
57 def f(*args, **kwargs):
58 return self.mthd(*args, **kwargs)
59
60 return self.use_parent_doc(f, overridden)
61
62 def use_parent_doc(self, func, source):
63 if source is None:
64 raise NameError, ("Can't find '%s' in parents" % self.name)
65 func.__doc__ = source.__doc__
66 return func
67
68 doc_inherit = DocInherit
@@ -1,271 +1,278 b''
1 1 #!/usr/bin/env python
2 2 """A really simple notebook to rst/html exporter.
3 3
4 4 Usage
5 5
6 6 ./nb2html.py file.ipynb
7 7
8 8 Produces 'file.rst' and 'file.html', along with auto-generated figure files
9 9 called nb_figure_NN.png.
10 10
11 11 """
12 12
13 13 import os
14 14 import subprocess
15 15 import sys
16 16 from IPython.external import argparse
17 17 from IPython.nbformat import current as nbformat
18 18 from IPython.utils.text import indent
19
19 from decorators import DocInherit
20 20
21 21 # Cell converters
22 22
23 23 def unknown_cell(cell):
24 24 """Default converter for cells of unknown type.
25 25 """
26 26
27 27 return rst_directive('.. warning:: Unknown cell') + \
28 28 [repr(cell)]
29 29
30 30
31 31 def rst_directive(directive, text=''):
32 32 out = [directive, '']
33 33 if text:
34 34 out.extend([indent(text), ''])
35 35 return out
36 36
37 37 # Converters for parts of a cell.
38 38
39 39
40 40 class ConversionException(Exception):
41 41 pass
42 42
43 43
44 44 class Converter(object):
45 45 default_encoding = 'utf-8'
46 46
47 47 def __init__(self, infile):
48 48 self.infile = infile
49 49 self.dirpath = os.path.dirname(infile)
50 50
51 51 @property
52 52 def extension(self):
53 53 raise ConversionException("""extension must be defined in Converter
54 54 subclass""")
55 55
56 56 def dispatch(self, cell_type):
57 57 """return cell_type dependent render method, for example render_code
58 58 """
59 59 return getattr(self, 'render_' + cell_type, unknown_cell)
60 60
61 61 def convert(self):
62 62 lines = []
63 63 for cell in self.nb.worksheets[0].cells:
64 64 conv_fn = self.dispatch(cell.cell_type)
65 65 lines.extend(conv_fn(cell))
66 66 lines.append('')
67 67 return '\n'.join(lines)
68 68
69 69 def render(self):
70 70 "read, convert, and save self.infile"
71 71 self.read()
72 72 self.output = self.convert()
73 73 return self.save()
74 74
75 75 def read(self):
76 76 "read and parse notebook into NotebookNode called self.nb"
77 77 with open(self.infile) as f:
78 78 self.nb = nbformat.read(f, 'json')
79 79
80 80 def save(self, infile=None, encoding=None):
81 81 "read and parse notebook into self.nb"
82 82 if infile is None:
83 83 infile = os.path.splitext(self.infile)[0] + '.' + self.extension
84 84 if encoding is None:
85 85 encoding = self.default_encoding
86 86 with open(infile, 'w') as f:
87 87 f.write(self.output.encode(encoding))
88 88 return infile
89 89
90 90 def render_heading(self, cell):
91 """convert a heading cell
92
93 Returns list."""
91 94 raise NotImplementedError
92 95
93 96 def render_code(self, cell):
97 """Convert a code cell
98
99 Returns list."""
94 100 raise NotImplementedError
95 101
96 102 def render_markdown(self, cell):
103 """convert a markdown cell
104
105 Returns list."""
97 106 raise NotImplementedError
98 107
99 108 def render_pyout(self, cell):
109 """convert pyout part of a code cell
110
111 Returns list."""
100 112 raise NotImplementedError
101 113
102 114 def render_display_data(self, cell):
115 """convert display data from the output of a code cell
116
117 Returns list.
118 """
103 119 raise NotImplementedError
104 120
105 121 def render_stream(self, cell):
122 """convert stream part of a code cell
123
124 Returns list."""
125 raise NotImplementedError
126
127 def render_plaintext(self, cell):
128 """convert plain text
129
130 Returns list."""
106 131 raise NotImplementedError
107 132
108 133
109 134 class ConverterRST(Converter):
110 135 extension = 'rst'
111 136 figures_counter = 0
112 137 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
113 138
139 @DocInherit
114 140 def render_heading(self, cell):
115 """convert a heading cell to rst
116
117 Returns list."""
118 141 marker = self.heading_level[cell.level]
119 142 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
120 143
144 @DocInherit
121 145 def render_code(self, cell):
122 """Convert a code cell to rst
123
124 Returns list."""
125
126 146 if not cell.input:
127 147 return []
128 148
129 149 lines = ['In[%s]:' % cell.prompt_number, '']
130 150 lines.extend(rst_directive('.. code:: python', cell.input))
131 151
132 152 for output in cell.outputs:
133 153 conv_fn = self.dispatch(output.output_type)
134 154 lines.extend(conv_fn(output))
135
155
136 156 return lines
137 157
158 @DocInherit
138 159 def render_markdown(self, cell):
139 """convert a markdown cell to rst
140
141 Returns list."""
142 160 return [cell.source]
143 161
162 @DocInherit
144 163 def render_plaintext(self, cell):
145 """convert plain text to rst
146
147 Returns list."""
148 164 return [cell.source]
149 165
166 @DocInherit
150 167 def render_pyout(self, output):
151 """convert pyout part of a code cell to rst
152
153 Returns list."""
154
155 168 lines = ['Out[%s]:' % output.prompt_number, '']
156 169
157 170 # output is a dictionary like object with type as a key
158 171 if 'latex' in output:
159 172 lines.extend(rst_directive('.. math::', output.latex))
160 173
161 174 if 'text' in output:
162 175 lines.extend(rst_directive('.. parsed-literal::', output.text))
163 176
164 177 return lines
165 178
179 @DocInherit
166 180 def render_display_data(self, output):
167 """convert display data from the output of a code cell to rst.
168
169 Returns list.
170 """
171 181 lines = []
172 182
173 183 if 'png' in output:
174 184 infile = 'nb_figure_%s.png' % self.figures_counter
175 185 fullname = os.path.join(self.dirpath, infile)
176 186 with open(fullname, 'w') as f:
177 187 f.write(output.png.decode('base64'))
178 188
179 189 self.figures_counter += 1
180 190 lines.append('.. image:: %s' % infile)
181 191 lines.append('')
182 192
183 193 return lines
184 194
195 @DocInherit
185 196 def render_stream(self, output):
186 """convert stream part of a code cell to rst
187
188 Returns list."""
189
190 197 lines = []
191 198
192 199 if 'text' in output:
193 200 lines.extend(rst_directive('.. parsed-literal::', output.text))
194 201
195 202 return lines
196 203
197 204
198 205 def rst2simplehtml(infile):
199 206 """Convert a rst file to simplified html suitable for blogger.
200 207
201 208 This just runs rst2html with certain parameters to produce really simple
202 209 html and strips the document header, so the resulting file can be easily
203 210 pasted into a blogger edit window.
204 211 """
205 212
206 213 # This is the template for the rst2html call that produces the cleanest,
207 214 # simplest html I could find. This should help in making it easier to
208 215 # paste into the blogspot html window, though I'm still having problems
209 216 # with linebreaks there...
210 217 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
211 218 "--no-generator --no-datestamp --no-source-link "
212 219 "--no-toc-backlinks --no-section-numbering "
213 220 "--strip-comments ")
214 221
215 222 cmd = "%s %s" % (cmd_template, infile)
216 223 proc = subprocess.Popen(cmd,
217 224 stdout=subprocess.PIPE,
218 225 stderr=subprocess.PIPE,
219 226 shell=True)
220 227 html, stderr = proc.communicate()
221 228 if stderr:
222 229 raise IOError(stderr)
223 230
224 231 # Make an iterator so breaking out holds state. Our implementation of
225 232 # searching for the html body below is basically a trivial little state
226 233 # machine, so we need this.
227 234 walker = iter(html.splitlines())
228 235
229 236 # Find start of main text, break out to then print until we find end /div.
230 237 # This may only work if there's a real title defined so we get a 'div class'
231 238 # tag, I haven't really tried.
232 239 for line in walker:
233 240 if line.startswith('<body>'):
234 241 break
235 242
236 243 newfname = os.path.splitext(infile)[0] + '.html'
237 244 with open(newfname, 'w') as f:
238 245 for line in walker:
239 246 if line.startswith('</body>'):
240 247 break
241 248 f.write(line)
242 249 f.write('\n')
243 250
244 251 return newfname
245 252
246 253
247 254 def main(infile, format='rst'):
248 255 """Convert a notebook to html in one step"""
249 256 if format == 'rst':
250 257 converter = ConverterRST(infile)
251 258 converter.render()
252 259 elif format == 'html':
253 260 #Currently, conversion to html is a 2 step process, nb->rst->html
254 261 converter = ConverterRST(infile)
255 262 rstfname = converter.render()
256 263 rst2simplehtml(rstfname)
257 264
258 265
259 266 if __name__ == '__main__':
260 267 parser = argparse.ArgumentParser(description='nbconvert: Convert IPython notebooks to other formats')
261 268
262 269 # TODO: consider passing file like object around, rather than filenames
263 270 # would allow us to process stdin, or even http streams
264 271 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
265 272
266 273 #Require a filename as a positional argument
267 274 parser.add_argument('infile', nargs=1)
268 275 parser.add_argument('-f', '--format', default='rst',
269 276 help='Output format. Supported formats: rst (default), html.')
270 277 args = parser.parse_args()
271 278 main(infile=args.infile[0], format=args.format)
General Comments 0
You need to be logged in to leave comments. Login now