summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/latex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2020-10-20 20:39:10 +0000
committerKarl Berry <karl@freefriends.org>2020-10-20 20:39:10 +0000
commit91c386913cf9993cb4271471921934a5c2e019a0 (patch)
tree392fd984d6c207e452f86f4644e330fac61a4140 /Master/texmf-dist/tex/latex
parent735245a8b0ba602e5e319d146fbbb7ffe1c39ca5 (diff)
jupynotex (20oct20)
git-svn-id: svn://tug.org/texlive/trunk@56715 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex')
-rw-r--r--Master/texmf-dist/tex/latex/jupynotex/jupynotex.py166
-rw-r--r--Master/texmf-dist/tex/latex/jupynotex/jupynotex.sty7
2 files changed, 173 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/latex/jupynotex/jupynotex.py b/Master/texmf-dist/tex/latex/jupynotex/jupynotex.py
new file mode 100644
index 00000000000..036be088ce5
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/jupynotex/jupynotex.py
@@ -0,0 +1,166 @@
+# Copyright 2020 Facundo Batista
+# All Rights Reserved
+# Licensed under Apache 2.0
+
+"""USAGE: jupynote.py notebook.ipynb cells
+
+ cells is a string with which cells to include, separate groups
+ with comma, ranges with dash (with defaults to start and end.
+"""
+
+import base64
+import json
+import sys
+import tempfile
+import traceback
+
+
+def _verbatimize(lines):
+ """Wrap a series of lines around a verbatim indication."""
+ result = [r"\begin{verbatim}"]
+ for line in lines:
+ result.append(line.rstrip())
+ result.append(r"\end{verbatim}")
+ return result
+
+
+def _save_content(data):
+ """Save the received b64encoded data to a temp file."""
+ _, fname = tempfile.mkstemp(suffix='.png')
+ with open(fname, 'wb') as fh:
+ fh.write(base64.b64decode(data))
+ return fname
+
+
+class Notebook:
+ """The notebook converter to latex."""
+
+ def __init__(self, path):
+ with open(path, 'rt', encoding='utf8') as fh:
+ nb_data = json.load(fh)
+
+ self._cells = nb_data['cells']
+
+ def __len__(self):
+ return len(self._cells)
+
+ def _proc_src(self, content):
+ """Process the source of a cell."""
+ source = content['source']
+ result = []
+ if content['cell_type'] == 'code':
+ result.extend(_verbatimize(source))
+ elif content['cell_type'] == 'markdown':
+ # XXX: maybe we could parse this?
+ result.extend(_verbatimize(source))
+ else:
+ raise ValueError(
+ "Cell type not supported when processing source: {!r}".format(
+ content['cell_type']))
+
+ return '\n'.join(result) + '\n'
+
+ def _proc_out(self, content):
+ """Process the output of a cell."""
+ outputs = content.get('outputs')
+ if not outputs:
+ return
+
+ result = []
+ for item in outputs:
+ output_type = item['output_type']
+ if output_type == 'execute_result':
+ data = item['data']
+ if 'image/png' in data:
+ fname = _save_content(data['image/png'])
+ result.append(r"\includegraphics{{{}}}".format(fname))
+ elif 'text/latex' in data:
+ result.extend(data["text/latex"])
+ else:
+ result.extend(_verbatimize(data["text/plain"]))
+ elif output_type == 'stream':
+ result.extend(_verbatimize(x.rstrip() for x in item["text"]))
+ elif output_type == 'display_data':
+ data = item['data']
+ fname = _save_content(data['image/png'])
+ result.append(r"\includegraphics{{{}}}".format(fname))
+ else:
+ raise ValueError("Output type not supported in item {!r}".format(item))
+
+ return '\n'.join(result) + '\n'
+
+ def get(self, cell_idx):
+ """Return the content from a specific cell in the notebook.
+
+ The content is already splitted in source and output, and converted to latex.
+ """
+ content = self._cells[cell_idx - 1]
+ source = self._proc_src(content)
+ output = self._proc_out(content)
+ return source, output
+
+
+def _parse_cells(spec, maxlen):
+ """Convert the cells spec to a range of ints."""
+ if not spec:
+ raise ValueError("Empty cells spec not allowed")
+ if set(spec) - set('0123456789-,'):
+ raise ValueError(
+ "Found forbidden characters in cells definition (allowed digits, '-' and ',')")
+
+ cells = set()
+ groups = spec.split(',')
+ for group in groups:
+ if '-' in group:
+ cfrom, cto = group.split('-')
+ cfrom = 1 if cfrom == '' else int(cfrom)
+ cto = maxlen if cto == '' else int(cto)
+ if cfrom >= cto:
+ raise ValueError(
+ "Range 'from' need to be smaller than 'to' (got {!r})".format(group))
+ cells.update(range(cfrom, cto + 1))
+ else:
+ cells.add(int(group))
+ cells = sorted(cells)
+
+ if any(x < 1 for x in cells):
+ raise ValueError("Cells need to be >=1")
+ if maxlen < cells[-1]:
+ raise ValueError(
+ "Notebook loaded of len {}, smaller than requested cells: {}".format(maxlen, cells))
+
+ return cells
+
+
+def main(notebook_path, cells_spec):
+ """Main entry point."""
+ nb = Notebook(notebook_path)
+ cells = _parse_cells(cells_spec, len(nb))
+
+ for cell in cells:
+ try:
+ src, out = nb.get(cell)
+ except Exception:
+ title = "ERROR when parsing cell {}".format(cell)
+ print(
+ r"\begin{{tcolorbox}}"
+ r"[colback=red!5!white,colframe=red!75!,title={{{}}}]".format(title))
+ tb = traceback.format_exc()
+ print('\n'.join(_verbatimize(tb.split('\n'))))
+ print(r"\end{tcolorbox}")
+ continue
+
+ print(r"\begin{{tcolorbox}}[title=Cell {{{:02d}}}]".format(cell))
+ print(src)
+ if out:
+ print(r"\tcblower")
+ print(out)
+ print(r"\end{tcolorbox}")
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print(__doc__)
+ exit()
+
+ main(*sys.argv[1:3])
diff --git a/Master/texmf-dist/tex/latex/jupynotex/jupynotex.sty b/Master/texmf-dist/tex/latex/jupynotex/jupynotex.sty
new file mode 100644
index 00000000000..205343d0974
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/jupynotex/jupynotex.sty
@@ -0,0 +1,7 @@
+\ProvidesPackage{jupynotex}[0.1]
+
+\usepackage{tcolorbox}
+
+\newcommand{\jupynotex}[2][-]{
+ \input|"python3 jupynotex.py #2 #1"
+}