diff --git a/.coveragerc b/.coveragerc index ad6dc16..5187b38 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,3 @@ [run] -omit = +omit = ipypublish/scripts/nb_setup.py diff --git a/.flake8 b/.flake8 index 884318a..ae89533 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,9 @@ [flake8] max-line-length = 120 -exclude = +ignore = + E203, # not compatible with black + W503 # not compatible with black +exclude = setup.py, ipypublish/scripts/ipynb_latex_setup.py, - ipypublish/tests/test_files/basic_nb/expected/python_with_meta.py \ No newline at end of file + ipypublish/tests/test_files/basic_nb/expected/python_with_meta.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4a902d7..464ca6f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -18,7 +18,7 @@ Steps to reproduce the behavior: 1. step 1 2. step 2 - + (for extended explanation, use the additional context section) ## Minimal Notebook Example diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9491db8..c382d5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,39 +1,49 @@ # Install pre-commit hooks via # pre-commit install -- repo: git://github.com/pre-commit/pre-commit-hooks - sha: v2.2.3 - hooks: - - id: check-json - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - id: double-quote-string-fixer - - id: flake8 - -- repo: local - hooks: - - - id: yapf - name: Yet Another Python Formatter - entry: yapf - language: system - types: [python] - args: ["-i", "-vv"] - exclude: > - (?x)^( - setup.py - )$ - - - id: doc8 - name: RST Linting - entry: doc8 - language: system - types: [rst] - - # - id: travis-linter - # name: Travis Lint - # entry: travis lint - # files: .travis.yml - # language: ruby - # additional_dependencies: ['travis'] +exclude: >- + (?x)^( + .vscode/settings.json| + docs/build/.*| + example/.*| + ipypublish/tests/test_files/.*| + ipypublish/sphinx/tests/sourcedirs/.*| + ipypublish/sphinx/config.yaml| + docs/source/custom_export_config.rst + )$ + + # TODO docs/source/custom_export_config.rst is excleded because doc8 + # complains about :linenos: in code-block + +repos: + + - repo: git://github.com/pre-commit/pre-commit-hooks + sha: v2.2.3 + hooks: + - id: check-json + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: flake8 + + - repo: https://github.com/psf/black + rev: 19.3b0 + hooks: + - id: black + + - repo: local + hooks: + + - id: doc8 + name: RST Linting + entry: doc8 + language: system + exclude: docs/build/* + types: [rst] + + # - id: travis-linter + # name: Travis Lint + # entry: travis lint + # files: .travis.yml + # language: ruby + # additional_dependencies: ['travis'] diff --git a/.readthedocs.yml b/.readthedocs.yml index 1e18a7e..253d50b 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -16,4 +16,3 @@ python: # builder: html # configuration: conf.py # fail_on_warning: true - diff --git a/.style.yapf b/.style.yapf deleted file mode 100644 index b3d849f..0000000 --- a/.style.yapf +++ /dev/null @@ -1,3 +0,0 @@ -[style] -based_on_style = google -column_limit = 120 diff --git a/.travis.yml b/.travis.yml index 69ae187..082d9f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: sudo: required python: 3.6 dist: trusty - env: TEST_TYPE="flake8" + env: TEST_TYPE="pre-commit" - os: linux sudo: required python: 3.6 @@ -18,18 +18,18 @@ matrix: env: TEST_TYPE="pytest" PYPI_DEPLOY=true - os: linux sudo: required - python: 2.7 - dist: trusty + python: 3.7 + dist: xenial env: TEST_TYPE="pytest" - os: linux sudo: required - python: 3.5 + python: 2.7 dist: trusty env: TEST_TYPE="pytest" - os: linux sudo: required - python: 3.7 - dist: xenial + python: 3.5 + dist: trusty env: TEST_TYPE="pytest" # TODO this takes too long to install # - os: osx @@ -76,13 +76,13 @@ matrix: allow_failures: - os: linux sudo: required - python: 3.5 + python: 2.7 dist: trusty env: TEST_TYPE="pytest" - os: linux sudo: required - python: 3.7 - dist: xenial + python: 3.5 + dist: trusty env: TEST_TYPE="pytest" - os: osx language: generic @@ -123,13 +123,16 @@ install: - pip install -U pip setuptools wheel - if [[ "$TEST_TYPE" == "pytest" ]]; then travis_wait pip install .[tests] ; fi - if [[ "$TEST_TYPE" == "pytest" ]]; then pip install --quiet coveralls ; fi -- if [[ "$TEST_TYPE" == "flake8" ]]; then travis_wait pip install "flake8(>=3.7,<3.8)" ; fi +- if [[ "$TEST_TYPE" == "pre-commit" ]]; then pip install -e .[code_style]; fi - if [[ "$TEST_TYPE" == "rtd" ]]; then travis_wait pip install .[rtd] ; fi script: - if [[ "$TEST_TYPE" == "pytest" ]]; then pytest -v --cov=ipypublish --cov-config .coveragerc --cov-report= ipypublish ; fi - if [[ "$TEST_TYPE" == "pytest" ]]; then nbpublish -pdf --pdf-debug -log debug example/notebooks/Example.ipynb ; fi -- if [[ "$TEST_TYPE" == "flake8" ]]; then flake8 . ; fi +- | + if [[ "$TEST_TYPE" == "pre-commit" ]]; then + pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) + fi - if [[ "$TEST_TYPE" == "rtd" ]]; then cd docs; make ; fi after_success: diff --git a/.vscode/jinja2-latex.code-snippets b/.vscode/jinja2-latex.code-snippets index 3d5e90d..08d2fe3 100644 --- a/.vscode/jinja2-latex.code-snippets +++ b/.vscode/jinja2-latex.code-snippets @@ -10,13 +10,13 @@ "scope": "jinja-latex", "body": "((* set ${1:name} = ${2:value} *))", "description": "set variable" - }, + }, "print": { "prefix": "print", "scope": "inja-latex", "body": "((( ${1:variable} )))", "description": "print variable" - }, + }, "block": { "prefix": "block", "scope": "jinja-latex", @@ -73,4 +73,4 @@ ], "description": "if-else condition" } -} \ No newline at end of file +} diff --git a/.vscode/jinja2.code-snippets b/.vscode/jinja2.code-snippets index 4615ac8..e4840fb 100644 --- a/.vscode/jinja2.code-snippets +++ b/.vscode/jinja2.code-snippets @@ -10,13 +10,13 @@ "scope": "jinja,jinja-yaml,jinja-html", "body": "{% set ${1:name} = ${2:value} %}", "description": "set variable" - }, + }, "print": { "prefix": "print", "scope": "jinja,jinja-yaml,jinja-html", "body": "{{ ${1:variable} }}", "description": "print variable" - }, + }, "block": { "prefix": "block", "scope": "jinja,jinja-yaml,jinja-html", @@ -73,4 +73,4 @@ ], "description": "if-else condition" } -} \ No newline at end of file +} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 2ead2a6..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Current File (Integrated Terminal)", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal" - }, - { - "name": "Python: Attach", - "type": "python", - "request": "attach", - "port": 5678, - "host": "localhost" - }, - { - "name": "Python: Module", - "type": "python", - "request": "launch", - "module": "enter-your-module-name-here", - "console": "integratedTerminal" - }, - { - "name": "Python: Django", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/manage.py", - "console": "integratedTerminal", - "args": [ - "runserver", - "--noreload", - "--nothreading" - ], - "django": true - }, - { - "name": "Python: Flask", - "type": "python", - "request": "launch", - "module": "flask", - "env": { - "FLASK_APP": "app.py" - }, - "args": [ - "run", - "--no-debugger", - "--no-reload" - ], - "jinja": true - }, - { - "name": "Python: Current File (External Terminal)", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "externalTerminal" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 010f705..5708289 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,12 +26,13 @@ "python.linting.pylamaEnabled": false, "python.linting.enabled": true, "python.linting.flake8Enabled": true, - "python.formatting.provider": "yapf", + "python.formatting.provider": "black", "cSpell.words": [ "Jupyter", "docutils", "ipynb", "ipypublish", + "ipywidgets", "jupytext", "nbconvert", "nbpresent", @@ -39,33 +40,33 @@ "placeholders", "plugins" ], - "todo-tree.tags": [ - "TODO", - "todo::", - "FIXME", - "NOTE" - ], - "todo-tree.regex": "((//|#-?| >**Attention**: @@ -28,7 +29,7 @@ For an example of the potential input/output, see: Or, for a practical example of the ipypublish capability, see these documents on Atomic 3D Visualisation: [Notebook](https://github.com/chrisjsewell/chrisjsewell.github.io/blob/master/3d_atomic/3D%20Atomic%20Visualisation.ipynb), [PDF](https://chrisjsewell.github.io/3d_atomic/converted/3D%20Atomic%20Visualisation.view_pdf.html), -[HTML](https://chrisjsewell.github.io/3d_atomic/converted/3D%20Atomic%20Visualisation.html) or +[HTML](https://chrisjsewell.github.io/3d_atomic/converted/3D%20Atomic%20Visualisation.html) or [Reveal.JS slideshow](https://chrisjsewell.github.io/3d_atomic/converted/3D%20Atomic%20Visualisation.slides.html). ## Design Philosophy diff --git a/bandit.yml b/bandit.yml index 11d2e06..e96b97a 100644 --- a/bandit.yml +++ b/bandit.yml @@ -1 +1 @@ -skips: ['B101'] # TODO this should only skip in test files, see https://github.com/PyCQA/bandit/issues/346 \ No newline at end of file +skips: ['B101'] # TODO this should only skip in test files, see https://github.com/PyCQA/bandit/issues/346 diff --git a/binder/environment.yml b/binder/environment.yml index b397ce2..7cdf2f7 100644 --- a/binder/environment.yml +++ b/binder/environment.yml @@ -8,3 +8,4 @@ dependencies: - pandas - sympy - pillow + - ipywidgets diff --git a/conda_dev_env.yaml b/conda_dev_env.yaml index 3227323..5c52c1e 100644 --- a/conda_dev_env.yaml +++ b/conda_dev_env.yaml @@ -24,7 +24,7 @@ dependencies: - ruamel.yaml - setuptools - six -- sphinx >=1.6 +- sphinx >=1.8 - sphinxcontrib-bibtex - texsoup <0.2 - tornado @@ -41,7 +41,7 @@ dependencies: - flake8 <3.8.0,>=3.7.0 - rope - pre_commit =1.14.4 -- yapf =0.26.0 +- black =19.3b0 # docs - sphinx_rtd_theme - jupyter diff --git a/conftest.py b/conftest.py index 1ece6b4..970578a 100644 --- a/conftest.py +++ b/conftest.py @@ -1 +1 @@ -pytest_plugins = 'sphinx.testing.fixtures' +pytest_plugins = "sphinx.testing.fixtures" diff --git a/converted/.gitignore b/converted/.gitignore index 08924fd..d918ff6 100644 --- a/converted/.gitignore +++ b/converted/.gitignore @@ -250,5 +250,4 @@ TSWLatexianTemp* # added by CJS .DS_Store -*.frm - +*.frm diff --git a/converted/Example.html b/converted/Example.html index 631083e..28e086e 100644 --- a/converted/Example.html +++ b/converted/Example.html @@ -21619,4 +21619,4 @@

Embed interactive HTML (like i - \ No newline at end of file + diff --git a/converted/Example.slides.html b/converted/Example.slides.html index 49429d2..df35dcf 100644 --- a/converted/Example.slides.html +++ b/converted/Example.slides.html @@ -21711,4 +21711,4 @@

6. Embed interactive HTML ( - \ No newline at end of file + diff --git a/converted/Example.tex b/converted/Example.tex index 6e5a958..691ccdb 100644 --- a/converted/Example.tex +++ b/converted/Example.tex @@ -6,14 +6,14 @@ captions=tableheading,numbers=noendperiod]{scrartcl} %\usepackage{polyglossia} %\setmainlanguage{british} -%\DeclareTextCommandDefault{\nobreakspace}{\leavevmode\nobreak\ } +%\DeclareTextCommandDefault{\nobreakspace}{\leavevmode\nobreak\ } \usepackage[british]{babel} \usepackage[T1]{fontenc} % Nicer default font (+ math font) than Computer Modern for most use cases \usepackage{mathpazo} \usepackage{graphicx} - \usepackage[skip=3pt]{caption} - \usepackage{adjustbox} % Used to constrain images to a maximum size + \usepackage[skip=3pt]{caption} + \usepackage{adjustbox} % Used to constrain images to a maximum size \usepackage[table]{xcolor} % Allow colors to be defined \usepackage{enumerate} % Needed for markdown enumerations to work \usepackage{amsmath} % Equations @@ -28,8 +28,8 @@ \usepackage[mathletters]{ucs} % Extended unicode (utf-8) support \usepackage[utf8x]{inputenc} % Allow utf-8 characters in the tex document \usepackage{fancyvrb} % verbatim replacement that allows latex - \usepackage{grffile} % extends the file name processing of package graphics - % to support a larger range + \usepackage{grffile} % extends the file name processing of package graphics + % to support a larger range % The hyperref package gives us a pdf with properly built % internal navigation ('pdf bookmarks' for the table of contents, % internal cross-reference links, web links for URLs, etc.) @@ -57,7 +57,7 @@ % bibliography formatting \usepackage[numbers, square, super, sort&compress]{natbib} % hyperlink doi's - \usepackage{doi} + \usepackage{doi} % define a code float \usepackage{newfloat} % to define a new float types @@ -259,7 +259,7 @@ \DeclareTranslation{French}{List of Codes}{Liste des Codes} \DeclareTranslation{Italian}{List of Codes}{Elenco dei Codici} \DeclareTranslation{Dutch}{List of Codes}{Lijst van Codes} -\DeclareTranslation{Portuges}{List of Codes}{Lista de C\'{o}digos} +\DeclareTranslation{Portuges}{List of Codes}{Lista de C\'{o}digos} \DeclareTranslationFallback{Supervisors}{Supervisors} \DeclareTranslation{Catalan}{Supervisors}{Supervisors} @@ -269,7 +269,7 @@ \DeclareTranslation{French}{Supervisors}{Superviseurs} \DeclareTranslation{Italian}{Supervisors}{Le autorit\`{a} di vigilanza} \DeclareTranslation{Dutch}{Supervisors}{supervisors} -\DeclareTranslation{Portuguese}{Supervisors}{Supervisores} +\DeclareTranslation{Portuguese}{Supervisors}{Supervisores} \definecolor{codegreen}{rgb}{0,0.6,0} \definecolor{codegray}{rgb}{0.5,0.5,0.5} @@ -282,20 +282,20 @@ numberstyle=\tiny\color{codegray}, stringstyle=\color{codepurple}, basicstyle=\ttfamily, - breakatwhitespace=false, - keepspaces=true, - numbers=left, - numbersep=10pt, - showspaces=false, + breakatwhitespace=false, + keepspaces=true, + numbers=left, + numbersep=10pt, + showspaces=false, showstringspaces=false, - showtabs=false, + showtabs=false, tabsize=2, breaklines=true, literate={\-}{}{0\discretionary{-}{}{-}}, postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space}, } -\lstset{style=mystyle} +\lstset{style=mystyle} \surroundwithmdframed[ hidealllines=true, @@ -309,13 +309,13 @@ \usepackage{geometry} \geometry{tmargin=1in,bmargin=1in,lmargin=1in,rmargin=1in, nohead,includefoot,footskip=25pt} -% you can use showframe option to check the margins visually +% you can use showframe option to check the margins visually % ensure new section starts on new page \addtokomafont{section}{\clearpage} % Prevent overflowing lines due to hard-to-break entities - \sloppy + \sloppy % Setup hyperref package \hypersetup{ @@ -358,7 +358,7 @@ % align captions to left (indented) \captionsetup{justification=raggedright, - singlelinecheck=false,format=hang,labelfont={it,bf}} + singlelinecheck=false,format=hang,labelfont={it,bf}} % shift footer down so space between separation line \ModifyLayer[addvoffset=.6ex]{scrheadings.foot.odd} @@ -405,8 +405,8 @@ \vspace{1.5cm} - \begin{minipage}{0.8\textwidth} - \begin{center} + \begin{minipage}{0.8\textwidth} + \begin{center} \begin{minipage}{0.39\textwidth} \begin{flushleft} \Large \emph{\GetTranslation{Author}:}\\Authors Name\\\href{mailto:authors@email.com}{authors@email.com} @@ -419,14 +419,14 @@ Second Supervisor \end{flushright} \end{minipage} - \end{center} + \end{center} \end{minipage} \vfill \begin{minipage}{0.8\textwidth} \begin{center}\LARGE{A tagline for the report.} - \end{center} + \end{center} \end{minipage} \vspace{0.8cm} @@ -524,7 +524,7 @@ \subsection{Displaying a plot with its \begin{codecell} \caption{The plotting code for a matplotlib figure (\cref{fig:example_mpl}).}\label{code:example_mpl}\begin{lstlisting}[language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt] -plt.scatter(np.random.rand(10), np.random.rand(10), +plt.scatter(np.random.rand(10), np.random.rand(10), label='data label') plt.ylabel(r'a y label with latex $\alpha$') plt.legend(); @@ -565,8 +565,8 @@ \section{Tables (with pandas)}\label{tables-with-pandas} \section{Equations (with ipython or sympy)}\label{equations-with-ipython-or-sympy} - \begin{equation}\label{eqn:example_ipy} - a = b+c + \begin{equation}\label{eqn:example_ipy} + a = b+c \end{equation} \begin{codecell}[H] @@ -578,7 +578,7 @@ \section{Equations (with ipython or sym.rsolve(f,y(n),[1,4]) \end{lstlisting}\end{codecell} - \begin{equation}\label{eqn:example_sympy} + \begin{equation}\label{eqn:example_sympy} \left(\sqrt{5} i\right)^{\alpha} \left(\frac{1}{2} - \frac{2 i}{5} \sqrt{5}\right) + \left(- \sqrt{5} i\right)^{\alpha} \left(\frac{1}{2} + \frac{2 i}{5} \sqrt{5}\right) \end{equation} @@ -593,4 +593,3 @@ \section{Embed interactive HTML (like \bibliography{Example_files/example.bib} \end{document} - diff --git a/converted/Example_files/example.bib b/converted/Example_files/example.bib index 50e638d..0bc65c4 100644 --- a/converted/Example_files/example.bib +++ b/converted/Example_files/example.bib @@ -34,5 +34,3 @@ @article{zelenyak_molecular_2016 pages = {400--405}, file = {Zelenyak et al_2016_Molecular dynamics study of perovskite structures with modified interatomic.pdf:/Users/cjs14/Library/Application Support/Firefox/Profiles/gignsb3n.default/zotero/storage/H5NVC2I5/Zelenyak et al_2016_Molecular dynamics study of perovskite structures with modified interatomic.pdf:application/pdf} } - - diff --git a/docs/.gitignore b/docs/.gitignore index 3dbe5de..e306c3f 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,4 @@ # source/releases.* source/api/ source/*_nbfiles/ -source/converted/ \ No newline at end of file +source/converted/ diff --git a/docs/get_intersphinx_inv.py b/docs/get_intersphinx_inv.py index 214f1f6..7f835ee 100644 --- a/docs/get_intersphinx_inv.py +++ b/docs/get_intersphinx_inv.py @@ -3,23 +3,25 @@ def fetch_inventory(uri): """Read a Sphinx inventory file into a dictionary.""" + class MockConfig(object): intersphinx_timeout = None # type: int tls_verify = False class MockApp(object): - srcdir = '' + srcdir = "" config = MockConfig() def warn(self, msg): warnings.warn(msg) - return intersphinx.fetch_inventory(MockApp(), '', uri) + return intersphinx.fetch_inventory(MockApp(), "", uri) if __name__ == "__main__": from sphinx.ext import intersphinx import warnings + # uri = 'http://jinja.pocoo.org/docs/dev/objects.inv' # uri = "http://nbconvert.readthedocs.io/en/latest/objects.inv" # uri = "http://nbformat.readthedocs.io/en/latest/objects.inv" @@ -28,9 +30,9 @@ def warn(self, msg): # uri = "https://networkx.github.io/documentation/stable/objects.inv" # uri = "http://docs.scipy.org/doc/scipy/reference/objects.inv" # uri = "http://pillow.readthedocs.org/en/latest/objects.inv" - uri = 'http://www.sphinx-doc.org/en/latest/objects.inv' + uri = "http://www.sphinx-doc.org/en/latest/objects.inv" # Read inventory into a dictionary inv = fetch_inventory(uri) # Or just print it - intersphinx.debug(['', uri]) + intersphinx.debug(["", uri]) diff --git a/docs/requirements.txt b/docs/requirements.txt index ea53355..f19e362 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ numpy matplotlib pandas -sympy<1.3 \ No newline at end of file +sympy<1.3 diff --git a/docs/run_apidoc b/docs/run_apidoc index 2b78826..3f6d1bd 100755 --- a/docs/run_apidoc +++ b/docs/run_apidoc @@ -1,3 +1,3 @@ rm -f source/api/*.rst sphinx-apidoc --force --separate -o . ../../ipypublish/ ../../ipypublish/ipypublish/tests ../../ipypublish/setup.py -rm -f source/api/modules.rst \ No newline at end of file +rm -f source/api/modules.rst diff --git a/docs/source/_static/example.bib b/docs/source/_static/example.bib index 3f1df55..5273a35 100644 --- a/docs/source/_static/example.bib +++ b/docs/source/_static/example.bib @@ -38,5 +38,3 @@ @article{zelenyak_molecular_2016 pages = {400--405}, file = {Zelenyak et al_2016_Molecular dynamics study of perovskite structures with modified interatomic.pdf:/Users/cjs14/Library/Application Support/Firefox/Profiles/gignsb3n.default/zotero/storage/H5NVC2I5/Zelenyak et al_2016_Molecular dynamics study of perovskite structures with modified interatomic.pdf:application/pdf} } - - diff --git a/docs/source/_static/example_glossary.bib b/docs/source/_static/example_glossary.bib index 81c2a8f..c7c8820 100644 --- a/docs/source/_static/example_glossary.bib +++ b/docs/source/_static/example_glossary.bib @@ -21,4 +21,4 @@ @glssymbol{symbol1 plural = {\ensuremath{\pi}s}, text = {alternative text}, sort = {b} -} \ No newline at end of file +} diff --git a/docs/source/_static/other_glossary.bib b/docs/source/_static/other_glossary.bib index 2fe4999..0895140 100644 --- a/docs/source/_static/other_glossary.bib +++ b/docs/source/_static/other_glossary.bib @@ -21,4 +21,4 @@ @glssymbol{symbol2 plural = {\ensuremath{\pi}s}, text = {alternative text}, sort = {b} -} \ No newline at end of file +} diff --git a/docs/source/_static/process.svg b/docs/source/_static/process.svg index b9d12d0..d5207fc 100755 --- a/docs/source/_static/process.svg +++ b/docs/source/_static/process.svg @@ -142,7 +142,7 @@ stroke-miterlimit: 7; diff --git a/docs/source/applications.md b/docs/source/applications.md index 6847995..e19c177 100644 --- a/docs/source/applications.md +++ b/docs/source/applications.md @@ -7,4 +7,4 @@ - 2019 Bogota Experimental Economics Workshop: [Intro to Dynamic documents](https://rebelbogota.github.io/beec2019/BEEW2019.html) - Lead by Berkeley Initiative for Transparency in the Social Sciences - See also [Experimetrics-BITSS-Workshop](https://github.com/lordflaron/Experimetrics-BITSS-Workshop) -- [Generating Software Tests eBook](https://www.fuzzingbook.org/) \ No newline at end of file +- [Generating Software Tests eBook](https://www.fuzzingbook.org/) diff --git a/docs/source/code_cells.ipynb b/docs/source/code_cells.ipynb index 415badb..5296f1e 100644 --- a/docs/source/code_cells.ipynb +++ b/docs/source/code_cells.ipynb @@ -1,524 +1,620 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ".. _code_cells:\n", - "\n", - "# Writing Code and Formatting Output\n", - "\n", - "IPyPublish utilises metadata to mark-up the notebook with information on\n", - "how output should be represented in the converted notebook,\n", - "as shown in :numref:`fig:mpl1`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "figure": { - "caption": "This is a Matplotlib figure, with a caption, a label and a set width", - "label": "fig:mpl1", - "width": 0.4 - } - } - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "plt.plot(np.sin(np.linspace(0, 6)))\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ".. seealso::\n", - "\n", - " [The PDF representation of this notebook](_static/code_cells.pdf)\n", - "\n", - " :ref:`metadata_tags`, for a full description and list of ipypublish metadata\n", - "\n", - ".. _jupytext_python:\n", - "\n", - "## Converting Notebooks to Pure Python\n", - "\n", - "To write code, we can work in the conventional Jupyter Notebook environment,\n", - "or we can use [jupytext](https://github.com/mwouts/jupytext),\n", - "to convert between a notebook and the pure python\n", - "[percent format](https://github.com/mwouts/jupytext#the-percent-format)\n", - "\n", - "```console\n", - "$ jupytext --to py:percent notebook.ipynb\n", - "$ jupytext --to notebook notebook.py # overwrite notebook.ipynb\n", - "$ jupytext --to notebook --update notebook.py # update notebook.ipynb\n", - "```\n", - "\n", - "This will produce a standard python file,\n", - "with commented notebook level metadata commented at the top (in YAML format),\n", - "and each cell beginning with ``#%%`` (known as the percent format):\n", - "\n", - "The percent format can be utilised in IDEs, such as\n", - "[Spyder](https://docs.spyder-ide.org/editor.html#defining-code-cells),\n", - "[Atom](https://atom.io/packages/hydrogen),\n", - "[PyCharm](https://www.jetbrains.com/pycharm/), and\n", - "[VS Code](https://code.visualstudio.com/docs/python/jupyter-support),\n", - "to run individual cells:\n", - "\n", - "![Running Notebooks in VS Code](_static/vscode_python.png){#fig:vscode_py width=60%}\n", - "\n", - ".. important::\n", - "\n", - " To preserve ipypublish notebook metadata, you must add:\n", - " `\"jupytext\": {\"metadata_filter\": {\"notebook\": \"ipub\"}}` to\n", - " your notebooks metadata before conversion.\n", - "\n", - ".. seealso::\n", - "\n", - " :ref:`jupytext_rmarkdown`\n", - "\n", - " [Using YAML metadata blocks in Pandoc](https://pandoc.org/MANUAL.html#extension-yaml_metadata_block).\n", - "\n", - "## NB Setup Helper Functions\n", - "\n", - ":py:mod:`ipypublish.scripts.nb_setup` offers a number of useful functions,\n", - "to setup common packages (matplotlib, pandas, etc) for outputting content\n", - "in high quality formats." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "init_cell": true, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [], - "source": [ - "from ipypublish import nb_setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ".. note::\n", - "\n", - " `ipypublish.scripts.ipynb_latex_setup` is deprecated in v0.9\n", - "\n", - "## Text Output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "text": { - "format": { - "backgroundcolor": "\\color{blue!10}" - } - } - } - }, - "outputs": [], - "source": [ - "print(\"\"\"\n", - "This is some printed text,\n", - "with a nicely formatted output.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Images (with PIL)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "init_cell": true, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [], - "source": [ - "import os\n", - "from ipypublish.tests import TEST_PIC_PATH\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "figure": { - "caption": "Horizontally aligned images.", - "label": "fig:example_h", - "widefigure": false - } - } - }, - "outputs": [], - "source": [ - "nb_setup.images_hconcat([TEST_PIC_PATH, TEST_PIC_PATH],\n", - " width=600, gap=10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "figure": { - "caption": "Vertically aligned images.", - "label": "fig:example_v", - "widefigure": false - }, - "slide": "new" - } - }, - "outputs": [], - "source": [ - "nb_setup.images_vconcat([TEST_PIC_PATH, TEST_PIC_PATH],\n", - " height=400, gap=10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "figure": { - "caption": "Images aligned in a grid.", - "label": "fig:example_grid", - "widefigure": false - }, - "slide": "new" - } - }, - "outputs": [], - "source": [ - "nb_setup.images_gridconcat([[_,_] for _ in [TEST_PIC_PATH, TEST_PIC_PATH]],\n", - " height=300, vgap=10,hgap=20)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plots (with Matplotlib)\n", - "\n", - "A matplotlib figure (+@fig:example_mpl{}), and its code (+@code:example_mpl{})." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "code": { - "asfloat": true, - "caption": "The plotting code for a matplotlib figure (\\cref{fig:example_mpl}).", - "label": "code:example_mpl", - "widefigure": false - }, - "figure": { - "caption": "A matplotlib figure", - "label": "fig:example_mpl", - "widefigure": false, - "width": 0.7 - } - } - }, - "outputs": [], - "source": [ - "plt = nb_setup.setup_matplotlib(output=('pdf','svg'))\n", - "plt.scatter(np.random.rand(10), np.random.rand(10),\n", - " label='data label')\n", - "plt.ylabel(r'a y label with latex $\\alpha$')\n", - "plt.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ".. note::\n", - "\n", - " If outputting the Matplotlib figures in a PDF format.\n", - " See [usetex tutorial](https://matplotlib.org/users/usetex.html#usetex-tutorial), \n", - " and [Stackoverflow question](https://stackoverflow.com/questions/38731201/latex-escaping-in-matplotlib).\n", - "\n", - "## Tables (with pandas)\n", - "\n", - "A pandas table (+@tbl:example_pd{}), and its code (+@code:example_pd{})." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "code": { - "asfloat": true, - "caption": "The plotting code for a pandas Dataframe table (\\cref{tbl:example_pd}).", - "label": "code:example_pd", - "placement": "H", - "widefigure": false - }, - "table": { - "alternate": "gray!20", - "caption": "An example of a table created with a pandas dataframe.", - "label": "tbl:example_pd", - "placement": "H" - } - } - }, - "outputs": [], - "source": [ - "pd = nb_setup.setup_pandas(escape_latex=False)\n", - "df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d'])\n", - "df.a = ['$\\delta$','x','y']\n", - "df.b = ['l','m','n']\n", - "df.set_index(['a','b'])\n", - "df.round(3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ".. note::\n", - "\n", - " If using `escape_latex=False`, then PDF conversion will throw an error \n", - " if there are e.g. `_`'s in your column names. You either need to escape\n", - " these manually (`\\_`) or use `escape_latex=True`. But note that, \n", - " `escape_latex=True` will also escape math (e.g. `$\\delta$`) causing it not\n", - " to render.\n", - "\n", - "## Equations (with ipython or sympy)\n", - "\n", - "An ipython and sympy equation =[@eqn:example_ipy;@eqn:example_sympy]." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "equation": { - "label": "eqn:example_ipy" - } - } - }, - "outputs": [], - "source": [ - "from IPython.display import Latex\n", - "Latex('$$ a = b+c $$')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "code": { - "asfloat": true, - "caption": "The plotting code for a sympy equation \\eqref{eqn:example_sympy}.", - "label": "code:example_sym", - "placement": "H", - "widefigure": false - }, - "equation": { - "environment": "equation", - "label": "eqn:example_sympy" - } - } - }, - "outputs": [], - "source": [ - "sym = nb_setup.setup_sympy()\n", - "f = sym.Function('f')\n", - "y = sym.Function('y')\n", - "n = sym.symbols(r'\\alpha')\n", - "f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)\n", - "sym.rsolve(f,y(n),[1,4])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Object Output Formats\n", - "\n", - "The format of the Jupyter Notebook file allows for the storage\n", - "of a single output in multiple formats. This is taken advantage of by\n", - "packages such as matplotlib and pandas, etc to store a figure/table in\n", - "both latex and html formats, which can then be selected by ipypublish\n", - "based on the document type required.\n", - "\n", - "Sometimes a user may wish to have greater control over the output format\n", - "and/or which output types are to be stored. It it possible to achieve\n", - "this *via* the Jupyter `display` function. For example, if we wanted\n", - "to display a pandas.DataFrame table without the index column, such that\n", - "it can be output to both a pdf and html document:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import display\n", - "df = pd.DataFrame(np.random.random((3, 3)))\n", - "latex = df.to_latex(index=False)\n", - "html = df.to_html(index=False)\n", - "display({'text/latex': latex,\n", - " 'text/html': html}, raw=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you wish to create your own object with multiple output formats, you\n", - "should create a class with multiple `_repr_*_()` methods:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ipub": { - "equation": false, - "table": false - } - }, - "outputs": [], - "source": [ - "class MyObject(object):\n", - " def __init__(self, text):\n", - " self.text = text\n", - "\n", - " def _repr_latex_(self):\n", - " return \"\\\\textbf{LaTex: \" + self.text + \"}\"\n", - "\n", - " def _repr_html_(self):\n", - " return \"HTML: \" + self.text + \"\"\n", - "\n", - "MyObject('hallo')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ".. seealso::\n", - "\n", - " :ref:`nbformat:notebook_file_format`\n", - "\n", - " [IPython Rich Display](http://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display)\n", - "\n", - ".. _multiple_outputs:\n", - "\n", - "## Multiple Outputs from a Single Code Cell\n", - "\n", - "Similarly, with the Jupyter `display` functionality, you can control the output\n", - "metadata for multiple outputs in a single code cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import display\n", - "from IPython.display import display_latex\n", - "from IPython.display import display_markdown\n", - "\n", - "x = np.linspace(0, 3.42)\n", - "\n", - "for i in range(1,3):\n", - "\n", - " display_markdown(\n", - " '### Code Created Heading {0}'.format(i), raw=True)\n", - "\n", - " fig, ax = plt.subplots()\n", - " ax.plot(x, np.sin(x*i))\n", - " metadata={'ipub': {\n", - " 'figure': {\n", - " 'caption': 'Code Created Heading {0}'.format(i)}}}\n", - " display(fig, metadata=metadata)\n", - " plt.close()\n" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. _code_cells:\n", + "\n", + "# Writing Code and Formatting Output\n", + "\n", + "IPyPublish utilises metadata to mark-up the notebook with information on\n", + "how output should be represented in the converted notebook,\n", + "as shown in :numref:`fig:mpl1`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "figure": { + "caption": "This is a Matplotlib figure, with a caption, a label and a set width", + "label": "fig:mpl1", + "width": 0.4 + } + } + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "plt.plot(np.sin(np.linspace(0, 6)))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. seealso::\n", + "\n", + " [The PDF representation of this notebook](_static/code_cells.pdf)\n", + "\n", + " :ref:`metadata_tags`, for a full description and list of ipypublish metadata\n", + "\n", + ".. _jupytext_python:\n", + "\n", + "## Converting Notebooks to Pure Python\n", + "\n", + "To write code, we can work in the conventional Jupyter Notebook environment,\n", + "or we can use [jupytext](https://github.com/mwouts/jupytext),\n", + "to convert between a notebook and the pure python\n", + "[percent format](https://github.com/mwouts/jupytext#the-percent-format)\n", + "\n", + "```console\n", + "$ jupytext --to py:percent notebook.ipynb\n", + "$ jupytext --to notebook notebook.py # overwrite notebook.ipynb\n", + "$ jupytext --to notebook --update notebook.py # update notebook.ipynb\n", + "```\n", + "\n", + "This will produce a standard python file,\n", + "with commented notebook level metadata commented at the top (in YAML format),\n", + "and each cell beginning with ``#%%`` (known as the percent format):\n", + "\n", + "The percent format can be utilised in IDEs, such as\n", + "[Spyder](https://docs.spyder-ide.org/editor.html#defining-code-cells),\n", + "[Atom](https://atom.io/packages/hydrogen),\n", + "[PyCharm](https://www.jetbrains.com/pycharm/), and\n", + "[VS Code](https://code.visualstudio.com/docs/python/jupyter-support),\n", + "to run individual cells:\n", + "\n", + "![Running Notebooks in VS Code](_static/vscode_python.png){#fig:vscode_py width=60%}\n", + "\n", + ".. important::\n", + "\n", + " To preserve ipypublish notebook metadata, you must add:\n", + " `\"jupytext\": {\"metadata_filter\": {\"notebook\": \"ipub\"}}` to\n", + " your notebooks metadata before conversion.\n", + "\n", + ".. seealso::\n", + "\n", + " :ref:`jupytext_rmarkdown`\n", + "\n", + " [Using YAML metadata blocks in Pandoc](https://pandoc.org/MANUAL.html#extension-yaml_metadata_block).\n", + "\n", + "## NB Setup Helper Functions\n", + "\n", + ":py:mod:`ipypublish.scripts.nb_setup` offers a number of useful functions,\n", + "to setup common packages (matplotlib, pandas, etc) for outputting content\n", + "in high quality formats." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true, + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "from ipypublish import nb_setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. note::\n", + "\n", + " `ipypublish.scripts.ipynb_latex_setup` is deprecated in v0.9\n", + "\n", + "## Text Output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "text": { + "format": { + "backgroundcolor": "\\color{blue!10}" + } + } + } + }, + "outputs": [], + "source": [ + "print(\"\"\"\n", + "This is some printed text,\n", + "with a nicely formatted output.\n", + "\"\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Images (with PIL)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "init_cell": true, + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import os\n", + "from ipypublish.tests import TEST_PIC_PATH\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "figure": { + "caption": "Horizontally aligned images.", + "label": "fig:example_h", + "widefigure": false + } + } + }, + "outputs": [], + "source": [ + "nb_setup.images_hconcat([TEST_PIC_PATH, TEST_PIC_PATH],\n", + " width=600, gap=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "figure": { + "caption": "Vertically aligned images.", + "label": "fig:example_v", + "widefigure": false + }, + "slide": "new" + } + }, + "outputs": [], + "source": [ + "nb_setup.images_vconcat([TEST_PIC_PATH, TEST_PIC_PATH],\n", + " height=400, gap=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "figure": { + "caption": "Images aligned in a grid.", + "label": "fig:example_grid", + "widefigure": false + }, + "slide": "new" + } + }, + "outputs": [], + "source": [ + "nb_setup.images_gridconcat([[_,_] for _ in [TEST_PIC_PATH, TEST_PIC_PATH]],\n", + " height=300, vgap=10,hgap=20)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plots (with Matplotlib)\n", + "\n", + "A matplotlib figure (+@fig:example_mpl{}), and its code (+@code:example_mpl{})." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "code": { + "asfloat": true, + "caption": "The plotting code for a matplotlib figure (\\cref{fig:example_mpl}).", + "label": "code:example_mpl", + "widefigure": false + }, + "figure": { + "caption": "A matplotlib figure", + "label": "fig:example_mpl", + "widefigure": false, + "width": 0.7 + } + } + }, + "outputs": [], + "source": [ + "plt = nb_setup.setup_matplotlib(output=('pdf','svg'))\n", + "plt.scatter(np.random.rand(10), np.random.rand(10),\n", + " label='data label')\n", + "plt.ylabel(r'a y label with latex $\\alpha$')\n", + "plt.legend();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. note::\n", + "\n", + " If outputting the Matplotlib figures in a PDF format.\n", + " See [usetex tutorial](https://matplotlib.org/users/usetex.html#usetex-tutorial), \n", + " and [Stackoverflow question](https://stackoverflow.com/questions/38731201/latex-escaping-in-matplotlib).\n", + "\n", + "## Tables (with pandas)\n", + "\n", + "A pandas table (+@tbl:example_pd{}), and its code (+@code:example_pd{})." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "code": { + "asfloat": true, + "caption": "The plotting code for a pandas Dataframe table (\\cref{tbl:example_pd}).", + "label": "code:example_pd", + "placement": "H", + "widefigure": false + }, + "table": { + "alternate": "gray!20", + "caption": "An example of a table created with a pandas dataframe.", + "label": "tbl:example_pd", + "placement": "H" + } + } + }, + "outputs": [], + "source": [ + "pd = nb_setup.setup_pandas(escape_latex=False)\n", + "df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d'])\n", + "df.a = ['$\\delta$','x','y']\n", + "df.b = ['l','m','n']\n", + "df.set_index(['a','b'])\n", + "df.round(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. note::\n", + "\n", + " If using `escape_latex=False`, then PDF conversion will throw an error \n", + " if there are e.g. `_`'s in your column names. You either need to escape\n", + " these manually (`\\_`) or use `escape_latex=True`. But note that, \n", + " `escape_latex=True` will also escape math (e.g. `$\\delta$`) causing it not\n", + " to render.\n", + "\n", + "## Equations (with ipython or sympy)\n", + "\n", + "An ipython and sympy equation =[@eqn:example_ipy;@eqn:example_sympy]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "equation": { + "label": "eqn:example_ipy" + } + } + }, + "outputs": [], + "source": [ + "from IPython.display import Latex\n", + "Latex('$$ a = b+c $$')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "code": { + "asfloat": true, + "caption": "The plotting code for a sympy equation \\eqref{eqn:example_sympy}.", + "label": "code:example_sym", + "placement": "H", + "widefigure": false + }, + "equation": { + "environment": "equation", + "label": "eqn:example_sympy" + } + } + }, + "outputs": [], + "source": [ + "sym = nb_setup.setup_sympy()\n", + "f = sym.Function('f')\n", + "y = sym.Function('y')\n", + "n = sym.symbols(r'\\alpha')\n", + "f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)\n", + "sym.rsolve(f,y(n),[1,4])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## IPywidgets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[ipywidgets](https://ipywidgets.readthedocs.io/en/stable/)\n", + "can be added to the notebook, to create interactive elements.\n", + "These widgets are preserved in `sphinx` HTML outputs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets\n", + "slider1 = ipywidgets.FloatSlider()\n", + "slider1.description = \"Slide Me\"\n", + "slider1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Multiple views of the same widget can be created:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using `jslink`, widgets can also be synced,\n", + "without the need for an active python kernel:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider2 = ipywidgets.BoundedFloatText()\n", + "link = ipywidgets.jslink(\n", + " (slider1, 'value'),\n", + " (slider2, 'value'))\n", + "slider2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For more complex examples see: [jupyter.org/widgets](https://jupyter.org/widgets)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Object Output Formats\n", + "\n", + "The format of the Jupyter Notebook file allows for the storage\n", + "of a single output in multiple formats. This is taken advantage of by\n", + "packages such as matplotlib and pandas, etc to store a figure/table in\n", + "both latex and html formats, which can then be selected by ipypublish\n", + "based on the document type required.\n", + "\n", + "Sometimes a user may wish to have greater control over the output format\n", + "and/or which output types are to be stored. It it possible to achieve\n", + "this *via* the Jupyter `display` function. For example, if we wanted\n", + "to display a pandas.DataFrame table without the index column, such that\n", + "it can be output to both a pdf and html document:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "df = pd.DataFrame(np.random.random((3, 3)))\n", + "latex = df.to_latex(index=False)\n", + "html = df.to_html(index=False)\n", + "display({'text/latex': latex,\n", + " 'text/html': html}, raw=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you wish to create your own object with multiple output formats, you\n", + "should create a class with multiple `_repr_*_()` methods:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ipub": { + "equation": false, + "table": false + } + }, + "outputs": [], + "source": [ + "class MyObject(object):\n", + " def __init__(self, text):\n", + " self.text = text\n", + "\n", + " def _repr_latex_(self):\n", + " return \"\\\\textbf{LaTex: \" + self.text + \"}\"\n", + "\n", + " def _repr_html_(self):\n", + " return \"HTML: \" + self.text + \"\"\n", + "\n", + "MyObject('hallo')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".. seealso::\n", + "\n", + " :ref:`nbformat:notebook_file_format`\n", + "\n", + " [IPython Rich Display](http://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display)\n", + "\n", + ".. _multiple_outputs:\n", + "\n", + "## Multiple Outputs from a Single Code Cell\n", + "\n", + "Similarly, with the Jupyter `display` functionality, you can control the output\n", + "metadata for multiple outputs in a single code cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from IPython.display import display_latex\n", + "from IPython.display import display_markdown\n", + "\n", + "x = np.linspace(0, 3.42)\n", + "\n", + "for i in range(1,3):\n", + "\n", + " display_markdown(\n", + " '### Code Created Heading {0}'.format(i), raw=True)\n", + "\n", + " fig, ax = plt.subplots()\n", + " ax.plot(x, np.sin(x*i))\n", + " metadata={'ipub': {\n", + " 'figure': {\n", + " 'caption': 'Code Created Heading {0}'.format(i)}}}\n", + " display(fig, metadata=metadata)\n", + " plt.close()\n" + ] + } + ], + "metadata": { + "hide_input": false, + "ipub": { + "language": "british", + "listcode": true, + "listfigures": true, + "listtables": true, + "pandoc": { + "at_notation": true, + "convert_raw": true, + "use_numref": true + }, + "sphinx": { + "toggle_input": true, + "toggle_input_all": true, + "toggle_output": true, + "toggle_output_all": true + }, + "titlepage": { + "author": "Chris Sewell", + "email": "chrisj\\_sewell@hotmail.com", + "institution": [ + "Institution1", + "Institution2" ], - "metadata": { - "ipub": { - "language": "british", - "listcode": true, - "listfigures": true, - "listtables": true, - "pandoc": { - "at_notation": true, - "convert_raw": true, - "use_numref": true - }, - "sphinx": { - "toggle_input": true, - "toggle_input_all": true, - "toggle_output": true, - "toggle_output_all": true - }, - "titlepage": { - "author": "Chris Sewell", - "email": "chrisj\\_sewell@hotmail.com", - "institution": [ - "Institution1", - "Institution2" - ], - "logo": "_static/logo_example.png", - "subtitle": "Formatting Code Cells", - "supervisors": [ - "First Supervisor", - "Second Supervisor" - ], - "tagline": "Converted using IPyPublish ('latex\\_ipypublish\\_all.exec').", - "title": "Example of Converted Jupyter Notebook" - }, - "toc": true - }, - "jupytext": { - "metadata_filter": { - "notebook": "ipub" - }, - "text_representation": { - "extension": ".Rmd", - "format_name": "rmarkdown", - "format_version": "1.0", - "jupytext_version": "0.8.6" - } - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "logo": "_static/logo_example.png", + "subtitle": "Formatting Code Cells", + "supervisors": [ + "First Supervisor", + "Second Supervisor" + ], + "tagline": "Converted using IPyPublish ('latex\\_ipypublish\\_all.exec').", + "title": "Example of Converted Jupyter Notebook" + }, + "toc": true + }, + "jupytext": { + "notebook_metadata_filter": "ipub", + "text_representation": { + "extension": ".Rmd", + "format_name": "rmarkdown", + "format_version": "1.0", + "jupytext_version": "0.8.6" + } + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/docs/source/conf.py b/docs/source/conf.py index f935902..5379e99 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -33,38 +33,38 @@ import ipypublish from ipypublish.filters_pandoc.main import jinja_filter -on_rtd = os.environ.get('READTHEDOCS') == 'True' +on_rtd = os.environ.get("READTHEDOCS") == "True" # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # -needs_sphinx = '1.6' +needs_sphinx = "1.6" # The master toctree document. -master_doc = 'index' +master_doc = "index" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', # TODO is this needed? - 'sphinx.ext.napoleon', - 'sphinx.ext.autosummary', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", # TODO is this needed? + "sphinx.ext.napoleon", + "sphinx.ext.autosummary", # 'sphinx.ext.imgconverter' # converts svg to pdf in latex output # TODO imgconverter failing (I guess for process.svg), - 'ipypublish.sphinx.notebook', - 'ipypublish.sphinx.gls', - 'sphinxcontrib.bibtex', - 'recommonmark' + "ipypublish.sphinx.notebook", + "ipypublish.sphinx.gls", + "sphinxcontrib.bibtex", + "recommonmark", ] @@ -88,30 +88,33 @@ def process_citations(app, doctree, docname): try: label = app.env.bibtex_cache.get_label_from_key(key) except KeyError: - logger.warning("could not relabel citation [%s]" % key, - type="bibtex", subtype="relabel") + logger.warning( + "could not relabel citation [%s]" % key, + type="bibtex", + subtype="relabel", + ) else: - node[0] = docutils.nodes.label('', label) + node[0] = docutils.nodes.label("", label) sphinxcontrib.bibtex.process_citations = process_citations -suppress_warnings = ['bibtex.relabel'] +suppress_warnings = ["bibtex.relabel"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] if sphinx.version_info[0:2] < (1, 8): source_parsers = { # '.md': 'recommonmark.parser.CommonMarkParser', - '.Rmd': 'ipypublish.sphinx.notebook.parser.NBParser' + ".Rmd": "ipypublish.sphinx.notebook.parser.NBParser" } else: source_suffix = { - '.rst': 'restructuredtext', - '.md': 'markdown', - '.ipynb': 'jupyter_notebook', - '.Rmd': 'jupyter_notebook' + ".rst": "restructuredtext", + ".md": "markdown", + ".ipynb": "jupyter_notebook", + ".Rmd": "jupyter_notebook", } # import jupytext # ipysphinx_preconverters = { @@ -126,11 +129,12 @@ def process_citations(app, doctree, docname): # General information about the project. -project = u'ipypublish' -copyright = u'2017, Chris Sewell' -author = u'Chris Sewell' -description = ('Create quality publication and presentation' - 'directly from Jupyter Notebook(s)') +project = u"ipypublish" +copyright = u"2017, Chris Sewell" +author = u"Chris Sewell" +description = ( + "Create quality publication and presentation" "directly from Jupyter Notebook(s)" +) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -149,7 +153,7 @@ def process_citations(app, doctree, docname): language = None # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -170,14 +174,14 @@ def process_citations(app, doctree, docname): # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # html_logo = '_static/doc_icon_100px.png' -html_favicon = '_static/doc_icon_32px.ico' +html_favicon = "_static/doc_icon_32px.ico" # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'ipypublishdoc' +htmlhelp_basename = "ipypublishdoc" # -- Options for LaTeX output --------------------------------------------- @@ -185,15 +189,12 @@ def process_citations(app, doctree, docname): # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -203,18 +204,20 @@ def process_citations(app, doctree, docname): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'ipypublish.tex', u'ipypublish Documentation', - u'Chris Sewell', 'manual'), + ( + master_doc, + "ipypublish.tex", + u"ipypublish Documentation", + u"Chris Sewell", + "manual", + ) ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'ipypublish', u'ipypublish Documentation', - [author], 1) -] +man_pages = [(master_doc, "ipypublish", u"ipypublish Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -222,24 +225,32 @@ def process_citations(app, doctree, docname): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'ipypublish', u'IPyPublish', - author, 'ipypublish', description, - 'Miscellaneous'), + ( + master_doc, + "ipypublish", + u"IPyPublish", + author, + "ipypublish", + description, + "Miscellaneous", + ) ] # Numbered Elements numfig = True math_numfig = True numfig_secnum_depth = 2 -numfig_format = {'section': 'Section %s', - 'figure': 'Fig. %s', - 'table': 'Table %s', - 'code-block': 'Code Block %s'} +numfig_format = { + "section": "Section %s", + "figure": "Fig. %s", + "table": "Table %s", + "code-block": "Code Block %s", +} math_number_all = True math_eqref_format = "Eq. {number}" # TODO this isn't working mathjax_config = { - 'TeX': {'equationNumbers': {'autoNumber': 'AMS', 'useLabelIds': True}}, + "TeX": {"equationNumbers": {"autoNumber": "AMS", "useLabelIds": True}} } # Napoleon Docstring settings @@ -258,89 +269,115 @@ def process_citations(app, doctree, docname): # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/3.6', None), + "python": ("https://docs.python.org/3.6", None), # 'numpy': ('http://docs.scipy.org/doc/numpy/', None), # 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None), # 'matplotlib': ('http://matplotlib.sourceforge.net/', None), # 'pandas': ('http://pandas.pydata.org/pandas-docs/stable/', None), # 'IPython': ('http://ipython.org/ipython-doc/stable/', None), - 'PIL': ('http://pillow.readthedocs.org/en/latest/', None), - 'nbconvert': ("http://nbconvert.readthedocs.io/en/latest/", None), - 'nbformat': ("http://nbformat.readthedocs.io/en/latest/", None), - 'tornado': ("https://www.tornadoweb.org/en/stable/", None), - 'traitlets': ("https://traitlets.readthedocs.io/en/stable/", None), - 'jinja': ('http://jinja.pocoo.org/docs/dev', None), - 'bibtexparser': ('https://bibtexparser.readthedocs.io/en/master/', None), + "PIL": ("http://pillow.readthedocs.org/en/latest/", None), + "nbconvert": ("http://nbconvert.readthedocs.io/en/latest/", None), + "nbformat": ("http://nbformat.readthedocs.io/en/latest/", None), + "tornado": ("https://www.tornadoweb.org/en/stable/", None), + "traitlets": ("https://traitlets.readthedocs.io/en/stable/", None), + "jinja": ("http://jinja.pocoo.org/docs/dev", None), + "bibtexparser": ("https://bibtexparser.readthedocs.io/en/master/", None), # 'docutils': ("https://docutils.readthedocs.io/en/sphinx-docs", None), # # TODO docutils intersphinx - 'sphinx': ('http://www.sphinx-doc.org/en/latest/', None) + "sphinx": ("http://www.sphinx-doc.org/en/latest/", None), } intersphinx_aliases = { - ('py:class', 'dictionary'): - ('py:class', 'dict'), - ('py:class', 'PIL.Image'): - ('py:class', 'PIL.Image.Image'), - ('py:class', 'nbconvert.preprocessors.base.Preprocessor'): - ('py:class', 'nbconvert.preprocessors.Preprocessor'), - ('py:class', 'nbformat.notebooknode.NotebookNode'): - ('py:class', 'nbformat.NotebookNode'), - ('py:class', 'NotebookNode'): - ('py:class', 'nbformat.NotebookNode'), - ('py:class', 'traitlets.config.configurable.Configurable'): - ('py:module', 'traitlets.config') + ("py:class", "dictionary"): ("py:class", "dict"), + ("py:class", "PIL.Image"): ("py:class", "PIL.Image.Image"), + ("py:class", "nbconvert.preprocessors.base.Preprocessor"): ( + "py:class", + "nbconvert.preprocessors.Preprocessor", + ), + ("py:class", "nbformat.notebooknode.NotebookNode"): ( + "py:class", + "nbformat.NotebookNode", + ), + ("py:class", "NotebookNode"): ("py:class", "nbformat.NotebookNode"), + ("py:class", "traitlets.config.configurable.Configurable"): ( + "py:module", + "traitlets.config", + ), } # Warnings to ignore when using the -n (nitpicky) option # We should ignore any python built-in exception, for instance -nitpick_ignore = [('py:exc', 'ArithmeticError'), ('py:exc', 'AssertionError'), - ('py:exc', 'AttributeError'), ('py:exc', 'BaseException'), - ('py:exc', 'BufferError'), ('py:exc', 'DeprecationWarning'), - ('py:exc', 'EOFError'), ('py:exc', 'EnvironmentError'), - ('py:exc', 'Exception'), ('py:exc', 'FloatingPointError'), - ('py:exc', 'FutureWarning'), ('py:exc', 'GeneratorExit'), - ('py:exc', 'IOError'), ('py:exc', 'ImportError'), - ('py:exc', 'ImportWarning'), ('py:exc', 'IndentationError'), - ('py:exc', 'IndexError'), ('py:exc', 'KeyError'), - ('py:exc', 'KeyboardInterrupt'), ('py:exc', 'LookupError'), - ('py:exc', 'MemoryError'), ('py:exc', 'NameError'), - ('py:exc', 'NotImplementedError'), ('py:exc', 'OSError'), - ('py:exc', 'OverflowError'), - ('py:exc', 'PendingDeprecationWarning'), - ('py:exc', 'ReferenceError'), ('py:exc', 'RuntimeError'), - ('py:exc', 'RuntimeWarning'), ('py:exc', 'StandardError'), - ('py:exc', 'StopIteration'), ('py:exc', 'SyntaxError'), - ('py:exc', 'SyntaxWarning'), ('py:exc', 'SystemError'), - ('py:exc', 'SystemExit'), ('py:exc', 'TabError'), - ('py:exc', 'TypeError'), ('py:exc', 'UnboundLocalError'), - ('py:exc', 'UnicodeDecodeError'), - ('py:exc', 'UnicodeEncodeError'), ('py:exc', 'UnicodeError'), - ('py:exc', 'UnicodeTranslateError'), - ('py:exc', 'UnicodeWarning'), ('py:exc', 'UserWarning'), - ('py:exc', 'VMSError'), ('py:exc', 'ValueError'), - ('py:exc', 'Warning'), ('py:exc', 'WindowsError'), - ('py:exc', 'ZeroDivisionError'), ('py:obj', 'str'), - ('py:obj', 'list'), - ('py:obj', 'tuple'), - ('py:obj', 'int'), - ('py:obj', 'float'), - ('py:obj', 'bool'), - ('py:obj', 'Mapping'), - ('py:obj', 'MutableMapping'), - ('py:func', 'str.format'), - ('py:class', '_abcoll.MutableMapping'), - ('py:class', - 'traitlets.config.configurable.LoggingConfigurable'), - ('py:class', 'docutils.nodes.Element'), - ('py:class', 'docutils.nodes.General'), - ('py:class', 'docutils.nodes.document'), - ('py:class', 'docutils.parsers.rst.Directive'), - ('py:class', 'docutils.transforms.Transform'), - ('py:class', 'docutils.parsers.rst.Parser'), - ('py:class', 'sphinx.parsers.RSTParser'), - ('py:class', 'sphinx.roles.XRefRole'), - ('py:exc', 'nbconvert.pandoc.PandocMissing') - ] +nitpick_ignore = [ + ("py:exc", "ArithmeticError"), + ("py:exc", "AssertionError"), + ("py:exc", "AttributeError"), + ("py:exc", "BaseException"), + ("py:exc", "BufferError"), + ("py:exc", "DeprecationWarning"), + ("py:exc", "EOFError"), + ("py:exc", "EnvironmentError"), + ("py:exc", "Exception"), + ("py:exc", "FloatingPointError"), + ("py:exc", "FutureWarning"), + ("py:exc", "GeneratorExit"), + ("py:exc", "IOError"), + ("py:exc", "ImportError"), + ("py:exc", "ImportWarning"), + ("py:exc", "IndentationError"), + ("py:exc", "IndexError"), + ("py:exc", "KeyError"), + ("py:exc", "KeyboardInterrupt"), + ("py:exc", "LookupError"), + ("py:exc", "MemoryError"), + ("py:exc", "NameError"), + ("py:exc", "NotImplementedError"), + ("py:exc", "OSError"), + ("py:exc", "OverflowError"), + ("py:exc", "PendingDeprecationWarning"), + ("py:exc", "ReferenceError"), + ("py:exc", "RuntimeError"), + ("py:exc", "RuntimeWarning"), + ("py:exc", "StandardError"), + ("py:exc", "StopIteration"), + ("py:exc", "SyntaxError"), + ("py:exc", "SyntaxWarning"), + ("py:exc", "SystemError"), + ("py:exc", "SystemExit"), + ("py:exc", "TabError"), + ("py:exc", "TypeError"), + ("py:exc", "UnboundLocalError"), + ("py:exc", "UnicodeDecodeError"), + ("py:exc", "UnicodeEncodeError"), + ("py:exc", "UnicodeError"), + ("py:exc", "UnicodeTranslateError"), + ("py:exc", "UnicodeWarning"), + ("py:exc", "UserWarning"), + ("py:exc", "VMSError"), + ("py:exc", "ValueError"), + ("py:exc", "Warning"), + ("py:exc", "WindowsError"), + ("py:exc", "ZeroDivisionError"), + ("py:obj", "str"), + ("py:obj", "list"), + ("py:obj", "tuple"), + ("py:obj", "int"), + ("py:obj", "float"), + ("py:obj", "bool"), + ("py:obj", "Mapping"), + ("py:obj", "MutableMapping"), + ("py:func", "str.format"), + ("py:class", "_abcoll.MutableMapping"), + ("py:class", "traitlets.config.configurable.LoggingConfigurable"), + ("py:class", "docutils.nodes.Element"), + ("py:class", "docutils.nodes.General"), + ("py:class", "docutils.nodes.document"), + ("py:class", "docutils.parsers.rst.Directive"), + ("py:class", "docutils.transforms.Transform"), + ("py:class", "docutils.parsers.rst.Parser"), + ("py:class", "sphinx.parsers.RSTParser"), + ("py:class", "sphinx.roles.XRefRole"), + ("py:exc", "nbconvert.pandoc.PandocMissing"), +] try: out = subprocess.check_output(["git", "branch"]).decode("utf8") @@ -378,27 +415,31 @@ def process_citations(app, doctree, docname): {{%- endif %}} __ https://github.com/chrisjsewell/ipypublish/{gitpath}/{{{{ docname }}}} -""".format(gitpath=gitpath, binderpath=binderpath) # noqa: E501 +""".format( # noqa: E501 + gitpath=gitpath, binderpath=binderpath +) def create_git_releases(app): - this_folder = os.path.abspath( - os.path.dirname(os.path.realpath(__file__))) + this_folder = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) - git_history = urllib.request.urlopen( - 'https://api.github.com/repos/chrisjsewell/ipypublish/releases' - ).read().decode('utf-8') + git_history = ( + urllib.request.urlopen( + "https://api.github.com/repos/chrisjsewell/ipypublish/releases" + ) + .read() + .decode("utf-8") + ) # NOTE on vscode this could fail with urllib.error.HTTPError git_history_json = json.loads(git_history) # NOTE on vscode this was failing unless encoding='utf8' was present - with io.open(os.path.join(this_folder, 'releases.rst'), - 'w', encoding="utf8") as f: - f.write('.. _releases:\n\n') - f.write('Releases\n') - f.write('========\n\n') + with io.open(os.path.join(this_folder, "releases.rst"), "w", encoding="utf8") as f: + f.write(".. _releases:\n\n") + f.write("Releases\n") + f.write("========\n\n") for i, r in enumerate(git_history_json): - if r['tag_name'].split(".")[-1] == "0": + if r["tag_name"].split(".")[-1] == "0": level = 2 elif i == 0: f.write("Current Version\n") @@ -406,22 +447,23 @@ def create_git_releases(app): level = 3 else: level = 3 - subtitle = ' '.join([r['tag_name'], '-', r['name'].rstrip(), '\n']) + subtitle = " ".join([r["tag_name"], "-", r["name"].rstrip(), "\n"]) f.write(subtitle) if level == 2: - f.write("-" * (len(subtitle)-1)+"\n") + f.write("-" * (len(subtitle) - 1) + "\n") else: - f.write("~" * (len(subtitle)-1)+"\n") - f.write('\n') - source = jinja_filter(r['body'], "rst", {}, {}) - for line in source.split('\n'): - f.write(' '.join([line.rstrip(), '\n'])) - f.write('\n') + f.write("~" * (len(subtitle) - 1) + "\n") + f.write("\n") + source = jinja_filter(r["body"], "rst", {}, {}) + for line in source.split("\n"): + f.write(" ".join([line.rstrip(), "\n"])) + f.write("\n") def add_intersphinx_aliases_to_inv(app): """see https://github.com/sphinx-doc/sphinx/issues/5603""" from sphinx.ext.intersphinx import InventoryAdapter + inventories = InventoryAdapter(app.builder.env) for alias, target in app.config.intersphinx_aliases.items(): @@ -444,22 +486,20 @@ def run_apidoc(app): """ logger.info("running apidoc") # get correct paths - this_folder = os.path.abspath( - os.path.dirname(os.path.realpath(__file__))) + this_folder = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) api_folder = os.path.join(this_folder, "api") # module_path = ipypublish.utils.get_module_path(ipypublish) - module_path = os.path.normpath( - os.path.join(this_folder, "../../")) + module_path = os.path.normpath(os.path.join(this_folder, "../../")) ignore_paths = [ "../../setup.py", "../../conftest.py", "../../ipypublish/tests", - "../../ipypublish/sphinx/tests" + "../../ipypublish/sphinx/tests", ] ignore_paths = [ - os.path.normpath( - os.path.join(this_folder, p)) for p in ignore_paths] + os.path.normpath(os.path.join(this_folder, p)) for p in ignore_paths + ] if os.path.exists(api_folder): shutil.rmtree(api_folder) @@ -473,6 +513,7 @@ def run_apidoc(app): except ImportError: # Sphinx 1.6 (and earlier) from sphinx import apidoc + argv.insert(0, apidoc.__file__) apidoc.main(argv) @@ -485,13 +526,13 @@ def run_apidoc(app): def get_version(): """alternative to getting directly""" import re + this_folder = os.path.join(os.path.dirname(os.path.realpath(__file__))) init_file = os.path.join(this_folder, "../../ipypublish/__init__.py") with open(init_file) as fobj: content = fobj.read() - match = re.match( - "\\_\\_version\\_\\_\\s*\\=\\s*[\\'\\\"]([0-9\\.]+)", content) + match = re.match("\\_\\_version\\_\\_\\s*\\=\\s*[\\'\\\"]([0-9\\.]+)", content) if not match: raise IOError("couldn't find __version__ in: {}".format(init_file)) return match.group(1) @@ -506,7 +547,7 @@ def setup(app): # app.connect('autodoc-skip-member', skip_deprecated) # add aliases for intersphinx - app.add_config_value('intersphinx_aliases', {}, 'env') - app.connect('builder-inited', run_apidoc) + app.add_config_value("intersphinx_aliases", {}, "env") + app.connect("builder-inited", run_apidoc) # app.connect('builder-inited', create_git_releases) - app.connect('builder-inited', add_intersphinx_aliases_to_inv) + app.connect("builder-inited", add_intersphinx_aliases_to_inv) diff --git a/docs/source/dev_guide.rst b/docs/source/dev_guide.rst index f007ce2..eddfb13 100644 --- a/docs/source/dev_guide.rst +++ b/docs/source/dev_guide.rst @@ -36,24 +36,23 @@ Coding Style Requirements The code style is tested using `flake8 `__, with the configuration set in ``.flake8``, and code should be formatted -with `yapf `__ (configuration set in -``.style.yapf``). +with `black `__. -Installing with ``ipypublish[tests]`` makes the +Installing with ``ipypublish[code_style]`` makes the `pre-commit `__ package available, which will ensure these tests are passed by reformatting the code and testing for -lint errors before submitting a commit. It can be setup by: +lint errors before submitting a commit. It can be set-up by: .. code:: shell >> cd ipypublish >> pre-commit install -Optionally you can run ``yapf`` and ``flake8`` separately: +Optionally you can run ``black`` and ``flake8`` separately: .. code:: shell - >> yapf -i path/to/file # format file in-place + >> black path/to/file >> flake8 Editors like VS Code also have automatic code reformat utilities, which diff --git a/docs/source/getting_started.Rmd b/docs/source/getting_started.Rmd index 4272b43..7575559 100644 --- a/docs/source/getting_started.Rmd +++ b/docs/source/getting_started.Rmd @@ -139,7 +139,7 @@ For a more detailed explanation see the .. important:: - The default conversion (``latex_ipypublish_main``) will **NOT** + The default conversion (``latex_ipypublish_main``) will **NOT** output any cells that are not tagged with metadata. To output all notebook content by default, use ``_ipypublish_all``. diff --git a/docs/source/nb_conversion.rst b/docs/source/nb_conversion.rst index 26bf0bd..ee0f2f0 100644 --- a/docs/source/nb_conversion.rst +++ b/docs/source/nb_conversion.rst @@ -171,14 +171,17 @@ Simple Customisation of Outputs To customise the output of the above defaults, simply download one of: -- :download:`latex_ipypublish_all.json <../../ipypublish/export_plugins/latex_ipypublish_all.json>`. -- :download:`sphinx_ipypublish_all.json <../../ipypublish/export_plugins/html_ipypublish_all.json>`. -- :download:`html_ipypublish_all.json <../../ipypublish/export_plugins/html_ipypublish_all.json>`. -- :download:`slides_ipypublish_all.json <../../ipypublish/export_plugins/slides_ipypublish_all.json>`. +- :download:`latex_ipypublish_all.json + <../../ipypublish/export_plugins/latex_ipypublish_all.json>`. +- :download:`sphinx_ipypublish_all.json + <../../ipypublish/export_plugins/html_ipypublish_all.json>`. +- :download:`html_ipypublish_all.json + <../../ipypublish/export_plugins/html_ipypublish_all.json>`. +- :download:`slides_ipypublish_all.json + <../../ipypublish/export_plugins/slides_ipypublish_all.json>`. Then alter the ``cell_defaults`` and ``nb_defaults`` sections, and run: .. code-block:: console nbpublish -f path/to/new_config.json input.ipynb - diff --git a/docs/source/package_api.rst b/docs/source/package_api.rst index 886d206..9f887ab 100644 --- a/docs/source/package_api.rst +++ b/docs/source/package_api.rst @@ -4,4 +4,4 @@ Package API .. toctree:: :maxdepth: 3 - api/ipypublish \ No newline at end of file + api/ipypublish diff --git a/docs/source/releases.rst b/docs/source/releases.rst index 6b34031..42d7360 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -13,6 +13,19 @@ Releases Version 0.10 ------------ +v0.10.10 +~~~~~~~~ + +Add compatibility of sphinx conversion with +`ipywidgets `_. +For examples see the section in :ref:`code_cells`, and for sphinx extension +configuration see :ref:`sphinx_ext_notebook`. +Note, the sphinx version dependency is now ``>=1.8``. + +Additionally the code base has been formatted by +`black `__, +and some minor bugs/warnings have been fixed. + v0.10.9 ~~~~~~~ diff --git a/docs/source/sphinx_ext_bibgloss.rst b/docs/source/sphinx_ext_bibgloss.rst index bddd2e2..ff5b0b8 100644 --- a/docs/source/sphinx_ext_bibgloss.rst +++ b/docs/source/sphinx_ext_bibgloss.rst @@ -299,9 +299,3 @@ Entries have attributes for the main fields, and can output to latex. symbol={\ensuremath{n}}, text={alternative text} } - - - - - - diff --git a/docs/source/sphinx_ext_notebook.rst b/docs/source/sphinx_ext_notebook.rst index 687679f..039c4fd 100644 --- a/docs/source/sphinx_ext_notebook.rst +++ b/docs/source/sphinx_ext_notebook.rst @@ -114,9 +114,9 @@ setup by adding to the conf.py: .. table:: Configuration values to use in conf.py :name: tbl:sphinx_config - ============================= =========================== =================================================================== + ============================= =========================== ======================================================================= Name Default Description - ============================= =========================== =================================================================== + ============================= =========================== ======================================================================= ipysphinx_export_config "sphinx_ipypublish_all.ext" ipypublish configuration file to use for conversion to .rst ipysphinx_folder_suffix "_nbfiles" for dumping internal images, etc ipysphinx_overwrite_existing False raise error if nb_name.rst already exists @@ -126,8 +126,13 @@ setup by adding to the conf.py: ipysphinx_output_prompt "[{count}]:" format of output prompts ipysphinx_input_toggle False add a button at the right side of input cells, to toggle show/hide ipysphinx_output_toggle False add a button at the right side of output cells, to toggle show/hide - ipysphinx_preconverters {} a mapping of additional file extensions to preconversion functions - ============================= =========================== =================================================================== + ipysphinx_preconverters {} a mapping of additional file extensions to pre-conversion functions + ipysphinx_require_jsurl cdnjs.cloudflare.com<2.3.4> source URL for require package javascript + ipysphinx_requirejs_option {} additional options for require package javascript ``\n' + context['body'] - code = copy_javascript('toggle_output') - context['body'] = '\n\n' + context['body'] + if doctree and doctree.get("ipysphinx_include_js"): + code = copy_javascript("toggle_code") + context["body"] = "\n\n" + context["body"] + code = copy_javascript("toggle_output") + context["body"] = "\n\n" + context["body"] + + +def add_require_js_path(app, env): + """Insert the require javascript package url, to pages created from notebooks.""" + config = app.config + if not ( + getattr(env, "ipysphinx_created_from_nb", set()) + or config.ipysphinx_always_add_jsurls + ): + return + if config.ipysphinx_require_jsurl is None: + requirejs_path = ( + "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js" + ) + else: + requirejs_path = config.ipysphinx_require_jsurl + if config.ipysphinx_requirejs_options is None: + requirejs_options = { + "integrity": "sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=", + "crossorigin": "anonymous", + } + else: + requirejs_options = config.ipysphinx_requirejs_options + + app.add_js_file(requirejs_path, **requirejs_options) + + +def add_ipywidgets_js_path(app, env): + """Insert the ipywidgets javascript url, to pages created from notebooks containing widgets.""" + if not ( + getattr(env, "ipysphinx_widgets", set()) + or app.config.ipysphinx_always_add_jsurls + ): + return + sphinx = import_sphinx() + widgets_path = None + if app.config.ipysphinx_widgets_jsurl is None: + try: + from ipywidgets.embed import DEFAULT_EMBED_REQUIREJS_URL + except ImportError: + logger = sphinx.util.logging.getLogger(__name__) + logger.warning( + "ipysphinx_widgets_jsurl not given and ipywidgets module unavailable" + ) + else: + widgets_path = DEFAULT_EMBED_REQUIREJS_URL + else: + widgets_path = app.config.ipysphinx_widgets_jsurl + if widgets_path is not None: + app.add_js_file(widgets_path, **app.config.ipysphinx_widgetsjs_options) + + +def discard_document_variables(app, env, docname): + """Discard any document specific variables.""" + # Discard any ipywidgets gathered from the current notebook. + try: + env.ipysphinx_widgets.discard(docname) + except AttributeError: + pass + try: + env.ipysphinx_created_from_nb.discard(docname) + except AttributeError: + pass diff --git a/ipypublish/sphinx/notebook/js/__init__.py b/ipypublish/sphinx/notebook/js/__init__.py new file mode 100644 index 0000000..bb03989 --- /dev/null +++ b/ipypublish/sphinx/notebook/js/__init__.py @@ -0,0 +1 @@ +"""Module containing javascript files, that can be inserted into HTML pages.""" diff --git a/ipypublish/sphinx/notebook/parser.py b/ipypublish/sphinx/notebook/parser.py index 5ebcb3f..d9b6595 100644 --- a/ipypublish/sphinx/notebook/parser.py +++ b/ipypublish/sphinx/notebook/parser.py @@ -17,7 +17,7 @@ class NBParser(rst.Parser): adapted from nbsphinx """ - supported = 'jupyter_notebook', + supported = ("jupyter_notebook",) def __init__(self, *args, **kwargs): @@ -31,16 +31,16 @@ def __init__(self, *args, **kwargs): class NotebookError(sphinx.errors.SphinxError): """Error during notebook parsing.""" - category = 'Notebook error' + category = "Notebook error" self.error_nb = NotebookError self.error_config = sphinx.errors.ConfigError - self.logger = sphinx.util.logging.getLogger('nbparser') + self.logger = sphinx.util.logging.getLogger("nbparser") except (ImportError, AttributeError): self.error_nb = IOError self.error_config = TypeError - self.logger = logging.getLogger('nbparser') + self.logger = logging.getLogger("nbparser") super(NBParser, self).__init__(*args, **kwargs) @@ -70,21 +70,22 @@ def parse(self, inputstring, document): filedir = os.path.dirname(filepath) self.logger.info("ipypublish: converting {}".format(filepath)) - config = {"IpyPubMain": { - "conversion": self.config.ipysphinx_export_config, - "plugin_folder_paths": self.config.ipysphinx_config_folders, - "outpath": filedir, - "folder_suffix": self.config.ipysphinx_folder_suffix, - "log_to_stdout": False, - "log_to_file": False, - "default_pporder_kwargs": dict( - clear_existing=False, - dump_files=True) - }} + config = { + "IpyPubMain": { + "conversion": self.config.ipysphinx_export_config, + "plugin_folder_paths": self.config.ipysphinx_config_folders, + "outpath": filedir, + "folder_suffix": self.config.ipysphinx_folder_suffix, + "log_to_stdout": False, + "log_to_file": False, + "default_pporder_kwargs": dict(clear_existing=False, dump_files=True), + } + } if self.config.ipysphinx_preconverters: # NB: jupytext is already a default for .Rmd - config["IpyPubMain"]["pre_conversion_funcs"] = ( - self.config.ipysphinx_preconverters) + config["IpyPubMain"][ + "pre_conversion_funcs" + ] = self.config.ipysphinx_preconverters publish = IpyPubMain(config=config) outdata = publish(filepath) @@ -92,21 +93,23 @@ def parse(self, inputstring, document): # check we got back restructuredtext exporter = outdata["exporter"] - if not exporter.output_mimetype == 'text/restructuredtext': + if not exporter.output_mimetype == "text/restructuredtext": handle_error( "ipypublish: the output content is not of type " "text/restructuredtext: {}".format(exporter.output_mimetype), - TypeError, self.logger + TypeError, + self.logger, ) # TODO document use of orphan if outdata["resources"].get("ipub", {}).get("orphan", False): - rst.Parser.parse(self, ':orphan:', document) + rst.Parser.parse(self, ":orphan:", document) # parse a prolog if self.env.config.ipysphinx_prolog: prolog = exporter.environment.from_string( - self.env.config.ipysphinx_prolog).render(env=self.env) + self.env.config.ipysphinx_prolog + ).render(env=self.env) rst.Parser.parse(self, prolog, document) # parse the main body of the file @@ -115,5 +118,19 @@ def parse(self, inputstring, document): # parse an epilog if self.env.config.ipysphinx_epilog: prolog = exporter.environment.from_string( - self.env.config.ipysphinx_epilog).render(env=self.env) + self.env.config.ipysphinx_epilog + ).render(env=self.env) rst.Parser.parse(self, prolog, document) + + # TODO is there a better way to parse data back from the parser? + + # record if the notebook contains ipywidgets + if outdata["resources"].get("contains_ipywidgets", False): + if not hasattr(self.env, "ipysphinx_widgets"): + self.env.ipysphinx_widgets = set() + self.env.ipysphinx_widgets.add(self.env.docname) + + # record that the document was created from a notebook + if not hasattr(self.env, "ipysphinx_created_from_nb"): + self.env.ipysphinx_created_from_nb = set() + self.env.ipysphinx_created_from_nb.add(self.env.docname) diff --git a/ipypublish/sphinx/notebook/transforms.py b/ipypublish/sphinx/notebook/transforms.py index d89492e..18442de 100644 --- a/ipypublish/sphinx/notebook/transforms.py +++ b/ipypublish/sphinx/notebook/transforms.py @@ -3,6 +3,7 @@ """ import re import os + try: from urllib.parse import unquote # Python 3.x except ImportError: @@ -28,38 +29,38 @@ class CreateNotebookSectionAnchors(docutils.transforms.Transform): def apply(self): for section in self.document.traverse(docutils.nodes.section): title = section.children[0].astext() - link_id = title.replace(' ', '-') - section['ids'] = [link_id] + link_id = title.replace(" ", "-") + section["ids"] = [link_id] def _local_file_from_reference(node, document): """Get local file path from reference and split it into components.""" # NB: Anonymous hyperlinks must be already resolved at this point! - refuri = node.get('refuri') + refuri = node.get("refuri") if not refuri: - refname = node.get('refname') + refname = node.get("refname") if refname: refid = document.nameids.get(refname) else: # NB: This can happen for anonymous hyperlinks - refid = node.get('refid') + refid = node.get("refid") target = document.ids.get(refid) if not target: # No corresponding target, Sphinx may warn later - return '', '', '' - refuri = target.get('refuri') + return "", "", "" + refuri = target.get("refuri") if not refuri: # Target doesn't have URI - return '', '', '' - if '://' in refuri: + return "", "", "" + if "://" in refuri: # Not a local link - return '', '', '' - elif refuri.startswith('#') or refuri.startswith('mailto:'): + return "", "", "" + elif refuri.startswith("#") or refuri.startswith("mailto:"): # Not a local link - return '', '', '' + return "", "", "" # NB: We look for "fragment identifier" before unquoting - match = re.match(r'^([^#]+)(\.[^#]+)(#.+)$', refuri) + match = re.match(r"^([^#]+)(\.[^#]+)(#.+)$", refuri) if match: base = unquote(match.group(1)) # NB: The suffix and "fragment identifier" are not unquoted @@ -68,7 +69,7 @@ def _local_file_from_reference(node, document): else: base, suffix = os.path.splitext(refuri) base = unquote(base) - fragment = '' + fragment = "" return base, suffix, fragment @@ -97,8 +98,7 @@ def apply(self): sphinx = import_sphinx() env = self.document.settings.env for node in self.document.traverse(docutils.nodes.reference): - base, suffix, fragment = _local_file_from_reference(node, - self.document) + base, suffix, fragment = _local_file_from_reference(node, self.document) if not base: continue @@ -107,24 +107,30 @@ def apply(self): target = base if fragment: target_ext = suffix + fragment - reftype = 'ref' + reftype = "ref" else: - target_ext = '' - reftype = 'doc' + target_ext = "" + reftype = "doc" break else: continue # Not a link to a potential Sphinx source file - target_docname = nbconvert.filters.posix_path(os.path.normpath( - os.path.join(os.path.dirname(env.docname), target))) + target_docname = nbconvert.filters.posix_path( + os.path.normpath(os.path.join(os.path.dirname(env.docname), target)) + ) if target_docname in env.found_docs: - reftarget = '/' + target_docname + target_ext - if reftype == 'ref': + reftarget = "/" + target_docname + target_ext + if reftype == "ref": reftarget = reftarget.lower() linktext = node.astext() xref = sphinx.addnodes.pending_xref( - reftype=reftype, reftarget=reftarget, refdomain='std', - refwarn=True, refexplicit=True, refdoc=env.docname) + reftype=reftype, + reftarget=reftarget, + refdomain="std", + refwarn=True, + refexplicit=True, + refdoc=env.docname, + ) xref += docutils.nodes.Text(linktext, linktext) node.replace_self(xref) @@ -147,21 +153,17 @@ def apply(self): assert section.children assert isinstance(section.children[0], docutils.nodes.title) title = section.children[0].astext() - link_id = section['ids'][0] - label = '/' + env.docname + file_ext + '#' + link_id + link_id = section["ids"][0] + label = "/" + env.docname + file_ext + "#" + link_id label = label.lower() - env.domaindata['std']['labels'][label] = ( - env.docname, link_id, title) - env.domaindata['std']['anonlabels'][label] = ( - env.docname, link_id) + env.domaindata["std"]["labels"][label] = (env.docname, link_id, title) + env.domaindata["std"]["anonlabels"][label] = (env.docname, link_id) # Create a label for the whole document using the first section: if i_still_have_to_create_the_document_label: - label = '/' + env.docname.lower() + file_ext - env.domaindata['std']['labels'][label] = ( - env.docname, '', title) - env.domaindata['std']['anonlabels'][label] = ( - env.docname, '') + label = "/" + env.docname.lower() + file_ext + env.domaindata["std"]["labels"][label] = (env.docname, "", title) + env.domaindata["std"]["anonlabels"][label] = (env.docname, "") i_still_have_to_create_the_document_label = False @@ -176,15 +178,13 @@ def apply(self): file_ext = os.path.splitext(env.doc2path(env.docname))[1] for sig in self.document.traverse(sphinx.addnodes.desc_signature): try: - title = sig['ids'][0] + title = sig["ids"][0] except IndexError: # Object has same name as another, so skip it continue - link_id = title.replace(' ', '-') - sig['ids'] = [link_id] - label = '/' + env.docname + file_ext + '#' + link_id + link_id = title.replace(" ", "-") + sig["ids"] = [link_id] + label = "/" + env.docname + file_ext + "#" + link_id label = label.lower() - env.domaindata['std']['labels'][label] = ( - env.docname, link_id, title) - env.domaindata['std']['anonlabels'][label] = ( - env.docname, link_id) + env.domaindata["std"]["labels"][label] = (env.docname, link_id, title) + env.domaindata["std"]["anonlabels"][label] = (env.docname, link_id) diff --git a/ipypublish/sphinx/tests/.gitignore b/ipypublish/sphinx/tests/.gitignore index c6a151b..69fa449 100644 --- a/ipypublish/sphinx/tests/.gitignore +++ b/ipypublish/sphinx/tests/.gitignore @@ -1 +1 @@ -_build/ \ No newline at end of file +_build/ diff --git a/ipypublish/sphinx/tests/__init__.py b/ipypublish/sphinx/tests/__init__.py index c955b4f..ff5b8a7 100644 --- a/ipypublish/sphinx/tests/__init__.py +++ b/ipypublish/sphinx/tests/__init__.py @@ -3,7 +3,7 @@ def get_test_source_dir(subfolder=None): if subfolder is None: - return os.path.abspath(os.path.join( - os.path.dirname(__file__), 'sourcedirs')) - return os.path.abspath(os.path.join( - os.path.dirname(__file__), 'sourcedirs', subfolder)) + return os.path.abspath(os.path.join(os.path.dirname(__file__), "sourcedirs")) + return os.path.abspath( + os.path.join(os.path.dirname(__file__), "sourcedirs", subfolder) + ) diff --git a/ipypublish/sphinx/tests/conftest.py b/ipypublish/sphinx/tests/conftest.py index 5331d4c..6013fed 100644 --- a/ipypublish/sphinx/tests/conftest.py +++ b/ipypublish/sphinx/tests/conftest.py @@ -48,7 +48,7 @@ def test_basic(app, status, warning, get_sphinx_app_output): from ipypublish.sphinx.tests import get_test_source_dir -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def remove_sphinx_builds(): """ remove all build directories from the test folder """ @@ -60,12 +60,16 @@ def remove_sphinx_builds(): @pytest.fixture def get_sphinx_app_output(): - def read(app, buildername='html', - filename="contents.html", encoding='utf-8', - extract_body=False, remove_scripts=False): - - outpath = path(os.path.join( - str(app.srcdir), '_build', buildername, filename)) + def read( + app, + buildername="html", + filename="contents.html", + encoding="utf-8", + extract_body=False, + remove_scripts=False, + ): + + outpath = path(os.path.join(str(app.srcdir), "_build", buildername, filename)) if not outpath.exists(): raise IOError("no output file exists: {}".format(outpath)) diff --git a/ipypublish/sphinx/tests/sourcedirs/notebook_ipywidget/conf.py b/ipypublish/sphinx/tests/sourcedirs/notebook_ipywidget/conf.py new file mode 100644 index 0000000..22fb559 --- /dev/null +++ b/ipypublish/sphinx/tests/sourcedirs/notebook_ipywidget/conf.py @@ -0,0 +1,7 @@ +extensions = [ + 'ipypublish.sphinx.notebook', +] +exclude_patterns = ['_build'] +master_doc = 'contents' +ipysphinx_always_add_jsurls = True +ipysphinx_widgets_jsurl = 'https://unpkg.com/@jupyter-widgets/html-manager@^0.14.0/dist/embed-amd.js' diff --git a/ipypublish/sphinx/tests/sourcedirs/notebook_ipywidget/contents.rst b/ipypublish/sphinx/tests/sourcedirs/notebook_ipywidget/contents.rst new file mode 100644 index 0000000..b881d5d --- /dev/null +++ b/ipypublish/sphinx/tests/sourcedirs/notebook_ipywidget/contents.rst @@ -0,0 +1,64 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +.. nbinput:: ipython3 + :execution-count: 5 + :no-output: + + import ipywidgets + label = ipywidgets.HTML() + label.value = "Hello" + label + +.. only:: html + + .. nboutput:: rst + + .. raw:: html + + + +.. only:: latex + + .. nboutput:: + + HTML(value='Hallo') + +.. nbinput:: ipython3 + :execution-count: 7 + :no-output: + + input_text = ipywidgets.Textarea() + input_text.value = "Hello" + input_text + +.. only:: html + + .. nboutput:: rst + + .. raw:: html + + + +.. only:: latex + + .. nboutput:: + + Textarea(value='Hallo') + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + + link = ipywidgets.jslink( + (label, 'value'), + (input_text, 'value')) + +.. raw:: html + + diff --git a/ipypublish/sphinx/tests/test_bibgloss.py b/ipypublish/sphinx/tests/test_bibgloss.py index e423cb4..70ca6b3 100644 --- a/ipypublish/sphinx/tests/test_bibgloss.py +++ b/ipypublish/sphinx/tests/test_bibgloss.py @@ -14,88 +14,101 @@ from ipypublish.tests.utils import HTML2JSONParser -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('bibgloss_basic')) +@pytest.mark.sphinx(buildername="html", srcdir=get_test_source_dir("bibgloss_basic")) def test_basic(app, status, warning, get_sphinx_app_output, data_regression): app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - assert warnings == '' + assert warnings == "" - output = get_sphinx_app_output(app, buildername='html') + output = get_sphinx_app_output(app, buildername="html") parser = HTML2JSONParser() parser.feed(output) if sphinx.version_info >= (2,): - data_regression.check(parser.parsed, basename='test_basic_v2') + data_regression.check(parser.parsed, basename="test_basic_v2") else: - data_regression.check(parser.parsed, basename='test_basic_v1') + data_regression.check(parser.parsed, basename="test_basic_v1") -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('bibgloss_sortkeys')) +@pytest.mark.sphinx(buildername="html", srcdir=get_test_source_dir("bibgloss_sortkeys")) def test_sortkeys(app, status, warning, get_sphinx_app_output, data_regression): app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - assert warnings == '' + assert warnings == "" - output = get_sphinx_app_output(app, buildername='html') + output = get_sphinx_app_output(app, buildername="html") parser = HTML2JSONParser() parser.feed(output) if sphinx.version_info >= (2,): - data_regression.check(parser.parsed, basename='test_sortkeys_v2') + data_regression.check(parser.parsed, basename="test_sortkeys_v2") else: - data_regression.check(parser.parsed, basename='test_sortkeys_v1') + data_regression.check(parser.parsed, basename="test_sortkeys_v1") -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('bibgloss_unsorted')) +@pytest.mark.sphinx(buildername="html", srcdir=get_test_source_dir("bibgloss_unsorted")) def test_unsorted(app, status, warning, get_sphinx_app_output, data_regression): app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - assert warnings == '' + assert warnings == "" - output = get_sphinx_app_output(app, buildername='html') + output = get_sphinx_app_output(app, buildername="html") parser = HTML2JSONParser() parser.feed(output) if sphinx.version_info >= (2,): - data_regression.check(parser.parsed, basename='test_unsorted_v2') + data_regression.check(parser.parsed, basename="test_unsorted_v2") else: - data_regression.check(parser.parsed, basename='test_unsorted_v1') + data_regression.check(parser.parsed, basename="test_unsorted_v1") -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('bibgloss_missingref')) +@pytest.mark.sphinx( + buildername="html", srcdir=get_test_source_dir("bibgloss_missingref") +) def test_missingref(app, status, warning, get_sphinx_app_output): app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - if ('could not relabel bibglossary reference [missingkey]' not in warnings and # sphinx < 2 - 'WARNING: citation not found: missingkey' not in warnings): # sphinx >= 2 - raise AssertionError('should raise warning for missing citation `missingkey`: {}'.format(warnings)) - - -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('bibgloss_duplicatekey')) + if ( + "could not relabel bibglossary reference [missingkey]" not in warnings + and "WARNING: citation not found: missingkey" not in warnings # sphinx < 2 + ): # sphinx >= 2 + raise AssertionError( + "should raise warning for missing citation `missingkey`: {}".format( + warnings + ) + ) + + +@pytest.mark.sphinx( + buildername="html", srcdir=get_test_source_dir("bibgloss_duplicatekey") +) def test_duplicatekey(app, status, warning, get_sphinx_app_output): with pytest.raises(KeyError): app.build() -@pytest.mark.skipif(sys.version_info < (3, 0), reason='SyntaxError on import of texsoup/data.py line 135') -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('bibgloss_tex')) +@pytest.mark.skipif( + sys.version_info < (3, 0), + reason="SyntaxError on import of texsoup/data.py line 135", +) +@pytest.mark.sphinx(buildername="html", srcdir=get_test_source_dir("bibgloss_tex")) def test_load_tex(app, status, warning, get_sphinx_app_output): app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - assert warnings == '' + assert warnings == "" diff --git a/ipypublish/sphinx/tests/test_notebook.py b/ipypublish/sphinx/tests/test_notebook.py index 3d0b740..8ceffcc 100644 --- a/ipypublish/sphinx/tests/test_notebook.py +++ b/ipypublish/sphinx/tests/test_notebook.py @@ -13,39 +13,59 @@ from ipypublish.tests.utils import HTML2JSONParser -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('notebook')) +@pytest.mark.sphinx(buildername="html", srcdir=get_test_source_dir("notebook")) def test_basic(app, status, warning, get_sphinx_app_output, data_regression): app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - assert warnings == '' + assert warnings == "" - output = get_sphinx_app_output(app, buildername='html') + output = get_sphinx_app_output(app, buildername="html") parser = HTML2JSONParser() parser.feed(output) if sphinx.version_info >= (2,): - data_regression.check(parser.parsed, basename='test_basic_v2') + data_regression.check(parser.parsed, basename="test_basic_v2") else: - data_regression.check(parser.parsed, basename='test_basic_v1') + data_regression.check(parser.parsed, basename="test_basic_v1") -@pytest.mark.sphinx(buildername='html', srcdir=get_test_source_dir('notebook_cell_decor')) +@pytest.mark.sphinx( + buildername="html", srcdir=get_test_source_dir("notebook_cell_decor") +) def test_cell_decoration(app, status, warning, get_sphinx_app_output, data_regression): """ test a notebook with prompts and toggle buttons""" app.build() - assert 'build succeeded' in status.getvalue() # Build succeeded + assert "build succeeded" in status.getvalue() # Build succeeded warnings = warning.getvalue().strip() - assert warnings == '' + assert warnings == "" - output = get_sphinx_app_output(app, buildername='html') + output = get_sphinx_app_output(app, buildername="html") parser = HTML2JSONParser() parser.feed(output) if sphinx.version_info >= (2,): - data_regression.check(parser.parsed, basename='test_cell_decoration_v2') + data_regression.check(parser.parsed, basename="test_cell_decoration_v2") else: - data_regression.check(parser.parsed, basename='test_cell_decoration_v1') + data_regression.check(parser.parsed, basename="test_cell_decoration_v1") + + +@pytest.mark.sphinx( + buildername="html", srcdir=get_test_source_dir("notebook_ipywidget") +) +def test_ipywidget(app, status, warning, get_sphinx_app_output, data_regression): + """ test which contains an ipywidgets and the widget state has been saved.""" + app.build() + + assert "build succeeded" in status.getvalue() # Build succeeded + warnings = warning.getvalue().strip() + assert warnings == "" + + output = get_sphinx_app_output(app, buildername="html") + + parser = HTML2JSONParser() + parser.feed(output) + data_regression.check(parser.parsed, basename="test_ipywidget") diff --git a/ipypublish/sphinx/tests/test_notebook/test_ipywidget.yml b/ipypublish/sphinx/tests/test_notebook/test_ipywidget.yml new file mode 100644 index 0000000..131afb6 --- /dev/null +++ b/ipypublish/sphinx/tests/test_notebook/test_ipywidget.yml @@ -0,0 +1,377 @@ +4_children: +- 1_tag: html + 3_attributes: + xmlns: http://www.w3.org/1999/xhtml + 4_children: + - 1_tag: body + 4_children: + - 1_tag: div + 3_attributes: + class: + - document + 4_children: + - 1_tag: div + 3_attributes: + class: + - documentwrapper + 4_children: + - 1_tag: div + 3_attributes: + class: + - bodywrapper + 4_children: + - 1_tag: div + 3_attributes: + class: + - body + role: main + 4_children: + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - nbinput + - nblast + 4_children: + - 1_tag: div + 3_attributes: + class: + - highlight-ipython3 + - input_area + - notranslate + 4_children: + - 1_tag: div + 3_attributes: + class: + - highlight + 4_children: + - 1_tag: pre + 4_children: + - 1_tag: span + - 1_tag: span + 2_data: + - import + 3_attributes: + class: + - kn + - 1_tag: span + 2_data: + - ipywidgets + 3_attributes: + class: + - nn + - 1_tag: span + 2_data: + - label + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - '=' + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - ipywidgets + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - . + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - HTML + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - () + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - label + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - . + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - value + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - '=' + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - Hello + 3_attributes: + class: + - s2 + - 1_tag: span + 2_data: + - label + 3_attributes: + class: + - n + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - nblast + - nboutput + 4_children: + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - output_area + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - nbinput + - nblast + 4_children: + - 1_tag: div + 3_attributes: + class: + - highlight-ipython3 + - input_area + - notranslate + 4_children: + - 1_tag: div + 3_attributes: + class: + - highlight + 4_children: + - 1_tag: pre + 4_children: + - 1_tag: span + - 1_tag: span + 2_data: + - input_text + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - '=' + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - ipywidgets + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - . + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - Textarea + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - () + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - input_text + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - . + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - value + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - '=' + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - Hello + 3_attributes: + class: + - s2 + - 1_tag: span + 2_data: + - input_text + 3_attributes: + class: + - n + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - nblast + - nboutput + 4_children: + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - output_area + - 1_tag: div + 3_attributes: + class: + - container + - docutils + - nbinput + - nblast + 4_children: + - 1_tag: div + 3_attributes: + class: + - highlight-ipython3 + - input_area + - notranslate + 4_children: + - 1_tag: div + 3_attributes: + class: + - highlight + 4_children: + - 1_tag: pre + 4_children: + - 1_tag: span + - 1_tag: span + 2_data: + - link + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - '=' + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - ipywidgets + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - . + 3_attributes: + class: + - o + - 1_tag: span + 2_data: + - jslink + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - ( + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - ( + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - label + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - ',' + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - value + 3_attributes: + class: + - s1 + - 1_tag: span + 2_data: + - ), + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - ( + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - input_text + 3_attributes: + class: + - n + - 1_tag: span + 2_data: + - ',' + 3_attributes: + class: + - p + - 1_tag: span + 2_data: + - value + 3_attributes: + class: + - s1 + - 1_tag: span + 2_data: + - )) + 3_attributes: + class: + - p diff --git a/ipypublish/sphinx/utils.py b/ipypublish/sphinx/utils.py index 1070935..5ea87b0 100644 --- a/ipypublish/sphinx/utils.py +++ b/ipypublish/sphinx/utils.py @@ -1,4 +1,3 @@ - def import_sphinx(): """ sphinx is an optional extra, so only load it when necessary @@ -9,8 +8,8 @@ def import_sphinx(): raise ImportError( "Sphinx is not installed. " "Please install ipypublish with the sphinx extras: " - ">> pip install ipypublish[sphinx]") - if not sphinx.__version__ >= '1.6': - # This is required for sphinx.ext.imgconverter - raise ImportError("Sphinx version must be >= 1.6") + ">> pip install ipypublish[sphinx]" + ) + if not sphinx.__version__ >= "1.8": + raise ImportError("Sphinx version must be >= 1.8") return sphinx diff --git a/ipypublish/templates/create_template.py b/ipypublish/templates/create_template.py index b1fc686..3430ea9 100644 --- a/ipypublish/templates/create_template.py +++ b/ipypublish/templates/create_template.py @@ -18,8 +18,7 @@ # from ipypublish import __version__ from ipypublish import schema -from ipypublish.utils import (handle_error, - read_file_from_directory, get_module_path) +from ipypublish.utils import handle_error, read_file_from_directory, get_module_path logger = logging.getLogger("template") @@ -49,7 +48,7 @@ def multireplace(string, replacements): substrs = sorted(replacements, key=len, reverse=True) # Create a big OR regex that matches any of the substrings to replace - regexp = re.compile('|'.join(map(re.escape, substrs))) + regexp = re.compile("|".join(map(re.escape, substrs))) # For each match, look up the new string in the replacements return regexp.sub(lambda match: replacements[match.group(0)], string) @@ -57,14 +56,12 @@ def multireplace(string, replacements): def _output_to_file(content, outpath): if outpath is not None: - with io.open(outpath, "w", encoding='utf8') as f: # TODO use pathlib + with io.open(outpath, "w", encoding="utf8") as f: # TODO use pathlib f.write(content) return -def create_template(outline_template, outline_name, - segment_datas, - outpath=None): +def create_template(outline_template, outline_name, segment_datas, outpath=None): # type: (dict, Tuple[dict]) -> str """ build a latex jinja template from; @@ -85,16 +82,17 @@ def create_template(outline_template, outline_name, """ # get the placeholders @ipubreplace{above|below}{name} - regex = re.compile("\\@ipubreplace\\{([^\\}]+)\\}\\{([^\\}]+)\\}", - re.MULTILINE) + regex = re.compile("\\@ipubreplace\\{([^\\}]+)\\}\\{([^\\}]+)\\}", re.MULTILINE) placeholder_tuple = regex.findall(outline_template) if not placeholder_tuple: if segment_datas: handle_error( - "the segment data is provided, " + - "but the outline template contains no placeholders", - KeyError, logger) + "the segment data is provided, " + + "but the outline template contains no placeholders", + KeyError, + logger, + ) if outpath: _output_to_file(outline_template, outpath) @@ -105,9 +103,7 @@ def create_template(outline_template, outline_name, # with above and below replacements = {key: "" for key in placeholders.keys()} - docstrings = [ - "outline: {}".format(outline_name), - ] + docstrings = ["outline: {}".format(outline_name)] if segment_datas: docstrings.append("with segments:") @@ -117,7 +113,10 @@ def create_template(outline_template, outline_name, _SEGMENT_SCHEMA = read_file_from_directory( get_module_path(schema), _SEGMENT_SCHEMA_FILE, - "segment configuration schema", logger, interp_ext=True) + "segment configuration schema", + logger, + interp_ext=True, + ) for seg_num, segment_data in enumerate(segment_datas): @@ -127,41 +126,50 @@ def create_template(outline_template, outline_name, except jsonschema.ValidationError as err: handle_error( "validation of template segment {} failed: {}".format( - seg_num, err.message), - jsonschema.ValidationError, logger=logger) + seg_num, err.message + ), + jsonschema.ValidationError, + logger=logger, + ) # get description of segment docstrings.append( - "- {0}: {1}".format( - segment_data["identifier"], segment_data["description"]) + "- {0}: {1}".format(segment_data["identifier"], segment_data["description"]) ) # find what key to overwrite overwrite = segment_data.get("overwrite", []) - logger.debug('overwrite keys: {}'.format(overwrite)) + logger.debug("overwrite keys: {}".format(overwrite)) for key, segtext in segment_data.get("segments").items(): if key not in placeholders: handle_error( - "the segment key '{}' ".format(key) + - "is not contained in the outline template", - KeyError, logger) + "the segment key '{}' ".format(key) + + "is not contained in the outline template", + KeyError, + logger, + ) if not isinstance(segtext, string_types): segtext = "\n".join(segtext) if key in overwrite: replacements[key] = segtext elif placeholders[key] == "above": - replacements[key] = segtext + '\n' + replacements[key] + replacements[key] = segtext + "\n" + replacements[key] elif placeholders[key] == "below": - replacements[key] = replacements[key] + '\n' + segtext + replacements[key] = replacements[key] + "\n" + segtext else: - handle_error(( - "the placeholder @ipubreplace{{{0}}}{{{1}}} ".format( - key, placeholders[key]) + - "should specify 'above' or 'below' appending"), - jsonschema.ValidationError, logger=logger) + handle_error( + ( + "the placeholder @ipubreplace{{{0}}}{{{1}}} ".format( + key, placeholders[key] + ) + + "should specify 'above' or 'below' appending" + ), + jsonschema.ValidationError, + logger=logger, + ) if "meta_docstring" in placeholders: docstring = "\n".join([s for s in docstrings if s]).replace("'", '"') @@ -174,9 +182,9 @@ def create_template(outline_template, outline_name, prefix = "@ipubreplace{" replace_dict = { - prefix + append + "}{" + name + "}": replacements.get( - name, "") - for append, name in placeholder_tuple} + prefix + append + "}{" + name + "}": replacements.get(name, "") + for append, name in placeholder_tuple + } outline = multireplace(outline_template, replace_dict) if outpath: diff --git a/ipypublish/templates/outline_schemas/html_outline.html.j2 b/ipypublish/templates/outline_schemas/html_outline.html.j2 index a7c0135..7c73296 100644 --- a/ipypublish/templates/outline_schemas/html_outline.html.j2 +++ b/ipypublish/templates/outline_schemas/html_outline.html.j2 @@ -5,7 +5,7 @@ %% HTML Setup %% ==================== -{%- block header %} diff --git a/ipypublish/templates/outline_schemas/original/nbconvert.null.j2 b/ipypublish/templates/outline_schemas/original/nbconvert.null.j2 index 32886a8..deef0d2 100644 --- a/ipypublish/templates/outline_schemas/original/nbconvert.null.j2 +++ b/ipypublish/templates/outline_schemas/original/nbconvert.null.j2 @@ -26,7 +26,7 @@ consider calling super even if it is a leave block, we might insert more blocks {%- block body -%} {%- for cell in nb.cells -%} {%- block any_cell scoped -%} - {%- if cell.cell_type == 'code'-%} + {%- if cell.cell_type == 'code'-%} {%- if resources.global_content_filter.include_code -%} {%- block codecell scoped -%} {%- if resources.global_content_filter.include_input and not cell.get("transient",{}).get("remove_source", false) -%} diff --git a/ipypublish/templates/outline_schemas/original/nbconvert.rst.j2 b/ipypublish/templates/outline_schemas/original/nbconvert.rst.j2 index 2d888b0..8ce47b3 100644 --- a/ipypublish/templates/outline_schemas/original/nbconvert.rst.j2 +++ b/ipypublish/templates/outline_schemas/original/nbconvert.rst.j2 @@ -110,4 +110,4 @@ unknown type {{cell.type}} .. parsed-literal:: {{ output.data['text/plain'] | indent }} -{% endblock data_text %} \ No newline at end of file +{% endblock data_text %} diff --git a/ipypublish/templates/outline_schemas/original/nbsphinx.rst.j2 b/ipypublish/templates/outline_schemas/original/nbsphinx.rst.j2 index a0d3881..146a951 100644 --- a/ipypublish/templates/outline_schemas/original/nbsphinx.rst.j2 +++ b/ipypublish/templates/outline_schemas/original/nbsphinx.rst.j2 @@ -211,4 +211,4 @@ {% endif %} {{ super() }} -{% endblock footer %} \ No newline at end of file +{% endblock footer %} diff --git a/ipypublish/templates/outline_schemas/rst_outline.rst.j2 b/ipypublish/templates/outline_schemas/rst_outline.rst.j2 index 7afe413..64f831a 100644 --- a/ipypublish/templates/outline_schemas/rst_outline.rst.j2 +++ b/ipypublish/templates/outline_schemas/rst_outline.rst.j2 @@ -97,7 +97,7 @@ {# OUTPUT DATA #} -{# display_priority.tpl defines this, +{# display_priority.tpl defines this, which creates block for each data type, and selects which data type to proritise (if output.output_type == 'display_data') #} @@ -185,4 +185,4 @@ requires super() to be called in data_priority #} {# JINJA MACROS #} -@ipubreplace{below}{jinja_macros} \ No newline at end of file +@ipubreplace{below}{jinja_macros} diff --git a/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json b/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json index 5e01d7f..f9c988a 100644 --- a/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json +++ b/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json @@ -31,4 +31,4 @@ "((*- endif *))" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-contents_framed_code-new.yaml.tex.j2 b/ipypublish/templates/segments/ipy-contents_framed_code-new.yaml.tex.j2 index f81c089..69bceb0 100644 --- a/ipypublish/templates/segments/ipy-contents_framed_code-new.yaml.tex.j2 +++ b/ipypublish/templates/segments/ipy-contents_framed_code-new.yaml.tex.j2 @@ -68,7 +68,7 @@ \usepackage{listings} % a package for wrapping code in a box \usepackage[framemethod=tikz]{mdframed} % to fram code - + "notebook_input_code": |2 ((( draw_text(cell.metadata,cell.source,"code", diff --git a/ipypublish/templates/segments/ipy-contents_framed_code.latex-tpl.json b/ipypublish/templates/segments/ipy-contents_framed_code.latex-tpl.json index 079054d..930374c 100644 --- a/ipypublish/templates/segments/ipy-contents_framed_code.latex-tpl.json +++ b/ipypublish/templates/segments/ipy-contents_framed_code.latex-tpl.json @@ -166,4 +166,4 @@ "((*- endmacro *))" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 b/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 index 25cf025..53650a7 100644 --- a/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 +++ b/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 @@ -29,9 +29,9 @@ "notebook_input_raw": |2 - ((*- if cell.metadata.raw_mimetype: -*)) - ((*- if cell.metadata.raw_mimetype == "text/latex" -*)) - ((( super() ))) + ((*- if cell.metadata.raw_mimetype: -*)) + ((*- if cell.metadata.raw_mimetype == "text/latex" -*)) + ((( super() ))) ((*- endif *)) ((*- endif *)) @@ -187,5 +187,3 @@ ((*- endblock table -*)) ((*- endmacro *)) - - diff --git a/ipypublish/templates/segments/ipy-contents_output.latex-tpl.json b/ipypublish/templates/segments/ipy-contents_output.latex-tpl.json index d671295..12a7744 100644 --- a/ipypublish/templates/segments/ipy-contents_output.latex-tpl.json +++ b/ipypublish/templates/segments/ipy-contents_output.latex-tpl.json @@ -275,4 +275,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-doc_article.latex-tpl.json b/ipypublish/templates/segments/ipy-doc_article.latex-tpl.json index 7007d4f..715ef34 100644 --- a/ipypublish/templates/segments/ipy-doc_article.latex-tpl.json +++ b/ipypublish/templates/segments/ipy-doc_article.latex-tpl.json @@ -147,4 +147,4 @@ "\\creflabelformat{equation}{#2#1#3}" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-flexbox_css.html-tplx.json b/ipypublish/templates/segments/ipy-flexbox_css.html-tplx.json index 434c603..e88ddcf 100644 --- a/ipypublish/templates/segments/ipy-flexbox_css.html-tplx.json +++ b/ipypublish/templates/segments/ipy-flexbox_css.html-tplx.json @@ -89,4 +89,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-front_pages.latex-tpl.json b/ipypublish/templates/segments/ipy-front_pages.latex-tpl.json index 20ba046..2104a24 100644 --- a/ipypublish/templates/segments/ipy-front_pages.latex-tpl.json +++ b/ipypublish/templates/segments/ipy-front_pages.latex-tpl.json @@ -157,4 +157,4 @@ "((*- endif *))" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-glossary.yaml.tex.j2 b/ipypublish/templates/segments/ipy-glossary.yaml.tex.j2 index 81c2eb5..968b356 100644 --- a/ipypublish/templates/segments/ipy-glossary.yaml.tex.j2 +++ b/ipypublish/templates/segments/ipy-glossary.yaml.tex.j2 @@ -14,10 +14,10 @@ segments: ((* endif *)) ((* if nb.metadata.ipub.latex.style_main *)) ((* set gloss_style_main = nb.metadata.ipub.latex.style_main *)) - ((* endif *)) + ((* endif *)) ((* if nb.metadata.ipub.latex.style_acronyms *)) ((* set gloss_style_acronyms = nb.metadata.ipub.latex.style_acronyms *)) - ((* endif *)) + ((* endif *)) ((* if nb.metadata.ipub.latex.style_symbols *)) ((* set gloss_style_symbols = nb.metadata.ipub.latex.style_symbols *)) ((* endif *)) @@ -30,7 +30,7 @@ segments: ((* if gloss_filename *)) % see https://en.wikibooks.org/wiki/LaTeX/Glossary % NB: glossaries must come after cref package - \usepackage[automake,toc,acronym,symbols,nowarn]{glossaries} + \usepackage[automake,toc,acronym,symbols,nowarn]{glossaries} ((* endif *)) document_commands: | @@ -45,7 +45,6 @@ segments: document_glossary: | ((* if gloss_filename and gloss_show *)) \printglossary[type=main,style=((( gloss_style_main )))] - \printglossary[type=acronym,style=((( gloss_style_acronyms )))] + \printglossary[type=acronym,style=((( gloss_style_acronyms )))] \printglossary[type=symbols,style=((( gloss_style_symbols )))] ((* endif *)) - diff --git a/ipypublish/templates/segments/ipy-latex_doc.html-tplx.json b/ipypublish/templates/segments/ipy-latex_doc.html-tplx.json index 64818ac..6859b15 100644 --- a/ipypublish/templates/segments/ipy-latex_doc.html-tplx.json +++ b/ipypublish/templates/segments/ipy-latex_doc.html-tplx.json @@ -333,4 +333,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-slides_ipypublish.html-tplx.json b/ipypublish/templates/segments/ipy-slides_ipypublish.html-tplx.json index 31689ae..54bd809 100644 --- a/ipypublish/templates/segments/ipy-slides_ipypublish.html-tplx.json +++ b/ipypublish/templates/segments/ipy-slides_ipypublish.html-tplx.json @@ -112,4 +112,4 @@ "{{ cell.source | markdown2html | strip_files_prefix }}" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-slides_mkdown.html-tplx.json b/ipypublish/templates/segments/ipy-slides_mkdown.html-tplx.json index 2134f2d..9ff0c31 100644 --- a/ipypublish/templates/segments/ipy-slides_mkdown.html-tplx.json +++ b/ipypublish/templates/segments/ipy-slides_mkdown.html-tplx.json @@ -42,4 +42,4 @@ "{{ cell.source | markdown2html | strip_files_prefix }}" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-sphinx.yaml.j2 b/ipypublish/templates/segments/ipy-sphinx.yaml.j2 index 73031ea..7b00ade 100644 --- a/ipypublish/templates/segments/ipy-sphinx.yaml.j2 +++ b/ipypublish/templates/segments/ipy-sphinx.yaml.j2 @@ -168,12 +168,9 @@ "new_blocks": | {% block nboutput -%} - {%- set html_datatype, latex_datatype = output | choose_output_type %} + {%- set html_datatype, latex_datatype = output | choose_output_type(cell.metadata) %} {%- if html_datatype == latex_datatype %} {{ insert_nboutput(html_datatype, output, cell) }} - {% elif cell.metadata.ipub and cell.metadata.ipub.table %} - {# only use the latex output (since we convert both to rst) #} - {{ insert_nboutput(latex_datatype, output, cell) }} {% else %} .. only:: html @@ -240,6 +237,7 @@ + {% set x = resources.__setitem__("contains_ipywidgets", True) %} {% endif %} "jinja_macros": | diff --git a/ipypublish/templates/segments/ipy-toc_sidebar.html-tplx.json b/ipypublish/templates/segments/ipy-toc_sidebar.html-tplx.json index b88c0db..bf0dcb1 100644 --- a/ipypublish/templates/segments/ipy-toc_sidebar.html-tplx.json +++ b/ipypublish/templates/segments/ipy-toc_sidebar.html-tplx.json @@ -53,4 +53,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/ipy-toggle_buttons.html-tplx.json b/ipypublish/templates/segments/ipy-toggle_buttons.html-tplx.json index 868f4ce..f73ce41 100644 --- a/ipypublish/templates/segments/ipy-toggle_buttons.html-tplx.json +++ b/ipypublish/templates/segments/ipy-toggle_buttons.html-tplx.json @@ -70,4 +70,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/sphinx_rst.yaml.j2 b/ipypublish/templates/segments/sphinx_rst.yaml.j2 index 691e473..3048820 100644 --- a/ipypublish/templates/segments/sphinx_rst.yaml.j2 +++ b/ipypublish/templates/segments/sphinx_rst.yaml.j2 @@ -68,7 +68,7 @@ "notebook_input_unknown": | .. unknown type {{cell.type}} - + "notebook_output_all": | {{ super() }} @@ -85,8 +85,8 @@ {{ insert_nboutput(latex_datatype, output, cell) | indent }} {% endif %} - {% endblock nboutput %} - + {% endblock nboutput %} + "notebook_output_error": | {{ self.nboutput() }} @@ -95,7 +95,7 @@ "notebook_output_data_all": | {{ self.nboutput() }} - + "jinja_macros": | {% macro insert_empty_lines(text) %} {%- set before, after = text | get_empty_lines %} @@ -214,4 +214,4 @@ - {% endif %} \ No newline at end of file + {% endif %} diff --git a/ipypublish/templates/segments/std-content.html-tplx.json b/ipypublish/templates/segments/std-content.html-tplx.json index 605d4c4..ed60c0a 100644 --- a/ipypublish/templates/segments/std-content.html-tplx.json +++ b/ipypublish/templates/segments/std-content.html-tplx.json @@ -124,4 +124,4 @@ "{{ output.data[datatype] | json_dumps }}" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-content_tagging.html-tplx.json b/ipypublish/templates/segments/std-content_tagging.html-tplx.json index 419162c..8c684c8 100644 --- a/ipypublish/templates/segments/std-content_tagging.html-tplx.json +++ b/ipypublish/templates/segments/std-content_tagging.html-tplx.json @@ -211,4 +211,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-document.html-tplx.json b/ipypublish/templates/segments/std-document.html-tplx.json index 12594d5..d793161 100644 --- a/ipypublish/templates/segments/std-document.html-tplx.json +++ b/ipypublish/templates/segments/std-document.html-tplx.json @@ -65,4 +65,4 @@ " " ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-in_out_prompts.latex-tpl.json b/ipypublish/templates/segments/std-in_out_prompts.latex-tpl.json index 893a4b7..4606641 100644 --- a/ipypublish/templates/segments/std-in_out_prompts.latex-tpl.json +++ b/ipypublish/templates/segments/std-in_out_prompts.latex-tpl.json @@ -48,4 +48,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-inout_prompt.html-tplx.json b/ipypublish/templates/segments/std-inout_prompt.html-tplx.json index 16fc64a..cac461a 100644 --- a/ipypublish/templates/segments/std-inout_prompt.html-tplx.json +++ b/ipypublish/templates/segments/std-inout_prompt.html-tplx.json @@ -34,4 +34,4 @@ "{% endif %}" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-mathjax.html-tplx.json b/ipypublish/templates/segments/std-mathjax.html-tplx.json index 99d2563..8d5be0d 100644 --- a/ipypublish/templates/segments/std-mathjax.html-tplx.json +++ b/ipypublish/templates/segments/std-mathjax.html-tplx.json @@ -28,4 +28,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-slides.html-tplx.json b/ipypublish/templates/segments/std-slides.html-tplx.json index 639ca8b..1c5a9ca 100644 --- a/ipypublish/templates/segments/std-slides.html-tplx.json +++ b/ipypublish/templates/segments/std-slides.html-tplx.json @@ -215,4 +215,4 @@ "{%- endif -%}" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-standard_article.latex-tpl.json b/ipypublish/templates/segments/std-standard_article.latex-tpl.json index b3339bd..b89abc6 100644 --- a/ipypublish/templates/segments/std-standard_article.latex-tpl.json +++ b/ipypublish/templates/segments/std-standard_article.latex-tpl.json @@ -64,4 +64,4 @@ "\\maketitle" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-standard_contents.latex-tpl.json b/ipypublish/templates/segments/std-standard_contents.latex-tpl.json index e74581a..f4ca991 100644 --- a/ipypublish/templates/segments/std-standard_contents.latex-tpl.json +++ b/ipypublish/templates/segments/std-standard_contents.latex-tpl.json @@ -82,4 +82,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-standard_definitions.latex-tpl.json b/ipypublish/templates/segments/std-standard_definitions.latex-tpl.json index 3bbd291..2d0ec22 100644 --- a/ipypublish/templates/segments/std-standard_definitions.latex-tpl.json +++ b/ipypublish/templates/segments/std-standard_definitions.latex-tpl.json @@ -79,4 +79,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-standard_packages.latex-tpl.json b/ipypublish/templates/segments/std-standard_packages.latex-tpl.json index f0ee0ba..5e9323e 100644 --- a/ipypublish/templates/segments/std-standard_packages.latex-tpl.json +++ b/ipypublish/templates/segments/std-standard_packages.latex-tpl.json @@ -39,4 +39,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std-widgets.html-tplx.json b/ipypublish/templates/segments/std-widgets.html-tplx.json index de0e635..f18b416 100644 --- a/ipypublish/templates/segments/std-widgets.html-tplx.json +++ b/ipypublish/templates/segments/std-widgets.html-tplx.json @@ -26,4 +26,4 @@ "" ] } -} \ No newline at end of file +} diff --git a/ipypublish/templates/segments/std_rst.yaml.j2 b/ipypublish/templates/segments/std_rst.yaml.j2 index fc58c20..863311c 100644 --- a/ipypublish/templates/segments/std_rst.yaml.j2 +++ b/ipypublish/templates/segments/std_rst.yaml.j2 @@ -110,4 +110,4 @@ # "notebook_output_widget_state": | - # "notebook_output_widget_view": | \ No newline at end of file + # "notebook_output_widget_view": | diff --git a/ipypublish/tests/__init__.py b/ipypublish/tests/__init__.py index 8933742..ea0d148 100644 --- a/ipypublish/tests/__init__.py +++ b/ipypublish/tests/__init__.py @@ -1,6 +1,5 @@ import os -TEST_FILES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), - "test_files") +TEST_FILES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_files") TEST_PIC_PATH = os.path.join(TEST_FILES_DIR, "example.jpg") diff --git a/ipypublish/tests/conftest.py b/ipypublish/tests/conftest.py index de86e4f..8fb448a 100644 --- a/ipypublish/tests/conftest.py +++ b/ipypublish/tests/conftest.py @@ -58,16 +58,15 @@ def test_example(ipynb_app): @pytest.fixture(autouse=True) def dont_open_webbrowser(monkeypatch): - def nullfunc(*arg, **kwrgs): pass - monkeypatch.setattr('webbrowser.open', nullfunc) + monkeypatch.setattr("webbrowser.open", nullfunc) @pytest.fixture def external_export_plugin(): - return pathlib.Path(os.path.join(TEST_FILES_DIR, 'example_new_plugin.json')) + return pathlib.Path(os.path.join(TEST_FILES_DIR, "example_new_plugin.json")) @pytest.fixture @@ -86,10 +85,10 @@ def ipynb_params(request): # ##### process pytest.mark.ipynb - if hasattr(request.node, 'iter_markers'): # pytest-3.6.0 or newer - markers = request.node.iter_markers('ipynb') + if hasattr(request.node, "iter_markers"): # pytest-3.6.0 or newer + markers = request.node.iter_markers("ipynb") else: - markers = request.node.get_marker('ipynb') + markers = request.node.get_marker("ipynb") pargs = {} kwargs = {} @@ -102,23 +101,26 @@ def ipynb_params(request): args = [pargs[i] for i in sorted(pargs.keys())] - return namedtuple('ipynb_params', 'args,kwargs')(args, kwargs) # type: ignore + return namedtuple("ipynb_params", "args,kwargs")(args, kwargs) # type: ignore -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def ipynb_app(temp_folder, ipynb_params): args, kwargs = ipynb_params if len(args) <= 0: - raise ValueError('a subfolder must be supplied as the first argument to ' '@pytest.mark.ipynb') + raise ValueError( + "a subfolder must be supplied as the first argument to " + "@pytest.mark.ipynb" + ) subfolder = args[0] # 'ipynb_with_glossary' - input_file = kwargs.get('main_file', 'main.ipynb') - test_files_dir = kwargs.get('root', TEST_FILES_DIR) - source_folder = kwargs.get('source', 'source') - convert_folder = kwargs.get('converted', 'converted') - expected_folder = kwargs.get('expected', 'expected') - use_temp = kwargs.get('out_to_temp', True) + input_file = kwargs.get("main_file", "main.ipynb") + test_files_dir = kwargs.get("root", TEST_FILES_DIR) + source_folder = kwargs.get("source", "source") + convert_folder = kwargs.get("converted", "converted") + expected_folder = kwargs.get("expected", "expected") + use_temp = kwargs.get("out_to_temp", True) source_folder_path = os.path.join(test_files_dir, subfolder, source_folder) expected_folder_path = os.path.join(test_files_dir, subfolder, expected_folder) @@ -131,16 +133,10 @@ def ipynb_app(temp_folder, ipynb_params): else: converted_path = os.path.join(test_files_dir, subfolder, convert_folder) - yield IpyTestApp( - temp_source_path, - input_file, - converted_path, - expected_folder_path, - ) + yield IpyTestApp(temp_source_path, input_file, converted_path, expected_folder_path) class IpyTestApp(object): - def __init__(self, src_path, input_file, converted_path, expected_folder_path): self._src_folder_path = src_path self._converted_folder_path = converted_path @@ -173,28 +169,32 @@ def pandoc_version(self): def run(self, ipub_config=None): if ipub_config is None: ipub_config = {} - ipub_config['outpath'] = str(self.converted_path) - app = IpyPubMain(config={'IpyPubMain': ipub_config}) - self._output_data = app(self.input_file if self.input_file is not None else self.source_path) + ipub_config["outpath"] = str(self.converted_path) + app = IpyPubMain(config={"IpyPubMain": ipub_config}) + self._output_data = app( + self.input_file if self.input_file is not None else self.source_path + ) @property def output_data(self): if self._output_data is None: - raise ValueError('the app must be run first to retrieve output data') + raise ValueError("the app must be run first to retrieve output data") return copy.copy(self._output_data) @property def export_extension(self): if self._output_data is None: - raise ValueError('the app must be run first to retrieve export file extension') - exporter = self._output_data['exporter'] + raise ValueError( + "the app must be run first to retrieve export file extension" + ) + exporter = self._output_data["exporter"] return exporter.file_extension @property def export_mimetype(self): if self._output_data is None: - raise ValueError('the app must be run first to retrieve export mimetype') - exporter = self._output_data['exporter'] + raise ValueError("the app must be run first to retrieve export mimetype") + exporter = self._output_data["exporter"] return exporter.output_mimetype def assert_converted_exists(self, file_name=None, extension=None): @@ -208,9 +208,9 @@ def assert_converted_exists(self, file_name=None, extension=None): converted_path = self.converted_path.joinpath(file_name + extension) if not self.converted_path.joinpath(file_name + extension).exists(): - raise AssertionError('could not find: {}'.format(converted_path)) + raise AssertionError("could not find: {}".format(converted_path)) - def assert_converted_contains(self, regexes, encoding='utf8'): + def assert_converted_contains(self, regexes, encoding="utf8"): if self.input_file is None: file_name = self.source_path.name @@ -228,9 +228,9 @@ def assert_converted_contains(self, regexes, encoding='utf8'): for regex in regexes: if not re.search(regex, content): - raise AssertionError('content does not contain regex: {}'.format(regex)) + raise AssertionError("content does not contain regex: {}".format(regex)) - def assert_converted_equals_expected(self, expected_file_name, encoding='utf8'): + def assert_converted_equals_expected(self, expected_file_name, encoding="utf8"): if self.input_file is None: file_name = self.source_path.name @@ -242,21 +242,23 @@ def assert_converted_equals_expected(self, expected_file_name, encoding='utf8'): expected_path = self.expected_path.joinpath(expected_file_name + extension) mime_type = self.export_mimetype - if mime_type == 'text/latex': + if mime_type == "text/latex": compare_tex_files(converted_path, expected_path, encoding=encoding) - elif mime_type == 'text/html': + elif mime_type == "text/html": compare_html_files(converted_path, expected_path, encoding=encoding) - elif mime_type == 'text/restructuredtext': + elif mime_type == "text/restructuredtext": compare_rst_files(converted_path, expected_path, encoding=encoding) else: # TODO add comparison for nb (applicatio/json) # and python (application/x-python) - message = ('no comparison function exists for ' 'mimetype: {}'.format(mime_type)) + message = "no comparison function exists for " "mimetype: {}".format( + mime_type + ) # raise ValueError(message) - logger.warn(message) + logger.warning(message) -def compare_rst_files(testpath, outpath, encoding='utf8'): +def compare_rst_files(testpath, outpath, encoding="utf8"): # only compare body of html, since styles differ by # nbconvert/pandoc version (e.g. different versions of font-awesome) @@ -267,7 +269,7 @@ def compare_rst_files(testpath, outpath, encoding='utf8'): content = fobj.read() # python 3.5 used .jpg instead of .jpeg - content = content.replace('.jpg', '.jpeg') + content = content.replace(".jpg", ".jpeg") # a recent dependency change is inserting new lines at the top of the file content = content.lstrip() @@ -278,12 +280,20 @@ def compare_rst_files(testpath, outpath, encoding='utf8'): # only report differences if out_content != test_content: - raise AssertionError('\n' + '\n'.join( - context_diff( - test_content.splitlines(), out_content.splitlines(), fromfile=str(testpath), tofile=str(outpath)))) - - -def compare_html_files(testpath, outpath, encoding='utf8'): + raise AssertionError( + "\n" + + "\n".join( + context_diff( + test_content.splitlines(), + out_content.splitlines(), + fromfile=str(testpath), + tofile=str(outpath), + ) + ) + ) + + +def compare_html_files(testpath, outpath, encoding="utf8"): # only compare body of html, since styles differ by # nbconvert/pandoc version (e.g. different versions of font-awesome) @@ -295,18 +305,18 @@ def compare_html_files(testpath, outpath, encoding='utf8'): # extract only the body # could use html.parser or beautifulsoup to do this better - body_rgx = re.compile('\\(.*)\\', re.DOTALL) + body_rgx = re.compile("\\(.*)\\", re.DOTALL) body_search = body_rgx.search(content) if not body_search: - raise IOError('could not find body content of {}'.format(path)) + raise IOError("could not find body content of {}".format(path)) content = body_search.group(1) # remove script environments which can change (e.g. reveal) - script_rgx = re.compile('\\(.*)\\', re.DOTALL) - content = script_rgx.sub('', content) + script_rgx = re.compile("\\(.*)\\", re.DOTALL) + content = script_rgx.sub("", content) # remove trailing whitespace - content = '\n'.join([l.rstrip() for l in content.splitlines()]) + content = "\n".join([l.rstrip() for l in content.splitlines()]) output.append(content) @@ -314,12 +324,20 @@ def compare_html_files(testpath, outpath, encoding='utf8'): # only report differences if out_content != test_content: - raise AssertionError('\n' + '\n'.join( - context_diff( - test_content.splitlines(), out_content.splitlines(), fromfile=str(testpath), tofile=str(outpath)))) + raise AssertionError( + "\n" + + "\n".join( + context_diff( + test_content.splitlines(), + out_content.splitlines(), + fromfile=str(testpath), + tofile=str(outpath), + ) + ) + ) -def compare_tex_files(testpath, outpath, encoding='utf8'): +def compare_tex_files(testpath, outpath, encoding="utf8"): output = [] for path in [testpath, outpath]: @@ -330,35 +348,42 @@ def compare_tex_files(testpath, outpath, encoding='utf8'): # only certain versions of pandoc wrap sections with \hypertarget # NOTE a better way to do this might be to use TexSoup ht_rgx = re.compile( - '\\\\hypertarget\\{[^\\}]*\\}\\{[^\\\\]*' - '(\\\\[sub]*section\\{[^\\}]*\\}' - '\\\\label\\{[^\\}]*\\})' - '\\}', re.DOTALL) - content = ht_rgx.sub('\\g<1>', content) + "\\\\hypertarget\\{[^\\}]*\\}\\{[^\\\\]*" + "(\\\\[sub]*section\\{[^\\}]*\\}" + "\\\\label\\{[^\\}]*\\})" + "\\}", + re.DOTALL, + ) + content = ht_rgx.sub("\\g<1>", content) # newer versions of pandoc convert ![](file) to \begin{figure}[htbp] # TODO override pandoc figure placement of ![](file) in markdown2latex - content = content.replace('\\begin{figure}[htbp]', '\\begin{figure}') + content = content.replace("\\begin{figure}[htbp]", "\\begin{figure}") # at start of itemize - content = content.replace('\\itemsep1pt\\parskip0pt\\parsep0pt\n', '') + content = content.replace("\\itemsep1pt\\parskip0pt\\parsep0pt\n", "") # at start of enumerate - content = content.replace('\\tightlist\n', '') + content = content.replace("\\tightlist\n", "") # python 3.5 used .jpg instead of .jpeg - content = content.replace('.jpg', '.jpeg') + content = content.replace(".jpg", ".jpeg") # python < 3.6 sorts these differently - pyg_rgx = re.compile(('\\\\expandafter\\\\def\\\\csname ' - 'PY\\@tok\\@[0-9a-zA-Z]*\\\\endcsname[^\n]*'), re.MULTILINE) - content = pyg_rgx.sub(r'\', content) + pyg_rgx = re.compile( + ( + "\\\\expandafter\\\\def\\\\csname " + "PY\\@tok\\@[0-9a-zA-Z]*\\\\endcsname[^\n]*" + ), + re.MULTILINE, + ) + content = pyg_rgx.sub(r"\", content) # also remove all space from start of lines - space_rgx = re.compile(r'^[\s]*', re.MULTILINE) - content = space_rgx.sub('', content) + space_rgx = re.compile(r"^[\s]*", re.MULTILINE) + content = space_rgx.sub("", content) # remove trailing whitespace - content = '\n'.join([l.rstrip() for l in content.splitlines()]) + content = "\n".join([l.rstrip() for l in content.splitlines()]) output.append(content) @@ -366,6 +391,14 @@ def compare_tex_files(testpath, outpath, encoding='utf8'): # only report differences if out_content != test_content: - raise AssertionError('\n' + '\n'.join( - context_diff( - test_content.splitlines(), out_content.splitlines(), fromfile=str(testpath), tofile=str(outpath)))) + raise AssertionError( + "\n" + + "\n".join( + context_diff( + test_content.splitlines(), + out_content.splitlines(), + fromfile=str(testpath), + tofile=str(outpath), + ) + ) + ) diff --git a/ipypublish/tests/convert_notebooks.py b/ipypublish/tests/convert_notebooks.py index d8d08ec..5b0cd2b 100644 --- a/ipypublish/tests/convert_notebooks.py +++ b/ipypublish/tests/convert_notebooks.py @@ -13,11 +13,8 @@ def convert_all(inpath, outpath): out_folder = tempfile.mkdtemp() publish = IpyPubMain( - config={ - "IpyPubMain": { - "conversion": plugin_name, - "outpath": out_folder - }}) + config={"IpyPubMain": {"conversion": plugin_name, "outpath": out_folder}} + ) try: outdata = publish(str(inpath)) @@ -25,16 +22,15 @@ def convert_all(inpath, outpath): outpath = outdata["outpath"] extension = exporter.file_extension - out_name = os.path.splitext( - os.path.basename(str(inpath)))[0] + extension + out_name = os.path.splitext(os.path.basename(str(inpath)))[0] + extension outfile = os.path.join(out_folder, out_name) if not os.path.exists(outfile): - raise IOError("could not find: {} for {}".format( - outfile, plugin_name)) + raise IOError("could not find: {} for {}".format(outfile, plugin_name)) - shutil.copyfile(outfile, os.path.join( - str(outpath), plugin_name + extension)) + shutil.copyfile( + outfile, os.path.join(str(outpath), plugin_name + extension) + ) finally: shutil.rmtree(out_folder) diff --git a/ipypublish/tests/test_convert_all_plugins.py b/ipypublish/tests/test_convert_all_plugins.py index 378c50a..3172799 100644 --- a/ipypublish/tests/test_convert_all_plugins.py +++ b/ipypublish/tests/test_convert_all_plugins.py @@ -5,15 +5,13 @@ from ipypublish.convert.config_manager import iter_all_export_paths -@pytest.mark.parametrize( - "plugin_name,plugin_path", - list(iter_all_export_paths()) -) -@pytest.mark.ipynb('basic_nb') +@pytest.mark.parametrize("plugin_name,plugin_path", list(iter_all_export_paths())) +@pytest.mark.ipynb("basic_nb") def test_basic_all_plugins(ipynb_app, plugin_name, plugin_path): - if ((plugin_name in ["sphinx_ipypublish_all.ext"] - or "exec" in plugin_name) and sys.version_info[0] < 3): + if ( + plugin_name in ["sphinx_ipypublish_all.ext"] or "exec" in plugin_name + ) and sys.version_info[0] < 3: # TODO this fails because the kernel is set as python3 in the notebook # could add a replacement variable e.g. ${pykernel} # and allow parsing of it to main.publish (default = "") @@ -23,16 +21,17 @@ def test_basic_all_plugins(ipynb_app, plugin_name, plugin_path): # no output file to compare if plugin_name in [ - "python_with_meta_stream", - "sphinx_ipypublish_all.ext", "sphinx_ipypublish_all.ext.noexec"]: + "python_with_meta_stream", + "sphinx_ipypublish_all.ext", + "sphinx_ipypublish_all.ext.noexec", + ]: return # test build exists only - if plugin_name in [ - "sphinx_ipypublish_all.run", "sphinx_ipypublish_main.run"]: + if plugin_name in ["sphinx_ipypublish_all.run", "sphinx_ipypublish_main.run"]: outpath = ipynb_app.converted_path.joinpath( - "build", "html", - os.path.splitext(ipynb_app.input_file.name)[0] + ".html") + "build", "html", os.path.splitext(ipynb_app.input_file.name)[0] + ".html" + ) assert outpath.exists() return diff --git a/ipypublish/tests/test_convert_basic.py b/ipypublish/tests/test_convert_basic.py index 9ba29e7..1506632 100644 --- a/ipypublish/tests/test_convert_basic.py +++ b/ipypublish/tests/test_convert_basic.py @@ -1,32 +1,37 @@ import pytest -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_basic_latex(ipynb_app): ipynb_app.run({"conversion": "latex_ipypublish_main"}) ipynb_app.assert_converted_exists() -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_basic_html(ipynb_app): ipynb_app.run({"conversion": "html_ipypublish_main"}) ipynb_app.assert_converted_exists() -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_basic_slides(ipynb_app): - ipynb_app.run({ - "conversion": "slides_ipypublish_main", - "default_pporder_kwargs": {"slides": True} - }) + ipynb_app.run( + { + "conversion": "slides_ipypublish_main", + "default_pporder_kwargs": {"slides": True}, + } + ) ipynb_app.assert_converted_exists() @pytest.mark.requires_latexmk -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_basic_pdf(ipynb_app): - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") diff --git a/ipypublish/tests/test_convert_bib_and_gloss.py b/ipypublish/tests/test_convert_bib_and_gloss.py index 989999e..1c05580 100644 --- a/ipypublish/tests/test_convert_bib_and_gloss.py +++ b/ipypublish/tests/test_convert_bib_and_gloss.py @@ -2,45 +2,52 @@ @pytest.mark.requires_latexmk -@pytest.mark.ipynb('nb_with_bib') +@pytest.mark.ipynb("nb_with_bib") def test_withbib_latex(ipynb_app): - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") - ipynb_app.assert_converted_contains([ - r"\\cite\{zelenyak_molecular_2016\}", - r"\\bibliographystyle\{unsrtnat\}", - r"\\bibliography\{main_files/test\}" - ]) + ipynb_app.assert_converted_contains( + [ + r"\\cite\{zelenyak_molecular_2016\}", + r"\\bibliographystyle\{unsrtnat\}", + r"\\bibliography\{main_files/test\}", + ] + ) @pytest.mark.requires_latexmk -@pytest.mark.ipynb('nb_with_glossary_tex', out_to_temp=True) +@pytest.mark.ipynb("nb_with_glossary_tex", out_to_temp=True) def test_withglossary_tex_latex(ipynb_app): - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") - ipynb_app.assert_converted_contains([ - r"\\makeglossaries", - r"\\gls\{term1\}", - r"\\printglossary" - ]) + ipynb_app.assert_converted_contains( + [r"\\makeglossaries", r"\\gls\{term1\}", r"\\printglossary"] + ) @pytest.mark.requires_latexmk -@pytest.mark.ipynb('nb_with_glossary_bib', out_to_temp=True) +@pytest.mark.ipynb("nb_with_glossary_bib", out_to_temp=True) def test_withglossary_bib_latex(ipynb_app): - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") - ipynb_app.assert_converted_contains([ - r"\\makeglossaries", - r"\\gls\{term1\}", - r"\\printglossary" - ]) + ipynb_app.assert_converted_contains( + [r"\\makeglossaries", r"\\gls\{term1\}", r"\\printglossary"] + ) diff --git a/ipypublish/tests/test_convert_complex_outputs.py b/ipypublish/tests/test_convert_complex_outputs.py index 2b77982..387053d 100644 --- a/ipypublish/tests/test_convert_complex_outputs.py +++ b/ipypublish/tests/test_convert_complex_outputs.py @@ -2,7 +2,7 @@ @pytest.mark.requires_latexmk -@pytest.mark.ipynb('nb_complex_outputs') +@pytest.mark.ipynb("nb_complex_outputs") def test_complex_latex(ipynb_app): """ includes: @@ -17,17 +17,20 @@ def test_complex_latex(ipynb_app): presumable this is due to the way the ipynb is being encoded on osx """ - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}, - "default_ppconfig_kwargs": {"pdf_debug": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + "default_ppconfig_kwargs": {"pdf_debug": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") ipynb_app.assert_converted_equals_expected("latex_ipypublish_main") -@pytest.mark.ipynb('nb_complex_outputs') +@pytest.mark.ipynb("nb_complex_outputs") def test_complex_html(ipynb_app): """ includes: @@ -35,14 +38,13 @@ def test_complex_html(ipynb_app): - external logo and bib """ - ipynb_app.run({ - "conversion": "html_ipypublish_main"}) + ipynb_app.run({"conversion": "html_ipypublish_main"}) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_equals_expected("html_ipypublish_main") -@pytest.mark.ipynb('nb_complex_outputs') +@pytest.mark.ipynb("nb_complex_outputs") def test_complex_slides(ipynb_app): """ includes: @@ -50,15 +52,18 @@ def test_complex_slides(ipynb_app): - external logo and bib """ - ipynb_app.run({ - "conversion": "slides_ipypublish_main", - "default_pporder_kwargs": {"slides": True}}) + ipynb_app.run( + { + "conversion": "slides_ipypublish_main", + "default_pporder_kwargs": {"slides": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_equals_expected("slides_ipypublish_main") -@pytest.mark.ipynb('nb_complex_outputs') +@pytest.mark.ipynb("nb_complex_outputs") def test_complex_sphinx_rst(ipynb_app): """ includes: @@ -66,13 +71,15 @@ def test_complex_sphinx_rst(ipynb_app): - external logo and bib """ - if ipynb_app.pandoc_version < '2.6': + if ipynb_app.pandoc_version < "2.6": raise AssertionError("pandoc version must be >= 2.6") - ipynb_app.run({ - "conversion": "sphinx_ipypublish_all", - "default_pporder_kwargs": {"slides": True}}) + ipynb_app.run( + { + "conversion": "sphinx_ipypublish_all", + "default_pporder_kwargs": {"slides": True}, + } + ) ipynb_app.assert_converted_exists() - ipynb_app.assert_converted_equals_expected( - "sphinx_ipypublish_all.pandoc.2-6") + ipynb_app.assert_converted_equals_expected("sphinx_ipypublish_all.pandoc.2-6") diff --git a/ipypublish/tests/test_convert_markdown_cells.py b/ipypublish/tests/test_convert_markdown_cells.py index 700b9ce..8f3326c 100644 --- a/ipypublish/tests/test_convert_markdown_cells.py +++ b/ipypublish/tests/test_convert_markdown_cells.py @@ -2,41 +2,42 @@ @pytest.mark.requires_latexmk -@pytest.mark.ipynb('nb_markdown_cells') +@pytest.mark.ipynb("nb_markdown_cells") def test_latex_and_pdf(ipynb_app): - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") # Table format has changed through the versions - if ipynb_app.pandoc_version < '2.2': + if ipynb_app.pandoc_version < "2.2": raise AssertionError("pandoc version must be >= 2.2") - ipynb_app.assert_converted_equals_expected( - "latex_ipypublish_main.pandoc.2-2") + ipynb_app.assert_converted_equals_expected("latex_ipypublish_main.pandoc.2-2") -@pytest.mark.ipynb('nb_markdown_cells') +@pytest.mark.ipynb("nb_markdown_cells") def test_sphinx_rst(ipynb_app): - ipynb_app.run({ - "conversion": "sphinx_ipypublish_main"}) + ipynb_app.run({"conversion": "sphinx_ipypublish_main"}) ipynb_app.assert_converted_exists() # Table format has changed through the versions - if ipynb_app.pandoc_version < '2.6': + if ipynb_app.pandoc_version < "2.6": raise AssertionError("pandoc version must be >= 2.6") - ipynb_app.assert_converted_equals_expected( - 'sphinx_ipypublish_main.pandoc.2-6') + ipynb_app.assert_converted_equals_expected("sphinx_ipypublish_main.pandoc.2-6") -@pytest.mark.ipynb('nb_with_mkdown_images') # out_to_temp=False +@pytest.mark.ipynb("nb_with_mkdown_images") # out_to_temp=False def test_sphinx_rst_with_mkdown_images(ipynb_app): """ test a notebook with multiple images """ - ipynb_app.run({ - "conversion": "sphinx_ipypublish_main.run", - "log_to_file": True, - "default_pporder_kwargs": {"dump_files": True}}) + ipynb_app.run( + { + "conversion": "sphinx_ipypublish_main.run", + "log_to_file": True, + "default_pporder_kwargs": {"dump_files": True}, + } + ) ipynb_app.assert_converted_exists() - ipynb_app.assert_converted_equals_expected( - 'sphinx_ipypublish_main') - assert ipynb_app.converted_path.joinpath( - "main_files/example.jpg").is_file() + ipynb_app.assert_converted_equals_expected("sphinx_ipypublish_main") + assert ipynb_app.converted_path.joinpath("main_files/example.jpg").is_file() diff --git a/ipypublish/tests/test_convert_with_cell_attachment.py b/ipypublish/tests/test_convert_with_cell_attachment.py index 2da5b47..c783e1a 100644 --- a/ipypublish/tests/test_convert_with_cell_attachment.py +++ b/ipypublish/tests/test_convert_with_cell_attachment.py @@ -2,11 +2,14 @@ @pytest.mark.requires_latexmk -@pytest.mark.ipynb('nb_with_attachment') +@pytest.mark.ipynb("nb_with_attachment") def test_basic_pdf(ipynb_app): - ipynb_app.run({ - "conversion": "latex_ipypublish_main", - "default_pporder_kwargs": {"create_pdf": True}}) + ipynb_app.run( + { + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + } + ) ipynb_app.assert_converted_exists() ipynb_app.assert_converted_exists(extension=".pdf") ipynb_app.assert_converted_equals_expected("latex_ipypublish_main") diff --git a/ipypublish/tests/test_convert_with_ipywidget.py b/ipypublish/tests/test_convert_with_ipywidget.py new file mode 100644 index 0000000..d817201 --- /dev/null +++ b/ipypublish/tests/test_convert_with_ipywidget.py @@ -0,0 +1,16 @@ +import sys + +import pytest + + +@pytest.mark.skipif( + sys.version_info[:2] == (3, 5), reason="json_dumps puts keys in different order" +) +@pytest.mark.ipynb("nb_with_ipywidget") # , out_to_temp=False) +def test_ipywidget_sphinx_rst(ipynb_app): + """The notebook contains an ipywidgets and the widget state has been saved.""" + + ipynb_app.run({"conversion": "sphinx_ipypublish_all"}) + + ipynb_app.assert_converted_exists() + ipynb_app.assert_converted_equals_expected("main") diff --git a/ipypublish/tests/test_files/nb_with_ipywidget/expected/main.rst b/ipypublish/tests/test_files/nb_with_ipywidget/expected/main.rst new file mode 100644 index 0000000..b881d5d --- /dev/null +++ b/ipypublish/tests/test_files/nb_with_ipywidget/expected/main.rst @@ -0,0 +1,64 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +.. nbinput:: ipython3 + :execution-count: 5 + :no-output: + + import ipywidgets + label = ipywidgets.HTML() + label.value = "Hello" + label + +.. only:: html + + .. nboutput:: rst + + .. raw:: html + + + +.. only:: latex + + .. nboutput:: + + HTML(value='Hallo') + +.. nbinput:: ipython3 + :execution-count: 7 + :no-output: + + input_text = ipywidgets.Textarea() + input_text.value = "Hello" + input_text + +.. only:: html + + .. nboutput:: rst + + .. raw:: html + + + +.. only:: latex + + .. nboutput:: + + Textarea(value='Hallo') + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + + link = ipywidgets.jslink( + (label, 'value'), + (input_text, 'value')) + +.. raw:: html + + diff --git a/ipypublish/tests/test_files/nb_with_ipywidget/source/main.ipynb b/ipypublish/tests/test_files/nb_with_ipywidget/source/main.ipynb new file mode 100644 index 0000000..7dcae84 --- /dev/null +++ b/ipypublish/tests/test_files/nb_with_ipywidget/source/main.ipynb @@ -0,0 +1,412 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "567cbbb8b92f441a8704ea4a8896b751", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Hallo')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ipywidgets\n", + "label = ipywidgets.HTML()\n", + "label.value = \"Hello\"\n", + "label" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "17ac180dd4a84779b2eee1add6ae57ce", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Textarea(value='Hallo')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "input_text = ipywidgets.Textarea()\n", + "input_text.value = \"Hello\"\n", + "input_text" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "link = ipywidgets.jslink(\n", + " (label, 'value'),\n", + " (input_text, 'value'))" + ] + } + ], + "metadata": { + "hide_input": false, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "074f9bae66bb41c4939fe269168c81cb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "0a6fc589e593436485aa29294a2d3f12": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "0b0ac26f30284345b16a98ea2f65e3ec": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "0fb56c94b0bf42509bfdeb52d2685fe6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "17ac180dd4a84779b2eee1add6ae57ce": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "TextareaModel", + "state": { + "layout": "IPY_MODEL_ecb11562e09a4354bbc412fe8141984b", + "style": "IPY_MODEL_447bdef11d09478eb73e52e60903cf43", + "value": "Hello" + } + }, + "19922a98931a4f5a96f582bc9309e311": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "19a44d2ac1574ecd856f62599767679d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "layout": "IPY_MODEL_19922a98931a4f5a96f582bc9309e311", + "style": "IPY_MODEL_483ce6db6a404e428204c91e6b86f9ff" + } + }, + "1f88a8d40b964fd09097a75dde54f513": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "23caef4d9b8342d2af72a007558d53db": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "285a51831f6645c7851afac5fbe4903f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "layout": "IPY_MODEL_907e277227e5473dacfd1e06942fd487", + "style": "IPY_MODEL_3e88d22cc99349dbac90ed9483343e15", + "value": "Hallo" + } + }, + "2d05ab7bf9bc4785a26c98dbdacf7417": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "TextareaModel", + "state": { + "layout": "IPY_MODEL_487c730f55a84b4bb59b67faebaaa16c", + "style": "IPY_MODEL_0a6fc589e593436485aa29294a2d3f12" + } + }, + "2e5fa6a6ff624f1f97f3c3e6d9a34212": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "3e88d22cc99349dbac90ed9483343e15": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "447bdef11d09478eb73e52e60903cf43": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "47ac16d7a41141b28cce3aa1c6af9b5f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "description": "Hallo", + "layout": "IPY_MODEL_0fb56c94b0bf42509bfdeb52d2685fe6", + "style": "IPY_MODEL_5dabe6a821db44cc8dc5d534e3ab8164" + } + }, + "483ce6db6a404e428204c91e6b86f9ff": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "487c730f55a84b4bb59b67faebaaa16c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "4ed50195d7a14e8da57fdc3f3d69f810": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "layout": "IPY_MODEL_1f88a8d40b964fd09097a75dde54f513", + "style": "IPY_MODEL_a150ddee2425493d807bc0dcae7c75c9" + } + }, + "537a83dea36b471cbb61dd977f3f9a98": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "567cbbb8b92f441a8704ea4a8896b751": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "layout": "IPY_MODEL_8e7c46c8882140d1b442aeca10427385", + "style": "IPY_MODEL_a43541a7abc747099b77c50de41ae4c8", + "value": "Hello" + } + }, + "5dabe6a821db44cc8dc5d534e3ab8164": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "669d53a06ed846a685ddda74f0e9336a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "description": "Hallo", + "layout": "IPY_MODEL_b924c12db07a455c8bed22b608e2df8b", + "style": "IPY_MODEL_074f9bae66bb41c4939fe269168c81cb" + } + }, + "71652fdef80148c4b871fcc19c0a6daa": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "7e7b81c9efde4c39bca1edfabec5228b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "description": "Hallo", + "layout": "IPY_MODEL_a2d334f57c854964955c292f842a0b18", + "style": "IPY_MODEL_acabe332361445d9b5838f6290b1e2a8" + } + }, + "8e7c46c8882140d1b442aeca10427385": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "907e277227e5473dacfd1e06942fd487": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "9f969cb1012b440dbcae193eba7d0754": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_567cbbb8b92f441a8704ea4a8896b751", + "value" + ], + "target": [ + "IPY_MODEL_17ac180dd4a84779b2eee1add6ae57ce", + "value" + ] + } + }, + "a150ddee2425493d807bc0dcae7c75c9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "a2d334f57c854964955c292f842a0b18": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "a43541a7abc747099b77c50de41ae4c8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "acabe332361445d9b5838f6290b1e2a8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "acc4d506800940edbd569adb2c0eec1d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "b924c12db07a455c8bed22b608e2df8b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "d4c0623dc3f94e0b8ef89b633e91b914": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "layout": "IPY_MODEL_0b0ac26f30284345b16a98ea2f65e3ec", + "style": "IPY_MODEL_23caef4d9b8342d2af72a007558d53db" + } + }, + "e0ebc1ea89d9478db6501906faccf907": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "layout": "IPY_MODEL_537a83dea36b471cbb61dd977f3f9a98", + "style": "IPY_MODEL_2e5fa6a6ff624f1f97f3c3e6d9a34212" + } + }, + "e4bd39947b384e9b9f7391a2b313f474": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "description": "Hallo", + "layout": "IPY_MODEL_acc4d506800940edbd569adb2c0eec1d", + "style": "IPY_MODEL_71652fdef80148c4b871fcc19c0a6daa" + } + }, + "e69bbc94d44445f397b49c89510a351b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_567cbbb8b92f441a8704ea4a8896b751", + "value" + ], + "target": [ + "IPY_MODEL_17ac180dd4a84779b2eee1add6ae57ce", + "value" + ] + } + }, + "ecb11562e09a4354bbc412fe8141984b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ipypublish/tests/test_frontend.py b/ipypublish/tests/test_frontend.py index 68e22f3..0bd085b 100644 --- a/ipypublish/tests/test_frontend.py +++ b/ipypublish/tests/test_frontend.py @@ -1,66 +1,112 @@ import os import pytest -from ipypublish.utils import pathlib # noqa: F401 +from ipypublish.utils import pathlib # noqa: F401 from ipypublish.frontend import nbpresent from ipypublish.frontend import nbpublish -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbpresent_bad_exporter(ipynb_app): # type: (str, pathlib.Path) -> None - assert 1 == nbpresent.run([str(ipynb_app.input_file), - "-f", "non-existent", - "--outpath", str(ipynb_app.converted_path), - "--dry-run", "--log-level", "debug"]) - - -@pytest.mark.ipynb('basic_nb') + assert 1 == nbpresent.run( + [ + str(ipynb_app.input_file), + "-f", + "non-existent", + "--outpath", + str(ipynb_app.converted_path), + "--dry-run", + "--log-level", + "debug", + ] + ) + + +@pytest.mark.ipynb("basic_nb") def test_nbpublish_bad_exporter(ipynb_app): # type: (str, pathlib.Path) -> None - assert 1 == nbpublish.run([str(ipynb_app.input_file), - "-f", "non-existent", - "--outpath", str(ipynb_app.converted_path), - "--dry-run", "--log-level", "debug"]) - - -@pytest.mark.ipynb('basic_nb') + assert 1 == nbpublish.run( + [ + str(ipynb_app.input_file), + "-f", + "non-existent", + "--outpath", + str(ipynb_app.converted_path), + "--dry-run", + "--log-level", + "debug", + ] + ) + + +@pytest.mark.ipynb("basic_nb") def test_nbpresent_dry_run(ipynb_app): # type: (str, pathlib.Path) -> None - assert 0 == nbpresent.run([str(ipynb_app.input_file), - "--outpath", str(ipynb_app.converted_path), - "--dry-run", "--log-level", "debug", "-pt"]) - - -@pytest.mark.ipynb('basic_nb') + assert 0 == nbpresent.run( + [ + str(ipynb_app.input_file), + "--outpath", + str(ipynb_app.converted_path), + "--dry-run", + "--log-level", + "debug", + "-pt", + ] + ) + + +@pytest.mark.ipynb("basic_nb") def test_nbpublish_dry_run(ipynb_app): # type: (str, pathlib.Path) -> None - assert 0 == nbpublish.run([str(ipynb_app.input_file), - "--outpath", str(ipynb_app.converted_path), - "--dry-run", "--log-level", "debug", "-pt"]) - - -@pytest.mark.ipynb('basic_nb') -def test_nbpublish_dry_run_with_external_plugin( - ipynb_app, external_export_plugin): + assert 0 == nbpublish.run( + [ + str(ipynb_app.input_file), + "--outpath", + str(ipynb_app.converted_path), + "--dry-run", + "--log-level", + "debug", + "-pt", + ] + ) + + +@pytest.mark.ipynb("basic_nb") +def test_nbpublish_dry_run_with_external_plugin(ipynb_app, external_export_plugin): # type: (str, pathlib.Path) -> None - assert 0 == nbpublish.run([str(ipynb_app.input_file), - "--outformat", str(external_export_plugin), - "--outpath", str(ipynb_app.converted_path), - "--dry-run", "--log-level", "debug", "-pt"]) - - -@pytest.mark.ipynb('basic_nb') -def test_nbpublish_dry_run_with_external_plugin_key( - ipynb_app, external_export_plugin): + assert 0 == nbpublish.run( + [ + str(ipynb_app.input_file), + "--outformat", + str(external_export_plugin), + "--outpath", + str(ipynb_app.converted_path), + "--dry-run", + "--log-level", + "debug", + "-pt", + ] + ) + + +@pytest.mark.ipynb("basic_nb") +def test_nbpublish_dry_run_with_external_plugin_key(ipynb_app, external_export_plugin): # type: (str, pathlib.Path, pathlib.Path) -> None - assert 0 == nbpublish.run([str(ipynb_app.input_file), - "--export-paths", - str(external_export_plugin.parent), - "--outformat", - os.path.splitext( - str(external_export_plugin.name))[0], - "--outpath", str(ipynb_app.converted_path), - "--dry-run", "--log-level", "debug", "-pt"]) + assert 0 == nbpublish.run( + [ + str(ipynb_app.input_file), + "--export-paths", + str(external_export_plugin.parent), + "--outformat", + os.path.splitext(str(external_export_plugin.name))[0], + "--outpath", + str(ipynb_app.converted_path), + "--dry-run", + "--log-level", + "debug", + "-pt", + ] + ) def test_nbpresent_list_exports(): @@ -77,24 +123,39 @@ def test_nbpublish_list_exports(): assert out.value.code == 0 -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbpublish_write(ipynb_app): # type: (str, pathlib.Path) -> None - assert 0 == nbpublish.run([str(ipynb_app.input_file), - "--outformat", "latex_ipypublish_main", - "--outpath", str(ipynb_app.converted_path), - "-pt"]) + assert 0 == nbpublish.run( + [ + str(ipynb_app.input_file), + "--outformat", + "latex_ipypublish_main", + "--outpath", + str(ipynb_app.converted_path), + "-pt", + ] + ) assert ipynb_app.converted_path.joinpath( - ipynb_app.input_file.name.replace(".ipynb", ".tex")).exists() + ipynb_app.input_file.name.replace(".ipynb", ".tex") + ).exists() @pytest.mark.requires_latexmk -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbpublish_to_pdf(ipynb_app): # type: (str, pathlib.Path) -> None - assert 0 == nbpublish.run([str(ipynb_app.input_file), - "--outformat", "latex_ipypublish_main", - "--outpath", str(ipynb_app.converted_path), - "--create-pdf", "-pt"]) + assert 0 == nbpublish.run( + [ + str(ipynb_app.input_file), + "--outformat", + "latex_ipypublish_main", + "--outpath", + str(ipynb_app.converted_path), + "--create-pdf", + "-pt", + ] + ) assert ipynb_app.converted_path.joinpath( - ipynb_app.input_file.name.replace(".ipynb", ".pdf")).exists() + ipynb_app.input_file.name.replace(".ipynb", ".pdf") + ).exists() diff --git a/ipypublish/tests/test_nbexport.py b/ipypublish/tests/test_nbexport.py index 85fb42d..1d3fd16 100644 --- a/ipypublish/tests/test_nbexport.py +++ b/ipypublish/tests/test_nbexport.py @@ -1,101 +1,117 @@ import pytest from ipypublish.convert import nbmerge -from ipypublish.convert.config_manager import ( - create_exporter_cls, str_to_jinja -) +from ipypublish.convert.config_manager import create_exporter_cls, str_to_jinja from ipypublish.convert.main import dict_to_config, IpyPubMain -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbexport_latex_empty(ipynb_app): - template = str_to_jinja('', "template_name") - config = dict_to_config({'LatexExporter.template_file': "template_name"}) - exporter_cls = create_exporter_cls('nbconvert.exporters.LatexExporter') + template = str_to_jinja("", "template_name") + config = dict_to_config({"LatexExporter.template_file": "template_name"}) + exporter_cls = create_exporter_cls("nbconvert.exporters.LatexExporter") nb, path = nbmerge.merge_notebooks(ipynb_app.input_file) - exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, - config, template) - assert exporter.output_mimetype == 'text/latex' - assert body == '' + exporter, body, resources = IpyPubMain().export_notebook( + nb, exporter_cls, config, template + ) + assert exporter.output_mimetype == "text/latex" + assert body == "" -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbexport_latex_mkdown1(ipynb_app): - template = str_to_jinja(""" + template = str_to_jinja( + """ ((* block markdowncell scoped *)) test123 ((* endblock markdowncell *)) - """, "template_name") - config = dict_to_config({'LatexExporter.template_file': "template_name"}) + """, + "template_name", + ) + config = dict_to_config({"LatexExporter.template_file": "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb_app.input_file) - exporter_cls = create_exporter_cls('nbconvert.exporters.LatexExporter') - exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, - config, template) - assert exporter.output_mimetype == 'text/latex' - assert body.strip() == 'test123' + exporter_cls = create_exporter_cls("nbconvert.exporters.LatexExporter") + exporter, body, resources = IpyPubMain().export_notebook( + nb, exporter_cls, config, template + ) + assert exporter.output_mimetype == "text/latex" + assert body.strip() == "test123" -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbexport_latex_mkdown2(ipynb_app): - template = str_to_jinja(""" + template = str_to_jinja( + """ ((*- extends 'display_priority.tplx' -*)) ((* block markdowncell scoped *)) (((cell.source))) ((* endblock markdowncell *)) - """, "template_name") - config = dict_to_config({'LatexExporter.template_file': "template_name"}) + """, + "template_name", + ) + config = dict_to_config({"LatexExporter.template_file": "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb_app.input_file) - exporter_cls = create_exporter_cls('nbconvert.exporters.LatexExporter') - exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, - config, template) - assert exporter.output_mimetype == 'text/latex' + exporter_cls = create_exporter_cls("nbconvert.exporters.LatexExporter") + exporter, body, resources = IpyPubMain().export_notebook( + nb, exporter_cls, config, template + ) + assert exporter.output_mimetype == "text/latex" - assert body.strip() == '# a title\n\nsome text' + assert body.strip() == "# a title\n\nsome text" -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbexport_html_empty(ipynb_app): - template = str_to_jinja('', "template_name") - config = dict_to_config({'HTMLExporter.template_file': "template_name"}) + template = str_to_jinja("", "template_name") + config = dict_to_config({"HTMLExporter.template_file": "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb_app.input_file) - exporter_cls = create_exporter_cls('nbconvert.exporters.HTMLExporter') - exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, - config, template) - assert exporter.output_mimetype == 'text/html' + exporter_cls = create_exporter_cls("nbconvert.exporters.HTMLExporter") + exporter, body, resources = IpyPubMain().export_notebook( + nb, exporter_cls, config, template + ) + assert exporter.output_mimetype == "text/html" - assert body == '' + assert body == "" -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbexport_html_mkdown1(ipynb_app): - template = str_to_jinja(""" + template = str_to_jinja( + """ {% block markdowncell scoped %} test123 {% endblock markdowncell %} - """, "template_name") - config = dict_to_config({'HTMLExporter.template_file': "template_name"}) + """, + "template_name", + ) + config = dict_to_config({"HTMLExporter.template_file": "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb_app.input_file) - exporter_cls = create_exporter_cls('nbconvert.exporters.HTMLExporter') - exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, - config, template) - assert exporter.output_mimetype == 'text/html' + exporter_cls = create_exporter_cls("nbconvert.exporters.HTMLExporter") + exporter, body, resources = IpyPubMain().export_notebook( + nb, exporter_cls, config, template + ) + assert exporter.output_mimetype == "text/html" - assert body.strip() == 'test123' + assert body.strip() == "test123" -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbexport_html_mkdown2(ipynb_app): - template = str_to_jinja(""" + template = str_to_jinja( + """ {%- extends 'display_priority.tpl' -%} {% block markdowncell scoped %} {{cell.source}} {% endblock markdowncell %} - """, "template_name") - config = dict_to_config({'HTMLExporter.template_file': "template_name"}) + """, + "template_name", + ) + config = dict_to_config({"HTMLExporter.template_file": "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb_app.input_file) - exporter_cls = create_exporter_cls('nbconvert.exporters.HTMLExporter') - exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, - config, template) - assert exporter.output_mimetype == 'text/html' + exporter_cls = create_exporter_cls("nbconvert.exporters.HTMLExporter") + exporter, body, resources = IpyPubMain().export_notebook( + nb, exporter_cls, config, template + ) + assert exporter.output_mimetype == "text/html" - assert body.strip() == '# a title\n\nsome text' + assert body.strip() == "# a title\n\nsome text" diff --git a/ipypublish/tests/test_nbmerge.py b/ipypublish/tests/test_nbmerge.py index 2ad80cd..4082b7d 100644 --- a/ipypublish/tests/test_nbmerge.py +++ b/ipypublish/tests/test_nbmerge.py @@ -2,21 +2,21 @@ from ipypublish.convert import nbmerge -@pytest.mark.ipynb('basic_nb') +@pytest.mark.ipynb("basic_nb") def test_nbmerge_one_notebook(ipynb_app): nb, path = nbmerge.merge_notebooks(ipynb_app.source_path) assert nb.metadata.test_name == "notebook1" assert len(nb.cells) == 2 -@pytest.mark.ipynb('merge_nbs', main_file=None) +@pytest.mark.ipynb("merge_nbs", main_file=None) def test_nbmerge_two_notebooks(ipynb_app): nb, path = nbmerge.merge_notebooks(ipynb_app.source_path) assert nb.metadata.test_name == "notebook1" assert len(nb.cells) == 4 -@pytest.mark.ipynb('merge_nbs', main_file=None) +@pytest.mark.ipynb("merge_nbs", main_file=None) def test_merge_latex(ipynb_app): ipynb_app.run() ipynb_app.assert_converted_exists() diff --git a/ipypublish/tests/test_port_api.py b/ipypublish/tests/test_port_api.py index c40a475..5a5c247 100644 --- a/ipypublish/tests/test_port_api.py +++ b/ipypublish/tests/test_port_api.py @@ -8,46 +8,50 @@ def test_plugin_to_json_html(): - out_str = convert_to_json(os.path.join(TEST_FILES_DIR, 'port_api_files', - "html_ipypublish_all.py.txt")) + out_str = convert_to_json( + os.path.join(TEST_FILES_DIR, "port_api_files", "html_ipypublish_all.py.txt") + ) dct = json.loads(out_str) assert "exporter" in dct # TODO test against schema def test_plugin_to_json_latex(): - out_str = convert_to_json(os.path.join(TEST_FILES_DIR, 'port_api_files', - "latex_ipypublish_all.py.txt")) + out_str = convert_to_json( + os.path.join(TEST_FILES_DIR, "port_api_files", "latex_ipypublish_all.py.txt") + ) dct = json.loads(out_str) assert "exporter" in dct # TODO test against schema def test_py_to_json(): - out_str = py_to_json(os.path.join(TEST_FILES_DIR, 'port_api_files', - "front_pages.py.txt")) + out_str = py_to_json( + os.path.join(TEST_FILES_DIR, "port_api_files", "front_pages.py.txt") + ) dct = json.loads(out_str) assert "segments" in dct # TODO test against schema def test_convert_format_str(): - template = ["{{%- extends 'null.tpl' -%}}", - "{{% block header %}}", - "{{{{ nb.metadata | meta2yaml('#~~ ') }}}}", - "{{% endblock header %}}", - "{{% block codecell %}}", - "#%%", - "{{{{ super() }}}}", - "{{% endblock codecell %}}", - "{{% block in_prompt %}}{{% endblock in_prompt %}}", - "{{% block input %}}{{{{ cell.metadata | meta2yaml('#~~ ') }}}}", # noqa: E501 - "{{{{ cell.source | ipython2python }}}}", - "{{% endblock input %}}", - "{{% block markdowncell scoped %}}#%% [markdown]", - "{{{{ cell.metadata | meta2yaml('#~~ ') }}}}", - "{{{{ cell.source | comment_lines }}}}", - "{{% endblock markdowncell %}}" - ] + template = [ + "{{%- extends 'null.tpl' -%}}", + "{{% block header %}}", + "{{{{ nb.metadata | meta2yaml('#~~ ') }}}}", + "{{% endblock header %}}", + "{{% block codecell %}}", + "#%%", + "{{{{ super() }}}}", + "{{% endblock codecell %}}", + "{{% block in_prompt %}}{{% endblock in_prompt %}}", + "{{% block input %}}{{{{ cell.metadata | meta2yaml('#~~ ') }}}}", # noqa: E501 + "{{{{ cell.source | ipython2python }}}}", + "{{% endblock input %}}", + "{{% block markdowncell scoped %}}#%% [markdown]", + "{{{{ cell.metadata | meta2yaml('#~~ ') }}}}", + "{{{{ cell.source | comment_lines }}}}", + "{{% endblock markdowncell %}}", + ] convert_format_str(template) diff --git a/ipypublish/tests/test_postprocessors.py b/ipypublish/tests/test_postprocessors.py index bfbcc8b..2154e6c 100644 --- a/ipypublish/tests/test_postprocessors.py +++ b/ipypublish/tests/test_postprocessors.py @@ -12,14 +12,14 @@ def test_pdf_export(temp_folder): \\end{document} """ - tex_path = os.path.join(temp_folder, 'test.tex') - pdf_path = os.path.join(temp_folder, 'test.pdf') + tex_path = os.path.join(temp_folder, "test.tex") + pdf_path = os.path.join(temp_folder, "test.pdf") - with open(tex_path, 'w') as f: + with open(tex_path, "w") as f: f.write(tex_content) pdfexport = PDFExport() - pdfexport.postprocess(tex_content, 'text/latex', tex_path) + pdfexport.postprocess(tex_content, "text/latex", tex_path) assert os.path.exists(pdf_path) diff --git a/ipypublish/tests/utils.py b/ipypublish/tests/utils.py index e5b16da..9476a75 100644 --- a/ipypublish/tests/utils.py +++ b/ipypublish/tests/utils.py @@ -1,5 +1,6 @@ from copy import deepcopy import six + try: from html.parser import HTMLParser except ImportError: @@ -14,18 +15,20 @@ class HTML2JSONParser(HTMLParser, object): """ - _tag_key = '1_tag' - _data_key = '2_data' - _tag_attrs_key = '3_attributes' - _children_key = '4_children' - - def __init__(self, - ignore_tags=('head', 'script', 'style'), - ignore_classes=('footer', 'sphinxsidebar', 'clearer'), - rstrip_data=True, - sort_class_attr=True, - replace_data_lines=None, - convert_charrefs=False): + _tag_key = "1_tag" + _data_key = "2_data" + _tag_attrs_key = "3_attributes" + _children_key = "4_children" + + def __init__( + self, + ignore_tags=("head", "script", "style"), + ignore_classes=("footer", "sphinxsidebar", "clearer"), + rstrip_data=True, + sort_class_attr=True, + replace_data_lines=None, + convert_charrefs=False, + ): """parses html content to a JSON object, of the form:: @@ -82,7 +85,7 @@ def _get_subcontent(self): def handle_starttag(self, tag, attrs): self._curr_depth += 1 attr_dict = dict(attrs) - classes = attr_dict.get('class', '').split() + classes = attr_dict.get("class", "").split() if self._ignore_depth is not None: return elif tag in self._ignore_tags or self._ignore_classes.intersection(classes): @@ -90,8 +93,8 @@ def handle_starttag(self, tag, attrs): self._ignore_depth = self._curr_depth return sub_content = self._get_subcontent() - if self._sort_class_attr and 'class' in attr_dict: - attr_dict['class'] = sorted(classes) + if self._sort_class_attr and "class" in attr_dict: + attr_dict["class"] = sorted(classes) tag_dict = {self._tag_key: tag} if attr_dict: tag_dict[self._tag_attrs_key] = attr_dict @@ -108,9 +111,11 @@ def handle_endtag(self, tag): return if tag != self._get_subcontent()[self._tag_key]: - raise AssertionError('{} != {} (current path: {})'.format(tag, - self._get_subcontent()[self._tag_key], - self._curr_tags)) + raise AssertionError( + "{} != {} (current path: {})".format( + tag, self._get_subcontent()[self._tag_key], self._curr_tags + ) + ) self._curr_tags = self._curr_tags[:-1] def handle_data(self, data): diff --git a/ipypublish/utils.py b/ipypublish/utils.py index b9419e2..ac6e379 100644 --- a/ipypublish/utils.py +++ b/ipypublish/utils.py @@ -26,11 +26,14 @@ def handle_error(msg, err_type, logger, raise_msg=None, log_msg=None): raise err_type(raise_msg) -def read_file_from_directory(dir_path, file_name, jtype, - logger, interp_ext=False, - ext_types=( - ("json", (".json",)), - ("yaml", (".yaml", ".yaml.j2")))): +def read_file_from_directory( + dir_path, + file_name, + jtype, + logger, + interp_ext=False, + ext_types=(("json", (".json",)), ("yaml", (".yaml", ".yaml.j2"))), +): """load a file situated in a directory if ``interp_ext=True``: @@ -45,8 +48,8 @@ def read_file_from_directory(dir_path, file_name, jtype, if not file_path.exists(): handle_error( - "the {} does not exist: {}".format(jtype, file_path), - IOError, logger=logger) + "the {} does not exist: {}".format(jtype, file_path), IOError, logger=logger + ) ext_type = None ext_map = {ext: ftype for ftype, exts in ext_types for ext in exts} @@ -65,8 +68,11 @@ def read_file_from_directory(dir_path, file_name, jtype, else: raise ValueError("extension type not recognised") except Exception as err: - handle_error("failed to read {} ({}): {}".format( - jtype, file_path, err), IOError, logger=logger) + handle_error( + "failed to read {} ({}): {}".format(jtype, file_path, err), + IOError, + logger=logger, + ) else: with file_path.open() as fobj: data = fobj.read() @@ -76,15 +82,17 @@ def read_file_from_directory(dir_path, file_name, jtype, def get_module_path(module): """return a directory path to a module""" - return pathlib.Path(os.path.dirname( - os.path.abspath(inspect.getfile(module)))) + return pathlib.Path(os.path.dirname(os.path.abspath(inspect.getfile(module)))) -def read_file_from_module(module_path, file_name, jtype, - logger, interp_ext=False, - ext_types=( - ("json", (".json")), - ("yaml", (".yaml", ".yaml.j2", "yaml.tex.j2")))): +def read_file_from_module( + module_path, + file_name, + jtype, + logger, + interp_ext=False, + ext_types=(("json", (".json")), ("yaml", (".yaml", ".yaml.j2", "yaml.tex.j2"))), +): """load a file situated in a python module if ``interp_ext=True``: @@ -97,13 +105,20 @@ def read_file_from_module(module_path, file_name, jtype, except ModuleNotFoundError: # noqa: F821 handle_error( "module {} containing {} {} not found".format( - module_path, jtype, file_name), - ModuleNotFoundError, logger=logger) # noqa: F821 - - return read_file_from_directory(get_module_path(outline_module), - file_name, jtype, logger, - interp_ext=interp_ext, - ext_types=ext_types) + module_path, jtype, file_name + ), + ModuleNotFoundError, + logger=logger, + ) # noqa: F821 + + return read_file_from_directory( + get_module_path(outline_module), + file_name, + jtype, + logger, + interp_ext=interp_ext, + ext_types=ext_types, + ) def get_valid_filename(s): @@ -115,8 +130,8 @@ def get_valid_filename(s): >>> get_valid_filename("john's portrait in 2004.jpg") 'johns_portrait_in_2004.jpg' """ - s = str(s).strip().replace(' ', '_') - return re.sub(r'(?u)[^-\w.]', '', s) + s = str(s).strip().replace(" ", "_") + return re.sub(r"(?u)[^-\w.]", "", s) def find_entry_point(name, group, logger, preferred=None): @@ -132,27 +147,31 @@ def find_entry_point(name, group, logger, preferred=None): if multiple matches are found, prefer one from this module """ - entry_points = list(pkg_resources.iter_entry_points( - group, name)) + entry_points = list(pkg_resources.iter_entry_points(group, name)) if len(entry_points) == 0: handle_error( - "The {0} entry point " - "{1} could not be found".format(group, name), - pkg_resources.ResolutionError, logger) + "The {0} entry point " "{1} could not be found".format(group, name), + pkg_resources.ResolutionError, + logger, + ) elif len(entry_points) != 1: # default to the preferred package oentry_points = [] if preferred: - oentry_points = [ep for ep in entry_points - if ep.module_name.startswith(preferred)] + oentry_points = [ + ep for ep in entry_points if ep.module_name.startswith(preferred) + ] if len(oentry_points) != 1: handle_error( "Multiple {0} plugins found for " "{1}: {2}".format(group, name, entry_points), - pkg_resources.ResolutionError, logger) + pkg_resources.ResolutionError, + logger, + ) logger.info( "Multiple {0} plugins found for {1}, " - "defaulting to the {2} version".format(group, name, preferred)) + "defaulting to the {2} version".format(group, name, preferred) + ) entry_point = oentry_points[0] else: entry_point = entry_points[0] diff --git a/ipypublish_plugins/example_new_plugin.json b/ipypublish_plugins/example_new_plugin.json index ef37c7c..d528751 100644 --- a/ipypublish_plugins/example_new_plugin.json +++ b/ipypublish_plugins/example_new_plugin.json @@ -82,4 +82,4 @@ } ] } -} \ No newline at end of file +} diff --git a/pytest.ini b/pytest.ini index 92c3749..1e87518 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,8 @@ [pytest] addopts = --doctest-modules --ignore=setup.py --ignore=docs/source/conf.py --ignore=ipypublish/sphinx/tests/sourcedirs --ignore=ipypublish/tests/test_files markers = - requires_latexmk: mark a test which requires latexmk. \ No newline at end of file + requires_latexmk: mark a test which requires latexmk. + ipynb: set parameters for the `ipynb_app` fixture (see ipypublish/tests/conftest.py) + sphinx: set parameters for the sphinx `app` fixture (see ipypublish/sphinx/tests/conftest.py) +filterwarnings = + ignore::UserWarning:bibtexparser.* diff --git a/requirements.txt b/requirements.txt index a87b374..78b89c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,4 @@ shutilwhich; python_version < '3.3' six>=1.11.0 traitlets typing; python_version < '3.5' -tornado \ No newline at end of file +tornado diff --git a/setup.cfg b/setup.cfg index c34b498..79bc678 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,4 @@ # This flag says that the code is written to work on both Python 2 and Python # 3. If at all possible, it is good practice to do this. If you cannot, you # will need to generate wheels for each Python version that you support. -universal=1 \ No newline at end of file +universal=1 diff --git a/setup.py b/setup.py index a301ed7..339889d 100755 --- a/setup.py +++ b/setup.py @@ -8,109 +8,105 @@ from setuptools import setup, find_packages -with open('requirements.txt') as f: +with open("requirements.txt") as f: requirements = f.read().splitlines() -with io.open('README.md') as readme: +with io.open("README.md") as readme: readme_str = readme.read() setup( - name='ipypublish', - version=import_module('ipypublish').__version__, + name="ipypublish", + version=import_module("ipypublish").__version__, description=( - 'A workflow for creating and editing publication ready ' - 'scientific reports, from one or more Jupyter Notebooks'), + "A workflow for creating and editing publication ready " + "scientific reports, from one or more Jupyter Notebooks" + ), long_description=readme_str, - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", install_requires=requirements, extras_require={ - 'sphinx': { - 'sphinx>=1.6', - 'sphinxcontrib-bibtex', + "sphinx": {"sphinx>=1.8", "sphinxcontrib-bibtex"}, + "tests": { + "pytest>=3.6", + "pytest-regressions", + "pytest-cov", + "coverage", + "pillow", + "nbsphinx", + "ipykernel", + "sphinx>=1.6", + "sphinxcontrib-bibtex", + "texsoup<=0.1.4", }, - 'tests': { - 'pytest>=3.6', - 'pytest-regressions', - 'pytest-cov', - 'flake8(>=3.7,<3.8)', - 'coverage', - 'pillow', - 'nbsphinx', - 'ipykernel', - 'sphinx>=1.6', - 'sphinxcontrib-bibtex', - 'texsoup<=0.1.4', - 'doc8' - }, - 'science': { - 'matplotlib', - 'numpy', - 'pandas', - 'sympy' + "code_style": [ + "black==19.3b0", + "pre-commit==1.17.0", + "flake8<3.8.0,>=3.7.0", + "doc8<0.9.0,>=0.8.0", + "pygments", # required by doc8 + ], + "science": {"matplotlib", "numpy", "pandas", "sympy"}, + "rtd": { + "recommonmark>=0.5", + "pytest>=3.6", + "pillow", + "numpy", + "matplotlib", + "pandas", + "sympy<1.3", + "sphinx>=1.8", + "sphinxcontrib-bibtex", + "ipykernel", + "ipywidgets>=7.5,<8", }, - 'rtd': { - 'recommonmark>=0.5', - 'pytest>=3.6', - 'pillow', - 'numpy', - 'matplotlib', - 'pandas', - 'sympy<1.3', - 'sphinx>=1.6', - 'sphinxcontrib-bibtex', - 'ipykernel' - } }, - license='MIT', - author='Chris Sewell', - author_email='chrisj_sewell@hotmail.com', - url='https://github.com/chrisjsewell/ipypublish', + license="MIT", + author="Chris Sewell", + author_email="chrisj_sewell@hotmail.com", + url="https://github.com/chrisjsewell/ipypublish", classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Environment :: Web Environment', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Financial and Insurance Industry', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Scientific/Engineering :: Chemistry', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities', + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Environment :: Web Environment", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Science/Research", + "Intended Audience :: Financial and Insurance Industry", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", + "Framework :: Sphinx :: Extension", ], - keywords='python, jupyter-notebook, nbconvert, pandoc, latex, pdf', + keywords="python, jupyter-notebook, nbconvert, pandoc, latex, pdf", zip_safe=True, packages=find_packages(), include_package_data=True, entry_points={ - 'console_scripts': [ - 'nbpublish = ipypublish.frontend.nbpublish:run', - 'nbpresent = ipypublish.frontend.nbpresent:run', - 'ipubpandoc = ipypublish.filters_pandoc.main:pandoc_filters' + "console_scripts": [ + "nbpublish = ipypublish.frontend.nbpublish:run", + "nbpresent = ipypublish.frontend.nbpresent:run", + "ipubpandoc = ipypublish.filters_pandoc.main:pandoc_filters", ], - 'ipypublish.postprocessors': [ - 'remove-blank-lines = ipypublish.postprocessors.stream_modify:RemoveBlankLines', - 'remove-trailing-space = ipypublish.postprocessors.stream_modify:RemoveTrailingSpace', - 'filter-output-files = ipypublish.postprocessors.stream_modify:FilterOutputFiles', - 'fix-slide-refs = ipypublish.postprocessors.stream_modify:FixSlideReferences', - 'pdf-export = ipypublish.postprocessors.pdfexport:PDFExport', - 'write-stream = ipypublish.postprocessors.to_stream:WriteStream', - 'write-text-file = ipypublish.postprocessors.file_actions:WriteTextFile', - 'remove-folder = ipypublish.postprocessors.file_actions:RemoveFolder', - 'write-resource-files = ipypublish.postprocessors.file_actions:WriteResourceFiles', - 'copy-resource-paths = ipypublish.postprocessors.file_actions:CopyResourcePaths', - 'reveal-server = ipypublish.postprocessors.reveal_serve:RevealServer', - 'run-sphinx = ipypublish.postprocessors.sphinx:RunSphinx [sphinx]', - 'convert-bibgloss = ipypublish.postprocessors.convert_bibgloss:ConvertBibGloss' - ] - } + "ipypublish.postprocessors": [ + "remove-blank-lines = ipypublish.postprocessors.stream_modify:RemoveBlankLines", + "remove-trailing-space = ipypublish.postprocessors.stream_modify:RemoveTrailingSpace", + "filter-output-files = ipypublish.postprocessors.stream_modify:FilterOutputFiles", + "fix-slide-refs = ipypublish.postprocessors.stream_modify:FixSlideReferences", + "pdf-export = ipypublish.postprocessors.pdfexport:PDFExport", + "write-stream = ipypublish.postprocessors.to_stream:WriteStream", + "write-text-file = ipypublish.postprocessors.file_actions:WriteTextFile", + "remove-folder = ipypublish.postprocessors.file_actions:RemoveFolder", + "write-resource-files = ipypublish.postprocessors.file_actions:WriteResourceFiles", + "copy-resource-paths = ipypublish.postprocessors.file_actions:CopyResourcePaths", + "reveal-server = ipypublish.postprocessors.reveal_serve:RevealServer", + "run-sphinx = ipypublish.postprocessors.sphinx:RunSphinx [sphinx]", + "convert-bibgloss = ipypublish.postprocessors.convert_bibgloss:ConvertBibGloss", + ], + }, )