##// END OF EJS Templates
Merge pull request #26 from Carreau/utf8fix...
Bussonnier Matthias -
r8175:ef67f84d merge
parent child Browse files
Show More
@@ -1,1439 +1,1439 b''
1 1 #!/usr/bin/env python
2 2 """Convert IPython notebooks to other formats, such as ReST, and HTML.
3 3
4 4 Example:
5 5 ./nbconvert.py --format rst file.ipynb
6 6
7 7 Produces 'file.rst', along with auto-generated figure files
8 8 called nb_figure_NN.png.
9 9 """
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib
16 16 import codecs
17 17 import io
18 18 import logging
19 19 import os
20 20 import pprint
21 21 import re
22 22 import subprocess
23 23 import sys
24 24 import json
25 25 import copy
26 26 from shutil import rmtree
27 27
28 28 inkscape = 'inkscape'
29 29 if sys.platform == 'darwin':
30 30 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
31 31 if not os.path.exists(inkscape):
32 32 inkscape = None
33 33
34 34 # From IPython
35 35 from IPython.external import argparse
36 36 from IPython.nbformat import current as nbformat
37 37 from IPython.utils.text import indent
38 38 from IPython.nbformat.v3.nbjson import BytesEncoder
39 39 from IPython.utils import path, py3compat
40 40
41 41 # local
42 42 from decorators import DocInherit
43 43 from lexers import IPythonLexer
44 44
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Utility functions
48 48 #-----------------------------------------------------------------------------
49 49
50 50 def DocInherit(f):
51 51 return f
52 52
53 53 def remove_fake_files_url(cell):
54 54 """Remove from the cell source the /files/ pseudo-path we use.
55 55 """
56 56 src = cell.source
57 57 cell.source = src.replace('/files/', '')
58 58
59 59
60 60 # ANSI color functions:
61 61
62 62 def remove_ansi(src):
63 63 """Strip all ANSI color escape sequences from input string.
64 64
65 65 Parameters
66 66 ----------
67 67 src : string
68 68
69 69 Returns
70 70 -------
71 71 string
72 72 """
73 73 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
74 74
75 75
76 76 def ansi2html(txt):
77 77 """Render ANSI colors as HTML colors
78 78
79 79 This is equivalent to util.fixConsole in utils.js
80 80
81 81 Parameters
82 82 ----------
83 83 txt : string
84 84
85 85 Returns
86 86 -------
87 87 string
88 88 """
89 89
90 90 ansi_colormap = {
91 91 '30': 'ansiblack',
92 92 '31': 'ansired',
93 93 '32': 'ansigreen',
94 94 '33': 'ansiyellow',
95 95 '34': 'ansiblue',
96 96 '35': 'ansipurple',
97 97 '36': 'ansicyan',
98 98 '37': 'ansigrey',
99 99 '01': 'ansibold',
100 100 }
101 101
102 102 # do ampersand first
103 103 txt = txt.replace('&', '&')
104 104 html_escapes = {
105 105 '<': '&lt;',
106 106 '>': '&gt;',
107 107 "'": '&apos;',
108 108 '"': '&quot;',
109 109 '`': '&#96;',
110 110 }
111 111 for c, escape in html_escapes.iteritems():
112 112 txt = txt.replace(c, escape)
113 113
114 114 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
115 115 m = ansi_re.search(txt)
116 116 opened = False
117 117 cmds = []
118 118 opener = ''
119 119 closer = ''
120 120 while m:
121 121 cmds = m.groups()[0].split(';')
122 122 closer = '</span>' if opened else ''
123 123 opened = len(cmds) > 1 or cmds[0] != '0'*len(cmds[0]);
124 124 classes = []
125 125 for cmd in cmds:
126 126 if cmd in ansi_colormap:
127 127 classes.append(ansi_colormap.get(cmd))
128 128
129 129 if classes:
130 130 opener = '<span class="%s">' % (' '.join(classes))
131 131 else:
132 132 opener = ''
133 133 txt = re.sub(ansi_re, closer + opener, txt, 1)
134 134
135 135 m = ansi_re.search(txt)
136 136
137 137 if opened:
138 138 txt += '</span>'
139 139 return txt
140 140
141 141
142 142 # Pandoc-dependent code
143 143
144 144 def markdown2latex(src):
145 145 """Convert a markdown string to LaTeX via pandoc.
146 146
147 147 This function will raise an error if pandoc is not installed.
148 148
149 149 Any error messages generated by pandoc are printed to stderr.
150 150
151 151 Parameters
152 152 ----------
153 153 src : string
154 154 Input string, assumed to be valid markdown.
155 155
156 156 Returns
157 157 -------
158 158 out : string
159 159 Output as returned by pandoc.
160 160 """
161 161 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
162 162 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
163 163 out, err = p.communicate(src.encode('utf-8'))
164 164 if err:
165 165 print(err, file=sys.stderr)
166 166 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
167 167 return unicode(out,'utf-8')
168 168
169 169
170 170 def markdown2rst(src):
171 171 """Convert a markdown string to LaTeX via pandoc.
172 172
173 173 This function will raise an error if pandoc is not installed.
174 174
175 175 Any error messages generated by pandoc are printed to stderr.
176 176
177 177 Parameters
178 178 ----------
179 179 src : string
180 180 Input string, assumed to be valid markdown.
181 181
182 182 Returns
183 183 -------
184 184 out : string
185 185 Output as returned by pandoc.
186 186 """
187 187 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
188 188 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
189 189 out, err = p.communicate(src.encode('utf-8'))
190 190 if err:
191 191 print(err, file=sys.stderr)
192 192 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
193 193 return unicode(out,'utf-8')
194 194
195 195
196 196 def rst_directive(directive, text=''):
197 197 out = [directive, '']
198 198 if text:
199 199 out.extend([indent(text), ''])
200 200 return out
201 201
202 202
203 203 def coalesce_streams(outputs):
204 204 """merge consecutive sequences of stream output into single stream
205 205
206 206 to prevent extra newlines inserted at flush calls
207 207
208 208 TODO: handle \r deletion
209 209 """
210 210 new_outputs = []
211 211 last = outputs[0]
212 212 new_outputs = [last]
213 213 for output in outputs[1:]:
214 214 if (output.output_type == 'stream' and
215 215 last.output_type == 'stream' and
216 216 last.stream == output.stream
217 217 ):
218 218 last.text += output.text
219 219 else:
220 220 new_outputs.append(output)
221 221
222 222 return new_outputs
223 223
224 224
225 225 #-----------------------------------------------------------------------------
226 226 # Class declarations
227 227 #-----------------------------------------------------------------------------
228 228
229 229 class ConversionException(Exception):
230 230 pass
231 231
232 232
233 233 class Converter(object):
234 234 default_encoding = 'utf-8'
235 235 extension = str()
236 236 figures_counter = 0
237 237 infile = str()
238 238 infile_dir = str()
239 239 infile_root = str()
240 240 files_dir = str()
241 241 with_preamble = True
242 242 user_preamble = None
243 243 output = str()
244 244 raw_as_verbatim = False
245 245
246 246 def __init__(self, infile):
247 247 self.infile = infile
248 248 self.infile_dir, infile_root = os.path.split(infile)
249 249 infile_root = os.path.splitext(infile_root)[0]
250 250 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
251 251 if not os.path.isdir(files_dir):
252 252 os.mkdir(files_dir)
253 253 self.infile_root = infile_root
254 254 self.files_dir = files_dir
255 255 self.outbase = os.path.join(self.infile_dir, infile_root)
256 256
257 257 def dispatch(self, cell_type):
258 258 """return cell_type dependent render method, for example render_code
259 259 """
260 260 return getattr(self, 'render_' + cell_type, self.render_unknown)
261 261
262 262 def dispatch_display_format(self, format):
263 263 """return output_type dependent render method, for example render_output_text
264 264 """
265 265 return getattr(self, 'render_display_format_' + format, self.render_unknown_display)
266 266
267 267 def convert(self, cell_separator='\n'):
268 268 lines = []
269 269 lines.extend(self.optional_header())
270 270 converted_cells = []
271 271 for worksheet in self.nb.worksheets:
272 272 for cell in worksheet.cells:
273 273 #print(cell.cell_type) # dbg
274 274 conv_fn = self.dispatch(cell.cell_type)
275 275 if cell.cell_type in ('markdown', 'raw'):
276 276 remove_fake_files_url(cell)
277 277 converted_cells.append('\n'.join(conv_fn(cell)))
278 278 cell_lines = cell_separator.join(converted_cells).split('\n')
279 279 lines.extend(cell_lines)
280 280 lines.extend(self.optional_footer())
281 281 return u'\n'.join(lines)
282 282
283 283 def render(self):
284 284 "read, convert, and save self.infile"
285 285 if not hasattr(self, 'nb'):
286 286 self.read()
287 287 self.output = self.convert()
288 288 return self.save()
289 289
290 290 def read(self):
291 291 "read and parse notebook into NotebookNode called self.nb"
292 292 with open(self.infile) as f:
293 293 self.nb = nbformat.read(f, 'json')
294 294
295 295 def save(self, outfile=None, encoding=None):
296 296 "read and parse notebook into self.nb"
297 297 if outfile is None:
298 298 outfile = self.outbase + '.' + self.extension
299 299 if encoding is None:
300 300 encoding = self.default_encoding
301 with open(outfile, 'w') as f:
302 f.write(self.output.encode(encoding))
301 with io.open(outfile, 'w', encoding=encoding) as f:
302 f.write(self.output)
303 303 return os.path.abspath(outfile)
304 304
305 305 def optional_header(self):
306 306 return []
307 307
308 308 def optional_footer(self):
309 309 return []
310 310
311 311 def _new_figure(self, data, fmt):
312 312 """Create a new figure file in the given format.
313 313
314 314 Returns a path relative to the input file.
315 315 """
316 316 figname = '%s_fig_%02i.%s' % (self.infile_root,
317 317 self.figures_counter, fmt)
318 318 self.figures_counter += 1
319 319 fullname = os.path.join(self.files_dir, figname)
320 320
321 321 # Binary files are base64-encoded, SVG is already XML
322 322 if fmt in ('png', 'jpg', 'pdf'):
323 323 data = data.decode('base64')
324 324 fopen = lambda fname: open(fname, 'wb')
325 325 else:
326 326 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
327 327
328 328 with fopen(fullname) as f:
329 329 f.write(data)
330 330
331 331 return fullname
332 332
333 333 def render_heading(self, cell):
334 334 """convert a heading cell
335 335
336 336 Returns list."""
337 337 raise NotImplementedError
338 338
339 339 def render_code(self, cell):
340 340 """Convert a code cell
341 341
342 342 Returns list."""
343 343 raise NotImplementedError
344 344
345 345 def render_markdown(self, cell):
346 346 """convert a markdown cell
347 347
348 348 Returns list."""
349 349 raise NotImplementedError
350 350
351 351 def _img_lines(self, img_file):
352 352 """Return list of lines to include an image file."""
353 353 # Note: subclasses may choose to implement format-specific _FMT_lines
354 354 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
355 355 raise NotImplementedError
356 356
357 357 def render_display_data(self, output):
358 358 """convert display data from the output of a code cell
359 359
360 360 Returns list.
361 361 """
362 362 lines = []
363 363
364 364 for fmt in output.keys():
365 365 if fmt in ['png', 'svg', 'jpg', 'pdf']:
366 366 img_file = self._new_figure(output[fmt], fmt)
367 367 # Subclasses can have format-specific render functions (e.g.,
368 368 # latex has to auto-convert all SVG to PDF first).
369 369 lines_fun = getattr(self, '_%s_lines' % fmt, None)
370 370 if not lines_fun:
371 371 lines_fun = self._img_lines
372 372 lines.extend(lines_fun(img_file))
373 373 elif fmt != 'output_type':
374 374 conv_fn = self.dispatch_display_format(fmt)
375 375 lines.extend(conv_fn(output))
376 376 return lines
377 377
378 378 def render_raw(self, cell):
379 379 """convert a cell with raw text
380 380
381 381 Returns list."""
382 382 raise NotImplementedError
383 383
384 384 def render_unknown(self, cell):
385 385 """Render cells of unkown type
386 386
387 387 Returns list."""
388 388 data = pprint.pformat(cell)
389 389 logging.warning('Unknown cell: %s' % cell.cell_type)
390 390 return self._unknown_lines(data)
391 391
392 392 def render_unknown_display(self, output, type):
393 393 """Render cells of unkown type
394 394
395 395 Returns list."""
396 396 data = pprint.pformat(output)
397 397 logging.warning('Unknown output: %s' % output.output_type)
398 398 return self._unknown_lines(data)
399 399
400 400 def render_stream(self, output):
401 401 """render the stream part of an output
402 402
403 403 Returns list.
404 404
405 405 Identical to render_display_format_text
406 406 """
407 407 return self.render_display_format_text(output)
408 408
409 409 def render_pyout(self, output):
410 410 """convert pyout part of a code cell
411 411
412 412 Returns list."""
413 413 raise NotImplementedError
414 414
415 415
416 416 def render_pyerr(self, output):
417 417 """convert pyerr part of a code cell
418 418
419 419 Returns list."""
420 420 raise NotImplementedError
421 421
422 422 def _unknown_lines(self, data):
423 423 """Return list of lines for an unknown cell.
424 424
425 425 Parameters
426 426 ----------
427 427 data : str
428 428 The content of the unknown data as a single string.
429 429 """
430 430 raise NotImplementedError
431 431
432 432 # These are the possible format types in an output node
433 433
434 434 def render_display_format_text(self, output):
435 435 """render the text part of an output
436 436
437 437 Returns list.
438 438 """
439 439 raise NotImplementedError
440 440
441 441 def render_display_format_html(self, output):
442 442 """render the html part of an output
443 443
444 444 Returns list.
445 445 """
446 446 raise NotImplementedError
447 447
448 448 def render_display_format_latex(self, output):
449 449 """render the latex part of an output
450 450
451 451 Returns list.
452 452 """
453 453 raise NotImplementedError
454 454
455 455 def render_display_format_json(self, output):
456 456 """render the json part of an output
457 457
458 458 Returns list.
459 459 """
460 460 raise NotImplementedError
461 461
462 462 def render_display_format_javascript(self, output):
463 463 """render the javascript part of an output
464 464
465 465 Returns list.
466 466 """
467 467 raise NotImplementedError
468 468
469 469
470 470 class ConverterRST(Converter):
471 471 extension = 'rst'
472 472 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
473 473
474 474 @DocInherit
475 475 def render_heading(self, cell):
476 476 marker = self.heading_level[cell.level]
477 477 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
478 478
479 479 @DocInherit
480 480 def render_code(self, cell):
481 481 if not cell.input:
482 482 return []
483 483
484 484 lines = ['In[%s]:' % cell.prompt_number, '']
485 485 lines.extend(rst_directive('.. code:: python', cell.input))
486 486
487 487 for output in cell.outputs:
488 488 conv_fn = self.dispatch(output.output_type)
489 489 lines.extend(conv_fn(output))
490 490
491 491 return lines
492 492
493 493 @DocInherit
494 494 def render_markdown(self, cell):
495 495 #return [cell.source]
496 496 return [markdown2rst(cell.source)]
497 497
498 498 @DocInherit
499 499 def render_raw(self, cell):
500 500 if self.raw_as_verbatim:
501 501 return ['::', '', indent(cell.source), '']
502 502 else:
503 503 return [cell.source]
504 504
505 505 @DocInherit
506 506 def render_pyout(self, output):
507 507 lines = ['Out[%s]:' % output.prompt_number, '']
508 508
509 509 # output is a dictionary like object with type as a key
510 510 if 'latex' in output:
511 511 lines.extend(rst_directive('.. math::', output.latex))
512 512
513 513 if 'text' in output:
514 514 lines.extend(rst_directive('.. parsed-literal::', output.text))
515 515
516 516 return lines
517 517
518 518 @DocInherit
519 519 def render_pyerr(self, output):
520 520 # Note: a traceback is a *list* of frames.
521 521 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
522 522
523 523 @DocInherit
524 524 def _img_lines(self, img_file):
525 525 return ['.. image:: %s' % img_file, '']
526 526
527 527 @DocInherit
528 528 def render_display_format_text(self, output):
529 529 return rst_directive('.. parsed-literal::', output.text)
530 530
531 531 @DocInherit
532 532 def _unknown_lines(self, data):
533 533 return rst_directive('.. warning:: Unknown cell') + [data]
534 534
535 535 def render_display_format_html(self, output):
536 536 """render the html part of an output
537 537
538 538 Returns list.
539 539 """
540 540 return rst_directive('.. raw:: html', output.html)
541 541
542 542 def render_display_format_latex(self, output):
543 543 """render the latex part of an output
544 544
545 545 Returns list.
546 546 """
547 547 return rst_directive('.. math::', output.latex)
548 548
549 549 def render_display_format_json(self, output):
550 550 """render the json part of an output
551 551
552 552 Returns list.
553 553 """
554 554 return rst_directive('.. raw:: json', output.json)
555 555
556 556
557 557 def render_display_format_javascript(self, output):
558 558 """render the javascript part of an output
559 559
560 560 Returns list.
561 561 """
562 562 return rst_directive('.. raw:: javascript', output.javascript)
563 563
564 564
565 565
566 566
567 567 def highlight(src, lang='ipython'):
568 568 """Return a syntax-highlighted version of the input source.
569 569 """
570 570 from pygments import highlight
571 571 from pygments.lexers import get_lexer_by_name
572 572 from pygments.formatters import HtmlFormatter
573 573
574 574 if lang == 'ipython':
575 575 lexer = IPythonLexer()
576 576 else:
577 577 lexer = get_lexer_by_name(lang, stripall=True)
578 578
579 579 return highlight(src, lexer, HtmlFormatter())
580 580
581 581
582 582 class ConverterMarkdown(Converter):
583 583 extension = 'md'
584 584
585 585 def __init__(self, infile, highlight_source=True, show_prompts=False,
586 586 inline_prompt=False):
587 587 super(ConverterMarkdown, self).__init__(infile)
588 588 self.highlight_source = highlight_source
589 589 self.show_prompts = show_prompts
590 590 self.inline_prompt = inline_prompt
591 591
592 592 @DocInherit
593 593 def render_heading(self, cell):
594 594 return ['{0} {1}'.format('#'*cell.level, cell.source), '']
595 595
596 596 @DocInherit
597 597 def render_code(self, cell):
598 598 if not cell.input:
599 599 return []
600 600 lines = []
601 601 if self.show_prompts and not self.inline_prompt:
602 602 lines.extend(['*In[%s]:*' % cell.prompt_number, ''])
603 603 if self.show_prompts and self.inline_prompt:
604 604 prompt = 'In[%s]: ' % cell.prompt_number
605 605 input_lines = cell.input.split('\n')
606 606 src = prompt + input_lines[0] + '\n' + indent('\n'.join(input_lines[1:]), nspaces=len(prompt))
607 607 else:
608 608 src = cell.input
609 609 src = highlight(src) if self.highlight_source else indent(src)
610 610 lines.extend([src, ''])
611 611 if cell.outputs and self.show_prompts and not self.inline_prompt:
612 612 lines.extend(['*Out[%s]:*' % cell.prompt_number, ''])
613 613 for output in cell.outputs:
614 614 conv_fn = self.dispatch(output.output_type)
615 615 lines.extend(conv_fn(output))
616 616
617 617 #lines.append('----')
618 618 lines.append('')
619 619 return lines
620 620
621 621 @DocInherit
622 622 def render_markdown(self, cell):
623 623 return [cell.source, '']
624 624
625 625 @DocInherit
626 626 def render_raw(self, cell):
627 627 if self.raw_as_verbatim:
628 628 return [indent(cell.source), '']
629 629 else:
630 630 return [cell.source, '']
631 631
632 632 @DocInherit
633 633 def render_pyout(self, output):
634 634 lines = []
635 635
636 636 ## if 'text' in output:
637 637 ## lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
638 638
639 639 # output is a dictionary like object with type as a key
640 640 if 'latex' in output:
641 641 pass
642 642
643 643 if 'text' in output:
644 644 lines.extend(['<pre>', indent(output.text), '</pre>'])
645 645
646 646 lines.append('')
647 647 return lines
648 648
649 649 @DocInherit
650 650 def render_pyerr(self, output):
651 651 # Note: a traceback is a *list* of frames.
652 652 return [indent(remove_ansi('\n'.join(output.traceback))), '']
653 653
654 654 @DocInherit
655 655 def _img_lines(self, img_file):
656 656 return ['', '![](%s)' % img_file, '']
657 657
658 658 @DocInherit
659 659 def render_display_format_text(self, output):
660 660 return [indent(output.text)]
661 661
662 662 @DocInherit
663 663 def _unknown_lines(self, data):
664 664 return ['Warning: Unknown cell', data]
665 665
666 666 def render_display_format_html(self, output):
667 667 """render the html part of an output
668 668
669 669 Returns list.
670 670 """
671 671 return [output.html]
672 672
673 673 def render_display_format_latex(self, output):
674 674 """render the latex part of an output
675 675
676 676 Returns list.
677 677 """
678 678 return ['LaTeX::', indent(output.latex)]
679 679
680 680 def render_display_format_json(self, output):
681 681 """render the json part of an output
682 682
683 683 Returns list.
684 684 """
685 685 return ['JSON:', indent(output.json)]
686 686
687 687
688 688 def render_display_format_javascript(self, output):
689 689 """render the javascript part of an output
690 690
691 691 Returns list.
692 692 """
693 693 return ['JavaScript:', indent(output.javascript)]
694 694
695 695
696 696 def return_list(x):
697 697 """Ensure that x is returned as a list or inside one"""
698 698 return x if isinstance(x, list) else [x]
699 699
700 700
701 701 # decorators for HTML output
702 702 def output_container(f):
703 703 """add a prompt-area next to an output"""
704 704 def wrapped(self, output):
705 705 rendered = f(self, output)
706 706 if not rendered:
707 707 # empty output
708 708 return []
709 709 lines = []
710 710 lines.append('<div class="hbox output_area">')
711 711 lines.extend(self._out_prompt(output))
712 712 classes = "output_subarea output_%s" % output.output_type
713 713 if output.output_type == 'stream':
714 714 classes += " output_%s" % output.stream
715 715 lines.append('<div class="%s">' % classes)
716 716 lines.extend(rendered)
717 717 lines.append('</div>') # subarea
718 718 lines.append('</div>') # output_area
719 719
720 720 return lines
721 721
722 722 return wrapped
723 723
724 724 def text_cell(f):
725 725 """wrap text cells in appropriate divs"""
726 726 def wrapped(self, cell):
727 727 rendered = f(self, cell)
728 728 classes = "text_cell_render border-box-sizing rendered_html"
729 729 lines = ['<div class="%s">' % classes] + rendered + ['</div>']
730 730 return lines
731 731 return wrapped
732 732
733 733 class ConverterHTML(Converter):
734 734 extension = 'html'
735 735
736 736 def in_tag(self, tag, src, attrs={}):
737 737 """Return a list of elements bracketed by the given tag"""
738 738 attr_s = ""
739 739 for attr, value in attrs.iteritems():
740 740 attr_s += "%s=%s" % (attr, value)
741 741 return ['<%s %s>' % (tag, attr_s), src, '</%s>' % tag]
742 742
743 743 def _ansi_colored(self, text):
744 744 return ['<pre>%s</pre>' % ansi2html(text)]
745 745
746 746 def _stylesheet(self, fname):
747 747 with io.open(fname, encoding='utf-8') as f:
748 748 s = f.read()
749 749 return self.in_tag('style', s, dict(type='text/css'))
750 750
751 751 def _out_prompt(self, output):
752 752 if output.output_type == 'pyout':
753 753 n = output.prompt_number if output.prompt_number is not None else '&nbsp;'
754 754 content = 'Out [%s]:' % n
755 755 else:
756 756 content = ''
757 757 return ['<div class="prompt output_prompt">%s</div>' % content]
758 758
759 759 def optional_header(self):
760 760 from pygments.formatters import HtmlFormatter
761 761
762 762 header = ['<html>', '<head>']
763 763
764 764 static = os.path.join(path.get_ipython_package_dir(),
765 765 'frontend', 'html', 'notebook', 'static',
766 766 )
767 767 here = os.path.split(os.path.abspath(__file__))[0]
768 768 css = os.path.join(static, 'css')
769 769 for sheet in [
770 770 # do we need jquery and prettify?
771 771 # os.path.join(static, 'jquery', 'css', 'themes', 'base', 'jquery-ui.min.css'),
772 772 # os.path.join(static, 'prettify', 'prettify.css'),
773 773 os.path.join(css, 'boilerplate.css'),
774 774 os.path.join(css, 'fbm.css'),
775 775 os.path.join(css, 'notebook.css'),
776 776 os.path.join(css, 'renderedhtml.css'),
777 777 # our overrides:
778 778 os.path.join(here, 'css', 'static_html.css'),
779 779 ]:
780 780 header.extend(self._stylesheet(sheet))
781 781
782 782 # pygments css
783 783 pygments_css = HtmlFormatter().get_style_defs('.highlight')
784 header.extend(['<meta charset="UTF-8">'])
784 785 header.extend(self.in_tag('style', pygments_css, dict(type='text/css')))
785 786
786 787 # TODO: this should be allowed to use local mathjax:
787 788 header.extend(self.in_tag('script', '', {'type':'text/javascript',
788 789 'src': '"https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"',
789 790 }))
790 791 with io.open(os.path.join(here, 'js', 'initmathjax.js'), encoding='utf-8') as f:
791 792 header.extend(self.in_tag('script', f.read(), {'type': 'text/javascript'}))
792 793
793 794 header.extend(['</head>', '<body>'])
794 795
795 796 return header
796 797
797 798 def optional_footer(self):
798 799 lines = []
799 800 lines.extend([
800 801 '</body>',
801 802 '</html>',
802 803 ])
803 804 return lines
804 805
805 806 @DocInherit
806 807 @text_cell
807 808 def render_heading(self, cell):
808 809 marker = cell.level
809 810 return [u'<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
810 811
811 812 @DocInherit
812 813 def render_code(self, cell):
813 814 if not cell.input:
814 815 return []
815 816
816 817 lines = ['<div class="cell border-box-sizing code_cell vbox">']
817 818
818 819 lines.append('<div class="input hbox">')
819 820 n = cell.prompt_number if getattr(cell, 'prompt_number', None) is not None else '&nbsp;'
820 821 lines.append('<div class="prompt input_prompt">In [%s]:</div>' % n)
821 822 lines.append('<div class="input_area box-flex1">')
822 823 lines.append(highlight(cell.input))
823 824 lines.append('</div>') # input_area
824 825 lines.append('</div>') # input
825 826
826 827 if cell.outputs:
827 828 lines.append('<div class="vbox output_wrapper">')
828 829 lines.append('<div class="output vbox">')
829 830
830 831 for output in coalesce_streams(cell.outputs):
831 832 conv_fn = self.dispatch(output.output_type)
832 833 lines.extend(conv_fn(output))
833 834
834 835 lines.append('</div>') # output
835 836 lines.append('</div>') # output_wrapper
836 837
837 838 lines.append('</div>') # cell
838 839
839 840 return lines
840 841
841 842 @DocInherit
842 843 @text_cell
843 844 def render_markdown(self, cell):
844 845 p = subprocess.Popen(['markdown'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
845 846 out, _ = p.communicate(cell.source.encode('utf-8'))
846 847 return [out.decode('utf-8')]
847 848
848 849 @DocInherit
849 850 def render_raw(self, cell):
850 851 if self.raw_as_verbatim:
851 852 return self.in_tag('pre', cell.source)
852 853 else:
853 854 return [cell.source]
854 855
855 856 @DocInherit
856 857 @output_container
857 858 def render_pyout(self, output):
858 859 for fmt in ['html', 'latex', 'png', 'jpeg', 'svg', 'text']:
859 860 if fmt in output:
860 861 conv_fn = self.dispatch_display_format(fmt)
861 862 return conv_fn(output)
862 863 return []
863 864
864 865 render_display_data = render_pyout
865 866
866 867 @DocInherit
867 868 @output_container
868 869 def render_stream(self, output):
869 870 return self._ansi_colored(output.text)
870 871
871 872
872 873 @DocInherit
873 874 @output_container
874 875 def render_pyerr(self, output):
875 876 # Note: a traceback is a *list* of frames.
876 877 # lines = []
877 878
878 879 # stb =
879 880 return self._ansi_colored('\n'.join(output.traceback))
880 881
881 882 @DocInherit
882 883 def _img_lines(self, img_file):
883 884 return ['<img src="%s">' % img_file, '</img>']
884 885
885 886 @DocInherit
886 887 def _unknown_lines(self, data):
887 888 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
888 889
889 890
890 891 def render_display_format_png(self, output):
891 892 """render the png part of an output
892 893
893 894 Returns list.
894 895 """
895 896 return ['<img src="data:image/png;base64,%s"></img>' % output.png]
896 897
897 898 def render_display_format_svg(self, output):
898 899 """render the svg part of an output
899 900
900 901 Returns list.
901 902 """
902 903 return [output.svg]
903 904
904 905 def render_display_format_jpeg(self, output):
905 906 """render the jpeg part of an output
906 907
907 908 Returns list.
908 909 """
909 910 return ['<img src="data:image/jpeg;base64,%s"></img>' % output.jpeg]
910 911
911 912 def render_display_format_text(self, output):
912 913 """render the text part of an output
913 914
914 915 Returns list.
915 916 """
916 917 return self._ansi_colored(output.text)
917 918
918 919 def render_display_format_html(self, output):
919 920 """render the html part of an output
920 921
921 922 Returns list.
922 923 """
923 924 return [output.html]
924 925
925 926 def render_display_format_latex(self, output):
926 927 """render the latex part of an output
927 928
928 929 Returns list.
929 930 """
930 931 return [output.latex]
931 932
932 933 def render_display_format_json(self, output):
933 934 """render the json part of an output
934 935
935 936 Returns [].
936 937 """
937 938 # html ignores json
938 939 return []
939 940
940 941
941 942 def render_display_format_javascript(self, output):
942 943 """render the javascript part of an output
943 944
944 945 Returns list.
945 946 """
946 947 return [output.javascript]
947 948
948 949
949 950 class ConverterLaTeX(Converter):
950 951 """Converts a notebook to a .tex file suitable for pdflatex.
951 952
952 953 Note: this converter *needs*:
953 954
954 955 - `pandoc`: for all conversion of markdown cells. If your notebook only
955 956 has Raw cells, pandoc will not be needed.
956 957
957 958 - `inkscape`: if your notebook has SVG figures. These need to be
958 959 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
959 960 understand SVG natively.
960 961
961 962 You will in general obtain much better final PDF results if you configure
962 963 the matplotlib backend to create SVG output with
963 964
964 965 %config InlineBackend.figure_format = 'svg'
965 966
966 967 (or set the equivalent flag at startup or in your configuration profile).
967 968 """
968 969 extension = 'tex'
969 970 documentclass = 'article'
970 971 documentclass_options = '11pt,english'
971 972 heading_map = {1: r'\section',
972 973 2: r'\subsection',
973 974 3: r'\subsubsection',
974 975 4: r'\paragraph',
975 976 5: r'\subparagraph',
976 977 6: r'\subparagraph'}
977 978
978 979 def in_env(self, environment, lines):
979 980 """Return list of environment lines for input lines
980 981
981 982 Parameters
982 983 ----------
983 984 env : string
984 985 Name of the environment to bracket with begin/end.
985 986
986 987 lines: """
987 988 out = [ur'\begin{%s}' % environment]
988 989 if isinstance(lines, basestring):
989 990 out.append(lines)
990 991 else: # list
991 992 out.extend(lines)
992 993 out.append(ur'\end{%s}' % environment)
993 994 return out
994 995
995 996 def convert(self):
996 997 # The main body is done by the logic in the parent class, and that's
997 998 # all we need if preamble support has been turned off.
998 999 body = super(ConverterLaTeX, self).convert()
999 1000 if not self.with_preamble:
1000 1001 return body
1001 1002 # But if preamble is on, then we need to construct a proper, standalone
1002 1003 # tex file.
1003 1004
1004 1005 # Tag the document at the top and set latex class
1005 1006 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
1006 1007 r'%% Conversion from the original notebook file:',
1007 1008 r'%% {0}'.format(self.infile),
1008 1009 r'%%',
1009 1010 r'\documentclass[%s]{%s}' % (self.documentclass_options,
1010 1011 self.documentclass),
1011 1012 '',
1012 1013 ]
1013 1014 # Load our own preamble, which is stored next to the main file. We
1014 1015 # need to be careful in case the script entry point is a symlink
1015 1016 myfile = __file__ if not os.path.islink(__file__) else \
1016 1017 os.readlink(__file__)
1017 1018 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
1018 1019 final.append(f.read())
1019 1020
1020 1021 # Load any additional user-supplied preamble
1021 1022 if self.user_preamble:
1022 1023 final.extend(['', '%% Adding user preamble from file:',
1023 1024 '%% {0}'.format(self.user_preamble), ''])
1024 1025 with open(self.user_preamble) as f:
1025 1026 final.append(f.read())
1026 1027
1027 1028 # Include document body
1028 1029 final.extend([ r'\begin{document}', '',
1029 1030 body,
1030 1031 r'\end{document}', ''])
1031 1032 # Retun value must be a string
1032 1033 return '\n'.join(final)
1033 1034
1034 1035 @DocInherit
1035 1036 def render_heading(self, cell):
1036 1037 marker = self.heading_map[cell.level]
1037 1038 return ['%s{%s}' % (marker, cell.source) ]
1038 1039
1039 1040 @DocInherit
1040 1041 def render_code(self, cell):
1041 1042 if not cell.input:
1042 1043 return []
1043 1044
1044 1045 # Cell codes first carry input code, we use lstlisting for that
1045 1046 lines = [ur'\begin{codecell}']
1046 1047
1047 1048 lines.extend(self.in_env('codeinput',
1048 1049 self.in_env('lstlisting', cell.input)))
1049 1050
1050 1051 outlines = []
1051 1052 for output in cell.outputs:
1052 1053 conv_fn = self.dispatch(output.output_type)
1053 1054 outlines.extend(conv_fn(output))
1054 1055
1055 1056 # And then output of many possible types; use a frame for all of it.
1056 1057 if outlines:
1057 1058 lines.extend(self.in_env('codeoutput', outlines))
1058 1059
1059 1060 lines.append(ur'\end{codecell}')
1060 1061
1061 1062 return lines
1062 1063
1063 1064
1064 1065 @DocInherit
1065 1066 def _img_lines(self, img_file):
1066 1067 return self.in_env('center',
1067 1068 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
1068 1069
1069 1070 def _svg_lines(self, img_file):
1070 1071 base_file = os.path.splitext(img_file)[0]
1071 1072 pdf_file = base_file + '.pdf'
1072 1073 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
1073 1074 img_file])
1074 1075 return self._img_lines(pdf_file)
1075 1076
1076 1077 @DocInherit
1077 1078 def render_markdown(self, cell):
1078 1079 return [markdown2latex(cell.source)]
1079 1080
1080 1081 @DocInherit
1081 1082 def render_pyout(self, output):
1082 1083 lines = []
1083 1084
1084 1085 # output is a dictionary like object with type as a key
1085 1086 if 'latex' in output:
1086 1087 lines.extend(output.latex)
1087 1088
1088 1089 if 'text' in output:
1089 1090 lines.extend(self.in_env('verbatim', output.text))
1090 1091
1091 1092 return lines
1092 1093
1093 1094 @DocInherit
1094 1095 def render_pyerr(self, output):
1095 1096 # Note: a traceback is a *list* of frames.
1096 1097 return self.in_env('traceback',
1097 1098 self.in_env('verbatim',
1098 1099 remove_ansi('\n'.join(output.traceback))))
1099 1100
1100 1101 @DocInherit
1101 1102 def render_raw(self, cell):
1102 1103 if self.raw_as_verbatim:
1103 1104 return self.in_env('verbatim', cell.source)
1104 1105 else:
1105 1106 return [cell.source]
1106 1107
1107 1108 @DocInherit
1108 1109 def _unknown_lines(self, data):
1109 1110 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
1110 1111 self.in_env('verbatim', data)
1111 1112
1112 1113
1113 1114 @DocInherit
1114 1115 def render_display_format_text(self, output):
1115 1116 lines = []
1116 1117
1117 1118 if 'text' in output:
1118 1119 lines.extend(self.in_env('verbatim', output.text.strip()))
1119 1120
1120 1121 return lines
1121 1122
1122 1123 def render_display_format_html(self, output):
1123 1124 """render the html part of an output
1124 1125
1125 1126 Returns [].
1126 1127 """
1127 1128 return []
1128 1129
1129 1130 def render_display_format_latex(self, output):
1130 1131 """render the latex part of an output
1131 1132
1132 1133 Returns list.
1133 1134 """
1134 1135 if type(output.latex) == type([]):
1135 1136 return output.latex
1136 1137 return [output.latex]
1137 1138
1138 1139 def render_display_format_json(self, output):
1139 1140 """render the json part of an output
1140 1141
1141 1142 Returns [].
1142 1143 """
1143 1144 # latex ignores json
1144 1145 return []
1145 1146
1146 1147
1147 1148 def render_display_format_javascript(self, output):
1148 1149 """render the javascript part of an output
1149 1150
1150 1151 Returns [].
1151 1152 """
1152 1153 # latex ignores javascript
1153 1154 return []
1154 1155
1155 1156 class ConverterNotebook(Converter):
1156 1157 """
1157 1158 A converter that is essentially a null-op.
1158 1159 This exists so it can be subclassed
1159 1160 for custom handlers of .ipynb files
1160 1161 that create new .ipynb files.
1161 1162
1162 1163 What distinguishes this from JSONWriter is that
1163 1164 subclasses can specify what to do with each type of cell.
1164 1165
1165 1166 Writes out a notebook file.
1166 1167
1167 1168 """
1168 1169 extension = 'ipynb'
1169 1170
1170 1171 def __init__(self, infile, outbase):
1171 1172 Converter.__init__(self, infile)
1172 1173 self.outbase = outbase
1173 1174 rmtree(self.files_dir)
1174 1175
1175 1176 def convert(self):
1176 1177 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
1177 1178
1178 1179 def optional_header(self):
1179 1180 s = \
1180 1181 """{
1181 1182 "metadata": {
1182 1183 "name": "%(name)s"
1183 1184 },
1184 1185 "nbformat": 3,
1185 1186 "worksheets": [
1186 1187 {
1187 1188 "cells": [""" % {'name':self.outbase}
1188 1189
1189 1190 return s.split('\n')
1190 1191
1191 1192 def optional_footer(self):
1192 1193 s = \
1193 1194 """]
1194 1195 }
1195 1196 ]
1196 1197 }"""
1197 1198 return s.split('\n')
1198 1199
1199 1200 @DocInherit
1200 1201 def render_heading(self, cell):
1201 1202 return cell_to_lines(cell)
1202 1203
1203 1204 @DocInherit
1204 1205 def render_code(self, cell):
1205 1206 return cell_to_lines(cell)
1206 1207
1207 1208 @DocInherit
1208 1209 def render_markdown(self, cell):
1209 1210 return cell_to_lines(cell)
1210 1211
1211 1212 @DocInherit
1212 1213 def render_raw(self, cell):
1213 1214 return cell_to_lines(cell)
1214 1215
1215 1216 @DocInherit
1216 1217 def render_pyout(self, output):
1217 1218 return cell_to_lines(output)
1218 1219
1219 1220 @DocInherit
1220 1221 def render_pyerr(self, output):
1221 1222 return cell_to_lines(output)
1222 1223
1223 1224 @DocInherit
1224 1225 def render_display_format_text(self, output):
1225 1226 return [output.text]
1226 1227
1227 1228 def render_display_format_html(self, output):
1228 1229 """render the html part of an output
1229 1230
1230 1231 Returns [].
1231 1232 """
1232 1233 return [output.html]
1233 1234
1234 1235 def render_display_format_latex(self, output):
1235 1236 """render the latex part of an output
1236 1237
1237 1238 Returns list.
1238 1239 """
1239 1240 return [output.latex]
1240 1241
1241 1242 def render_display_format_json(self, output):
1242 1243 """render the json part of an output
1243 1244
1244 1245 Returns [].
1245 1246 """
1246 1247 return [output.json]
1247 1248
1248 1249
1249 1250 def render_display_format_javascript(self, output):
1250 1251 """render the javascript part of an output
1251 1252
1252 1253 Returns [].
1253 1254 """
1254 1255 return [output.javascript]
1255 1256
1256 1257 #-----------------------------------------------------------------------------
1257 1258 # Standalone conversion functions
1258 1259 #-----------------------------------------------------------------------------
1259 1260
1260 1261 def rst2simplehtml(infile):
1261 1262 """Convert a rst file to simplified html suitable for blogger.
1262 1263
1263 1264 This just runs rst2html with certain parameters to produce really simple
1264 1265 html and strips the document header, so the resulting file can be easily
1265 1266 pasted into a blogger edit window.
1266 1267 """
1267 1268
1268 1269 # This is the template for the rst2html call that produces the cleanest,
1269 1270 # simplest html I could find. This should help in making it easier to
1270 1271 # paste into the blogspot html window, though I'm still having problems
1271 1272 # with linebreaks there...
1272 1273 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
1273 1274 "--no-generator --no-datestamp --no-source-link "
1274 1275 "--no-toc-backlinks --no-section-numbering "
1275 1276 "--strip-comments ")
1276 1277
1277 1278 cmd = "%s %s" % (cmd_template, infile)
1278 1279 proc = subprocess.Popen(cmd,
1279 1280 stdout=subprocess.PIPE,
1280 1281 stderr=subprocess.PIPE,
1281 1282 shell=True)
1282 1283 html, stderr = proc.communicate()
1283 1284 if stderr:
1284 1285 raise IOError(stderr)
1285 1286
1286 1287 # Make an iterator so breaking out holds state. Our implementation of
1287 1288 # searching for the html body below is basically a trivial little state
1288 1289 # machine, so we need this.
1289 1290 walker = iter(html.splitlines())
1290 1291
1291 1292 # Find start of main text, break out to then print until we find end /div.
1292 1293 # This may only work if there's a real title defined so we get a 'div class'
1293 1294 # tag, I haven't really tried.
1294 1295 for line in walker:
1295 1296 if line.startswith('<body>'):
1296 1297 break
1297 1298
1298 1299 newfname = os.path.splitext(infile)[0] + '.html'
1299 1300 with open(newfname, 'w') as f:
1300 1301 for line in walker:
1301 1302 if line.startswith('</body>'):
1302 1303 break
1303 1304 f.write(line)
1304 1305 f.write('\n')
1305 1306
1306 1307 return newfname
1307 1308
1308 1309
1309 1310 def md2html(infile):
1310 1311 """Convert a markdown file to simplified html suitable for blogger.
1311 1312
1312 1313 """
1313 1314
1314 1315 proc = subprocess.Popen(['markdown', infile],
1315 1316 stdout=subprocess.PIPE,
1316 1317 stderr=subprocess.PIPE)
1317 1318 html, stderr = proc.communicate()
1318 1319 if stderr:
1319 1320 raise IOError(stderr)
1320 1321
1321 1322 from pygments.formatters import HtmlFormatter
1322 1323 css = HtmlFormatter().get_style_defs('.highlight')
1323 1324
1324 1325 template = """
1325 1326 <!DOCTYPE HTML>
1326 1327 <html>
1327 1328
1328 1329 <head>
1329 1330 <title>{infile}</title>
1330
1331 1331 <style type="text/css">
1332 1332 {css}
1333 1333 </style>
1334 1334
1335 1335 </head>
1336 1336
1337 1337 <body>
1338 1338 {html}
1339 1339 </body>
1340 1340
1341 1341 </html>
1342 1342 """
1343 1343 full_html = template.format(**locals())
1344 1344 newfname = os.path.splitext(infile)[0] + '.html'
1345 1345 with open(newfname, 'w') as f:
1346 1346 f.write(full_html)
1347 1347
1348 1348 return newfname
1349 1349
1350 1350 #-----------------------------------------------------------------------------
1351 1351 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
1352 1352 # but at cell level instead of whole notebook level
1353 1353 #-----------------------------------------------------------------------------
1354 1354
1355 1355 def writes_cell(cell, **kwargs):
1356 1356 kwargs['cls'] = BytesEncoder
1357 1357 kwargs['indent'] = 3
1358 1358 kwargs['sort_keys'] = True
1359 1359 kwargs['separators'] = (',',': ')
1360 1360 if kwargs.pop('split_lines', True):
1361 1361 cell = split_lines_cell(copy.deepcopy(cell))
1362 1362 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
1363 1363
1364 1364
1365 1365 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
1366 1366
1367 1367
1368 1368 def split_lines_cell(cell):
1369 1369 """
1370 1370 Split lines within a cell as in
1371 1371 IPython.nbformat.v3.rwbase.split_lines
1372 1372
1373 1373 """
1374 1374 if cell.cell_type == 'code':
1375 1375 if 'input' in cell and isinstance(cell.input, basestring):
1376 1376 cell.input = (cell.input + '\n').splitlines()
1377 1377 for output in cell.outputs:
1378 1378 for key in _multiline_outputs:
1379 1379 item = output.get(key, None)
1380 1380 if isinstance(item, basestring):
1381 1381 output[key] = (item + '\n').splitlines()
1382 1382 else: # text, heading cell
1383 1383 for key in ['source', 'rendered']:
1384 1384 item = cell.get(key, None)
1385 1385 if isinstance(item, basestring):
1386 1386 cell[key] = (item + '\n').splitlines()
1387 1387 return cell
1388 1388
1389 1389
1390 1390 def cell_to_lines(cell):
1391 1391 '''
1392 1392 Write a cell to json, returning the split lines.
1393 1393 '''
1394 1394 split_lines_cell(cell)
1395 1395 s = writes_cell(cell).strip()
1396 1396 return s.split('\n')
1397 1397
1398 1398
1399 1399 known_formats = "rst (default), html, quick-html, latex, markdown"
1400 1400
1401 1401 def main(infile, format='rst'):
1402 1402 """Convert a notebook to html in one step"""
1403 1403 # XXX: this is just quick and dirty for now. When adding a new format,
1404 1404 # make sure to add it to the `known_formats` string above, which gets
1405 1405 # printed in in the catch-all else, as well as in the help
1406 1406 if format == 'rst':
1407 1407 converter = ConverterRST(infile)
1408 1408 converter.render()
1409 1409 elif format == 'markdown':
1410 1410 converter = ConverterMarkdown(infile)
1411 1411 converter.render()
1412 1412 elif format == 'html':
1413 1413 converter = ConverterHTML(infile)
1414 1414 htmlfname = converter.render()
1415 1415 elif format == 'latex':
1416 1416 converter = ConverterLaTeX(infile)
1417 1417 latexfname = converter.render()
1418 1418 else:
1419 1419 raise SystemExit("Unknown format '%s', " % format +
1420 1420 "known formats are: " + known_formats)
1421 1421
1422 1422 #-----------------------------------------------------------------------------
1423 1423 # Script main
1424 1424 #-----------------------------------------------------------------------------
1425 1425
1426 1426 if __name__ == '__main__':
1427 1427 parser = argparse.ArgumentParser(description=__doc__,
1428 1428 formatter_class=argparse.RawTextHelpFormatter)
1429 1429 # TODO: consider passing file like object around, rather than filenames
1430 1430 # would allow us to process stdin, or even http streams
1431 1431 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1432 1432
1433 1433 #Require a filename as a positional argument
1434 1434 parser.add_argument('infile', nargs=1)
1435 1435 parser.add_argument('-f', '--format', default='rst',
1436 1436 help='Output format. Supported formats: \n' +
1437 1437 known_formats)
1438 1438 args = parser.parse_args()
1439 1439 main(infile=args.infile[0], format=args.format)
General Comments 0
You need to be logged in to leave comments. Login now