nbpy.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. """Read and write notebooks as regular .py files.
  2. Authors:
  3. * Brian Granger
  4. """
  5. #-----------------------------------------------------------------------------
  6. # Copyright (C) 2008-2011 The IPython Development Team
  7. #
  8. # Distributed under the terms of the BSD License. The full license is in
  9. # the file COPYING, distributed as part of this software.
  10. #-----------------------------------------------------------------------------
  11. #-----------------------------------------------------------------------------
  12. # Imports
  13. #-----------------------------------------------------------------------------
  14. import re
  15. from .rwbase import NotebookReader, NotebookWriter
  16. from .nbbase import (
  17. new_code_cell, new_text_cell, new_worksheet,
  18. new_notebook, new_heading_cell, nbformat, nbformat_minor,
  19. )
  20. #-----------------------------------------------------------------------------
  21. # Code
  22. #-----------------------------------------------------------------------------
  23. _encoding_declaration_re = re.compile(r"^#.*coding[:=]\s*([-\w.]+)")
  24. class PyReaderError(Exception):
  25. pass
  26. class PyReader(NotebookReader):
  27. def reads(self, s, **kwargs):
  28. return self.to_notebook(s,**kwargs)
  29. def to_notebook(self, s, **kwargs):
  30. lines = s.splitlines()
  31. cells = []
  32. cell_lines = []
  33. kwargs = {}
  34. state = u'codecell'
  35. for line in lines:
  36. if line.startswith(u'# <nbformat>') or _encoding_declaration_re.match(line):
  37. pass
  38. elif line.startswith(u'# <codecell>'):
  39. cell = self.new_cell(state, cell_lines, **kwargs)
  40. if cell is not None:
  41. cells.append(cell)
  42. state = u'codecell'
  43. cell_lines = []
  44. kwargs = {}
  45. elif line.startswith(u'# <htmlcell>'):
  46. cell = self.new_cell(state, cell_lines, **kwargs)
  47. if cell is not None:
  48. cells.append(cell)
  49. state = u'htmlcell'
  50. cell_lines = []
  51. kwargs = {}
  52. elif line.startswith(u'# <markdowncell>'):
  53. cell = self.new_cell(state, cell_lines, **kwargs)
  54. if cell is not None:
  55. cells.append(cell)
  56. state = u'markdowncell'
  57. cell_lines = []
  58. kwargs = {}
  59. # VERSIONHACK: plaintext -> raw
  60. elif line.startswith(u'# <rawcell>') or line.startswith(u'# <plaintextcell>'):
  61. cell = self.new_cell(state, cell_lines, **kwargs)
  62. if cell is not None:
  63. cells.append(cell)
  64. state = u'rawcell'
  65. cell_lines = []
  66. kwargs = {}
  67. elif line.startswith(u'# <headingcell'):
  68. cell = self.new_cell(state, cell_lines, **kwargs)
  69. if cell is not None:
  70. cells.append(cell)
  71. cell_lines = []
  72. m = re.match(r'# <headingcell level=(?P<level>\d)>',line)
  73. if m is not None:
  74. state = u'headingcell'
  75. kwargs = {}
  76. kwargs['level'] = int(m.group('level'))
  77. else:
  78. state = u'codecell'
  79. kwargs = {}
  80. cell_lines = []
  81. else:
  82. cell_lines.append(line)
  83. if cell_lines and state == u'codecell':
  84. cell = self.new_cell(state, cell_lines)
  85. if cell is not None:
  86. cells.append(cell)
  87. ws = new_worksheet(cells=cells)
  88. nb = new_notebook(worksheets=[ws])
  89. return nb
  90. def new_cell(self, state, lines, **kwargs):
  91. if state == u'codecell':
  92. input = u'\n'.join(lines)
  93. input = input.strip(u'\n')
  94. if input:
  95. return new_code_cell(input=input)
  96. elif state == u'htmlcell':
  97. text = self._remove_comments(lines)
  98. if text:
  99. return new_text_cell(u'html',source=text)
  100. elif state == u'markdowncell':
  101. text = self._remove_comments(lines)
  102. if text:
  103. return new_text_cell(u'markdown',source=text)
  104. elif state == u'rawcell':
  105. text = self._remove_comments(lines)
  106. if text:
  107. return new_text_cell(u'raw',source=text)
  108. elif state == u'headingcell':
  109. text = self._remove_comments(lines)
  110. level = kwargs.get('level',1)
  111. if text:
  112. return new_heading_cell(source=text,level=level)
  113. def _remove_comments(self, lines):
  114. new_lines = []
  115. for line in lines:
  116. if line.startswith(u'#'):
  117. new_lines.append(line[2:])
  118. else:
  119. new_lines.append(line)
  120. text = u'\n'.join(new_lines)
  121. text = text.strip(u'\n')
  122. return text
  123. def split_lines_into_blocks(self, lines):
  124. if len(lines) == 1:
  125. yield lines[0]
  126. raise StopIteration()
  127. import ast
  128. source = '\n'.join(lines)
  129. code = ast.parse(source)
  130. starts = [x.lineno-1 for x in code.body]
  131. for i in range(len(starts)-1):
  132. yield '\n'.join(lines[starts[i]:starts[i+1]]).strip('\n')
  133. yield '\n'.join(lines[starts[-1]:]).strip('\n')
  134. class PyWriter(NotebookWriter):
  135. def writes(self, nb, **kwargs):
  136. lines = [u'# -*- coding: utf-8 -*-']
  137. lines.extend([
  138. u'# <nbformat>%i.%i</nbformat>' % (nbformat, nbformat_minor),
  139. u'',
  140. ])
  141. for ws in nb.worksheets:
  142. for cell in ws.cells:
  143. if cell.cell_type == u'code':
  144. input = cell.get(u'input')
  145. if input is not None:
  146. lines.extend([u'# <codecell>',u''])
  147. lines.extend(input.splitlines())
  148. lines.append(u'')
  149. elif cell.cell_type == u'html':
  150. input = cell.get(u'source')
  151. if input is not None:
  152. lines.extend([u'# <htmlcell>',u''])
  153. lines.extend([u'# ' + line for line in input.splitlines()])
  154. lines.append(u'')
  155. elif cell.cell_type == u'markdown':
  156. input = cell.get(u'source')
  157. if input is not None:
  158. lines.extend([u'# <markdowncell>',u''])
  159. lines.extend([u'# ' + line for line in input.splitlines()])
  160. lines.append(u'')
  161. elif cell.cell_type == u'raw':
  162. input = cell.get(u'source')
  163. if input is not None:
  164. lines.extend([u'# <rawcell>',u''])
  165. lines.extend([u'# ' + line for line in input.splitlines()])
  166. lines.append(u'')
  167. elif cell.cell_type == u'heading':
  168. input = cell.get(u'source')
  169. level = cell.get(u'level',1)
  170. if input is not None:
  171. lines.extend([u'# <headingcell level=%s>' % level,u''])
  172. lines.extend([u'# ' + line for line in input.splitlines()])
  173. lines.append(u'')
  174. lines.append('')
  175. return u'\n'.join(lines)
  176. _reader = PyReader()
  177. _writer = PyWriter()
  178. reads = _reader.reads
  179. read = _reader.read
  180. to_notebook = _reader.to_notebook
  181. write = _writer.write
  182. writes = _writer.writes