| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- """Read and write notebooks as regular .py files.
- Authors:
- * Brian Granger
- """
- # -----------------------------------------------------------------------------
- # Copyright (C) 2008-2011 The IPython Development Team
- #
- # Distributed under the terms of the BSD License. The full license is in
- # the file LICENSE, distributed as part of this software.
- # -----------------------------------------------------------------------------
- # -----------------------------------------------------------------------------
- # Imports
- # -----------------------------------------------------------------------------
- from __future__ import annotations
- import re
- from .nbbase import (
- nbformat,
- nbformat_minor,
- new_code_cell,
- new_heading_cell,
- new_notebook,
- new_text_cell,
- new_worksheet,
- )
- from .rwbase import NotebookReader, NotebookWriter
- # -----------------------------------------------------------------------------
- # Code
- # -----------------------------------------------------------------------------
- _encoding_declaration_re = re.compile(r"^#.*coding[:=]\s*([-\w.]+)")
- class PyReaderError(Exception):
- """An error raised for a pyreader error."""
- class PyReader(NotebookReader):
- """A python notebook reader."""
- def reads(self, s, **kwargs):
- """Convert a string to a notebook"""
- return self.to_notebook(s, **kwargs)
- def to_notebook(self, s, **kwargs):
- """Convert a string to a notebook"""
- lines = s.splitlines()
- cells = []
- cell_lines: list[str] = []
- kwargs = {}
- state = "codecell"
- for line in lines:
- if line.startswith("# <nbformat>") or _encoding_declaration_re.match(line):
- pass
- elif line.startswith("# <codecell>"):
- cell = self.new_cell(state, cell_lines, **kwargs)
- if cell is not None:
- cells.append(cell)
- state = "codecell"
- cell_lines = []
- kwargs = {}
- elif line.startswith("# <htmlcell>"):
- cell = self.new_cell(state, cell_lines, **kwargs)
- if cell is not None:
- cells.append(cell)
- state = "htmlcell"
- cell_lines = []
- kwargs = {}
- elif line.startswith("# <markdowncell>"):
- cell = self.new_cell(state, cell_lines, **kwargs)
- if cell is not None:
- cells.append(cell)
- state = "markdowncell"
- cell_lines = []
- kwargs = {}
- # VERSIONHACK: plaintext -> raw
- elif line.startswith(("# <rawcell>", "# <plaintextcell>")):
- cell = self.new_cell(state, cell_lines, **kwargs)
- if cell is not None:
- cells.append(cell)
- state = "rawcell"
- cell_lines = []
- kwargs = {}
- elif line.startswith("# <headingcell"):
- cell = self.new_cell(state, cell_lines, **kwargs)
- if cell is not None:
- cells.append(cell)
- cell_lines = []
- m = re.match(r"# <headingcell level=(?P<level>\d)>", line)
- if m is not None:
- state = "headingcell"
- kwargs = {}
- kwargs["level"] = int(m.group("level"))
- else:
- state = "codecell"
- kwargs = {}
- cell_lines = []
- else:
- cell_lines.append(line)
- if cell_lines and state == "codecell":
- cell = self.new_cell(state, cell_lines)
- if cell is not None:
- cells.append(cell)
- ws = new_worksheet(cells=cells)
- return new_notebook(worksheets=[ws])
- def new_cell(self, state, lines, **kwargs):
- """Create a new cell."""
- if state == "codecell":
- input_ = "\n".join(lines)
- input_ = input_.strip("\n")
- if input_:
- return new_code_cell(input=input_)
- elif state == "htmlcell":
- text = self._remove_comments(lines)
- if text:
- return new_text_cell("html", source=text)
- elif state == "markdowncell":
- text = self._remove_comments(lines)
- if text:
- return new_text_cell("markdown", source=text)
- elif state == "rawcell":
- text = self._remove_comments(lines)
- if text:
- return new_text_cell("raw", source=text)
- elif state == "headingcell":
- text = self._remove_comments(lines)
- level = kwargs.get("level", 1)
- if text:
- return new_heading_cell(source=text, level=level)
- def _remove_comments(self, lines):
- new_lines = []
- for line in lines:
- if line.startswith("#"):
- new_lines.append(line[2:])
- else:
- new_lines.append(line)
- text = "\n".join(new_lines)
- text = text.strip("\n")
- return text # noqa: RET504
- def split_lines_into_blocks(self, lines):
- """Split lines into code blocks."""
- if len(lines) == 1:
- yield lines[0]
- raise StopIteration()
- import ast
- source = "\n".join(lines)
- code = ast.parse(source)
- starts = [x.lineno - 1 for x in code.body]
- for i in range(len(starts) - 1):
- yield "\n".join(lines[starts[i] : starts[i + 1]]).strip("\n")
- yield "\n".join(lines[starts[-1] :]).strip("\n")
- class PyWriter(NotebookWriter):
- """A Python notebook writer."""
- def writes(self, nb, **kwargs):
- """Convert a notebook to a string."""
- lines = ["# -*- coding: utf-8 -*-"]
- lines.extend(
- [
- "# <nbformat>%i.%i</nbformat>" % (nbformat, nbformat_minor),
- "",
- ]
- )
- for ws in nb.worksheets:
- for cell in ws.cells:
- if cell.cell_type == "code":
- input_ = cell.get("input")
- if input_ is not None:
- lines.extend(["# <codecell>", ""])
- lines.extend(input_.splitlines())
- lines.append("")
- elif cell.cell_type == "html":
- input_ = cell.get("source")
- if input_ is not None:
- lines.extend(["# <htmlcell>", ""])
- lines.extend(["# " + line for line in input_.splitlines()])
- lines.append("")
- elif cell.cell_type == "markdown":
- input_ = cell.get("source")
- if input_ is not None:
- lines.extend(["# <markdowncell>", ""])
- lines.extend(["# " + line for line in input_.splitlines()])
- lines.append("")
- elif cell.cell_type == "raw":
- input_ = cell.get("source")
- if input_ is not None:
- lines.extend(["# <rawcell>", ""])
- lines.extend(["# " + line for line in input_.splitlines()])
- lines.append("")
- elif cell.cell_type == "heading":
- input_ = cell.get("source")
- level = cell.get("level", 1)
- if input_ is not None:
- lines.extend(["# <headingcell level=%s>" % level, ""])
- lines.extend(["# " + line for line in input_.splitlines()])
- lines.append("")
- lines.append("")
- return "\n".join(lines)
- _reader = PyReader()
- _writer = PyWriter()
- reads = _reader.reads
- read = _reader.read
- to_notebook = _reader.to_notebook
- write = _writer.write
- writes = _writer.writes
|