diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..f46fbb8 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +omit = + ipypublish/ipysphinx/docutils_transforms.py + ipypublish/ipysphinx/extension.py + ipypublish/ipysphinx/directives.py + ipypublish/scripts/nb_setup.py diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..c44965a --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,17 @@ +version: 2 + +conda: + environment: docs/environment.yaml + +python: + version: 3.6 + install: + - method: pip + path: . + +# default +# sphinx: +# builder: html +# configuration: conf.py +# fail_on_warning: true + diff --git a/.travis.yml b/.travis.yml index 0faad0a..53addea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,8 +66,15 @@ matrix: # TODO add read the docs test `pip install .[docs] cd docs; make` before_install: +# Pandoc +- url="https://github.com/jgm/pandoc/releases/tag/2.6" +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then path=$(curl -L $url | grep -o '/jgm/pandoc/releases/download/.*-amd64\.deb') ; fi +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then downloadUrl="https://github.com$path" ; fi +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then file=${path##*/} ; fi +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wget $downloadUrl && sudo dpkg -i $file ; fi +# LaTeX - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi -- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y pandoc ; fi +# - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y pandoc ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y texlive ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y texlive-xetex ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y texlive-latex-extra ; fi @@ -78,13 +85,11 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y latexmk ; fi - pip install -U pip setuptools wheel install: -- travis_wait pip install -r requirements.txt -- pip install -r test_requirements.txt -- pip install . +- travis_wait pip install .[tests,sphinx] - pip install --quiet coveralls script: # - nosetests -v --nocapture --with-doctest --with-coverage --exe --cover-package=ipypublish -- pytest -v --cov=ipypublish --cov-report= ipypublish +- pytest -v --cov=ipypublish --cov-config .coveragerc --cov-report= ipypublish - nbpublish -pdf --pdf-debug -log debug example/notebooks/Example.ipynb after_success: - coveralls diff --git a/.vscode/jinja2.code-snippets b/.vscode/jinja2.code-snippets new file mode 100644 index 0000000..4615ac8 --- /dev/null +++ b/.vscode/jinja2.code-snippets @@ -0,0 +1,76 @@ +{ + "super": { + "prefix": "super", + "scope": "jinja,jinja-yaml,jinja-html", + "body": "{{ super() }}", + "description": "call inherited block" + }, + "set": { + "prefix": "set", + "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", + "body": [ + "{% block ${1:name} %}", + "$2", + "{% endblock ${1:name} %}" + ], + "description": "jinja block" + }, + "macro": { + "prefix": "macro", + "scope": "jinja,jinja-yaml,jinja-html", + "body": [ + "{% macro ${1:name} %}", + "$2", + "{% endmacro %}" + ], + "description": "macro function" + }, + "if": { + "prefix": "if", + "scope": "jinja,jinja-yaml,jinja-html", + "body": [ + "{% if ${1:condition} %}", + "$2", + "{% endif %}" + ], + "description": "if condition" + }, + "if-else": { + "prefix": "if-else", + "scope": "jinja,jinja-yaml,jinja-html", + "body": [ + "{% if ${1:condition} %}", + "$2", + "{% else %}", + "$3", + "{% endif %}" + ], + "description": "if-else condition" + }, + "if-elif-else": { + "prefix": "if-elif-else", + "scope": "jinja,jinja-yaml,jinja-html", + "body": [ + "{% if ${1:condition} %}", + "$2", + "{% else %}", + "$3", + "{% elif ${1:condition2} %}", + "", + "{% endif %}" + ], + "description": "if-else condition" + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index bb87823..30cd7cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,6 +28,7 @@ "python.linting.flake8Enabled": true, "latex-workshop.latex.autoBuild.onSave.enabled": false, "cSpell.words": [ + "Jupyter", "ipynb", "ipypublish", "jupytext", @@ -42,7 +43,7 @@ "FIXME", "NOTE" ], - "todo-tree.regex": "((//|#| \n' + context['body'] diff --git a/ipypublish/ipysphinx/parser.py b/ipypublish/ipysphinx/parser.py new file mode 100644 index 0000000..fb99ccc --- /dev/null +++ b/ipypublish/ipysphinx/parser.py @@ -0,0 +1,118 @@ +import os +import logging + +from docutils.parsers import rst +from ipypublish.utils import handle_error +from ipypublish.ipysphinx.utils import import_sphinx +from ipypublish.convert.main import IpyPubMain + + +# TODO should inherit from sphinx.parsers.RSTParser +# https://www.sphinx-doc.org/en/master/extdev/parserapi.html +# however, sphinx is an optional dependency +class NBParser(rst.Parser): + """Sphinx source parser for Jupyter notebooks. + adapted from nbsphinx + """ + + supported = 'jupyter_notebook', + + def __init__(self, *args, **kwargs): + + self.app = None + self.config = None + self.env = None + + try: + sphinx = import_sphinx() + + class NotebookError(sphinx.errors.SphinxError): + """Error during notebook parsing.""" + + category = 'Notebook error' + + self.error_nb = NotebookError + self.error_config = sphinx.errors.ConfigError + self.logger = sphinx.util.logging.getLogger('nbparser') + + except (ImportError, AttributeError): + self.error_nb = IOError + self.error_config = TypeError + self.logger = logging.getLogger('nbparser') + + super(NBParser, self).__init__(*args, **kwargs) + + def set_application(self, app): + # type: (Sphinx) -> None + """set_application will be called from Sphinx to set app + and other instance variables + + Parameters + ---------- + app: sphinx.application.Sphinx + Sphinx application object + """ + self.app = app + self.config = app.config + self.env = app.env + + def parse(self, inputstring, document): + # type: (Union[str, StringList], nodes.document) -> None + """Parse text and generate a document tree.""" + + # fix for when calling on readthedocs + self.env = self.env or document.settings.env + self.config = self.config or document.settings.env.config + + # get file for conversion + filepath = self.env.doc2path(self.env.docname) + 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) + }} + if self.config.ipysphinx_preconverters: + # NB: jupytext is already a default for .Rmd + config["IpyPubMain"]["pre_conversion_funcs"] = ( + self.config.ipysphinx_preconverters) + publish = IpyPubMain(config=config) + outdata = publish(filepath) + + self.logger.info("ipypublish: successful conversion") + + # check we got back restructuredtext + exporter = outdata["exporter"] + 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 + ) + + # TODO document use of orphan + if outdata["resources"].get("ipub", {}).get("orphan", False): + 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) + rst.Parser.parse(self, prolog, document) + + # parse the main body of the file + rst.Parser.parse(self, outdata["stream"], document) + + # parse an epilog + if self.env.config.ipysphinx_epilog: + prolog = exporter.environment.from_string( + self.env.config.ipysphinx_epilog).render(env=self.env) + rst.Parser.parse(self, prolog, document) diff --git a/ipypublish/ipysphinx/utils.py b/ipypublish/ipysphinx/utils.py new file mode 100644 index 0000000..59f110e --- /dev/null +++ b/ipypublish/ipysphinx/utils.py @@ -0,0 +1,16 @@ + +def import_sphinx(): + """ + sphinx is an optional extra, so only load it when necessary + """ + try: + import sphinx + except ImportError: + 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") + return sphinx \ No newline at end of file diff --git a/ipypublish/postprocessors/__init__.py b/ipypublish/postprocessors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ipypublish/postprocessors/base.py b/ipypublish/postprocessors/base.py new file mode 100644 index 0000000..cf8e021 --- /dev/null +++ b/ipypublish/postprocessors/base.py @@ -0,0 +1,176 @@ +import logging + +from six import string_types +from traitlets import Bool +from traitlets.config.configurable import Configurable + +from ipypublish.utils import handle_error, pathlib + +try: + from shutil import which as exe_exists +except ImportError: + from distutils.spawn import find_executable as exe_exists # noqa: F401 + + +class IPyPostProcessor(Configurable): + """ an abstract class for post-processors + """ + + @property + def allowed_mimetypes(self): + """ override in subclasses + + return a list of allowed mime types + if None, then all are allowed + + Text based mime-types include: text/plain, text/latex, + text/restructuredtext, text/html, text/x-python, application/json, + text/markdown, text/asciidoc, text/yaml + + """ + raise NotImplementedError('allowed_mimetypes') + + @property + def requires_path(self): + """ override in subclasses + + whether the prostprocessor requires the supplied filepath + to have an existing parent directory + + if True and filepath is None, will raise an IOError, otherwise, + will try to make the directory if it doesn't exist + + """ + raise NotImplementedError('requires_path') + + @property + def logger_name(self): + """ override in subclass + """ + return "post-processor" + + @property + def logger(self): + return logging.getLogger(self.logger_name) + + skip_mime = Bool( + True, + help="if False, raise a TypeError if the mimetype is not allowed, " + "else return without processing").tag(config=True) + + def __init__(self, config=None): + super(IPyPostProcessor, self).__init__(config=config) + + def __call__(self, stream, mimetype, filepath, resources=None): + """ + See def postprocess() ... + """ + self.postprocess(stream, mimetype, filepath, resources) + + def postprocess(self, stream, mimetype, filepath, + resources=None): + """ Post-process output. + + Parameters + ---------- + stream: str + the main file contents + mimetype: str + the mimetype of the file + filepath: None or str or pathlib.Path + the path to the output file + the path does not have to exist, but must be absolute + resources: None or dict + a resources dict, output from exporter.from_notebook_node + + Returns + ------- + stream: str + filepath: None or str or pathlib.Path + + """ + + if (self.allowed_mimetypes is not None + and mimetype not in self.allowed_mimetypes): + if not self.skip_mime: + self.handle_error( + "the mimetype {0} is not in the allowed list: {1}".format( + mimetype, self.allowed_mimetypes), + TypeError) + else: + self.logger.debug( + "skipping incorrect mime type: {}".format(mimetype)) + return stream, filepath, resources + + if self.requires_path and filepath is None: + self.handle_error( + "the filepath is None, " + "but the post-processor requires a folder", + IOError) + + if filepath is not None and isinstance(filepath, string_types): + filepath = pathlib.Path(filepath) + + if self.requires_path: + + if not filepath.is_absolute(): + self.handle_error( + "the post-processor requires a folder, " + "but the filepath is not absolute", + IOError) + + if filepath.parent.exists() and not filepath.parent.is_dir(): + self.handle_error( + "the filepath's parent is not a folder: {}".format( + filepath), + TypeError) + + if not filepath.parent.exists(): + filepath.parent.mkdir(parents=True) + + if resources is None: + resources = {} + + return self.run_postprocess(stream, filepath, resources) + + def run_postprocess(self, stream, filepath, resources): + """ should not be called directly + override in sub-class + + Parameters + ---------- + stream: str + the main file contents + filepath: None or pathlib.Path + the path to the output file + resources: dict + a resources dict, output from exporter.from_notebook_node + + Returns + ------- + stream: str + filepath: None or pathlib.Path + resources: dict + + """ + raise NotImplementedError('run_postprocess') + + def handle_error(self, msg, err_type, + raise_msg=None, log_msg=None): + """ handle error by logging it then raising + """ + handle_error(msg, err_type, self.logger, + raise_msg=raise_msg, log_msg=log_msg) + + def check_exe_exists(self, name, error_msg): + """ test if an executable exists + """ + if not exe_exists(name): + self.handle_error(error_msg, RuntimeError) + return True + + +if __name__ == "__main__": + + print(IPyPostProcessor.allowed_mimetypes) + IPyPostProcessor()("stream", "a") diff --git a/ipypublish/postprocessors/file_actions.py b/ipypublish/postprocessors/file_actions.py new file mode 100644 index 0000000..98a1ca7 --- /dev/null +++ b/ipypublish/postprocessors/file_actions.py @@ -0,0 +1,173 @@ +import os +import shutil +from traitlets import Unicode, List + +from ipypublish.postprocessors.base import IPyPostProcessor + + +class WriteTextFile(IPyPostProcessor): + """ write the stream to a text based file + """ + @property + def allowed_mimetypes(self): + return ("text/latex", "text/restructuredtext", "text/html", + "text/x-python", "application/json", "text/markdown") + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "write-text-file" + + encoding = Unicode( + "utf8", + help="the encoding of the output file" + ).tag(config=True) + + def run_postprocess(self, stream, filepath, resources): + + self.logger.info('writing stream to file: {}'.format(filepath)) + with filepath.open("w", encoding=self.encoding) as fh: + fh.write(stream) + + return stream, filepath, resources + + +class RemoveFolder(IPyPostProcessor): + """ remove a folder and all its contents + """ + @property + def allowed_mimetypes(self): + return None + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "remove-folder" + + files_folder = Unicode( + "_static", + help="the path (relative to the main file path) to remove" + ).tag(config=True) + + def run_postprocess(self, stream, filepath, resources): + + remove_folder = filepath.parent.joinpath(self.files_folder) + if remove_folder.exists() and remove_folder.is_dir(): + self.logger.info( + 'removing folder: {0}'.format(remove_folder)) + shutil.rmtree(remove_folder) + + return stream, filepath, resources + + +class WriteResourceFiles(IPyPostProcessor): + """ write content contained in the resources dict to file (as bytes) + """ + @property + def allowed_mimetypes(self): + return None + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "write-resource-files" + + resource_keys = List( + Unicode, + ["outputs"], + help="the key names in the resources dict that contain files" + ).tag(config=True) + + # The files already have a relative path + # files_folder = Unicode( + # "_static", + # help="the path (relative to the main file path) to write to" + # ).tag(config=True) + + def run_postprocess(self, stream, filepath, resources): + + output_folder = filepath.parent + if not output_folder.exists(): + output_folder.mkdir(parents=True) + + for key in self.resource_keys: + if key not in resources: + continue + if not hasattr(resources[key], "items"): + self.handle_error( + "the value of resources[{0}] is not a mapping".format(key), + TypeError) + self.logger.info( + 'writing files in resources[{0}] to: {1}'.format( + key, output_folder)) + for filename, content in resources[key].items(): + + outpath = output_folder.joinpath(filename) + if not outpath.parent.exists(): + outpath.parent.mkdir(parents=True) + + with outpath.open("wb") as fh: + fh.write(content) + + self.logger.debug("finished") + + return stream, filepath, resources + + +class CopyResourcePaths(IPyPostProcessor): + """ copy filepaths in the resources dict to another folder + """ + @property + def allowed_mimetypes(self): + return None + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "copy-resource-paths" + + resource_keys = List( + Unicode, + ["external_file_paths"], + help="the key names in the resources dict that contain filepaths" + ).tag(config=True) + + files_folder = Unicode( + "_static", + help="the path (relative to the main file path) to copy to" + ).tag(config=True) + + def run_postprocess(self, stream, filepath, resources): + + output_folder = filepath.parent.joinpath(self.files_folder) + if not output_folder.exists(): + output_folder.mkdir(parents=True) + + for key in self.resource_keys: + if key not in resources: + continue + if not isinstance(resources[key], (list, tuple, set)): + self.handle_error( + "the value of resources[{0}] is not an iterable".format( + key), TypeError) + self.logger.info( + 'copying files in resources[{0}] to: {1}'.format( + key, output_folder)) + for resfilepath in resources[key]: + shutil.copyfile(resfilepath, + str(output_folder.joinpath( + os.path.basename(resfilepath)))) + + return stream, filepath, resources diff --git a/ipypublish/postprocessors/pdfexport.py b/ipypublish/postprocessors/pdfexport.py new file mode 100755 index 0000000..341b2a3 --- /dev/null +++ b/ipypublish/postprocessors/pdfexport.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python +""" a module for exporting latex file to pdf +TODO could this be refactored as nbconvert postprocessor +""" +import os +import shutil +import tempfile +from subprocess import Popen, PIPE, STDOUT +import webbrowser + +import six +from traitlets import Bool, Unicode + +from ipypublish.postprocessors.base import IPyPostProcessor + + +class PDFExport(IPyPostProcessor): + """ a post processor to convert tex to pdf using latexmk + """ + @property + def allowed_mimetypes(self): + return ("text/latex") + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "pdf-export" + + files_folder = Unicode( + "_static", + help="the path (relative to the main file path) " + "containing external files" + ).tag(config=True) + + convert_in_temp = Bool( + False, + help="run conversion in a temporary directory, " + "and copy back only PDF file" + ).tag(config=True) + + debug_mode = Bool( + False, + help="run in debug mode").tag(config=True) + + open_in_browser = Bool( + False, + help="launch a html page containing a pdf browser").tag(config=True) + + def run_postprocess(self, stream, filepath, resources): + """ should not be called directly + + Parameters + ---------- + stream: str + the main file contents + filepath: None or pathlib.Path + the path to the output file + + Returns + ------- + stream: str + filepath: None or pathlib.Path + + """ + self.logger.info('running pdf conversion') + self._export_pdf(filepath) + return stream, filepath, resources + + def _export_pdf(self, texpath): + + if not texpath.exists(): + self.handle_error( + 'the target file path does not exist: {}'.format( + texpath), IOError) + + texname = os.path.splitext(texpath.name)[0] + # NOTE outdir was originally passed, but would this ever be different + # to the texpath's parent + + external_files = texpath.parent.joinpath(self.files_folder) + + if external_files.exists() and not external_files.is_dir(): + self.handle_error( + 'the external folder path is not a directory: {}'.format( + external_files), IOError) + + self.check_exe_exists( + 'latexmk', + 'requires the latexmk executable to run. ' + 'See http://mg.readthedocs.io/latexmk.html#installation', + ) + + if self.convert_in_temp: + out_folder = tempfile.mkdtemp() + try: + exitcode = self._run_latexmk( + texpath, out_folder, external_files) + if exitcode == 0: + shutil.copyfile( + os.path.join(out_folder, texname + '.pdf'), + str(texpath.parent.joinpath(texname + '.pdf'))) + finally: + shutil.rmtree(out_folder) + else: + exitcode = self._run_latexmk( + texpath, str(texpath.parent), external_files) + + if exitcode == 0: + self.logger.info('pdf conversion complete') + + view_pdf = VIEW_PDF.format( + pdf_name=texname.replace(' ', '%20') + '.pdf') + view_pdf_path = texpath.parent.joinpath(texname + '.view_pdf.html') + with view_pdf_path.open('w', encoding='utf-8') as fobj: + fobj.write(six.u(view_pdf)) + else: + self.handle_error( + 'pdf conversion failed: ' + 'Try running with pdf-debug flag', + RuntimeError) + + if self.open_in_browser: + # 2 opens the url in a new tab + webbrowser.open(view_pdf_path.as_uri(), new=2) + + return + + def _run_latexmk(self, texpath, out_folder, external_files): + """ run latexmk conversion + """ + # make sure tex file in right place + outpath = os.path.join(out_folder, texpath.name) + if os.path.dirname(str(texpath)) != str(out_folder): + self.logger.debug('copying tex file to: {}'.format( + os.path.join(str(out_folder), texpath.name))) + shutil.copyfile(str(texpath), os.path.join( + str(out_folder), texpath.name)) + + # make sure the external files folder is in right place + if external_files.exists(): + self.logger.debug('external files folder set') + outfilespath = os.path.join(out_folder, str(external_files.name)) + if str(external_files) != str(outfilespath): + self.logger.debug( + 'copying external files to: {}'.format(outfilespath)) + if os.path.exists(outfilespath): + shutil.rmtree(outfilespath) + shutil.copytree(str(external_files), str(outfilespath)) + + # run latexmk in correct folder + with change_dir(out_folder): + latexmk = ['latexmk', '-xelatex', '-bibtex', '-pdf'] + latexmk += [] if self.debug_mode else ["--interaction=batchmode"] + latexmk += [outpath] + self.logger.info('running: ' + ' '.join(latexmk)) + + def log_latexmk_output(pipe): + for line in iter(pipe.readline, b''): + self.logger.info('latexmk: {}'.format( + line.decode("utf-8").strip())) + + process = Popen(latexmk, stdout=PIPE, stderr=STDOUT) + with process.stdout: + log_latexmk_output(process.stdout) + exitcode = process.wait() # 0 means success + + return exitcode + + +class change_dir: + """Context manager for changing the current working directory""" + + def __init__(self, new_path): + self.newPath = os.path.expanduser(new_path) + + def __enter__(self): + self.savedPath = os.getcwd() + os.chdir(self.newPath) + + def __exit__(self, etype, value, traceback): + os.chdir(self.savedPath) + + +VIEW_PDF = r""" + + + + + + View PDF + + + + + + + + +
+ + + +""" diff --git a/ipypublish/scripts/reveal_serve.py b/ipypublish/postprocessors/reveal_serve.py similarity index 72% rename from ipypublish/scripts/reveal_serve.py rename to ipypublish/postprocessors/reveal_serve.py index a3ebd72..caddacf 100644 --- a/ipypublish/scripts/reveal_serve.py +++ b/ipypublish/postprocessors/reveal_serve.py @@ -1,4 +1,4 @@ -""" serve HTML page +""" serve HTML page TODO the RevealServer setting should be available at front end """ @@ -7,7 +7,6 @@ from __future__ import print_function -import logging import os import signal import webbrowser @@ -15,7 +14,7 @@ from tornado import web, ioloop, httpserver, log from tornado.httpclient import AsyncHTTPClient from traitlets import Bool, Unicode, Int -from traitlets.config.configurable import LoggingConfigurable +from ipypublish.postprocessors.base import IPyPostProcessor class ProxyHandler(web.RequestHandler): @@ -40,11 +39,22 @@ def finish_get(self, response): self.finish(response.body) -class RevealServer(LoggingConfigurable): +class RevealServer(IPyPostProcessor): """Post processor designed to serve files Proxies reveal.js requests to a CDN if no local reveal.js is present """ + @property + def allowed_mimetypes(self): + return ("text/html") + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "reveal-server" open_in_browser = Bool( True, @@ -65,13 +75,16 @@ class RevealServer(LoggingConfigurable): port = Int( 8000, help="port for the server to listen on.").tag(config=True) - def serve(self, input): + def run_postprocess(self, stream, filepath, resources): """Serve the build directory with a webserver.""" - if not os.path.exists(input): - logging.error('the html path does not exist: {}'.format(input)) - raise IOError('the html path does not exist: {}'.format(input)) - dirname, filename = os.path.split(input) + if not filepath.exists(): + self.handle_error( + 'the target file path does not exist: {}'.format( + filepath), IOError) + + # TODO rewrite this as pathlib + dirname, filename = os.path.split(str(filepath)) handlers = [ (r"/(.+)", web.StaticFileHandler, {'path': dirname}), @@ -84,12 +97,12 @@ def serve(self, input): elif os.path.isdir(os.path.join(dirname, self.reveal_prefix)): # reveal prefix exists self.log.info("Serving local %s", self.reveal_prefix) - logging.info("Serving local %s", self.reveal_prefix) + self.logger.info("Serving local %s", self.reveal_prefix) else: self.log.info("Redirecting %s requests to %s", self.reveal_prefix, self.reveal_cdn) - logging.info("Redirecting %s requests to %s", - self.reveal_prefix, self.reveal_cdn) + self.logger.info("Redirecting %s requests to %s", + self.reveal_prefix, self.reveal_cdn) handlers.insert(0, (r"/(%s)/(.*)" % self.reveal_prefix, ProxyHandler)) @@ -98,7 +111,7 @@ def serve(self, input): client=AsyncHTTPClient(), ) - # hook up tornado logging to our logger + # hook up tornado logging to our self.logger log.app_log = self.log http_server = httpserver.HTTPServer(app) @@ -108,33 +121,35 @@ def serve(self, input): for port_attempt in port_attempts: try: url = "http://%s:%i/%s" % (self.ip, self.port, filename) - logging.info("Attempting to serve at %s" % url) + self.logger.info("Attempting to serve at %s" % url) http_server.listen(self.port, address=self.ip) break except IOError: self.port += 1 if port_attempt == port_attempts[-1]: - logging.error( + self.handle_error( 'no port available to launch slides on, ' - 'try closing some slideshows') - raise IOError( - 'no port available to launch slides on, ' - 'try closing some slideshows') + 'try closing some slideshows', + IOError) - logging.info("Serving your slides at %s" % url) - logging.info("Use Control-C to stop this server") + self.logger.info("Serving your slides at %s" % url) + self.logger.info("Use Control-C to stop this server") # don't let people press ctrl-z, which leaves port open def handler(signum, frame): - logging.info('Control-Z pressed, but ignored, use Control-C!') + self.logger.info('Control-Z pressed, but ignored, use Control-C!') signal.signal(signal.SIGTSTP, handler) if self.open_in_browser: + # 2 opens the url in a new tab webbrowser.open(url, new=2) + try: ioloop.IOLoop.instance().start() except KeyboardInterrupt: # dosen't look like line below is necessary # ioloop.IOLoop.instance().stop() - logging.info("\nInterrupted") + self.logger.info("\nInterrupted") + + return stream, filepath, resources diff --git a/ipypublish/postprocessors/sphinx.py b/ipypublish/postprocessors/sphinx.py new file mode 100644 index 0000000..b1f924a --- /dev/null +++ b/ipypublish/postprocessors/sphinx.py @@ -0,0 +1,185 @@ +import webbrowser +import shutil +from subprocess import Popen, PIPE, STDOUT +from distutils.spawn import find_executable + +from six import u +from traitlets import TraitError, validate, Bool, Dict, Unicode + +from ipypublish import __version__ +from ipypublish.utils import find_entry_point +from ipypublish.postprocessors.base import IPyPostProcessor +from ipypublish.ipysphinx.utils import import_sphinx +from ipypublish.ipysphinx.create_setup import make_conf, make_index + + +# NOTE Interesting note about adding a directive to actually run python code +# https://stackoverflow.com/questions/7250659/how-to-use-python-to-programmatically-generate-part-of-sphinx-documentation + +class RunSphinx(IPyPostProcessor): + """ run sphinx to create an html output + """ + @property + def allowed_mimetypes(self): + return ("text/restructuredtext",) + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "run-sphinx" + + open_in_browser = Bool( + True, + help="launch a html page containing a pdf browser").tag(config=True) + + numbered = Bool( + True, + help="set :numbered: in toc, which numbers sections, etc" + ).tag(config=True) + + show_prompts = Bool( + True, + help="whether to include cell prompts").tag(config=True) + + prompt_style = Unicode( + '[%s]:', + help="the style of cell prompts").tag(config=True) + + @validate('prompt_style') + def _valid_prompt_style(self, proposal): + try: + proposal % 1 + except TypeError: + raise TraitError("prompt_style should be formatable by a number") + return proposal['value'] + + conf_kwargs = Dict( + help=("additional key-word arguments to be included in the conf.py " + "as = ")).tag(config=True) + + override_defaults = Bool( + True, + help="if True, conf_kwargs override default values").tag(config=True) + + nitpick = Bool( + False, + help="nit-picky mode, warn about all missing references" + ) + + def run_postprocess(self, stream, filepath, resources): + + # check sphinx is available and the correct version + try: + import_sphinx() + except ImportError as err: + self.handle_error(err.msg, ImportError) + + self.logger.info("Creating Sphinx files") + + titlepage = {} + if "titlepage" in resources.get("ipub", {}): + titlepage = resources["ipub"]["titlepage"] + # includes ['author', 'email', 'supervisors', 'title', 'subtitle', + # 'tagline', 'institution', 'logo'] + + # create a conf.py + kwargs = {} if not self.conf_kwargs else self.conf_kwargs + kwargs["ipysphinx_show_prompts"] = self.show_prompts + kwargs["ipysphinx_input_prompt"] = self.prompt_style + kwargs["ipysphinx_output_prompt"] = self.prompt_style + if "author" in titlepage: + kwargs["author"] = titlepage["author"] + if "tagline" in titlepage: + kwargs["description"] = titlepage["tagline"] + if "email" in titlepage: + kwargs["email"] = titlepage["email"] + + conf_str = make_conf(overwrite=self.override_defaults, **kwargs) + conf_path = filepath.parent.joinpath("conf.py") + with conf_path.open("w", encoding="utf8") as f: + f.write(u(conf_str)) + + # create an index.rst + toc_files = [filepath.name] + + toc_depth = 3 + title = None + prolog = [] + epilog = [] + if "author" in titlepage: + prolog.append(".. sectionauthor:: {0}".format(titlepage["author"])) + prolog.append("") + if "title" in titlepage: + title = titlepage["title"] + if "tagline" in titlepage: + prolog.append(titlepage["tagline"]) + if "institution" in titlepage: + for inst in titlepage["institution"]: + epilog.append("| " + inst) + + epilog.append("") + epilog.append('Created by IPyPublish (version {})'.format(__version__)) + + toc = resources.get("ipub", {}).get("toc", {}) + if hasattr(toc, "get") and "depth" in toc: + toc_depth = toc["depth"] + + index_str = make_index(toc_files, + toc_depth=toc_depth, header=title, + toc_numbered=self.numbered, + prolog="\n".join(prolog), + epilog="\n".join(epilog)) + + index_path = filepath.parent.joinpath("index.rst") + with index_path.open("w", encoding="utf8") as f: + f.write(u(index_str)) + + # clear any existing build + build_dir = filepath.parent.joinpath('build/html') + if build_dir.exists(): + # >> rm -r build/html + shutil.rmtree(build_dir) + build_dir.mkdir(parents=True) + + # run sphinx + exec_path = find_executable('sphinx-build') + args = [exec_path, "-b", "html"] + if self.nitpick: + args.append("-n") + args.extend([str(filepath.parent.absolute()), + str(build_dir.absolute())]) + + self.logger.info("running: " + " ".join(args)) + + # this way overrides the logging + # sphinx_build = find_entry_point("sphinx-build", "console_scripts", + # self.logger, "sphinx") + + def log_process_output(pipe): + for line in iter(pipe.readline, b''): + self.logger.info('{}'.format( + line.decode("utf-8").strip())) + + process = Popen(args, stdout=PIPE, stderr=STDOUT) + with process.stdout: + log_process_output(process.stdout) + exitcode = process.wait() # 0 means success + + if exitcode: + self.logger.warn( + "sphinx-build exited with code: {}".format(exitcode)) + + if self.open_in_browser and not exitcode: + # get entry path + entry_path = filepath.parent.joinpath('build/html/index.html') + if entry_path.exists(): + # 2 opens the url in a new tab + webbrowser.open(entry_path.as_uri(), new=2) + else: + self.handle_error( + "can't find {0} to open".format(entry_path), IOError) + + return stream, filepath, resources diff --git a/ipypublish/postprocessors/stream_modify.py b/ipypublish/postprocessors/stream_modify.py new file mode 100644 index 0000000..0a11b0f --- /dev/null +++ b/ipypublish/postprocessors/stream_modify.py @@ -0,0 +1,93 @@ +""" +postprocessors that modify the output stream +""" +import re + +from ipypublish.postprocessors.base import IPyPostProcessor + + +class RemoveBlankLines(IPyPostProcessor): + """ remove multiple lines of blank space """ + @property + def allowed_mimetypes(self): + return ("text/latex", "text/restructuredtext", "text/html", + "text/x-python", "application/json", "text/markdown") + + @property + def requires_path(self): + return False + + @property + def logger_name(self): + return "remove-blank-lines" + + def run_postprocess(self, stream, filepath, resources): + stream = re.sub(r'\n\s*\n', '\n\n', stream) + return stream, filepath, resources + + +class RemoveTrailingSpace(IPyPostProcessor): + """ remove trailing whitespace on each line """ + @property + def allowed_mimetypes(self): + return ("text/latex", "text/restructuredtext", + "text/x-python", "application/json", "text/markdown") + + @property + def requires_path(self): + return False + + @property + def logger_name(self): + return "remove-trailing-space" + + def run_postprocess(self, stream, filepath, resources): + stream = "\n".join([l.rstrip() for l in stream.splitlines()]) + return stream, filepath, resources + + +class FilterOutputFiles(IPyPostProcessor): + """ filter internal files in resources['outputs'], + by those that are referenced in the stream """ + @property + def allowed_mimetypes(self): + return None + + @property + def requires_path(self): + return False + + @property + def logger_name(self): + return "filter-output-files" + + def run_postprocess(self, stream, filepath, resources): + + if 'outputs' in resources: + for path in list(resources['outputs'].keys()): + if path not in stream: + resources['outputs'].pop(path) + + return stream, filepath, resources + + +class FixSlideReferences(IPyPostProcessor): + """ make sure references refer to correct slides """ + @property + def allowed_mimetypes(self): + return ("text/html") + + @property + def requires_path(self): + return False + + @property + def logger_name(self): + return "fix-slide-refs" + + def run_postprocess(self, stream, filepath, resources): + if resources and 'refslide' in resources: + for k, (col, row) in resources['refslide'].items(): + stream = stream.replace('{{id_home_prefix}}{0}'.format( + k), '#/{0}/{1}{2}'.format(col, row, k)) + return stream, filepath, resources diff --git a/ipypublish/postprocessors/to_stream.py b/ipypublish/postprocessors/to_stream.py new file mode 100644 index 0000000..2107cd1 --- /dev/null +++ b/ipypublish/postprocessors/to_stream.py @@ -0,0 +1,42 @@ +import sys +from traitlets import Unicode, Enum + +from ipypublish.postprocessors.base import IPyPostProcessor + + +class WriteStream(IPyPostProcessor): + """ write the stream to the terminal + """ + @property + def allowed_mimetypes(self): + return ("text/latex", "text/restructuredtext", "text/html", + "text/x-python", "application/json", "text/markdown") + + @property + def requires_path(self): + return True + + @property + def logger_name(self): + return "write-text-file" + + encoding = Unicode( + default_value="utf8", + help="the encoding of the output file" + ).tag(config=True) + + pipe = Enum( + ["stdout", "stderr", "stdin"], + default_value="stdout", + help="where to write the output to" + ).tag(config=True) + + def run_postprocess(self, stream, filepath, resources): + + self.logger.info('writing stream to {}'.format(self.pipe)) + io_type = {"stdout": sys.stdout, + "stdin": sys.stdin, + "stderr": sys.stderr}.get(self.pipe) + io_type.write(stream) + + return stream, filepath, resources diff --git a/ipypublish/preprocessors/latex_doc_captions.py b/ipypublish/preprocessors/latex_doc_captions.py index 409c83d..fb25831 100644 --- a/ipypublish/preprocessors/latex_doc_captions.py +++ b/ipypublish/preprocessors/latex_doc_captions.py @@ -4,6 +4,8 @@ from nbconvert.preprocessors import Preprocessor from nbformat.notebooknode import NotebookNode +logger = logging.getLogger("captions") + class LatexCaptions(Preprocessor): """ a preprocessor to: @@ -19,7 +21,7 @@ class LatexCaptions(Preprocessor): def preprocess(self, nb, resources): - logging.info('extracting caption cells') + logger.info('extracting caption cells') # extract captions final_cells = [] @@ -62,7 +64,7 @@ def preprocess(self, nb, resources): for key in cell.metadata.ipub: if hasattr(cell.metadata.ipub[key], 'label'): if cell.metadata.ipub[key]['label'] in captions: - logging.debug('replacing caption for: {}'.format(cell.metadata.ipub[key]['label'])) + logger.debug('replacing caption for: {}'.format(cell.metadata.ipub[key]['label'])) cell.metadata.ipub[key]['caption'] = captions[cell.metadata.ipub[key]['label']] # add float type/number prefix to caption, if required diff --git a/ipypublish/preprocessors/latex_doc_defaults.py b/ipypublish/preprocessors/latex_doc_defaults.py index 7e1632f..9c2884d 100644 --- a/ipypublish/preprocessors/latex_doc_defaults.py +++ b/ipypublish/preprocessors/latex_doc_defaults.py @@ -54,9 +54,15 @@ class MetaDefaults(Preprocessor): """ - nb_defaults = traits.Dict(default_value={}, help='dict of notebook level defaults').tag(config=True) - cell_defaults = traits.Dict(default_value={}, help='dict of cell level defaults').tag(config=True) - overwrite = traits.Bool(False, help="whether existing values should be overwritten").tag(config=True) + nb_defaults = traits.Dict( + default_value={}, + help='dict of notebook level defaults').tag(config=True) + cell_defaults = traits.Dict( + default_value={}, + help='dict of cell level defaults').tag(config=True) + overwrite = traits.Bool( + False, + help="whether existing values should be overwritten").tag(config=True) def preprocess(self, nb, resources): @@ -74,13 +80,25 @@ def preprocess(self, nb, resources): dct[keys[-1]] = val for cell in nb.cells: + for keys, val in flatten(self.cell_defaults).items(): dct = cell.metadata + leaf_not_dict = False for key in keys[:-1]: if key not in dct: dct[key] = NotebookNode({}) + elif dct[key] is False and self.overwrite: + dct[key] = NotebookNode({}) + elif dct[key] is True: + dct[key] = NotebookNode({}) + elif not hasattr(dct[key], 'items'): + leaf_not_dict = True + break dct = dct[key] - if keys[-1] not in dct: + + if leaf_not_dict: + pass + elif keys[-1] not in dct: dct[keys[-1]] = val elif self.overwrite: dct[keys[-1]] = val diff --git a/ipypublish/preprocessors/latex_doc_links.py b/ipypublish/preprocessors/latex_doc_links.py index 234811e..7639f3f 100644 --- a/ipypublish/preprocessors/latex_doc_links.py +++ b/ipypublish/preprocessors/latex_doc_links.py @@ -3,12 +3,20 @@ from binascii import a2b_base64 import sys import json +import re from mimetypes import guess_extension from six import string_types import traitlets as traits from nbconvert.preprocessors import Preprocessor +if sys.version_info[0] == 2: + from urlparse import urlparse +else: + from urllib.parse import urlparse + +logger = logging.getLogger("resolve_links") + def guess_extension_without_jpe(mimetype): """ @@ -23,38 +31,113 @@ def guess_extension_without_jpe(mimetype): return ext +def is_hyperlink(path): + """test whether a path is a hyperlink, e.g. https://site.org""" + if urlparse(path).scheme: + return True + return False + + +def resolve_path(fpath, filepath): + """resolve a relative path, w.r.t. another filepath """ + + if is_hyperlink(fpath): + return fpath + + if not os.path.isabs(fpath): + fpath = os.path.join(os.path.dirname(str(filepath)), fpath) + fpath = os.path.abspath(fpath) + + return os.path.normpath(fpath) + + +def extract_file_links(source, parent_path, redirect_path, + replace_nonexistent=False): + """ extract local linked files + + Examples + -------- + >>> source = '''## Cell with Linked Image + ... ![test_image](subdir/logo_example.png) + ... a [test_link](other_doc#a-link)''' + >>> src, rpaths, npaths = extract_file_links( + ... source, '/root/nb.ipynb', 'redirect', True) + >>> print(src) + ## Cell with Linked Image + ![test_image](redirect/logo_example.png) + a [test_link](redirect/other_doc#a-link) + + >>> print(rpaths[0]) + /root/subdir/logo_example.png + >>> print(rpaths[1]) + /root/other_doc + + + """ + # TODO is this robust enough + regex = re.compile('\\[([^\\]]*)\\]\\(([^\\)^\\#]*)([^\\)]*)\\)') + new_source = source + redirected_paths = [] + nonexistent_paths = [] + for text, path, label in regex.findall(source): + if path.startswith("attachment:"): + continue + if not path: # internal links + continue + respath = resolve_path(path, parent_path) + if is_hyperlink(respath): + continue + if not os.path.exists(respath): + nonexistent_paths.append(respath) + if os.path.exists(respath) or replace_nonexistent: + redirected_paths.append(respath) + new_path = os.path.normpath(os.path.join( + redirect_path, os.path.basename(path))) + new_source = new_source.replace( + "[{0}]({1}{2})".format(text, path, label), + "[{0}]({1}{2})".format(text, new_path, label)) + + return new_source, redirected_paths, nonexistent_paths + + class LatexDocLinks(Preprocessor): """ a preprocessor to resolve file paths in the notebook: - - retrieve external file paths from ipub metadata section, - - resolve where they are, if the path is relative - - make sure that the link points to a single folder - - add 'external_file_paths' and 'bibliopath' (if present) to resources - - extract attachments from input cells and rename their file links + 1. Extract attachments from markdown cells, to resources['outputs'], + and redirect their file links to self.filesfolder + + 2. If nb.metadata.ipub.bibliography, create resources['bibliopath'] + + 3. Creates resources['external_file_paths'] = [] and adds to it: + + - local relative file paths referenced in markdown cells by + '[](path/to/file)' + - path to nb.metadata.ipub.bibliography (if present) + - path to nb.metadata.ipub.titlepage.logo (if present) + + 4. If self.redirect_external=True, + redirects relative external file paths to self.filesfolder """ metapath = traits.Unicode( - '', help="the path to the meta data").tag(config=True) + '', help="the file path to the notebook").tag(config=True) filesfolder = traits.Unicode( - '', help="the folder to point towards").tag(config=True) + '', help=("the folder path to dump dump internal content to " + "(e.g. images, etc)")).tag(config=True) + redirect_external = traits.Bool( + True, + help="if True, redirect relatively linked paths to filesfolder" + ).tag(config=True) extract_attachments = traits.Bool( True, - help=("extract attachments " + help=("extract attachments stored in the notebook" "(created by dragging and dropping files into markdown cells)") ).tag(config=True) output_attachment_template = traits.Unicode( "{unique_key}_{cell_index}_{key}{extension}" ).tag(config=True) - def resolve_path(self, fpath, filepath): - """resolve a relative path, w.r.t. another filepath """ - if not os.path.isabs(fpath): - fpath = os.path.join(os.path.dirname(str(filepath)), fpath) - fpath = os.path.abspath(fpath) - - return fpath - def preprocess_cell(self, cell, resources, cell_index): """ Extract attachment @@ -67,10 +150,21 @@ def preprocess_cell(self, cell, resources, cell_index): Additional resources used in the conversion process. Allows preprocessors to pass variables into the Jinja engine. cell_index : int - Index of the cell being processed (see base.py) + Index of the cell being processed """ - unique_key = resources.get('unique_key', 'attach') + if cell.cell_type != "markdown": + return cell, resources + + # extract local linked files + source, rpaths, npaths = extract_file_links( + cell.source, self.metapath, self.filesfolder) + if self.redirect_external: + cell.source = source + resources['external_file_paths'].extend(rpaths) + resources['unfound_file_paths'].extend(npaths) + # extract attachments + unique_key = resources.get('unique_key', 'attach') if 'attachments' in cell and self.extract_attachments: attachments = cell.pop('attachments') @@ -140,56 +234,51 @@ def preprocess(self, nb, resources): Preprocessing to apply on each notebook. """ - logging.info('resolving external file paths' + - ' in ipub metadata to: {}'.format(self.metapath)) - external_files = [] - if 'ipub' in nb.metadata: + logger.info('resolving external file paths' + + ' in ipub metadata to: {}'.format(self.metapath)) - # if hasattr(nb.metadata.ipub, 'files'): - # mfiles = [] - # for fpath in nb.metadata.ipub.files: - # fpath = self.resolve_path(fpath, self.metapath) - # if not os.path.exists(fpath): - # logging.warning('file in metadata does not exist' - # ': {}'.format(fpath)) - # else: - # external_files.append(fpath) - # mfiles.append(os.path.join( - # self.filesfolder, os.path.basename(fpath))) - # - # nb.metadata.ipub.files = mfiles + resources.setdefault("external_file_paths", []) + resources.setdefault("unfound_file_paths", []) + + if 'ipub' in nb.metadata: if hasattr(nb.metadata.ipub, 'bibliography'): bib = nb.metadata.ipub.bibliography - bib = self.resolve_path(bib, self.metapath) + bib = resolve_path(bib, self.metapath) if not os.path.exists(bib): - logging.warning('bib in metadata does not exist' - ': {}'.format(bib)) + resources['unfound_file_paths'].append(bib) else: - external_files.append(bib) + resources['external_file_paths'].append(bib) resources['bibliopath'] = bib - nb.metadata.ipub.bibliography = os.path.join( - self.filesfolder, os.path.basename(bib)) + if self.redirect_external: + nb.metadata.ipub.bibliography = os.path.join( + self.filesfolder, os.path.basename(bib)) if hasattr(nb.metadata.ipub, 'titlepage'): if hasattr(nb.metadata.ipub.titlepage, 'logo'): logo = nb.metadata.ipub.titlepage.logo - logo = self.resolve_path(logo, self.metapath) + logo = resolve_path(logo, self.metapath) if not os.path.exists(logo): - logging.warning('logo in metadata does not exist' - ': {}'.format(logo)) + resources['unfound_file_paths'].append(logo) else: - external_files.append(logo) + resources['external_file_paths'].append(logo) - nb.metadata.ipub.titlepage.logo = os.path.join( - self.filesfolder, os.path.basename(logo)) - - resources.setdefault("external_file_paths", []) - resources['external_file_paths'] += external_files + if self.redirect_external: + nb.metadata.ipub.titlepage.logo = os.path.join( + self.filesfolder, os.path.basename(logo)) for index, cell in enumerate(nb.cells): nb.cells[index], resources = self.preprocess_cell( cell, resources, index) + # filter unique + resources['external_file_paths'] = list( + set(resources['external_file_paths'])) + + upaths = set(resources.pop("unfound_file_paths")) + if upaths: + logger.warning('referenced file(s) do not exist' + ': {}'.format(list(upaths))) + return nb, resources diff --git a/ipypublish/schema/doc_metadata.schema.json b/ipypublish/schema/doc_metadata.schema.json index a5066d8..200ad4e 100644 --- a/ipypublish/schema/doc_metadata.schema.json +++ b/ipypublish/schema/doc_metadata.schema.json @@ -5,6 +5,7 @@ "properties": { "ipub": { "type": "object", + "additionalProperties": false, "properties": { "language": { "description": "the language can be any specified in the babel package", @@ -17,6 +18,7 @@ "boolean", "object" ], + "additionalProperties": false, "properties": { "author": { "type": "string", @@ -123,6 +125,48 @@ "super", "sort&compress" ] + }, + "pandoc": { + "description": "settings for running the ipypandoc filters, to convert markdown to other formats", + "type": "object", + "additionalProperties": false, + "properties": { + "apply_filters": { + "description": "whether to apply filters to markdown", + "type": "boolean", + "default": true + }, + "convert_raw": { + "description": "attempt to extract non-markdown formats and convert them to the target format, e.g. rst roles to latex tags", + "type": "boolean", + "default": true + }, + "hide_raw": { + "description": "if the extracted non-markdown does not match the output format, this controls if it is shown in the output document", + "type": "boolean", + "default": false + }, + "at_notation": { + "description": "interpret +@label as a reference type based on its prefix modifier", + "type": "boolean", + "default": true + }, + "use_numref": { + "description": "in rst, whether to use the ``:numref:`` role or just ``:ref:``", + "type": "boolean", + "default": true + }, + "reftag": { + "description": "in latex, default tag for citations", + "type": "string", + "default": "cite" + } + } + }, + "orphan": { + "description": "whether the :orphan: directive is added in sphinx extension (to allow it to not be included in toc tree)", + "type": "boolean", + "default": false } } } diff --git a/ipypublish/schema/export_config.schema.json b/ipypublish/schema/export_config.schema.json index d01e92e..6ccbdf7 100644 --- a/ipypublish/schema/export_config.schema.json +++ b/ipypublish/schema/export_config.schema.json @@ -94,6 +94,50 @@ } } } + }, + "postprocessors": { + "description": "configuration for post-processing of the stream output from the exporter", + "type": "object", + "properties": { + "order": { + "description": "the order of application of the post-processors, named by their entry-point", + "type": "array", + "items": {"type": "string"}, + "default": [ + "remove-blank-lines", + "remove-trailing-space", + "filter-output-files", + "fix-slide-refs", + "remove-folder", + "write-text-file", + "write-resource-files", + "copy-resource-paths" + ] + }, + "config": { + "description": "the traitlets configuration for post-processor classes", + "type": "object", + "default": { + "IPyPostProcessor": { + "skip_mime": true + }, + "PDFExport": { + "files_folder": "${files_path}", + "convert_in_temp": false, + "debug_mode": false, + "open_in_browser": false, + "skip_mime": false + }, + "RemoveFolder": { + "files_folder": "${files_path}" + }, + "CopyResourcePaths": { + "files_folder": "${files_path}" + } + } + } + } + } }, "definitions": { diff --git a/ipypublish/scripts/__init__.py b/ipypublish/scripts/__init__.py index e69de29..3fdf68b 100644 --- a/ipypublish/scripts/__init__.py +++ b/ipypublish/scripts/__init__.py @@ -0,0 +1,3 @@ +""" +contains helpful scripts for use in jupyter notebooks +""" \ No newline at end of file diff --git a/ipypublish/scripts/ipynb_latex_setup.py b/ipypublish/scripts/ipynb_latex_setup.py old mode 100755 new mode 100644 index 84436ee..68c4c41 --- a/ipypublish/scripts/ipynb_latex_setup.py +++ b/ipypublish/scripts/ipynb_latex_setup.py @@ -9,12 +9,20 @@ from ipynb_latex_setup import * """ - from __future__ import division as _division # Py2/Py3 compatibility # ===================== from __future__ import print_function as _print_function +import warnings +warnings.warn( + "this approach is now deprecated, " + "instead please use\n`from ipypublish.scripts import nb_setup`\n" + "then use the individual functions it provides to setup matplotlib, etc", + DeprecationWarning, + stacklevel=2) + + # PYTHON # ======= @@ -31,9 +39,12 @@ if _ipy_present: ipython = get_ipython() if ipython is not None: - ipython.magic("config InlineBackend.figure_format = 'svg'") - ipython.magic("matplotlib inline") - set_matplotlib_formats('pdf', 'svg') + try: + ipython.magic("config InlineBackend.figure_format = 'svg'") + ipython.magic("matplotlib inline") + set_matplotlib_formats('pdf', 'svg') + except Exception: + pass # NUMPY # ===== @@ -78,7 +89,7 @@ if _pandas_present and ipython: pd.set_option('display.latex.repr', True) pd.set_option('display.latex.longtable', False) - pd.set_option('display.latex.escape', False) + pd.set_option('display.latex.escape', True) # SYMPY # ===== diff --git a/ipypublish/scripts/nb_setup.py b/ipypublish/scripts/nb_setup.py new file mode 100644 index 0000000..dadefc4 --- /dev/null +++ b/ipypublish/scripts/nb_setup.py @@ -0,0 +1,464 @@ +""" +Some useful functions for creating +publishable jupyter notebooks + +usage: + +.. code-block:: python + + from ipypublish import nb_setup + plt = nb_setup.setup_matplotlib( + print_errors=True, + output=('pdf',)) + pd = nb_setup.setup_pandas(escape_latex=True) + sym = nb_setup.setup_sympy() + import numpy as np + from IPython.display import Image, Latex + +""" +# Py2/Py3 compatibility +# ===================== +# from __future__ import division as _division +# from __future__ import print_function as _print_function +import json +from io import BytesIO + +# from IPython.display import Image, Latex + +MPL_OPTIONS = ( + ('lines.linewidth', 1.5), + ('lines.markeredgewidth', 1.0), + ('lines.markersize', 8), + ('lines.antialiased', True), + ('lines.dashed_pattern', (3.7, 1.6)), + ('lines.dashdot_pattern', (6.4, 1.6, 1, 1.6)), + ('lines.dotted_pattern', [1, 1.65]), + ('lines.scale_dashes', True), + ('patch.linewidth', 1.0), + ('patch.force_edgecolor', False), + ('patch.antialiased', True), + ('hatch.linewidth', 1.0), + ('hist.bins', 10), + ('boxplot.notch', False), + ('boxplot.vertical', True), + ('boxplot.whiskers', 1.5), + ('boxplot.patchartist', False), + ('boxplot.showmeans', False), + ('boxplot.showcaps', True), + ('boxplot.showbox', True), + ('boxplot.showfliers', True), + ('boxplot.meanline', False), + ('boxplot.flierprops.markersize', 6), + ('boxplot.flierprops.linewidth', 1.0), + ('boxplot.boxprops.linewidth', 1.0), + ('boxplot.whiskerprops.linewidth', 1.0), + ('boxplot.capprops.linewidth', 1.0), + ('boxplot.medianprops.linewidth', 1.0), + ('boxplot.meanprops.markersize', 6), + ('boxplot.meanprops.linewidth', 1.0), + ('font.family', 'sans-serif'), + ('font.serif', 'cm'), + ('font.size', 16), + ('text.usetex', True), + # ('text.latex.unicode', False), + ('text.latex.preamble', ( + '\\usepackage{subdepth}', '\\usepackage{type1cm}')), + ('text.latex.preview', False), + ('text.hinting_factor', 8), + ('text.antialiased', True), + ('mathtext.fallback_to_cm', True), + ('image.lut', 256), + ('image.resample', True), + ('image.composite_image', True), + ('contour.corner_mask', True), + ('errorbar.capsize', 0), + ('axes.labelsize', 18), + ('axes.linewidth', 2.0), + ('axes.spines.left', True), + ('axes.spines.right', True), + ('axes.spines.bottom', True), + ('axes.spines.top', True), + ('axes.titlesize', 20), + ('axes.titlepad', 6.0), + ('axes.grid', False), + ('axes.labelpad', 4.0), + ('axes.formatter.limits', (-7, 7)), + ('axes.formatter.use_locale', False), + ('axes.formatter.use_mathtext', False), + ('axes.formatter.min_exponent', 0), + ('axes.formatter.useoffset', True), + ('axes.formatter.offset_threshold', 4), + ('axes.unicode_minus', True), + ('axes.xmargin', 0.05), + ('axes.ymargin', 0.05), + ('polaraxes.grid', True), + ('axes3d.grid', True), + ('legend.fontsize', 14), + ('legend.fancybox', True), + ('legend.numpoints', 1), + ('legend.scatterpoints', 1), + ('legend.markerscale', 1.0), + ('legend.shadow', False), + ('legend.frameon', True), + ('legend.framealpha', 0.8), + ('legend.borderpad', 0.4), + ('legend.labelspacing', 0.5), + ('legend.handlelength', 2.0), + ('legend.handleheight', 0.7), + ('legend.handletextpad', 0.8), + ('legend.borderaxespad', 0.5), + ('legend.columnspacing', 2.0), + ('xtick.top', False), + ('xtick.bottom', True), + ('xtick.labeltop', False), + ('xtick.labelbottom', True), + ('xtick.major.size', 3.5), + ('xtick.minor.size', 2), + ('xtick.major.width', 0.8), + ('xtick.minor.width', 0.6), + ('xtick.major.pad', 3.5), + ('xtick.minor.pad', 3.4), + ('xtick.minor.visible', False), + ('xtick.minor.top', True), + ('xtick.minor.bottom', True), + ('xtick.major.top', True), + ('xtick.major.bottom', True), + ('ytick.left', True), + ('ytick.right', False), + ('ytick.labelleft', True), + ('ytick.labelright', False), + ('ytick.major.size', 3.5), + ('ytick.minor.size', 2), + ('ytick.major.width', 0.8), + ('ytick.minor.width', 0.6), + ('ytick.major.pad', 3.5), + ('ytick.minor.pad', 3.4), + ('ytick.minor.visible', False), + ('ytick.minor.left', True), + ('ytick.minor.right', True), + ('ytick.major.left', True), + ('ytick.major.right', True), + ('grid.linewidth', 0.8), + ('grid.alpha', 1.0), + ('figure.figsize', (6.4, 4.8)), + ('figure.dpi', 100), + ('figure.frameon', True), + ('figure.autolayout', False), + ('figure.max_open_warning', 20), + ('figure.subplot.left', 0.125), + ('figure.subplot.right', 0.9), + ('figure.subplot.bottom', 0.11), + ('figure.subplot.top', 0.88), + ('figure.subplot.wspace', 0.2), + ('figure.subplot.hspace', 0.2), + ('figure.constrained_layout.use', False), + ('figure.constrained_layout.hspace', 0.02), + ('figure.constrained_layout.wspace', 0.02), + ('figure.constrained_layout.h_pad', 0.04167), + ('figure.constrained_layout.w_pad', 0.04167), + ('savefig.frameon', True), + ('savefig.jpeg_quality', 95), + ('savefig.pad_inches', 0.1), + ('savefig.transparent', False), + ('savefig.dpi', 75), + ('svg.image_inline', True), + ('path.simplify', True), + ('path.simplify_threshold', 0.1111111111111111), + ('path.snap', True), + ('path.effects', ()), + ('agg.path.chunksize', 0), + ('animation.embed_limit', 20), + ('animation.bitrate', -1), + ('animation.html_args', ()), + ('animation.ffmpeg_args', ()), + ('animation.avconv_args', ()), + ('animation.convert_args', ()) +) + + +def setup_matplotlib( + output=('pdf', 'svg'), + rcparams=None, + usetex=True, + print_errors=False +): + """ import and setup matplotlib in the jupyter notebook + + Parameters + ---------- + output: tuple[str] + the output formats to save to the notebook + rcparams: None or dict + update default parameters set for matplotlib + usetex: bool + if True, and the 'latex' command is available, + create figures with LaTeX + print_errors: bool + print errors for unavailable rcparams + + """ + from IPython import get_ipython + from IPython.display import set_matplotlib_formats + import matplotlib as mpl + from shutilwhich import which + + ipython = get_ipython() + latex_available = which('latex') is not None + # if not latex_available: + # output = [o for o in output if o != "pdf"] + + set_matplotlib_formats(*output) + ipython.magic("matplotlib inline") + if 'svg' in output: + ipython.magic("config InlineBackend.figure_format = 'svg'") + + final_params = dict(MPL_OPTIONS) + if rcparams is not None: + final_params.update(rcparams) + if usetex and latex_available: + final_params.update({'text.usetex': True}) + else: + final_params.update({'text.usetex': False}) + + keyerrors = [] + valerrors = {} + for key, val in final_params.items(): + try: + mpl.rcParams[key] = val + except KeyError: + keyerrors.append(key) + except ValueError: + valerrors[key] = val + + if print_errors: + if keyerrors: + print("KeyErrors:") + for key in keyerrors: + print("- key") + if valerrors: + print("ValueError:") + print(json.dumps(valerrors, indent=2)) + + return mpl.pyplot + + +def setup_pandas(escape_latex=False, use_longtable=False): + """ import and setup pandas in the jupyter notebook + + Parameters + ---------- + escape_latex: bool + whether to excape special latex character, e.g. `_` -> `\_` + + """ + import pandas as pd + pd.set_option('display.latex.repr', True) + pd.set_option('display.latex.longtable', use_longtable) + pd.set_option('display.latex.escape', escape_latex) + return pd + + +def setup_sympy(): + import sympy + sympy.init_printing(use_latex=True) + return sympy + + +def get_pimage(): + try: + from PIL import Image + except ImportError: + raise ImportError("to use this function; pip install pillow") + return Image + + +def create_test_image(size=(50, 50)): + file = BytesIO() + image = get_pimage().new('RGBA', size=size, color=(155, 0, 0)) + image.save(file, 'png') + file.name = 'test.png' + file.seek(0) + return file + + +def images_read(paths): + """read a list of image paths to a list of PIL.IMAGE instances """ + return [get_pimage().open(i).convert("RGBA") for i in paths] + + +def images_hconcat(images, width=700, height=700, + gap=0, aspaths=True): + """concatenate multiple images horizontally + + Parameters + ---------- + images : list + if aspaths=True, list of path strings, else list of PIL.Image instances + width : int or list[int] + maximum width of final image, or of individual images + height : int or list[int] + maximum height of final image, or of individual images + gap : int + size of space between images + + Returns + ------- + image : PIL.Image + + Examples + -------- + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_hconcat([img_path,img_path]) + >>> img.size + (100, 50) + + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_hconcat([img_path,img_path],width=40,height=40) + >>> img.size + (40, 20) + + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_hconcat([img_path,img_path],width=[40,30]) + >>> img.size + (70, 40) + + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_hconcat([img_path,img_path],gap=10) + >>> img.size + (110, 50) + + """ + pimage = get_pimage() + + images = images_read(images) if aspaths else images + if not isinstance(width, list): + widths = [width for _ in images] + else: + widths = width[:] + width = sum(widths) + gap * (len(images) - 1) + if not isinstance(height, list): + heights = [height for _ in images] + else: + heights = height[:] + height = sum(heights) + for im, w, h in zip(images, widths, heights): + im.thumbnail((w, h), pimage.ANTIALIAS) + widths, heights = zip(*(i.size for i in images)) + total_width = sum(widths) + gap * (len(images) - 1) + max_height = max(heights) + new_im = pimage.new('RGBA', (total_width, max_height)) + x_offset = 0 + for im in images: + new_im.paste(im, (x_offset, 0), mask=im) + x_offset += im.size[0] + gap + new_im.thumbnail((width, height), pimage.ANTIALIAS) + return new_im + + +def images_vconcat(images, width=700, height=700, + gap=0, aspaths=True): + """concatenate multiple images vertically + + Parameters + ---------- + images : list + if aspaths=True, list of path strings, else list of PIL.Image instances + width : int or list[int] + maximum width of final image, or of individual images + height : int or list[int] + maximum height of final image, or of individual images + gap : int + size of space between images + + Returns + ------- + image : PIL.Image + + Examples + -------- + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_vconcat([img_path,img_path]) + >>> img.size + (50, 100) + + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_vconcat([img_path,img_path],width=40,height=40) + >>> img.size + (20, 40) + + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_vconcat([img_path,img_path],width=[40,30]) + >>> img.size + (40, 70) + + >>> img_path = create_test_image(size=(50,50)) + >>> img = images_vconcat([img_path,img_path],gap=10) + >>> img.size + (50, 110) + + + """ + pimage = get_pimage() + + images = images_read(images) if aspaths else images + if not isinstance(width, list): + widths = [width for _ in images] + else: + widths = width[:] + width = sum(widths) + if not isinstance(height, list): + heights = [height for _ in images] + else: + heights = height[:] + height = sum(heights) + gap * (len(images) - 1) + for im, w, h in zip(images, widths, heights): + im.thumbnail((w, h), pimage.ANTIALIAS) + widths, heights = zip(*(i.size for i in images)) + max_width = max(widths) + total_height = sum(heights) + gap * (len(images) - 1) + new_im = pimage.new('RGBA', (max_width, total_height)) + y_offset = 0 + for im in images: + new_im.paste(im, (0, y_offset), mask=im) + y_offset += im.size[1] + gap + new_im.thumbnail((width, height), pimage.ANTIALIAS) + return new_im + + +def images_gridconcat(pathslist, width=700, height=700, + aspaths=True, hgap=0, vgap=0): + """concatenate multiple images in a grid + + Parameters + ---------- + pathslist : list[list] + if aspaths=True, list of path strings, else list of PIL.Image instances + each sub list constitutes a row + width : int + maximum width of final image + height : int + maximum height of final image + hgap : int + size of horizontal space between images + vgap : int + size of vertical space between images + + Returns + ------- + image : PIL.Image + + """ + pimage = get_pimage() + himages = [images_hconcat(paths, gap=hgap, aspaths=aspaths) + for paths in pathslist] + new_im = images_vconcat(himages, gap=vgap, aspaths=False) + new_im.thumbnail((width, height), pimage.ANTIALIAS) + return new_im + + +if __name__ == "__main__": + import matplotlib as mpl + print(json.dumps({ + k: v[0] + for k, v in mpl.rcsetup.defaultParams.items() + if isinstance(v[0], (int, float, list, tuple, set))}, indent=2)) diff --git a/ipypublish/scripts/pdfexport.py b/ipypublish/scripts/pdfexport.py deleted file mode 100755 index 1c010e4..0000000 --- a/ipypublish/scripts/pdfexport.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env python -""" a module for exporting latex file to pdf -TODO could this be refactored as nbconvert postprocessor -""" -import logging -import os -import shutil -import tempfile -from subprocess import Popen, PIPE, STDOUT -from six import string_types - -# python 3 to 2 compatibility -try: - import pathlib -except ImportError: - import pathlib2 as pathlib -try: - from shutil import which as exe_exists -except ImportError: - from distutils.spawn import find_executable as exe_exists - -from ipypublish.utils import handle_error - -logger = logging.getLogger("pdfexport") - -VIEW_PDF = r""" - - - - - - View PDF - - - - - - - - -
- - - -""" - - -class change_dir: - """Context manager for changing the current working directory""" - - def __init__(self, new_path): - self.newPath = os.path.expanduser(new_path) - - def __enter__(self): - self.savedPath = os.getcwd() - os.chdir(self.newPath) - - def __exit__(self, etype, value, traceback): - os.chdir(self.savedPath) - - -def export_pdf(texpath, outdir, files_path=None, - convert_in_temp=False, html_viewer=True, - debug_mode=False): - if isinstance(texpath, string_types): - texpath = pathlib.Path(texpath) - if not texpath.exists() or not texpath.is_file(): - handle_error( - 'the tex file path does not exist: {}'.format(texpath), - IOError, logger) - - texname = os.path.splitext(texpath.name)[0] - - if files_path is not None: - if isinstance(files_path, string_types): - files_path = pathlib.Path(files_path) - if not files_path.exists() or not files_path.is_dir(): - handle_error( - 'the external folder path does not exist: {}'.format( - files_path), IOError, logger) - - if not exe_exists('latexmk'): - handle_error( - 'requires the latexmk executable to run. ' - 'See http://mg.readthedocs.io/latexmk.html#installation', - RuntimeError, logger) - - if convert_in_temp: - out_folder = tempfile.mkdtemp() - try: - exitcode = run_conversion( - texpath, out_folder, files_path, debug_mode) - if exitcode == 0: - shutil.copyfile(os.path.join(out_folder, texname + '.pdf'), - os.path.join(outdir, texname + '.pdf')) - finally: - shutil.rmtree(out_folder) - else: - exitcode = run_conversion(texpath, outdir, files_path, debug_mode) - - if exitcode == 0: - logger.info('pdf conversion complete') - - if html_viewer: - view_pdf = VIEW_PDF.format( - pdf_name=texname.replace(' ', '%20') + '.pdf') - view_pdf_path = os.path.join(outdir, texname + '.view_pdf.html') - with open(view_pdf_path, 'w') as f: - f.write(view_pdf) - return True - else: - handle_error( - 'pdf conversion failed: ' - 'Try running with pdf_debug=True', - RuntimeError, logger) - return False - - -def run_conversion(texpath, out_folder, files_folder=None, debug_mode=False): - """ run latexmk conversion - """ - - # make sure tex file in right place - outpath = os.path.join(out_folder, texpath.name) - if os.path.dirname(str(texpath)) != str(out_folder): - logger.debug('copying tex file to: {}'.format( - os.path.join(str(out_folder), texpath.name))) - shutil.copyfile(str(texpath), os.path.join( - str(out_folder), texpath.name)) - - # make sure the external files folder is in right place - if files_folder is not None: - logger.debug('external files folder set') - outfilespath = os.path.join(out_folder, str(files_folder.name)) - if str(files_folder) != str(outfilespath): - logger.debug('copying external files to: {}'.format(outfilespath)) - if os.path.exists(outfilespath): - shutil.rmtree(outfilespath) - shutil.copytree(str(files_folder), str(outfilespath)) - - # run latexmk in correct folder - with change_dir(out_folder): - latexmk = ['latexmk', '-xelatex', '-bibtex', '-pdf'] - latexmk += [] if debug_mode else ["--interaction=batchmode"] - logger.info('running: ' + ' '.join(latexmk + [''])) - latexmk += [outpath] - - def log_latexmk_output(pipe): - for line in iter(pipe.readline, b''): - logger.info('latexmk: {}'.format( - line.decode("utf-8").strip())) - - process = Popen(latexmk, stdout=PIPE, stderr=STDOUT) - with process.stdout: - log_latexmk_output(process.stdout) - exitcode = process.wait() # 0 means success - - return exitcode diff --git a/ipypublish/templates/outline_schemas/latex_outline.latex.j2 b/ipypublish/templates/outline_schemas/latex_outline.latex.j2 index 56f682e..d8aa9d3 100644 --- a/ipypublish/templates/outline_schemas/latex_outline.latex.j2 +++ b/ipypublish/templates/outline_schemas/latex_outline.latex.j2 @@ -1,10 +1,12 @@ ((*- extends 'display_priority.tplx' -*)) +@ipubreplace{below}{globals} + %% Latex Document Setup %% ==================== ((* block header *)) -% A latex document created by ipypublish @ipubreplace{below}{ipypub_version} +% A latex document created by ipypublish@ipubreplace{below}{ipypub_version} ((( '@ipubreplace{below}{meta_docstring}' | comment_lines('% ') ))) % diff --git a/ipypublish/templates/outline_schemas/rst_outline.rst.j2 b/ipypublish/templates/outline_schemas/rst_outline.rst.j2 index d29b11a..7afe413 100644 --- a/ipypublish/templates/outline_schemas/rst_outline.rst.j2 +++ b/ipypublish/templates/outline_schemas/rst_outline.rst.j2 @@ -1,5 +1,7 @@ {% extends 'display_priority.tpl' %} +@ipubreplace{below}{globals} + @ipubreplace{below}{new_blocks} {# HEADER #} @@ -57,10 +59,10 @@ {# OUTPUT #} +{# This need to contain super to show any output #} {% block output %} @ipubreplace{above}{notebook_output_pre} -@ipubreplace{below}{notebook_output_prompt} -{{ super() }} +@ipubreplace{below}{notebook_output_all} @ipubreplace{below}{notebook_output_post} {% endblock output %} @@ -78,7 +80,10 @@ @ipubreplace{below}{notebook_output_traceback_line} {% endblock traceback_line %} -{# {% block stream %} #} +{% block stream %} +@ipubreplace{below}{notebook_output_stream} +{% endblock stream %} +{# stdout and stderr require super() to be called in stream #} {% block stream_stdout %} @ipubreplace{above}{notebook_output_stream_stdout_pre} @ipubreplace{below}{notebook_output_stream_stdout} @@ -89,7 +94,6 @@ @ipubreplace{below}{notebook_output_stream_stderr} @ipubreplace{below}{notebook_output_stream_stderr_post} {% endblock stream_stderr %} -{# {% endblock stream %} #} {# OUTPUT DATA #} diff --git a/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json b/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json index 222f6a8..5e01d7f 100644 --- a/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json +++ b/ipypublish/templates/segments/ipy-biblio_natbib.latex-tpl.json @@ -19,9 +19,9 @@ "((*- if nb.metadata.ipub: -*))", "((*- if nb.metadata.ipub.bibliography: -*))", "((* set filename = nb.metadata.ipub.bibliography | strip_ext | posix_path *))", - "((*- if nb.metadata.ipub.bibstyle: -*))", + "((*- if nb.metadata.ipub.bibstyle: *))", "\\bibliographystyle{((( nb.metadata.ipub.bibstyle )))}", - "((*- else -*))", + "((*- else *))", "% sort citations by order of first appearance", "\\bibliographystyle{unsrtnat}", "((*- endif *))", 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 new file mode 100644 index 0000000..f81c089 --- /dev/null +++ b/ipypublish/templates/segments/ipy-contents_framed_code-new.yaml.tex.j2 @@ -0,0 +1,148 @@ +"schema": "../../schema/segment.schema.json" +"description": "with the input code wrapped and framed" +"identifier": ipypublish-contents_framed_code +"segments": + + "document_definitions": |2 + + \definecolor{codegreen}{rgb}{0,0.6,0} + \definecolor{codegray}{rgb}{0.5,0.5,0.5} + \definecolor{codepurple}{rgb}{0.58,0,0.82} + \definecolor{backcolour}{rgb}{0.95,0.95,0.95} + + \lstdefinestyle{mystyle}{ + commentstyle=\color{codegreen}, + keywordstyle=\color{magenta}, + numberstyle=\tiny\color{codegray}, + stringstyle=\color{codepurple}, + basicstyle=\ttfamily, + breakatwhitespace=false, + keepspaces=true, + numbers=left, + numbersep=10pt, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + breaklines=true, + literate={\-}{}{0\discretionary{-}{}{-}}, + postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space}, + } + + \lstset{style=mystyle} + + \surroundwithmdframed[ + hidealllines=true, + backgroundcolor=backcolour, + innerleftmargin=0pt, + innerrightmargin=0pt, + innertopmargin=0pt, + innerbottommargin=0pt]{lstlisting} + + "document_header_end": |2 + + % make the code float work with cleverref + \crefname{codecell}{code}{codes} + \Crefname{codecell}{code}{codes} + % make the text float work with cleverref + \crefname{textcell}{text}{texts} + \Crefname{textcell}{text}{texts} + % make the text float work with cleverref + \crefname{errorcell}{error}{errors} + \Crefname{errorcell}{error}{errors} + + "document_packages": |2 + + + % define a code float + \usepackage{newfloat} % to define a new float types + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Code]{codecell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{textcell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{errorcell} + + \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", + "language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt") ))) + + "notebook_output_error": |2 + + ((( super() ))) + + "notebook_output_stream": |2 + + ((*- if cell.metadata.get("ipub",{}).get("text",{}).use_ansi : -*)) + ((( draw_text(cell.metadata,output.text | ansi2listings("%") ,"text", + "language={},postbreak={},numbers=none,xrightmargin=7pt,belowskip=5pt,aboveskip=5pt,breakindent=0pt,escapechar=\%") ))) + ((*- else -*)) + ((( draw_text(cell.metadata,output.text | strip_ansi ,"text", + "language={},postbreak={},numbers=none,xrightmargin=7pt,belowskip=5pt,aboveskip=5pt,breakindent=0pt") ))) + ((*- endif *)) + + "notebook_output_text": |2 + + ((( draw_text(cell.metadata,output.data['text/plain'] | strip_ansi,"text", + "language={},postbreak={},numbers=none,xrightmargin=7pt,breakindent=0pt,aboveskip=5pt,belowskip=5pt") ))) + + "notebook_output_traceback": |2 + + ((( draw_text(cell.metadata,line | indent | strip_ansi,"error", + "language=Python,numbers=none,xrightmargin=5pt,belowskip=2pt,aboveskip=2pt") ))) + + "jinja_macros": |2 + + ((* macro draw_text(meta,source,type,options) -*)) + + ((*- if meta.ipub: -*)) + + ((*- if meta.ipub[type] -*)) + + ((*- if meta.ipub[type].asfloat: -*)) + ((*- if meta.ipub[type].placement: -*)) + ((*- if meta.ipub[type].widefigure: -*)) + \begin{(((type)))cell*}[((meta.ipub[type].placement)))] + ((*- else -*)) + \begin{(((type)))cell}[(((meta.ipub[type].placement)))] + ((*- endif *)) + ((*- else -*)) + ((*- if meta.ipub[type].widefigure: -*)) + \begin{(((type)))cell*} + ((*- else -*)) + \begin{(((type)))cell} + ((*- endif *)) + ((*- endif *)) + + ((*- set caption = type | get_caption(meta, resources) -*)) + ((*- if caption *)) + \caption{((( caption | ipypandoc('latex', nb.metadata, meta) )))} + ((*- endif *)) + + ((*- endif *)) + + ((*- if meta.ipub[type].label: *)) + \label{((( meta.ipub[type].label )))} + ((*- endif *)) + + ((*- if meta.ipub[type].format: *)) + \begin{lstlisting}[((( meta.ipub[type].format | dict_to_kwds(options) )))] + ((*- else *)) + \begin{lstlisting}[((( options )))] + ((*- endif *)) + ((( source ))) + \end{lstlisting} + + ((*- if meta.ipub[type].asfloat: -*)) + \end{(((type)))cell} + ((*- endif *)) + ((*- endif *)) + ((*- endif *)) + ((*- endmacro *)) diff --git a/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 b/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 new file mode 100644 index 0000000..9b7bea4 --- /dev/null +++ b/ipypublish/templates/segments/ipy-contents_output-new.yaml.tex.j2 @@ -0,0 +1,178 @@ +"schema": "../../schema/segment.schema.json" +"description": "with the main ipypublish content" +"identifier": "ipypublish-contents_output" +"segments": + + "document_packages": |2 + + ((*- if nb.metadata.ipub: -*)) + ((*- if nb.metadata.ipub.enable_breqn: -*)) + \usepackage{breqn} + ((*- endif *)) + ((*- endif *)) + + "notebook_input": |2 + + ((*- if cell.metadata.ipub: -*)) + ((*- if cell.metadata.ipub.ignore: -*)) + ((*- elif cell.metadata.ipub.slideonly: -*)) + ((*- else -*)) + ((( super() ))) + ((*- endif *)) + ((*- else -*)) + ((( super() ))) + ((*- endif *)) + + "notebook_input_markdown": |2 + + ((( cell.source | citation2latex | strip_files_prefix | ipypandoc('latex', nb.metadata, cell.metadata) ))) + + "notebook_input_raw": |2 + + ((*- if cell.metadata.raw_mimetype: -*)) + ((*- if cell.metadata.raw_mimetype == "text/latex" -*)) + ((( super() ))) + ((*- endif *)) + ((*- endif *)) + + "notebook_output": |2 + + ((*- if cell.metadata.ipub: -*)) + ((*- if cell.metadata.ipub.ignore: -*)) + ((*- elif cell.metadata.ipub.slideonly: -*)) + ((*- else -*)) + ((( super() ))) + ((*- endif *)) + ((*- else -*)) + ((( super() ))) + ((*- endif *)) + + "notebook_output_latex": |2 + + ((*- if cell.metadata.ipub: *)) + ((*- if cell.metadata.ipub.table and cell.metadata.ipub.equation *)) + ((*- if output.data['text/latex'] | is_equation *)) + ((( output.data['text/latex'] | wrap_eqn(cell_meta=cell.metadata, nb_meta=nb.metadata) ))) + ((*- else *)) + ((( draw_table(cell, resources, output.data['text/latex']) ))) + ((*- endif *)) + ((*- else *)) + ((*- if cell.metadata.ipub.table: *)) + ((( draw_table(cell, resources, output.data['text/latex']) ))) + ((*- elif cell.metadata.ipub.equation: *)) + ((( output.data['text/latex'] | wrap_eqn(cell_meta=cell.metadata, nb_meta=nb.metadata) ))) + ((*- endif *)) + ((*- endif *)) + ((*- endif *)) + + "notebook_output_markdown": |2 + + ((*- if cell.metadata.ipub: -*)) + ((*- if cell.metadata.ipub.mkdown: -*)) + ((( output.data['text/markdown'] | citation2latex | strip_files_prefix | ipypandoc('latex', nb.metadata, cell.metadata) ))) + ((*- endif *)) + ((*- endif *)) + + "notebook_output_stream": |2 + + ((*- if cell.metadata.ipub and cell.metadata.ipub.ignore -*)) + ((*- else -*)) + ((( super() ))) + ((*- endif *)) + + "notebook_output_pdf": |2 + + ((( draw_figure(output.metadata.filenames['application/pdf'], + cell.metadata) ))) + + "notebook_output_png": |2 + + ((( draw_figure(output.metadata.filenames['image/png'], + cell.metadata) ))) + + "notebook_output_jpg": |2 + + ((( draw_figure(output.metadata.filenames['image/jpeg'], + cell.metadata) ))) + + "notebook_output_svg": |2 + + ((( draw_figure(output.metadata.filenames['image/svg+xml'], + cell.metadata) ))) + + "jinja_macros": |2 + + ((# draw figures #)) + ((* macro draw_figure(filename, meta) -*)) + ((*- if meta.ipub: -*)) + ((*- if meta.ipub.figure: -*)) + ((* set filename = filename | posix_path *)) + ((*- block figure scoped -*)) + + ((*- if meta.ipub.figure.placement: -*)) + ((*- if meta.ipub.figure.widefigure: -*)) + \begin{figure*}[(((meta.ipub.figure.placement)))] + ((*- else -*)) + \begin{figure}[(((meta.ipub.figure.placement)))] + ((*- endif *)) + ((*- else -*)) + ((*- if meta.ipub.figure.widefigure: -*)) + \begin{figure*} + ((*- else -*)) + \begin{figure} + ((*- endif *)) + ((*- endif *)) + + ((*- if meta.ipub.figure.width: -*)) + \begin{center}\adjustimage{max size={0.9\linewidth}{0.9\paperheight},width=(((meta.ipub.figure.width)))\linewidth}{((( filename )))}\end{center} + ((*- elif meta.ipub.figure.height: -*)) + \begin{center}\adjustimage{max size={0.9\linewidth}{0.9\paperheight},height=(((meta.ipub.figure.height)))\paperheight}{((( filename )))}\end{center} + ((*- else -*)) + \begin{center}\adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{((( filename )))}\end{center} + ((*- endif *)) + + ((*- set caption = "figure" | get_caption(meta, resources) -*)) + ((*- if caption -*)) + \caption{((( caption | ipypandoc('latex', nb.metadata, meta) )))} + ((*- endif *)) + ((*- if meta.ipub.figure.label: -*)) + \label{((( meta.ipub.figure.label )))} + ((*- endif *)) + \end{figure} + + ((*- endblock figure -*)) + ((*- endif *)) + ((*- endif *)) + ((*- endmacro *)) + + ((# draw tables #)) + ((* macro draw_table(cell, resources, text) -*)) + ((*- block table scoped -*)) + + ((*- if cell.metadata.ipub.table.placement: -*)) + \begin{table}[(((cell.metadata.ipub.table.placement)))] + ((*- else -*)) + \begin{table} + ((*- endif *)) + + ((*- set caption = "table" | get_caption(cell.metadata, resources) -*)) + ((*- if caption *)) + \caption{((( caption | ipypandoc('latex', nb.metadata, cell.metadata) )))} + ((*- endif *)) + + ((*- if cell.metadata.ipub.table.label -*)) + \label{((( cell.metadata.ipub.table.label )))} + ((*- endif *)) + \centering + \begin{adjustbox}{max width=\textwidth} + ((*- if cell.metadata.ipub.table.alternate: -*)) + \rowcolors{2}{(((cell.metadata.ipub.table.alternate)))}{white} + ((*- endif *)) + ((( text ))) + \end{adjustbox} + \end{table} + + ((*- endblock table -*)) + ((*- endmacro *)) + + diff --git a/ipypublish/templates/segments/ipy-sphinx.yaml.j2 b/ipypublish/templates/segments/ipy-sphinx.yaml.j2 new file mode 100644 index 0000000..988c57c --- /dev/null +++ b/ipypublish/templates/segments/ipy-sphinx.yaml.j2 @@ -0,0 +1,369 @@ +"$schema": "../../schema/segment.schema.json" +"identifier": "nbsphinx-ipypublish-content" +"description": "ipypublish sphinx content" +"segments": + + # TODO instead of filtering output/input cells/types could just mark them with e.g. :hidden: field + # TODO allow slide cells in rst but wrap them in directive + # TODO slide content control and creation (see https://github.com/tell-k/sphinxjp.themes.revealjs) + # NOTE adding a toc not in separate index.rst doesn't work at moment https://github.com/sphinx-doc/sphinx/issues/2103 + + "globals": | + {# re-route ipub metadata into resources, so we can use it in post-processing #} + {# see https://stackoverflow.com/a/43265291/5033292 #} + {% if "ipub" in nb.metadata %} + {% set x = resources.__setitem__("ipub", nb.metadata.ipub) %} + {% endif %} + + "notebook_input_all": | + {# ignore input cells if cell.metadata.ipub.ignore = True #} + {%- if "ipub" in cell.metadata and cell.metadata.ipub.ignore %} + {%- else -%} + {%- if "ipub" in cell.metadata and cell.metadata.ipub.slideonly %} + {%- else -%} + {{ super() }} + {% endif %} + {% endif %} + + "notebook_input_code": | + {# only output code blocks if cell.metadata.ipub.code = True #} + {% if "ipub" in cell.metadata and cell.metadata.ipub.code -%} + {# always create the input directive even if it is empty #} + {# supply the directive with the lexer to use (see http://pygments.org/docs/lexers/) #} + {{'.. nbinput:: '}} {%- if 'magics_language' in cell.metadata -%} + {{ cell.metadata.magics_language }} + {%- elif 'pygments_lexer' in nb.metadata.get('language_info', {}) -%} + {{ nb.metadata.language_info.pygments_lexer }} + {%- elif 'name' in nb.metadata.get('language_info', {}) -%} + {{ nb.metadata.language_info.name }} + {%- endif -%} + {#- add option fields for input directive -#} + {{ insert_empty_lines(cell.source) }} + {%- if cell.execution_count %} + :execution-count: {{ cell.execution_count }} + {%- endif %} + {%- if not cell.outputs %} + :no-output: + {%- endif -%} + {%- set ipubcode = cell.metadata.get("ipub", {}).get("code", {}) %} + {%- if ipubcode is mapping and ipubcode.get("label", False) %} + :name: {{ ipubcode['label'] }} + {%- endif -%} + {# find captions #} + {%- if resources.captions and resources.captions[ipubcode.get("label", False)] %} + {%- set caption = resources.captions[ipubcode["label"]] | ipypandoc('rst', nb.metadata, cell.metadata) %} + :caption: {{ caption.replace("\n", " ") }} + {%- elif ipubcode is mapping and "caption" in ipubcode and ipubcode["caption"] %} + {%- set caption = ipubcode.get("caption") | ipypandoc('rst', nb.metadata, cell.metadata) %} + :caption: {{ caption.replace("\n", " ") }} + {%- endif %} + {# add output code if there is any #} + {%- if cell.source.strip() %} + + {{ cell.source.strip('\n') | indent}} + {%- endif %} + {%- endif %} + + "notebook_input_markdown": | + {# only use @label notation if specified in nb/cell metadata #} + {{ cell.source | ipypandoc('rst', nb.metadata, cell.metadata) }} + + "notebook_input_raw": | + {# output raw input with the correct identifier #} + {%- set raw_mimetype = cell.metadata.get('raw_mimetype', '').lower() %} + {%- if raw_mimetype == '' %} + .. raw:: html + + {{ cell.source | indent }} + + .. raw:: latex + + {{ cell.source | indent }} + {%- elif raw_mimetype == 'text/html' %} + .. raw:: html + + {{ cell.source | indent }} + {%- elif raw_mimetype == 'text/latex' %} + .. raw:: latex + + {{ cell.source | indent }} + {%- elif raw_mimetype == 'text/markdown' %} + .. + {# Empty comment to make sure the preceding directive (if any) is closed #} + {{ cell.source | markdown2rst }} + {%- elif raw_mimetype == 'text/restructuredtext' %} + .. + {# Empty comment to make sure the preceding directive (if any) is closed #} + {{ cell.source }} + {% endif %} + + "notebook_input_heading": | + {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | convert_pandoc("markdown", "rst") }} + + "notebook_input_unknown": | + .. unknown type {{cell.type}} + + "notebook_output_all": | + {# ignore output cells if cell.metadata.ipub.ignore = True #} + {%- if "ipub" in cell.metadata and cell.metadata.ipub.ignore %} + {%- else -%} + {%- if "ipub" in cell.metadata and cell.metadata.ipub.slideonly %} + {%- else -%} + {{ super() }} + {% endif %} + {% endif %} + + "new_blocks": | + {% block nboutput -%} + {%- set html_datatype, latex_datatype = output | choose_output_type %} + {%- 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 + + {{ insert_nboutput(html_datatype, output, cell) | indent }} + .. only:: latex + + {{ insert_nboutput(latex_datatype, output, cell) | indent }} + {% endif %} + {% endblock nboutput %} + + "notebook_output_error": | + {%- if "ipub" in cell.metadata and cell.metadata.ipub.error -%} + {{ self.nboutput() }} + {% endif %} +# :: +# +# {{ super() }} + +# "notebook_output_traceback_line": | +# {{ line | indent | strip_ansi }} + + # stdin and stderr + "notebook_output_stream": | + {%- if "ipub" in cell.metadata and cell.metadata.ipub.text -%} + {{ self.nboutput() }} + {% endif %} +# .. parsed-literal:: +# +# {{ output.text | indent }} + + "notebook_output_data_all": | + {{ self.nboutput() }} + + "footer": | + {# output a bibligraphy if there is one + (see https://sphinxcontrib-bibtex.readthedocs.io/en/latest/usage.html) #} + {%- if nb.metadata.ipub: -%} + {%- if nb.metadata.ipub.bibliography: -%} + {% set filename = nb.metadata.ipub.bibliography | posix_path %} + .. bibliography:: {{ filename }} + {%- endif -%} + {% if nb.metadata.ipub.bibstyle and nb.metadata.ipub.bibstyle in ['plain', 'unsrt', 'unsrtalpha', 'alpha'] %} + :style: {{ nb.metadata.ipub.bibstyle }} + {%- endif %} + {%- endif %} + {% if 'application/vnd.jupyter.widget-state+json' in nb.metadata.widgets %} + + .. raw:: html + + + {% endif %} + + "jinja_macros": | + + {# tell the output directive about any empty lines before/after text #} + {% macro insert_empty_lines(text) %} + {%- set before, after = text | get_empty_lines %} + {%- if before %} + :empty-lines-before: {{ before }} + {%- endif %} + {%- if after %} + :empty-lines-after: {{ after }} + {%- endif %} + {%- endmacro %} + + {# initial filter of output by type #} + {% macro insert_nboutput(datatype, output, cell) -%} + {%- if datatype == 'text/plain' %} + {%- if "ipub" in cell.metadata and cell.metadata.ipub.text -%} + {{ create_nboutput(datatype, output, cell) }} + {%- endif %} + {%- elif datatype == 'text/markdown' %} + {%- if "ipub" in cell.metadata and cell.metadata.ipub.mkdown -%} + {{ create_nboutput(datatype, output, cell) }} + {%- endif %} + {%- else %} + {{ create_nboutput(datatype, output, cell) }} + {%- endif %} + {% endmacro %} + + {% macro create_nboutput(datatype, output, cell) -%} + {#- set main tags #} + {%- set ipub = cell.metadata.get('ipub', {}) %} + {% set ipubfigure = ipub.get("figure", {}) %} + {% set ipubtable = ipub.get("table", {}) %} + {% set ipubeqn = ipub.get("equation", {}) %} + {# find captions #} + {%- set ipubfigcaption = "figure" | get_caption(cell.metadata, resources) %} + {% if ipubfigcaption %} + {%- set ipubfigcaption = ipubfigcaption | ipypandoc('rst', nb.metadata, cell.metadata) %} + {% endif %} + {%- set ipubtblcaption = "table" | get_caption(cell.metadata, resources) %} + {% if ipubtblcaption %} + {%- set ipubtblcaption = ipubtblcaption | ipypandoc('rst', nb.metadata, cell.metadata) %} + {% endif %} + {# create the output directive and supply it with the type of data #} + .. nboutput:: + {%- if datatype == 'text/plain' %}{# nothing #} + {%- elif datatype == 'ansi' %} ansi + {%- else %} rst + {%- endif %} + {# add output directive options #} + {%- if output.output_type == 'execute_result' and cell.execution_count %} + :execution-count: {{ cell.execution_count }} + {%- endif %} + {%- if output != cell.outputs[-1] %} + :more-to-come: + {%- endif %} + {%- if output.name == 'stderr' %} + :class: stderr + {%- endif %} + + {# output plain text type outputs #} + {%- if datatype == 'text/plain' and ipub.get('text', False) -%} + {{ insert_empty_lines(output.data[datatype]) }} + + {{ output.data[datatype].strip('\n') | indent }} + {# output figure type outputs # TODO set width of figures from ipub metadata (need to convert fraction -> percentage) #} + {%- elif datatype in ['image/svg+xml', 'image/png', 'image/jpeg', 'application/pdf'] and ipub.get('figure', False) %} + + .. figure:: {{ output.metadata.filenames[datatype] | posix_path }} + :alt: {{ output.metadata.filenames[datatype] | posix_path | basename }} + :align: center + {%- if "width" in ipubfigure %} + {%- set width = ipubfigure.get("width") %} + :width: {{ width * 100 }}% + {%- elif datatype in output.metadata %} + {%- set width = output.metadata[datatype].width %} + {%- if width %} + :width: {{ width }} + {%- endif %} + {%- set height = output.metadata[datatype].height %} + {%- if height %} + :height: {{ height }} + {%- endif %} + {%- endif %} + {%- if ipubfigure.get("label", False) %} + :name: {{ ipubfigure["label"] }} + {%- endif %} + {%- if ipubfigcaption %} + + {{ ipubfigcaption | indent | indent }} + {%- else %} + {#- The figure must have a caption for the references to work #} + + {{ output.metadata.filenames[datatype] | posix_path | basename }} + {% endif %} + {# output markdown type outputs #} + {%- elif datatype in ['text/markdown'] and ipub.get('mkdown', False) %} + + {{ output.data['text/markdown'] | ipypandoc('rst', nb.metadata, cell.metadata) | indent }} + {#- output latex equations #} + {%- elif datatype in ['text/latex'] and ipub.get('equation', False) and output.data['text/latex'] | is_equation %} + + .. math:: + :nowrap: + {%- if ipubeqn is mapping and ipubeqn.get("label", False) %} + :label: {{ ipubeqn["label"] }} + {%- endif %} + + {{ output.data['text/latex'] | wrap_eqn(cell_meta=cell.metadata, nb_meta=nb.metadata, out="rst") | indent | indent }} + {#- output latex and html tables #} + {%- elif datatype in ['text/latex'] and ipub.get('table', False) %} + + .. table:: + {%- if ipubtblcaption %} {{ ipubtblcaption }} + {%- endif %} + {%- if ipubtable is mapping and ipubtable.get("label", False) %} + :name: {{ ipubtable["label"] }} + {%- endif %} + + {{ output.data['text/latex'] | convert_pandoc('latex', 'rst') | indent | indent }} + {%- elif datatype in ['text/html'] and ipub.get('table', False) %} + + .. table:: + {%- if ipubtblcaption %} {{ ipubtblcaption }} + {%- endif %} + {%- if ipubtable is mapping and ipubtable.get("label", False) %} + :name: {{ ipubtable["label"] }} + {%- endif %} + + {{ output.data['text/html'] | convert_pandoc('html-native_divs-native_spans', 'markdown') | convert_pandoc('markdown', 'rst') | indent | indent }} + {#- TODO need to strip any surrounding html (e.g. divs) and convert $$ :math:`` to math directive -#} + {#- the initial conversion to markdown is a quick fix to strip the divs #} + {%- elif datatype == 'text/html' %} + :class: rendered_html + + .. raw:: html + + {{ output.data['text/html'] | indent | indent }} + {%- elif datatype == 'application/javascript' %} + + .. raw:: html + +
+ + {%- elif datatype.startswith('application/vnd.jupyter') and datatype.endswith('+json') %} + + .. raw:: html + + + {%- elif datatype == 'ansi' and ipub.get('text', False) %} + + .. rst-class:: highlight + + .. raw:: html + +
+        {{ output.data[datatype] | ansi2html | indent | indent }}
+                
+ + .. raw:: latex + + {{ output.data[datatype] | escape_latex | ansi2latex | indent | indent }} + {# % + { + \\kern-\\sphinxverbatimsmallskipamount\\kern-\\baselineskip + \\kern+\\FrameHeightAdjust\\kern-\\fboxrule + \\vspace{\\nbsphinxcodecellspacing} + \\sphinxsetup{VerbatimBorderColor={named}{nbsphinx-code-border}} + {%- if output.name == 'stderr' %} + \\sphinxsetup{VerbatimColor={named}{nbsphinx-stderr}} + {%- else %} + \\sphinxsetup{VerbatimColor={named}{white}} + {%- endif %} + \\fvset{hllines={, ,}}% + \\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}] + {{ output.data[datatype] | escape_latex | ansi2latex | indent | indent }} + \\end{sphinxVerbatim} + } + % The following \\relax is needed to avoid problems with adjacent ANSI + % cells and some other stuff (e.g. bullet lists) following ANSI cells. + % See https://github.com/sphinx-doc/sphinx/issues/3594 + \\relax #} + {% else %} + + .. nbwarning:: Data type cannot be displayed: {{ datatype }} + {%- endif %} + {% endmacro %} + +# END \ 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 52d1b55..691e473 100644 --- a/ipypublish/templates/segments/sphinx_rst.yaml.j2 +++ b/ipypublish/templates/segments/sphinx_rst.yaml.j2 @@ -7,7 +7,7 @@ {%- if cell.metadata.nbsphinx != 'hidden' %} {{ super() }} {% endif %} - + "notebook_input_code": | .. nbinput:: {% if cell.metadata.magics_language -%} {{ cell.metadata.magics_language }} @@ -67,9 +67,10 @@ {{ ("#" * cell.level + cell.source) | replace('\n', ' ') | convert_pandoc("markdown", "rst") }} "notebook_input_unknown": | - unknown type {{cell.type}} + .. unknown type {{cell.type}} - + "notebook_output_all": | + {{ super() }} "new_blocks": | {% block nboutput -%} @@ -89,13 +90,10 @@ "notebook_output_error": | {{ self.nboutput() }} - "notebook_output_stream_stdout": | - {{ self.nboutput() }} - - "notebook_output_stream_stderr": | + "notebook_output_stream": | {{ self.nboutput() }} - "notebook_output_data_any": | + "notebook_output_data_all": | {{ self.nboutput() }} "jinja_macros": | diff --git a/ipypublish/templates/segments/std_rst.yaml.j2 b/ipypublish/templates/segments/std_rst.yaml.j2 index 601869b..fc58c20 100644 --- a/ipypublish/templates/segments/std_rst.yaml.j2 +++ b/ipypublish/templates/segments/std_rst.yaml.j2 @@ -34,6 +34,9 @@ "notebook_input_unknown": | unknown type {{cell.type}} + "notebook_output_all": | + {{ super() }} + "notebook_output_error": | :: @@ -42,6 +45,9 @@ "notebook_output_traceback_line": | {{ line | indent | strip_ansi }} + "notebook_output_stream": | + {{ super() }} + "notebook_output_stream_stdout": | .. parsed-literal:: diff --git a/ipypublish/tests/conftest.py b/ipypublish/tests/conftest.py index 2510867..4254e54 100644 --- a/ipypublish/tests/conftest.py +++ b/ipypublish/tests/conftest.py @@ -1,11 +1,18 @@ import os import shutil import tempfile +import logging import pytest +from nbconvert.utils.pandoc import get_pandoc_version from ipypublish.utils import pathlib from ipypublish.tests import TEST_FILES_DIR +@pytest.fixture(autouse=True) +def dont_open_webbrowser(monkeypatch): + def nullfunc(*arg, **kwrgs): + pass + monkeypatch.setattr('webbrowser.open', nullfunc) @pytest.fixture def ipynb1(): @@ -16,6 +23,31 @@ def ipynb1(): def ipynb2(): return pathlib.Path(os.path.join(TEST_FILES_DIR, 'ipynb2.ipynb')) +@pytest.fixture +def nb_markdown_cells(): + + # Table format has changed through the versions + logging.debug("pandoc version: {}".format(get_pandoc_version())) + if get_pandoc_version() < '1.18': + expected_ltx = 'latex_ipypublish_main.pandoc.1-12.tex' + expected_rst = 'sphinx_ipypublish_main.pandoc.1-12.rst' + else: + expected_ltx = 'latex_ipypublish_main.pandoc.2-2.tex' + if get_pandoc_version() < '2.6': + expected_rst = 'sphinx_ipypublish_main.pandoc.2-2.rst' + else: + expected_rst = 'sphinx_ipypublish_main.pandoc.2-6.rst' + + return { + "input_file": pathlib.Path(os.path.join( + TEST_FILES_DIR, 'nb_markdown_cells', + 'nb_markdown_cells.ipynb')), + "latex_ipypublish_main": pathlib.Path(os.path.join( + TEST_FILES_DIR, 'nb_markdown_cells', expected_ltx)), + "sphinx_ipypublish_main": pathlib.Path(os.path.join( + TEST_FILES_DIR, 'nb_markdown_cells', expected_rst)) + } + @pytest.fixture def ipynb_with_attach(): @@ -65,6 +97,16 @@ def ipynb_folder(temp_folder, ipynb1, ipynb2): @pytest.fixture def ipynb_folder_with_external(temp_folder): + # Table format has changed through the versions + logging.debug("pandoc version: {}".format(get_pandoc_version())) + if get_pandoc_version() < '1.18': + sphinx_ipypublish_all = 'sphinx_ipypublish_all.pandoc.1-12.rst' + else: + if get_pandoc_version() < '2.6': + sphinx_ipypublish_all = 'sphinx_ipypublish_all.pandoc.2-2.rst' + else: + sphinx_ipypublish_all = 'sphinx_ipypublish_all.pandoc.2-6.rst' + folder = os.path.join(temp_folder, "ipynb_with_external") os.makedirs(folder) @@ -84,10 +126,12 @@ def ipynb_folder_with_external(temp_folder): 'ipynb_with_external.html')) slides = pathlib.Path(os.path.join(TEST_FILES_DIR, 'ipynb_with_external', 'ipynb_with_external.slides.html')) - + rst = pathlib.Path(os.path.join(TEST_FILES_DIR, 'ipynb_with_external', + sphinx_ipypublish_all)) yield { "input_folder": folder, "latex_ipypublish_main": tex, "html_ipypublish_main": html, - "slides_ipypublish_main": slides + "slides_ipypublish_main": slides, + "sphinx_ipypublish_all": rst } diff --git a/ipypublish/tests/convert_notebooks.py b/ipypublish/tests/convert_notebooks.py index 03b4780..d8d08ec 100644 --- a/ipypublish/tests/convert_notebooks.py +++ b/ipypublish/tests/convert_notebooks.py @@ -3,7 +3,7 @@ import os from ipypublish.convert.config_manager import iter_all_export_paths -from ipypublish.convert.main import publish +from ipypublish.convert.main import IpyPubMain from ipypublish.tests import TEST_FILES_DIR @@ -12,9 +12,17 @@ def convert_all(inpath, outpath): for plugin_name, plugin_path in iter_all_export_paths(): out_folder = tempfile.mkdtemp() + publish = IpyPubMain( + config={ + "IpyPubMain": { + "conversion": plugin_name, + "outpath": out_folder + }}) try: - outpath, exporter = publish( - str(inpath), conversion=plugin_name, outpath=out_folder) + outdata = publish(str(inpath)) + + exporter = outdata["exporter"] + outpath = outdata["outpath"] extension = exporter.file_extension out_name = os.path.splitext( diff --git a/ipypublish/tests/test_files/.gitignore b/ipypublish/tests/test_files/.gitignore new file mode 100644 index 0000000..3f70810 --- /dev/null +++ b/ipypublish/tests/test_files/.gitignore @@ -0,0 +1 @@ +converted/ \ No newline at end of file diff --git a/ipypublish/tests/test_files/example.jpg b/ipypublish/tests/test_files/example.jpg new file mode 100644 index 0000000..1c1e83e Binary files /dev/null and b/ipypublish/tests/test_files/example.jpg differ diff --git a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.exec.tex b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.exec.tex new file mode 100644 index 0000000..70d431c --- /dev/null +++ b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.exec.tex @@ -0,0 +1,450 @@ + + +% A latex document created by ipypublish +% outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 +% with segments: +% - standard-standard_packages: with standard nbconvert packages +% - standard-standard_definitions: with standard nbconvert definitions +% - ipypublish-doc_article: with the main ipypublish article setup +% - ipypublish-front_pages: with the main ipypublish title and contents page setup +% - ipypublish-biblio_natbib: with the main ipypublish bibliography +% - ipypublish-contents_output: with the main ipypublish content +% - ipypublish-contents_framed_code: with the input code wrapped and framed +% +%%%%%%%%%%%% DOCCLASS + +\documentclass[10pt,parskip=half, +toc=sectionentrywithdots, +bibliography=totocnumbered, +captions=tableheading,numbers=noendperiod]{scrartcl} + +%%%%%%%%%%%% + +%%%%%%%%%%%% PACKAGES + +\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[table]{xcolor} % Allow colors to be defined +\usepackage{enumerate} % Needed for markdown enumerations to work +\usepackage{amsmath} % Equations +\usepackage{amssymb} % Equations +\usepackage{textcomp} % defines textquotesingle +% Hack from http://tex.stackexchange.com/a/47451/13684: +\AtBeginDocument{% + \def\PYZsq{\textquotesingle}% Upright quotes in Pygmentized code +} +\usepackage{upquote} % Upright quotes for verbatim code +\usepackage{eurosym} % defines \euro +\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 +% 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.) +\usepackage{hyperref} +\usepackage{longtable} % longtable support required by pandoc >1.10 +\usepackage{booktabs} % table support for pandoc > 1.12.2 +\usepackage[inline]{enumitem} % IRkernel/repr support (it uses the enumerate* environment) +\usepackage[normalem]{ulem} % ulem is needed to support strikethroughs (\sout) + % normalem makes italics be italics, not underlines + +\usepackage{translations} +\usepackage{microtype} % improves the spacing between words and letters +\usepackage{placeins} % placement of figures +% could use \usepackage[section]{placeins} but placing in subsection in command section +% Places the float at precisely the location in the LaTeX code (with H) +\usepackage{float} +\usepackage[colorinlistoftodos,obeyFinal,textwidth=.8in]{todonotes} % to mark to-dos +% number figures, tables and equations by section +% fix for new versions of texlive (see https://tex.stackexchange.com/a/425603/107738) +\let\counterwithout\relax +\let\counterwithin\relax +\usepackage{chngcntr} +% header/footer +\usepackage[footsepline=0.25pt]{scrlayer-scrpage} + +% bibliography formatting +\usepackage[numbers, square, super, sort&compress]{natbib} +% hyperlink doi's +\usepackage{doi} + + % define a code float + \usepackage{newfloat} % to define a new float types + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Code]{codecell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{textcell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{errorcell} + + \usepackage{listings} % a package for wrapping code in a box + \usepackage[framemethod=tikz]{mdframed} % to fram code + +%%%%%%%%%%%% + +%%%%%%%%%%%% DEFINITIONS + +% Pygments definitions + +\makeatletter +\def\PY@reset{\let\PY@it=\relax \let\PY@bf=\relax% + \let\PY@ul=\relax \let\PY@tc=\relax% + \let\PY@bc=\relax \let\PY@ff=\relax} +\def\PY@tok#1{\csname PY@tok@#1\endcsname} +\def\PY@toks#1+{\ifx\relax#1\empty\else% + \PY@tok{#1}\expandafter\PY@toks\fi} +\def\PY@do#1{\PY@bc{\PY@tc{\PY@ul{% + \PY@it{\PY@bf{\PY@ff{#1}}}}}}} +\def\PY#1#2{\PY@reset\PY@toks#1+\relax+\PY@do{#2}} + +\expandafter\def\csname PY@tok@w\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.73,0.73}{##1}}} +\expandafter\def\csname PY@tok@c\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.74,0.48,0.00}{##1}}} +\expandafter\def\csname PY@tok@k\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kt\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.69,0.00,0.25}{##1}}} +\expandafter\def\csname PY@tok@o\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@ow\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}} +\expandafter\def\csname PY@tok@nb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@nf\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@nc\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@nn\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@ne\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.82,0.25,0.23}{##1}}} +\expandafter\def\csname PY@tok@nv\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@no\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.53,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@nl\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.63,0.63,0.00}{##1}}} +\expandafter\def\csname PY@tok@ni\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.60,0.60,0.60}{##1}}} +\expandafter\def\csname PY@tok@na\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.49,0.56,0.16}{##1}}} +\expandafter\def\csname PY@tok@nt\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@nd\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}} +\expandafter\def\csname PY@tok@s\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sd\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@si\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}} +\expandafter\def\csname PY@tok@se\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.13}{##1}}} +\expandafter\def\csname PY@tok@sr\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}} +\expandafter\def\csname PY@tok@ss\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@sx\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@m\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@gh\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@gu\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.50,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@gd\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.63,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@gi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.63,0.00}{##1}}} +\expandafter\def\csname PY@tok@gr\endcsname{\def\PY@tc##1{\textcolor[rgb]{1.00,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@ge\endcsname{\let\PY@it=\textit} +\expandafter\def\csname PY@tok@gs\endcsname{\let\PY@bf=\textbf} +\expandafter\def\csname PY@tok@gp\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@go\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.53,0.53,0.53}{##1}}} +\expandafter\def\csname PY@tok@gt\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.27,0.87}{##1}}} +\expandafter\def\csname PY@tok@err\endcsname{\def\PY@bc##1{\setlength{\fboxsep}{0pt}\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}} +\expandafter\def\csname PY@tok@kc\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kd\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kn\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kr\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@bp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@fm\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@vc\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vg\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vm\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@sa\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sc\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@dl\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@s2\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sh\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@s1\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@mb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mf\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mh\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@il\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mo\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@ch\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cm\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cpf\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@c1\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cs\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} + +\def\PYZbs{\char`\\} +\def\PYZus{\char`\_} +\def\PYZob{\char`\{} +\def\PYZcb{\char`\}} +\def\PYZca{\char`\^} +\def\PYZam{\char`\&} +\def\PYZlt{\char`\<} +\def\PYZgt{\char`\>} +\def\PYZsh{\char`\#} +\def\PYZpc{\char`\%} +\def\PYZdl{\char`\$} +\def\PYZhy{\char`\-} +\def\PYZsq{\char`\'} +\def\PYZdq{\char`\"} +\def\PYZti{\char`\~} +% for compatibility with earlier versions +\def\PYZat{@} +\def\PYZlb{[} +\def\PYZrb{]} +\makeatother + +% ANSI colors +\definecolor{ansi-black}{HTML}{3E424D} +\definecolor{ansi-black-intense}{HTML}{282C36} +\definecolor{ansi-red}{HTML}{E75C58} +\definecolor{ansi-red-intense}{HTML}{B22B31} +\definecolor{ansi-green}{HTML}{00A250} +\definecolor{ansi-green-intense}{HTML}{007427} +\definecolor{ansi-yellow}{HTML}{DDB62B} +\definecolor{ansi-yellow-intense}{HTML}{B27D12} +\definecolor{ansi-blue}{HTML}{208FFB} +\definecolor{ansi-blue-intense}{HTML}{0065CA} +\definecolor{ansi-magenta}{HTML}{D160C4} +\definecolor{ansi-magenta-intense}{HTML}{A03196} +\definecolor{ansi-cyan}{HTML}{60C6C8} +\definecolor{ansi-cyan-intense}{HTML}{258F8F} +\definecolor{ansi-white}{HTML}{C5C1B4} +\definecolor{ansi-white-intense}{HTML}{A1A6B2} + +% commands and environments needed by pandoc snippets +% extracted from the output of `pandoc -s` +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} +% Add ',fontsize=\small' for more characters per line +\newenvironment{Shaded}{}{} +\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}} +\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}} +\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}} +\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}} +\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}} +\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}} +\newcommand{\RegionMarkerTok}[1]{{#1}} +\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}} +\newcommand{\NormalTok}[1]{{#1}} + +% Additional commands for more recent versions of Pandoc +\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{{#1}}} +\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{{#1}}} +\newcommand{\ImportTok}[1]{{#1}} +\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{{#1}}}} +\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{{#1}}} +\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}} +\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{{#1}}} +\newcommand{\BuiltInTok}[1]{{#1}} +\newcommand{\ExtensionTok}[1]{{#1}} +\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{{#1}}} +\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{{#1}}} +\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} + +% Define a nice break command that doesn't care if a line doesn't already +% exist. +\def\br{\hspace*{\fill} \\* } + +% Math Jax compatability definitions +\def\gt{>} +\def\lt{<} + +\setcounter{secnumdepth}{5} + +% Colors for the hyperref package +\definecolor{urlcolor}{rgb}{0,.145,.698} +\definecolor{linkcolor}{rgb}{.71,0.21,0.01} +\definecolor{citecolor}{rgb}{.12,.54,.11} + +\DeclareTranslationFallback{Author}{Author} +\DeclareTranslation{Portuges}{Author}{Autor} + +\DeclareTranslationFallback{List of Codes}{List of Codes} +\DeclareTranslation{Catalan}{List of Codes}{Llista de Codis} +\DeclareTranslation{Danish}{List of Codes}{Liste over Koder} +\DeclareTranslation{German}{List of Codes}{Liste der Codes} +\DeclareTranslation{Spanish}{List of Codes}{Lista de C\'{o}digos} +\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} + +\DeclareTranslationFallback{Supervisors}{Supervisors} +\DeclareTranslation{Catalan}{Supervisors}{Supervisors} +\DeclareTranslation{Danish}{Supervisors}{Vejledere} +\DeclareTranslation{German}{Supervisors}{Vorgesetzten} +\DeclareTranslation{Spanish}{Supervisors}{Supervisores} +\DeclareTranslation{French}{Supervisors}{Superviseurs} +\DeclareTranslation{Italian}{Supervisors}{Le autorit\`{a} di vigilanza} +\DeclareTranslation{Dutch}{Supervisors}{supervisors} +\DeclareTranslation{Portuguese}{Supervisors}{Supervisores} + +\definecolor{codegreen}{rgb}{0,0.6,0} +\definecolor{codegray}{rgb}{0.5,0.5,0.5} +\definecolor{codepurple}{rgb}{0.58,0,0.82} +\definecolor{backcolour}{rgb}{0.95,0.95,0.95} + +\lstdefinestyle{mystyle}{ + commentstyle=\color{codegreen}, + keywordstyle=\color{magenta}, + numberstyle=\tiny\color{codegray}, + stringstyle=\color{codepurple}, + basicstyle=\ttfamily, + breakatwhitespace=false, + keepspaces=true, + numbers=left, + numbersep=10pt, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + breaklines=true, + literate={\-}{}{0\discretionary{-}{}{-}}, + postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space}, +} + +\lstset{style=mystyle} + +\surroundwithmdframed[ + hidealllines=true, + backgroundcolor=backcolour, + innerleftmargin=0pt, + innerrightmargin=0pt, + innertopmargin=0pt, + innerbottommargin=0pt]{lstlisting} + +%%%%%%%%%%%% + +%%%%%%%%%%%% MARGINS + + % Used to adjust the document margins +\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 +%%%%%%%%%%%% + +%%%%%%%%%%%% COMMANDS + +% ensure new section starts on new page +\addtokomafont{section}{\clearpage} + +% Prevent overflowing lines due to hard-to-break entities +\sloppy + +% Setup hyperref package +\hypersetup{ + breaklinks=true, % so long urls are correctly broken across lines + colorlinks=true, + urlcolor=urlcolor, + linkcolor=linkcolor, + citecolor=citecolor, + } + +% ensure figures are placed within subsections +\makeatletter +\AtBeginDocument{% + \expandafter\renewcommand\expandafter\subsection\expandafter + {\expandafter\@fb@secFB\subsection}% + \newcommand\@fb@secFB{\FloatBarrier + \gdef\@fb@afterHHook{\@fb@topbarrier \gdef\@fb@afterHHook{}}}% + \g@addto@macro\@afterheading{\@fb@afterHHook}% + \gdef\@fb@afterHHook{}% +} +\makeatother + +% number figures, tables and equations by section +\counterwithout{figure}{section} +\counterwithout{table}{section} +\counterwithout{equation}{section} +\makeatletter +\@addtoreset{table}{section} +\@addtoreset{figure}{section} +\@addtoreset{equation}{section} +\makeatother +\renewcommand\thetable{\thesection.\arabic{table}} +\renewcommand\thefigure{\thesection.\arabic{figure}} +\renewcommand\theequation{\thesection.\arabic{equation}} + + % set global options for float placement + \makeatletter + \providecommand*\setfloatlocations[2]{\@namedef{fps@#1}{#2}} + \makeatother + +% align captions to left (indented) +\captionsetup{justification=raggedright, +singlelinecheck=false,format=hang,labelfont={it,bf}} + +% shift footer down so space between separation line +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.odd} +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.even} +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.oneside} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.odd} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.even} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.oneside} +\pagestyle{scrheadings} +\clearscrheadfoot{} +\ifoot{\leftmark} +\renewcommand{\sectionmark}[1]{\markleft{\thesection\ #1}} +\ofoot{\pagemark} +\cfoot{} + +%%%%%%%%%%%% + +%%%%%%%%%%%% FINAL HEADER MATERIAL + +% clereref must be loaded after anything that changes the referencing system +\usepackage{cleveref} +\creflabelformat{equation}{#2#1#3} + +% make the code float work with cleverref +\crefname{codecell}{code}{codes} +\Crefname{codecell}{code}{codes} +% make the text float work with cleverref +\crefname{textcell}{text}{texts} +\Crefname{textcell}{text}{texts} +% make the text float work with cleverref +\crefname{errorcell}{error}{errors} +\Crefname{errorcell}{error}{errors} +%%%%%%%%%%%% + +\begin{document} + + \title{Notebook} + \date{\today} + \maketitle + + \begingroup + \let\cleardoublepage\relax + \let\clearpage\relax\tableofcontents\listoffigures\listoftables\listof{codecell}{\GetTranslation{List of Codes}} + \endgroup + +\hypertarget{a-title}{% +\section{a title}\label{a-title}} + +some text + +\begin{lstlisting}[language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt] +a=1 +print(a) +\end{lstlisting} + +\begin{lstlisting}[language={},postbreak={},numbers=none,xrightmargin=7pt,belowskip=5pt,aboveskip=5pt,breakindent=0pt] +1 + +\end{lstlisting} + +\end{document} + diff --git a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.tex b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.tex index a877dae..70d431c 100644 --- a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.tex +++ b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_all.tex @@ -1,6 +1,6 @@ -% A latex document created by ipypublish +% A latex document created by ipypublish % outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 % with segments: % - standard-standard_packages: with standard nbconvert packages diff --git a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_main.tex b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_main.tex index 57fd6de..63b870b 100644 --- a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_main.tex +++ b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_main.tex @@ -1,6 +1,6 @@ -% A latex document created by ipypublish +% A latex document created by ipypublish % outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 % with segments: % - standard-standard_packages: with standard nbconvert packages diff --git a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_nocode.tex b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_nocode.tex index a18bf69..dc9b947 100644 --- a/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_nocode.tex +++ b/ipypublish/tests/test_files/ipynb1_converted/latex_ipypublish_nocode.tex @@ -1,6 +1,6 @@ -% A latex document created by ipypublish +% A latex document created by ipypublish % outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 % with segments: % - standard-standard_packages: with standard nbconvert packages diff --git a/ipypublish/tests/test_files/ipynb1_converted/latex_standard_article.tex b/ipypublish/tests/test_files/ipynb1_converted/latex_standard_article.tex index f48fd8f..346809d 100644 --- a/ipypublish/tests/test_files/ipynb1_converted/latex_standard_article.tex +++ b/ipypublish/tests/test_files/ipynb1_converted/latex_standard_article.tex @@ -1,6 +1,6 @@ -% A latex document created by ipypublish +% A latex document created by ipypublish % outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 % with segments: % - standard-standard_packages: with standard nbconvert packages diff --git a/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_all.exec.rst b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_all.exec.rst new file mode 100644 index 0000000..db3f15d --- /dev/null +++ b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_all.exec.rst @@ -0,0 +1,31 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +a title +======= + +some text + +.. nbinput:: ipython3 + :execution-count: 1 + :no-output: + + a=1 + print(a) + +.. nboutput:: ansi + + .. rst-class:: highlight + + .. raw:: html + +
+        1
+        
+ + .. raw:: latex + + 1 diff --git a/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_all.rst b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_all.rst new file mode 100644 index 0000000..c95cab0 --- /dev/null +++ b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_all.rst @@ -0,0 +1,31 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +a title +======= + +some text + +.. nbinput:: ipython3 + :execution-count: 2 + :no-output: + + a=1 + print(a) + +.. nboutput:: ansi + + .. rst-class:: highlight + + .. raw:: html + +
+        1
+        
+ + .. raw:: latex + + 1 diff --git a/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_main.rst b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_main.rst new file mode 100644 index 0000000..abae09b --- /dev/null +++ b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_main.rst @@ -0,0 +1,10 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +a title +======= + +some text diff --git a/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_nocode.rst b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_nocode.rst new file mode 100644 index 0000000..fa6e72a --- /dev/null +++ b/ipypublish/tests/test_files/ipynb1_converted/sphinx_ipypublish_nocode.rst @@ -0,0 +1,24 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +a title +======= + +some text + +.. nboutput:: ansi + + .. rst-class:: highlight + + .. raw:: html + +
+        1
+        
+ + .. raw:: latex + + 1 diff --git a/ipypublish/tests/test_files/ipynb1_converted/sphinx_nbsphinx.rst b/ipypublish/tests/test_files/ipynb1_converted/sphinx_nbsphinx.rst index 94806ad..c71a1d6 100644 --- a/ipypublish/tests/test_files/ipynb1_converted/sphinx_nbsphinx.rst +++ b/ipypublish/tests/test_files/ipynb1_converted/sphinx_nbsphinx.rst @@ -39,4 +39,3 @@ some text % cells and some other stuff (e.g. bullet lists) following ANSI cells. % See https://github.com/sphinx-doc/sphinx/issues/3594 \relax - diff --git a/ipypublish/tests/test_files/ipynb1_converted/sphinx_standard.rst b/ipypublish/tests/test_files/ipynb1_converted/sphinx_standard.rst index e3595c1..fe4ec75 100644 --- a/ipypublish/tests/test_files/ipynb1_converted/sphinx_standard.rst +++ b/ipypublish/tests/test_files/ipynb1_converted/sphinx_standard.rst @@ -17,4 +17,3 @@ some text .. parsed-literal:: 1 - diff --git a/ipypublish/tests/test_files/ipynb_with_external/example.bib b/ipypublish/tests/test_files/ipynb_with_external/example.bib index 50e638d..3f1df55 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/example.bib +++ b/ipypublish/tests/test_files/ipynb_with_external/example.bib @@ -9,10 +9,12 @@ @article{kirkeminde_thermodynamic_2012 langid = {english}, number = {1}, journaltitle = {Journal of Materials Chemistry A}, + journal = {Journal of Materials Chemistry A}, shortjournal = {J. Mater. Chem. A}, author = {Kirkeminde, Alec and Ren, Shenqiang}, urldate = {2017-06-18}, date = {2012-11-29}, + year = {2012}, pages = {49--54}, file = {Kirkeminde_Ren_2012_Thermodynamic control of iron pyrite nanocrystal synthesis with high.pdf:/Users/cjs14/Docear/projects/Corrosion/Zotero_Attachments//2012/Kirkeminde_Ren_2012_Thermodynamic control of iron pyrite nanocrystal synthesis with high.pdf:application/pdf} } @@ -27,10 +29,12 @@ @article{zelenyak_molecular_2016 langid = {english}, number = {5}, journaltitle = {High Energy Chemistry}, + journal = {High Energy Chemistry}, shortjournal = {High Energy Chem}, author = {Zelenyak, T. Yu and Kholmurodov, Kh T. and Tameev, A. R. and Vannikov, A. V. and Gladyshev, P. P.}, urldate = {2017-07-06}, date = {2016-09-01}, + year = {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/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.Rmd b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.Rmd new file mode 100644 index 0000000..8d0f065 --- /dev/null +++ b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.Rmd @@ -0,0 +1,161 @@ +--- +jupyter: + ipub: + at_notation: true + bibliography: example.bib + biboptions: + - super + - sort + bibstyle: unsrtnat + language: portuges + listcode: true + listfigures: true + listtables: true + titlepage: + author: Authors Name + email: authors@email.com + institution: + - Institution1 + - Institution2 + logo: logo_example.png + subtitle: Sub-Title + supervisors: + - First Supervisor + - Second Supervisor + tagline: A tagline for the report. + title: Main-Title + toc: + depth: 2 + use_numref: 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 +--- + +```{python init_cell=TRUE, slideshow={'slide_type': 'skip'}} +from ipypublish.scripts.ipynb_latex_setup import * +``` + +# Markdown + + +## General + + +Some markdown text. + +A list: + +- something +- something else + +A numbered list + +1. something +2. something else + +non-ascii characters TODO + + +This is a long section of text, which we only want in a document (not a presentation) +some text +some more text +some more text +some more text +some more text +some more text +some more text +some more text +some more text + + + +This is an abbreviated section of the document text, which we only want in a presentation + +- summary of document text + + +## References and Citations + + +References to \cref{fig:example}, \cref{tbl:example}, =@eqn:example_sympy and \cref{code:example_mpl}. + +A latex citation.\cite{zelenyak_molecular_2016} + +A html citation.(Kirkeminde, 2012) + + +## Todo notes + + +\todo[inline]{an inline todo} + +Some text.\todo{a todo in the margins} + + +# Text Output + +```{python ipub={'text': {'format': {'backgroundcolor': '\\color{blue!10}'}}}} +print(""" +This is some printed text, +with a nicely formatted output. +""") +``` + +# Images and Figures + +```{python ipub={'figure': {'caption': 'A nice picture.', 'label': 'fig:example', 'placement': '!bh'}}} +Image('example.jpg',height=400) +``` + +## Displaying a plot with its code + + +A matplotlib figure, with the caption set in the markdowncell above the figure. + + +The plotting code for a matplotlib figure (\cref{fig:example_mpl}). + +```{python ipub={'code': {'asfloat': True, 'caption': 'a', 'label': 'code:example_mpl', 'widefigure': False}, 'figure': {'caption': '', 'label': 'fig:example_mpl', 'widefigure': False}}} +plt.scatter(np.random.rand(10), np.random.rand(10), + label='data label') +plt.ylabel(r'a y label with latex $\alpha$') +plt.legend(); +``` + +# Tables (with pandas) + + +The plotting code for a pandas Dataframe table (\cref{tbl:example}). + +```{python ipub={'code': {'asfloat': True, 'caption': '', 'label': 'code:example_pd', 'placement': 'H', 'widefigure': False}, 'table': {'alternate': 'gray!20', 'caption': 'An example of a table created with pandas dataframe.', 'label': 'tbl:example', 'placement': 'H'}}} +df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d']) +df.a = ['$\delta$','x','y'] +df.b = ['l','m','n'] +df.set_index(['a','b']) +df.round(3) +``` + +# Equations (with ipython or sympy) + +```{python ipub={'equation': {'label': 'eqn:example_ipy'}}} +Latex('$$ a = b+c $$') +``` + +The plotting code for a sympy equation (=@eqn:example_sympy). + +```{python ipub={'code': {'asfloat': True, 'caption': '', 'label': 'code:example_sym', 'placement': 'H', 'widefigure': False}, 'equation': {'environment': 'equation', 'label': 'eqn:example_sympy'}}} +y = sym.Function('y') +n = sym.symbols(r'\alpha') +f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) +sym.rsolve(f,y(n),[1,4]) +``` diff --git a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.html b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.html index 2b678ce..a6f9421 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.html +++ b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.html @@ -1,6 +1,6 @@ - - +- ipypublish-latex_doc: caption and label elements according to ipub meta tags +--> + @@ -8462,15 +8464,15 @@ } /* Flexible box model classes */ /* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */ -/* This file is a compatability layer. It allows the usage of flexible box +/* This file is a compatability layer. It allows the usage of flexible box model layouts accross multiple browsers, including older browsers. The newest, universal implementation of the flexible box model is used when available (see -`Modern browsers` comments below). Browsers that are known to implement this +`Modern browsers` comments below). Browsers that are known to implement this new spec completely include: Firefox 28.0+ Chrome 29.0+ - Internet Explorer 11+ + Internet Explorer 11+ Opera 17.0+ Browsers not listed, including Safari, are supported via the styling under the @@ -11244,7 +11246,7 @@ background: #f7f7f7; border-top: 1px solid #cfcfcf; border-bottom: 1px solid #cfcfcf; - /* This injects handle bars (a short, wide = symbol) for + /* This injects handle bars (a short, wide = symbol) for the resize handle. */ } div#pager .ui-resizable-handle::after { @@ -11979,7 +11981,7 @@

References and Citations
-

References to fig. 1, tbl. 1, eqn. 1 and code 3.

+

References to fig. 1, tbl. 1, =@eqn:example_sympy and code 3.

A latex citation.[Zelenyak et al, 2016.]

A html citation.(Kirkeminde, 2012)

@@ -13105,7 +13107,7 @@

Displaying a plot with its code
-
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();
@@ -13134,33 +13136,33 @@ 

Displaying a plot with its code - - - @@ -13181,8 +13183,8 @@

Displaying a plot with its code - @@ -13192,32 +13194,32 @@

Displaying a plot with its code - - @@ -13237,25 +13239,25 @@

Displaying a plot with its code - @@ -13275,28 +13277,28 @@

Displaying a plot with its code - @@ -13316,26 +13318,26 @@

Displaying a plot with its code - @@ -13355,29 +13357,29 @@

Displaying a plot with its code - @@ -13393,8 +13395,8 @@

Displaying a plot with its code - @@ -13479,21 +13481,21 @@

Displaying a plot with its code - @@ -13507,277 +13509,277 @@

Displaying a plot with its code - - - - - - - - - - - @@ -13803,36 +13805,36 @@

Displaying a plot with its code - - - - - @@ -13844,27 +13846,27 @@

Displaying a plot with its code - @@ -14030,15 +14032,15 @@

Equations (with ipython or sympy) -
Code 8: The plotting code for a sympy equation (
eqn. 1). +
Code 8: The plotting code for a sympy equation (=@eqn:example_sympy).
-
f = sym.Function('f')
-y,n = sym.symbols(r'y \alpha')
+
y = sym.Function('y')
+n = sym.symbols(r'\alpha')
 f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)
 sym.rsolve(f,y(n),[1,4])
 
diff --git a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.ipynb b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.ipynb index 70ab27e..1ab70fa 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.ipynb +++ b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.ipynb @@ -110,7 +110,7 @@ } }, "source": [ - "References to \\cref{fig:example}, \\cref{tbl:example}, \\cref{eqn:example_sympy} and \\cref{code:example_mpl}.\n", + "References to \\cref{fig:example}, \\cref{tbl:example}, =@eqn:example_sympy and \\cref{code:example_mpl}.\n", "\n", "A latex citation.\\cite{zelenyak_molecular_2016}\n", "\n", @@ -1225,7 +1225,7 @@ } }, "source": [ - "The plotting code for a sympy equation (\\cref{eqn:example_sympy})." + "The plotting code for a sympy equation (=@eqn:example_sympy)." ] }, { @@ -1260,8 +1260,8 @@ } ], "source": [ - "f = sym.Function('f')\n", - "y,n = sym.symbols(r'y \\alpha')\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])" ] @@ -1297,7 +1297,11 @@ "tagline": "A tagline for the report.", "title": "Main-Title" }, - "toc": {"depth": 2} + "toc": {"depth": 2}, + "pandoc": { + "use_numref": true, + "at_notation": true + } }, "jupytext": { "metadata_filter": { diff --git a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.json b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.json new file mode 100644 index 0000000..4af5361 --- /dev/null +++ b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.json @@ -0,0 +1,1882 @@ +{ + "blocks": [ + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python init_cell=TRUE, slideshow={'slide_type': 'skip'}} from ipypublish.scripts.ipynb_latex_setup import *" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 1, + [ + "markdown", + [], + [] + ], + [ + { + "t": "Str", + "c": "Markdown" + } + ] + ] + }, + { + "t": "Header", + "c": [ + 2, + [ + "general", + [], + [] + ], + [ + { + "t": "Str", + "c": "General" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "Some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "markdown" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text." + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "A" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "list:" + } + ] + }, + { + "t": "BulletList", + "c": [ + [ + { + "t": "Plain", + "c": [ + { + "t": "Str", + "c": "something" + } + ] + } + ], + [ + { + "t": "Plain", + "c": [ + { + "t": "Str", + "c": "something" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "else" + } + ] + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "A" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "numbered" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "list" + } + ] + }, + { + "t": "OrderedList", + "c": [ + [ + 1, + { + "t": "Decimal" + }, + { + "t": "Period" + } + ], + [ + [ + { + "t": "Plain", + "c": [ + { + "t": "Str", + "c": "something" + } + ] + } + ], + [ + { + "t": "Plain", + "c": [ + { + "t": "Str", + "c": "something" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "else" + } + ] + } + ] + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "non-ascii" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "characters" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "TODO" + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "This" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "is" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "long" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "section" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "of" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text," + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "which" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "we" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "only" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "want" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "in" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "document" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "(not" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "presentation)" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + }, + { + "t": "SoftBreak" + }, + { + "t": "Str", + "c": "some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "more" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "This" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "is" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "an" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "abbreviated" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "section" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "of" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "the" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "document" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text," + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "which" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "we" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "only" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "want" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "in" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "presentation" + } + ] + }, + { + "t": "BulletList", + "c": [ + [ + { + "t": "Plain", + "c": [ + { + "t": "Str", + "c": "summary" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "of" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "document" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text" + } + ] + } + ] + ] + }, + { + "t": "Header", + "c": [ + 2, + [ + "references-and-citations", + [], + [] + ], + [ + { + "t": "Str", + "c": "References" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "and" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Citations" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "References" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "to" + }, + { + "t": "Space" + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\cref{fig:example}" + ] + }, + { + "t": "Str", + "c": "," + }, + { + "t": "Space" + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\cref{tbl:example}" + ] + }, + { + "t": "Str", + "c": "," + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "=" + }, + { + "t": "Cite", + "c": [ + [ + { + "citationSuffix": [], + "citationNoteNum": 0, + "citationMode": { + "t": "AuthorInText" + }, + "citationPrefix": [], + "citationId": "eqn:example_sympy", + "citationHash": 0 + } + ], + [ + { + "t": "Str", + "c": "@eqn:example_sympy" + } + ] + ] + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "and" + }, + { + "t": "Space" + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\cref{code:example_mpl}" + ] + }, + { + "t": "Str", + "c": "." + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "A" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "latex" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "citation." + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\cite{zelenyak_molecular_2016}" + ] + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "A" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "html" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "citation." + }, + { + "t": "RawInline", + "c": [ + "html", + "" + ] + }, + { + "t": "Str", + "c": "(Kirkeminde," + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "2012)" + }, + { + "t": "RawInline", + "c": [ + "html", + "" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 2, + [ + "todo-notes", + [], + [] + ], + [ + { + "t": "Str", + "c": "Todo" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "notes" + } + ] + ] + }, + { + "t": "RawBlock", + "c": [ + "latex", + "\\todo[inline]{an inline todo}" + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "Some" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "text." + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\todo{a todo in the margins}" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 1, + [ + "text-output", + [], + [] + ], + [ + { + "t": "Str", + "c": "Text" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Output" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python ipub={'text': {'format': {'backgroundcolor': '\\\\color{blue!10}'}}}} print(\"\"\" This is some printed text, with a nicely formatted output. \"\"\")" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 1, + [ + "images-and-figures", + [], + [] + ], + [ + { + "t": "Str", + "c": "Images" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "and" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Figures" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python ipub={'figure': {'caption': 'A nice picture.', 'label': 'fig:example', 'placement': '!bh'}}} Image('example.jpg',height=400)" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 2, + [ + "displaying-a-plot-with-its-code", + [], + [] + ], + [ + { + "t": "Str", + "c": "Displaying" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "plot" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "with" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "its" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "code" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "A" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "matplotlib" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "figure," + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "with" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "the" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "caption" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "set" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "in" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "the" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "markdowncell" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "above" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "the" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "figure." + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "The" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "plotting" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "code" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "for" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "matplotlib" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "figure" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "(" + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\cref{fig:example_mpl}" + ] + }, + { + "t": "Str", + "c": ")." + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python ipub={'code': {'asfloat': True, 'caption': 'a', 'label': 'code:example_mpl', 'widefigure': False}, 'figure': {'caption': '', 'label': 'fig:example_mpl', 'widefigure': False}}} plt.scatter(np.random.rand(10), np.random.rand(10), label='data label') plt.ylabel(r'a y label with latex $\\alpha$') plt.legend();" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 1, + [ + "tables-with-pandas", + [], + [] + ], + [ + { + "t": "Str", + "c": "Tables" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "(with" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "pandas)" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "The" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "plotting" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "code" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "for" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "pandas" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Dataframe" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "table" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "(" + }, + { + "t": "RawInline", + "c": [ + "tex", + "\\cref{tbl:example}" + ] + }, + { + "t": "Str", + "c": ")." + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python ipub={'code': {'asfloat': True, 'caption': '', 'label': 'code:example_pd', 'placement': 'H', 'widefigure': False}, 'table': {'alternate': 'gray!20', 'caption': 'An example of a table created with pandas dataframe.', 'label': 'tbl:example', 'placement': 'H'}}} df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d']) df.a = ['$\\delta$','x','y'] df.b = ['l','m','n'] df.set_index(['a','b']) df.round(3)" + ] + } + ] + }, + { + "t": "Header", + "c": [ + 1, + [ + "equations-with-ipython-or-sympy", + [], + [] + ], + [ + { + "t": "Str", + "c": "Equations" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "(with" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "ipython" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "or" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "sympy)" + } + ] + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python ipub={'equation': {'label': 'eqn:example_ipy'}}} Latex('$$ a = b+c $$')" + ] + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Str", + "c": "The" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "plotting" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "code" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "for" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "a" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "sympy" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "equation" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "(=" + }, + { + "t": "Cite", + "c": [ + [ + { + "citationSuffix": [], + "citationNoteNum": 0, + "citationMode": { + "t": "AuthorInText" + }, + "citationPrefix": [], + "citationId": "eqn:example_sympy", + "citationHash": 0 + } + ], + [ + { + "t": "Str", + "c": "@eqn:example_sympy" + } + ] + ] + }, + { + "t": "Str", + "c": ")." + } + ] + }, + { + "t": "Para", + "c": [ + { + "t": "Code", + "c": [ + [ + "", + [], + [] + ], + "{python ipub={'code': {'asfloat': True, 'caption': '', 'label': 'code:example_sym', 'placement': 'H', 'widefigure': False}, 'equation': {'environment': 'equation', 'label': 'eqn:example_sympy'}}} y = sym.Function('y') n = sym.symbols(r'\\alpha') f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) sym.rsolve(f,y(n),[1,4])" + ] + } + ] + } + ], + "pandoc-api-version": [ + 1, + 17, + 5, + 1 + ], + "meta": { + "jupyter": { + "t": "MetaMap", + "c": { + "jupytext": { + "t": "MetaMap", + "c": { + "metadata_filter": { + "t": "MetaMap", + "c": { + "notebook": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "ipub" + } + ] + } + } + }, + "text_representation": { + "t": "MetaMap", + "c": { + "format_version": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "1.0" + } + ] + }, + "jupytext_version": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "0.8.6" + } + ] + }, + "extension": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": ".Rmd" + } + ] + }, + "format_name": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "rmarkdown" + } + ] + } + } + } + } + }, + "kernelspec": { + "t": "MetaMap", + "c": { + "display_name": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Python" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "3" + } + ] + }, + "name": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "python3" + } + ] + }, + "language": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "python" + } + ] + } + } + }, + "ipub": { + "t": "MetaMap", + "c": { + "at_notation": { + "t": "MetaBool", + "c": true + }, + "toc": { + "t": "MetaMap", + "c": { + "depth": { + "t": "MetaString", + "c": "2" + } + } + }, + "listfigures": { + "t": "MetaBool", + "c": true + }, + "use_numref": { + "t": "MetaBool", + "c": true + }, + "bibstyle": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "unsrtnat" + } + ] + }, + "bibliography": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "example.bib" + } + ] + }, + "biboptions": { + "t": "MetaList", + "c": [ + { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "super" + } + ] + }, + { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "sort" + } + ] + } + ] + }, + "listcode": { + "t": "MetaBool", + "c": true + }, + "listtables": { + "t": "MetaBool", + "c": true + }, + "language": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "portuges" + } + ] + }, + "titlepage": { + "t": "MetaMap", + "c": { + "institution": { + "t": "MetaList", + "c": [ + { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Institution1" + } + ] + }, + { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Institution2" + } + ] + } + ] + }, + "email": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "authors@email.com" + } + ] + }, + "supervisors": { + "t": "MetaList", + "c": [ + { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "First" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Supervisor" + } + ] + }, + { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Second" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Supervisor" + } + ] + } + ] + }, + "subtitle": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Sub-Title" + } + ] + }, + "tagline": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "A" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "tagline" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "for" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "the" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "report." + } + ] + }, + "author": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Authors" + }, + { + "t": "Space" + }, + { + "t": "Str", + "c": "Name" + } + ] + }, + "title": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "Main-Title" + } + ] + }, + "logo": { + "t": "MetaInlines", + "c": [ + { + "t": "Str", + "c": "logo_example.png" + } + ] + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.slides.html b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.slides.html index b8765eb..60178b4 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.slides.html +++ b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.slides.html @@ -1,14 +1,16 @@ - - +- ipypublish-latex_doc: caption and label elements according to ipub meta tags +- ipypublish-slides_ipypublish: marks up html with slide tags, based on metadata +--> + @@ -8515,15 +8517,15 @@ } /* Flexible box model classes */ /* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */ -/* This file is a compatability layer. It allows the usage of flexible box +/* This file is a compatability layer. It allows the usage of flexible box model layouts accross multiple browsers, including older browsers. The newest, universal implementation of the flexible box model is used when available (see -`Modern browsers` comments below). Browsers that are known to implement this +`Modern browsers` comments below). Browsers that are known to implement this new spec completely include: Firefox 28.0+ Chrome 29.0+ - Internet Explorer 11+ + Internet Explorer 11+ Opera 17.0+ Browsers not listed, including Safari, are supported via the styling under the @@ -11297,7 +11299,7 @@ background: #f7f7f7; border-top: 1px solid #cfcfcf; border-bottom: 1px solid #cfcfcf; - /* This injects handle bars (a short, wide = symbol) for + /* This injects handle bars (a short, wide = symbol) for the resize handle. */ } div#pager .ui-resizable-handle::after { @@ -11951,7 +11953,7 @@

1.2. References and Citations
-

References to fig. 1, tbl. 1, eqn. 1 and code 3.

+

References to fig. 1, tbl. 1, =@eqn:example_sympy and code 3.

A latex citation.[Zelenyak et al, 2016.]

A html citation.(Kirkeminde, 2012)

@@ -13095,7 +13097,7 @@

3.1. Displaying a plot with its co
-
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();
@@ -13124,33 +13126,33 @@ 

3.1. Displaying a plot with its co - - - @@ -13171,8 +13173,8 @@

3.1. Displaying a plot with its co - @@ -13182,32 +13184,32 @@

3.1. Displaying a plot with its co - - @@ -13227,25 +13229,25 @@

3.1. Displaying a plot with its co - @@ -13265,28 +13267,28 @@

3.1. Displaying a plot with its co - @@ -13306,26 +13308,26 @@

3.1. Displaying a plot with its co - @@ -13345,29 +13347,29 @@

3.1. Displaying a plot with its co - @@ -13383,8 +13385,8 @@

3.1. Displaying a plot with its co - @@ -13469,21 +13471,21 @@

3.1. Displaying a plot with its co - @@ -13497,277 +13499,277 @@

3.1. Displaying a plot with its co - - - - - - - - - - - @@ -13793,36 +13795,36 @@

3.1. Displaying a plot with its co - - - - - @@ -13834,27 +13836,27 @@

3.1. Displaying a plot with its co - @@ -14032,15 +14034,15 @@

5. Equations (with ipython or symp

-
Code 8: The plotting code for a sympy equation (eqn. 1). +
Code 8: The plotting code for a sympy equation (=@eqn:example_sympy).
-
f = sym.Function('f')
-y,n = sym.symbols(r'y \alpha')
+
y = sym.Function('y')
+n = sym.symbols(r'\alpha')
 f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)
 sym.rsolve(f,y(n),[1,4])
 
diff --git a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.tex b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.tex index 839bafa..afde46f 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.tex +++ b/ipypublish/tests/test_files/ipynb_with_external/ipynb_with_external.tex @@ -1,6 +1,6 @@ -% A latex document created by ipypublish +% A latex document created by ipypublish % outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 % with segments: % - standard-standard_packages: with standard nbconvert packages @@ -425,6 +425,7 @@ % make the text float work with cleverref \crefname{errorcell}{error}{errors} \Crefname{errorcell}{error}{errors} + %%%%%%%%%%%% \begin{document} @@ -524,7 +525,7 @@ \subsection{General}\label{general}} \subsection{References and Citations}\label{references-and-citations}} References to \cref{fig:example}, \cref{tbl:example}, -\cref{eqn:example_sympy} and \cref{code:example_mpl}. +\eqref{eqn:example_sympy} and \cref{code:example_mpl}. A latex citation.\cite{zelenyak_molecular_2016} @@ -558,23 +559,26 @@ \subsection{Displaying a plot with its code}\label{displaying-a-plot-with-its-code}} \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), +\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), label='data label') plt.ylabel(r'a y label with latex $\alpha$') plt.legend(); \end{lstlisting}\end{codecell} -\begin{figure}\begin{center}\adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{ipynb_with_external_files/output_17_0.pdf}\end{center}\caption{A matplotlib figure, with the caption set in the markdowncell above the figure.}\label{fig:example_mpl} +\begin{figure}\begin{center}\adjustimage{max size={0.9\linewidth}{0.9\paperheight}}{ipynb_with_external_files/output_17_0.pdf}\end{center}\caption{A matplotlib figure, with the caption set in the markdowncell above the +figure.}\label{fig:example_mpl} \end{figure} \hypertarget{tables-with-pandas}{% \section{Tables (with pandas)}\label{tables-with-pandas}} \begin{codecell}[H] - - \caption{The plotting code for a pandas Dataframe table (\cref{tbl:example}).}\label{code:example_pd}\begin{lstlisting}[language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt] +\caption{The plotting code for a pandas Dataframe table (\cref{tbl:example}).} +\label{code:example_pd} +\begin{lstlisting}[language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt] df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d']) df.a = ['$\delta$','x','y'] df.b = ['l','m','n'] @@ -582,8 +586,8 @@ \section{Tables (with pandas)}\label{tables-with-pandas}} df.round(3) \end{lstlisting}\end{codecell} - \begin{table}[H]\caption{An example of a table created with pandas dataframe.}\label{tbl:example} - +\begin{table}[H] +\caption{An example of a table created with pandas dataframe.}\label{tbl:example} \centering \begin{adjustbox}{max width=\textwidth}\rowcolors{2}{gray!20}{white} \begin{tabular}{lllrr} @@ -606,10 +610,11 @@ \section{Equations (with ipython or $$ a = b+c $$ \begin{codecell}[H] - - \caption{The plotting code for a sympy equation (\cref{eqn:example_sympy}).}\label{code:example_sym}\begin{lstlisting}[language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt] -f = sym.Function('f') -y,n = sym.symbols(r'y \alpha') +\caption{The plotting code for a sympy equation (\eqref{eqn:example_sympy}).} +\label{code:example_sym} +\begin{lstlisting}[language=Python,numbers=left,xleftmargin=20pt,xrightmargin=5pt,belowskip=5pt,aboveskip=5pt] +y = sym.Function('y') +n = sym.symbols(r'\alpha') f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) sym.rsolve(f,y(n),[1,4]) \end{lstlisting}\end{codecell} @@ -622,4 +627,3 @@ \section{Equations (with ipython or \bibliography{ipynb_with_external_files/example} \end{document} - diff --git a/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.1-12.rst b/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.1-12.rst new file mode 100644 index 0000000..72b151e --- /dev/null +++ b/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.1-12.rst @@ -0,0 +1,194 @@ + +.. 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 + :no-output: + + from ipypublish.scripts.ipynb_latex_setup import * + +Markdown +======== + +General +------- + +Some markdown text. + +A list: + +- something +- something else + +A numbered list + +1. something +2. something else + +non-ascii characters TODO + +This is a long section of text, which we only want in a document (not a +presentation) some text some more text some more text some more text +some more text some more text some more text some more text some more +text + +References and Citations +------------------------ + +References to :numref:`fig:example`, :numref:`tbl:example`, +:eq:`eqn:example_sympy` and :numref:`code:example_mpl`. + +A latex citation. :cite:`zelenyak_molecular_2016` + +A html citation. :cite:`kirkeminde_thermodynamic_2012` + +Todo notes +---------- + +.. todo:: an inline todo + +Some text. + +.. todo:: a todo in the margins + +Text Output +=========== + +.. nbinput:: ipython3 + :execution-count: 3 + :no-output: + + print(""" + This is some printed text, + with a nicely formatted output. + """) + +.. nboutput:: ansi + + .. rst-class:: highlight + + .. raw:: html + +
+
+        This is some printed text,
+        with a nicely formatted output.
+
+        
+ + .. raw:: latex + + This is some printed text, + with a nicely formatted output. + +Images and Figures +================== + +.. nbinput:: ipython3 + :execution-count: 3 + :no-output: + + Image('example.jpg',height=400) + +.. nboutput:: rst + + .. figure:: ipynb_with_external_files/output_13_0.jpeg + :alt: output_13_0 + :align: center + :height: 400 + :name: fig:example + + A nice picture. + +Displaying a plot with its code +------------------------------- + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + :name: code:example_mpl + :caption: The plotting code for a matplotlib figure (:numref:`fig:example_mpl`). + + plt.scatter(np.random.rand(10), np.random.rand(10), + label='data label') + plt.ylabel(r'a y label with latex $\alpha$') + plt.legend(); + +.. nboutput:: rst + + .. figure:: ipynb_with_external_files/output_17_0.svg + :alt: output_17_0 + :align: center + :name: fig:example_mpl + + A matplotlib figure, with the caption set in the markdowncell above the + figure. + +Tables (with pandas) +==================== + +.. nbinput:: ipython3 + :execution-count: 8 + :no-output: + :name: code:example_pd + :caption: The plotting code for a pandas Dataframe table (:numref:`tbl:example`). + + df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d']) + df.a = ['$\delta$','x','y'] + df.b = ['l','m','n'] + df.set_index(['a','b']) + df.round(3) + +.. nboutput:: rst + + .. table:: An example of a table created with pandas dataframe. + :name: tbl:example + + | lllrr & a & b & c & d + | 0 & :math:`\delta` & l & 0.583 & 0.279 + | 1 & x & m & 0.914 & 0.021 + | 2 & y & n & 0.333 & 0.116 + +Equations (with ipython or sympy) +================================= + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + + Latex('$$ a = b+c $$') + +.. nboutput:: rst + + .. math:: + :nowrap: + :label: eqn:example_ipy + + \begin{equation} + a = b+c + \end{equation} + +.. nbinput:: ipython3 + :execution-count: 10 + :no-output: + :name: code:example_sym + :caption: The plotting code for a sympy equation (:eq:`eqn:example_sympy`). + + y = sym.Function('y') + n = sym.symbols(r'\alpha') + f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) + sym.rsolve(f,y(n),[1,4]) + +.. nboutput:: rst + + .. math:: + :nowrap: + :label: eqn:example_sympy + + \begin{equation} + \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} + +.. bibliography:: ipynb_with_external_files/example.bib diff --git a/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.2-2.rst b/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.2-2.rst new file mode 100644 index 0000000..e66dc44 --- /dev/null +++ b/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.2-2.rst @@ -0,0 +1,199 @@ + +.. 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 + :no-output: + + from ipypublish.scripts.ipynb_latex_setup import * + +Markdown +======== + +General +------- + +Some markdown text. + +A list: + +- something +- something else + +A numbered list + +1. something +2. something else + +non-ascii characters TODO + +This is a long section of text, which we only want in a document (not a +presentation) some text some more text some more text some more text +some more text some more text some more text some more text some more +text + +References and Citations +------------------------ + +References to :numref:`fig:example`, :numref:`tbl:example`, +:eq:`eqn:example_sympy` and :numref:`code:example_mpl`. + +A latex citation. :cite:`zelenyak_molecular_2016` + +A html citation. :cite:`kirkeminde_thermodynamic_2012` + +Todo notes +---------- + +.. todo:: an inline todo + +Some text. + +.. todo:: a todo in the margins + +Text Output +=========== + +.. nbinput:: ipython3 + :execution-count: 3 + :no-output: + + print(""" + This is some printed text, + with a nicely formatted output. + """) + +.. nboutput:: ansi + + .. rst-class:: highlight + + .. raw:: html + +
+
+        This is some printed text,
+        with a nicely formatted output.
+
+        
+ + .. raw:: latex + + This is some printed text, + with a nicely formatted output. + +Images and Figures +================== + +.. nbinput:: ipython3 + :execution-count: 3 + :no-output: + + Image('example.jpg',height=400) + +.. nboutput:: rst + + .. figure:: ipynb_with_external_files/output_13_0.jpeg + :alt: output_13_0 + :align: center + :height: 400 + :name: fig:example + + A nice picture. + +Displaying a plot with its code +------------------------------- + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + :name: code:example_mpl + :caption: The plotting code for a matplotlib figure (:numref:`fig:example_mpl`). + + plt.scatter(np.random.rand(10), np.random.rand(10), + label='data label') + plt.ylabel(r'a y label with latex $\alpha$') + plt.legend(); + +.. nboutput:: rst + + .. figure:: ipynb_with_external_files/output_17_0.svg + :alt: output_17_0 + :align: center + :name: fig:example_mpl + + A matplotlib figure, with the caption set in the markdowncell above the + figure. + +Tables (with pandas) +==================== + +.. nbinput:: ipython3 + :execution-count: 8 + :no-output: + :name: code:example_pd + :caption: The plotting code for a pandas Dataframe table (:numref:`tbl:example`). + + df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d']) + df.a = ['$\delta$','x','y'] + df.b = ['l','m','n'] + df.set_index(['a','b']) + df.round(3) + +.. nboutput:: rst + + .. table:: An example of a table created with pandas dataframe. + :name: tbl:example + + +---+----------------+---+-------+-------+ + | | a | b | c | d | + +===+================+===+=======+=======+ + | 0 | :math:`\delta` | l | 0.583 | 0.279 | + +---+----------------+---+-------+-------+ + | 1 | x | m | 0.914 | 0.021 | + +---+----------------+---+-------+-------+ + | 2 | y | n | 0.333 | 0.116 | + +---+----------------+---+-------+-------+ + +Equations (with ipython or sympy) +================================= + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + + Latex('$$ a = b+c $$') + +.. nboutput:: rst + + .. math:: + :nowrap: + :label: eqn:example_ipy + + \begin{equation} + a = b+c + \end{equation} + +.. nbinput:: ipython3 + :execution-count: 10 + :no-output: + :name: code:example_sym + :caption: The plotting code for a sympy equation (:eq:`eqn:example_sympy`). + + y = sym.Function('y') + n = sym.symbols(r'\alpha') + f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) + sym.rsolve(f,y(n),[1,4]) + +.. nboutput:: rst + + .. math:: + :nowrap: + :label: eqn:example_sympy + + \begin{equation} + \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} + +.. bibliography:: ipynb_with_external_files/example.bib diff --git a/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.2-6.rst b/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.2-6.rst new file mode 100644 index 0000000..584cbc1 --- /dev/null +++ b/ipypublish/tests/test_files/ipynb_with_external/sphinx_ipypublish_all.pandoc.2-6.rst @@ -0,0 +1,197 @@ + +.. 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 + :no-output: + + from ipypublish.scripts.ipynb_latex_setup import * + +Markdown +======== + +General +------- + +Some markdown text. + +A list: + +- something +- something else + +A numbered list + +1. something +2. something else + +non-ascii characters TODO + +This is a long section of text, which we only want in a document (not a +presentation) some text some more text some more text some more text +some more text some more text some more text some more text some more +text + +References and Citations +------------------------ + +References to :numref:`fig:example`, :numref:`tbl:example`, +:eq:`eqn:example_sympy` and :numref:`code:example_mpl`. + +A latex citation. :cite:`zelenyak_molecular_2016` + +A html citation. :cite:`kirkeminde_thermodynamic_2012` + +Todo notes +---------- + +.. todo:: an inline todo + +Some text. + +.. todo:: a todo in the margins + +Text Output +=========== + +.. nbinput:: ipython3 + :execution-count: 3 + :no-output: + + print(""" + This is some printed text, + with a nicely formatted output. + """) + +.. nboutput:: ansi + + .. rst-class:: highlight + + .. raw:: html + +
+
+        This is some printed text,
+        with a nicely formatted output.
+
+        
+ + .. raw:: latex + + This is some printed text, + with a nicely formatted output. + +Images and Figures +================== + +.. nbinput:: ipython3 + :execution-count: 3 + :no-output: + + Image('example.jpg',height=400) + +.. nboutput:: rst + + .. figure:: ipynb_with_external_files/output_13_0.jpeg + :alt: output_13_0 + :align: center + :height: 400 + :name: fig:example + + A nice picture. + +Displaying a plot with its code +------------------------------- + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + :name: code:example_mpl + :caption: The plotting code for a matplotlib figure (:numref:`fig:example_mpl`). + + plt.scatter(np.random.rand(10), np.random.rand(10), + label='data label') + plt.ylabel(r'a y label with latex $\alpha$') + plt.legend(); + +.. nboutput:: rst + + .. figure:: ipynb_with_external_files/output_17_0.svg + :alt: output_17_0 + :align: center + :name: fig:example_mpl + + A matplotlib figure, with the caption set in the markdowncell above the + figure. + +Tables (with pandas) +==================== + +.. nbinput:: ipython3 + :execution-count: 8 + :no-output: + :name: code:example_pd + :caption: The plotting code for a pandas Dataframe table (:numref:`tbl:example`). + + df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d']) + df.a = ['$\delta$','x','y'] + df.b = ['l','m','n'] + df.set_index(['a','b']) + df.round(3) + +.. nboutput:: rst + + .. table:: An example of a table created with pandas dataframe. + :name: tbl:example + + == ============== = ===== ===== + \ a b c d + == ============== = ===== ===== + 0 :math:`\delta` l 0.583 0.279 + 1 x m 0.914 0.021 + 2 y n 0.333 0.116 + == ============== = ===== ===== + +Equations (with ipython or sympy) +================================= + +.. nbinput:: ipython3 + :execution-count: 9 + :no-output: + + Latex('$$ a = b+c $$') + +.. nboutput:: rst + + .. math:: + :nowrap: + :label: eqn:example_ipy + + \begin{equation} + a = b+c + \end{equation} + +.. nbinput:: ipython3 + :execution-count: 10 + :no-output: + :name: code:example_sym + :caption: The plotting code for a sympy equation (:eq:`eqn:example_sympy`). + + y = sym.Function('y') + n = sym.symbols(r'\alpha') + f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) + sym.rsolve(f,y(n),[1,4]) + +.. nboutput:: rst + + .. math:: + :nowrap: + :label: eqn:example_sympy + + \begin{equation} + \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} + +.. bibliography:: ipynb_with_external_files/example.bib diff --git a/ipypublish/tests/test_files/ipynb_with_external/sphinx_nbsphinx.rst b/ipypublish/tests/test_files/ipynb_with_external/sphinx_nbsphinx.rst index dbf5997..598bdd7 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/sphinx_nbsphinx.rst +++ b/ipypublish/tests/test_files/ipynb_with_external/sphinx_nbsphinx.rst @@ -248,8 +248,8 @@ The plotting code for a sympy equation (:nbsphinx-math:`\cref{eqn:example_sympy} .. nbinput:: ipython3 :execution-count: 10 - f = sym.Function('f') - y,n = sym.symbols(r'y \alpha') + y = sym.Function('y') + n = sym.symbols(r'\alpha') f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) sym.rsolve(f,y(n),[1,4]) diff --git a/ipypublish/tests/test_files/ipynb_with_external/sphinx_standard.rst b/ipypublish/tests/test_files/ipynb_with_external/sphinx_standard.rst index cb011db..e4392ef 100644 --- a/ipypublish/tests/test_files/ipynb_with_external/sphinx_standard.rst +++ b/ipypublish/tests/test_files/ipynb_with_external/sphinx_standard.rst @@ -181,8 +181,8 @@ The plotting code for a sympy equation .. code:: ipython3 - f = sym.Function('f') - y,n = sym.symbols(r'y \alpha') + y = sym.Function('y') + n = sym.symbols(r'\alpha') f = y(n)-2*y(n-1/sym.pi)-5*y(n-2) sym.rsolve(f,y(n),[1,4]) diff --git a/ipypublish/tests/test_files/nb_markdown_cells/__init__.py b/ipypublish/tests/test_files/nb_markdown_cells/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ipypublish/tests/test_files/nb_markdown_cells/latex_ipypublish_main.pandoc.1-12.tex b/ipypublish/tests/test_files/nb_markdown_cells/latex_ipypublish_main.pandoc.1-12.tex new file mode 100644 index 0000000..0899a4d --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/latex_ipypublish_main.pandoc.1-12.tex @@ -0,0 +1,508 @@ + + +% A latex document created by ipypublish +% outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 +% with segments: +% - standard-standard_packages: with standard nbconvert packages +% - standard-standard_definitions: with standard nbconvert definitions +% - ipypublish-doc_article: with the main ipypublish article setup +% - ipypublish-front_pages: with the main ipypublish title and contents page setup +% - ipypublish-biblio_natbib: with the main ipypublish bibliography +% - ipypublish-contents_output: with the main ipypublish content +% - ipypublish-contents_framed_code: with the input code wrapped and framed +% +%%%%%%%%%%%% DOCCLASS + +\documentclass[10pt,parskip=half, +toc=sectionentrywithdots, +bibliography=totocnumbered, +captions=tableheading,numbers=noendperiod]{scrartcl} + +%%%%%%%%%%%% + +%%%%%%%%%%%% PACKAGES + +\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[table]{xcolor} % Allow colors to be defined +\usepackage{enumerate} % Needed for markdown enumerations to work +\usepackage{amsmath} % Equations +\usepackage{amssymb} % Equations +\usepackage{textcomp} % defines textquotesingle +% Hack from http://tex.stackexchange.com/a/47451/13684: +\AtBeginDocument{% + \def\PYZsq{\textquotesingle}% Upright quotes in Pygmentized code +} +\usepackage{upquote} % Upright quotes for verbatim code +\usepackage{eurosym} % defines \euro +\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 +% 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.) +\usepackage{hyperref} +\usepackage{longtable} % longtable support required by pandoc >1.10 +\usepackage{booktabs} % table support for pandoc > 1.12.2 +\usepackage[inline]{enumitem} % IRkernel/repr support (it uses the enumerate* environment) +\usepackage[normalem]{ulem} % ulem is needed to support strikethroughs (\sout) + % normalem makes italics be italics, not underlines + +\usepackage{translations} +\usepackage{microtype} % improves the spacing between words and letters +\usepackage{placeins} % placement of figures +% could use \usepackage[section]{placeins} but placing in subsection in command section +% Places the float at precisely the location in the LaTeX code (with H) +\usepackage{float} +\usepackage[colorinlistoftodos,obeyFinal,textwidth=.8in]{todonotes} % to mark to-dos +% number figures, tables and equations by section +% fix for new versions of texlive (see https://tex.stackexchange.com/a/425603/107738) +\let\counterwithout\relax +\let\counterwithin\relax +\usepackage{chngcntr} +% header/footer +\usepackage[footsepline=0.25pt]{scrlayer-scrpage} + +% bibliography formatting +\usepackage[numbers, square, super, sort&compress]{natbib} +% hyperlink doi's +\usepackage{doi} + + % define a code float + \usepackage{newfloat} % to define a new float types + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Code]{codecell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{textcell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{errorcell} + + \usepackage{listings} % a package for wrapping code in a box + \usepackage[framemethod=tikz]{mdframed} % to fram code + +%%%%%%%%%%%% + +%%%%%%%%%%%% DEFINITIONS + +% Pygments definitions + +\makeatletter +\def\PY@reset{\let\PY@it=\relax \let\PY@bf=\relax% + \let\PY@ul=\relax \let\PY@tc=\relax% + \let\PY@bc=\relax \let\PY@ff=\relax} +\def\PY@tok#1{\csname PY@tok@#1\endcsname} +\def\PY@toks#1+{\ifx\relax#1\empty\else% + \PY@tok{#1}\expandafter\PY@toks\fi} +\def\PY@do#1{\PY@bc{\PY@tc{\PY@ul{% + \PY@it{\PY@bf{\PY@ff{#1}}}}}}} +\def\PY#1#2{\PY@reset\PY@toks#1+\relax+\PY@do{#2}} + +\expandafter\def\csname PY@tok@w\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.73,0.73}{##1}}} +\expandafter\def\csname PY@tok@c\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.74,0.48,0.00}{##1}}} +\expandafter\def\csname PY@tok@k\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kt\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.69,0.00,0.25}{##1}}} +\expandafter\def\csname PY@tok@o\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@ow\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}} +\expandafter\def\csname PY@tok@nb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@nf\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@nc\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@nn\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@ne\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.82,0.25,0.23}{##1}}} +\expandafter\def\csname PY@tok@nv\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@no\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.53,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@nl\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.63,0.63,0.00}{##1}}} +\expandafter\def\csname PY@tok@ni\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.60,0.60,0.60}{##1}}} +\expandafter\def\csname PY@tok@na\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.49,0.56,0.16}{##1}}} +\expandafter\def\csname PY@tok@nt\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@nd\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}} +\expandafter\def\csname PY@tok@s\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sd\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@si\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}} +\expandafter\def\csname PY@tok@se\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.13}{##1}}} +\expandafter\def\csname PY@tok@sr\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}} +\expandafter\def\csname PY@tok@ss\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@sx\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@m\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@gh\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@gu\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.50,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@gd\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.63,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@gi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.63,0.00}{##1}}} +\expandafter\def\csname PY@tok@gr\endcsname{\def\PY@tc##1{\textcolor[rgb]{1.00,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@ge\endcsname{\let\PY@it=\textit} +\expandafter\def\csname PY@tok@gs\endcsname{\let\PY@bf=\textbf} +\expandafter\def\csname PY@tok@gp\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@go\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.53,0.53,0.53}{##1}}} +\expandafter\def\csname PY@tok@gt\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.27,0.87}{##1}}} +\expandafter\def\csname PY@tok@err\endcsname{\def\PY@bc##1{\setlength{\fboxsep}{0pt}\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}} +\expandafter\def\csname PY@tok@kc\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kd\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kn\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kr\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@bp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@fm\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@vc\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vg\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vm\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@sa\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sc\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@dl\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@s2\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sh\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@s1\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@mb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mf\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mh\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@il\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mo\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@ch\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cm\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cpf\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@c1\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cs\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} + +\def\PYZbs{\char`\\} +\def\PYZus{\char`\_} +\def\PYZob{\char`\{} +\def\PYZcb{\char`\}} +\def\PYZca{\char`\^} +\def\PYZam{\char`\&} +\def\PYZlt{\char`\<} +\def\PYZgt{\char`\>} +\def\PYZsh{\char`\#} +\def\PYZpc{\char`\%} +\def\PYZdl{\char`\$} +\def\PYZhy{\char`\-} +\def\PYZsq{\char`\'} +\def\PYZdq{\char`\"} +\def\PYZti{\char`\~} +% for compatibility with earlier versions +\def\PYZat{@} +\def\PYZlb{[} +\def\PYZrb{]} +\makeatother + +% ANSI colors +\definecolor{ansi-black}{HTML}{3E424D} +\definecolor{ansi-black-intense}{HTML}{282C36} +\definecolor{ansi-red}{HTML}{E75C58} +\definecolor{ansi-red-intense}{HTML}{B22B31} +\definecolor{ansi-green}{HTML}{00A250} +\definecolor{ansi-green-intense}{HTML}{007427} +\definecolor{ansi-yellow}{HTML}{DDB62B} +\definecolor{ansi-yellow-intense}{HTML}{B27D12} +\definecolor{ansi-blue}{HTML}{208FFB} +\definecolor{ansi-blue-intense}{HTML}{0065CA} +\definecolor{ansi-magenta}{HTML}{D160C4} +\definecolor{ansi-magenta-intense}{HTML}{A03196} +\definecolor{ansi-cyan}{HTML}{60C6C8} +\definecolor{ansi-cyan-intense}{HTML}{258F8F} +\definecolor{ansi-white}{HTML}{C5C1B4} +\definecolor{ansi-white-intense}{HTML}{A1A6B2} + +% commands and environments needed by pandoc snippets +% extracted from the output of `pandoc -s` +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} +% Add ',fontsize=\small' for more characters per line +\newenvironment{Shaded}{}{} +\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}} +\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}} +\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}} +\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}} +\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}} +\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}} +\newcommand{\RegionMarkerTok}[1]{{#1}} +\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}} +\newcommand{\NormalTok}[1]{{#1}} + +% Additional commands for more recent versions of Pandoc +\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{{#1}}} +\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{{#1}}} +\newcommand{\ImportTok}[1]{{#1}} +\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{{#1}}}} +\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{{#1}}} +\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}} +\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{{#1}}} +\newcommand{\BuiltInTok}[1]{{#1}} +\newcommand{\ExtensionTok}[1]{{#1}} +\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{{#1}}} +\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{{#1}}} +\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} + +% Define a nice break command that doesn't care if a line doesn't already +% exist. +\def\br{\hspace*{\fill} \\* } + +% Math Jax compatability definitions +\def\gt{>} +\def\lt{<} + +\setcounter{secnumdepth}{5} + +% Colors for the hyperref package +\definecolor{urlcolor}{rgb}{0,.145,.698} +\definecolor{linkcolor}{rgb}{.71,0.21,0.01} +\definecolor{citecolor}{rgb}{.12,.54,.11} + +\DeclareTranslationFallback{Author}{Author} +\DeclareTranslation{Portuges}{Author}{Autor} + +\DeclareTranslationFallback{List of Codes}{List of Codes} +\DeclareTranslation{Catalan}{List of Codes}{Llista de Codis} +\DeclareTranslation{Danish}{List of Codes}{Liste over Koder} +\DeclareTranslation{German}{List of Codes}{Liste der Codes} +\DeclareTranslation{Spanish}{List of Codes}{Lista de C\'{o}digos} +\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} + +\DeclareTranslationFallback{Supervisors}{Supervisors} +\DeclareTranslation{Catalan}{Supervisors}{Supervisors} +\DeclareTranslation{Danish}{Supervisors}{Vejledere} +\DeclareTranslation{German}{Supervisors}{Vorgesetzten} +\DeclareTranslation{Spanish}{Supervisors}{Supervisores} +\DeclareTranslation{French}{Supervisors}{Superviseurs} +\DeclareTranslation{Italian}{Supervisors}{Le autorit\`{a} di vigilanza} +\DeclareTranslation{Dutch}{Supervisors}{supervisors} +\DeclareTranslation{Portuguese}{Supervisors}{Supervisores} + +\definecolor{codegreen}{rgb}{0,0.6,0} +\definecolor{codegray}{rgb}{0.5,0.5,0.5} +\definecolor{codepurple}{rgb}{0.58,0,0.82} +\definecolor{backcolour}{rgb}{0.95,0.95,0.95} + +\lstdefinestyle{mystyle}{ + commentstyle=\color{codegreen}, + keywordstyle=\color{magenta}, + numberstyle=\tiny\color{codegray}, + stringstyle=\color{codepurple}, + basicstyle=\ttfamily, + breakatwhitespace=false, + keepspaces=true, + numbers=left, + numbersep=10pt, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + breaklines=true, + literate={\-}{}{0\discretionary{-}{}{-}}, + postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space}, +} + +\lstset{style=mystyle} + +\surroundwithmdframed[ + hidealllines=true, + backgroundcolor=backcolour, + innerleftmargin=0pt, + innerrightmargin=0pt, + innertopmargin=0pt, + innerbottommargin=0pt]{lstlisting} + +%%%%%%%%%%%% + +%%%%%%%%%%%% MARGINS + + % Used to adjust the document margins +\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 +%%%%%%%%%%%% + +%%%%%%%%%%%% COMMANDS + +% ensure new section starts on new page +\addtokomafont{section}{\clearpage} + +% Prevent overflowing lines due to hard-to-break entities +\sloppy + +% Setup hyperref package +\hypersetup{ + breaklinks=true, % so long urls are correctly broken across lines + colorlinks=true, + urlcolor=urlcolor, + linkcolor=linkcolor, + citecolor=citecolor, + } + +% ensure figures are placed within subsections +\makeatletter +\AtBeginDocument{% + \expandafter\renewcommand\expandafter\subsection\expandafter + {\expandafter\@fb@secFB\subsection}% + \newcommand\@fb@secFB{\FloatBarrier + \gdef\@fb@afterHHook{\@fb@topbarrier \gdef\@fb@afterHHook{}}}% + \g@addto@macro\@afterheading{\@fb@afterHHook}% + \gdef\@fb@afterHHook{}% +} +\makeatother + +% number figures, tables and equations by section +\counterwithout{figure}{section} +\counterwithout{table}{section} +\counterwithout{equation}{section} +\makeatletter +\@addtoreset{table}{section} +\@addtoreset{figure}{section} +\@addtoreset{equation}{section} +\makeatother +\renewcommand\thetable{\thesection.\arabic{table}} +\renewcommand\thefigure{\thesection.\arabic{figure}} +\renewcommand\theequation{\thesection.\arabic{equation}} + + % set global options for float placement + \makeatletter + \providecommand*\setfloatlocations[2]{\@namedef{fps@#1}{#2}} + \makeatother + +% align captions to left (indented) +\captionsetup{justification=raggedright, +singlelinecheck=false,format=hang,labelfont={it,bf}} + +% shift footer down so space between separation line +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.odd} +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.even} +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.oneside} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.odd} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.even} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.oneside} +\pagestyle{scrheadings} +\clearscrheadfoot{} +\ifoot{\leftmark} +\renewcommand{\sectionmark}[1]{\markleft{\thesection\ #1}} +\ofoot{\pagemark} +\cfoot{} + +%%%%%%%%%%%% + +%%%%%%%%%%%% FINAL HEADER MATERIAL + +% clereref must be loaded after anything that changes the referencing system +\usepackage{cleveref} +\creflabelformat{equation}{#2#1#3} + +% make the code float work with cleverref +\crefname{codecell}{code}{codes} +\Crefname{codecell}{code}{codes} +% make the text float work with cleverref +\crefname{textcell}{text}{texts} +\Crefname{textcell}{text}{texts} +% make the text float work with cleverref +\crefname{errorcell}{error}{errors} +\Crefname{errorcell}{error}{errors} +%%%%%%%%%%%% + +\begin{document} + + \title{Notebook} + \date{\today} + \maketitle + + \begingroup + \let\cleardoublepage\relax + \let\clearpage\relax + \endgroup + +\hypertarget{notebook-to-test-markdown-cells}{% +\section{Notebook to Test Markdown +Cells}\label{notebook-to-test-markdown-cells}} + +\hypertarget{cell-with-attached-image}{% +\subsection{Cell with Attached Image}\label{cell-with-attached-image}} + +\begin{figure}[H] +\hypertarget{fig:id1}{% +\begin{center} +\adjustimage{max size={0.9\linewidth}{0.9\paperheight},}{nb_markdown_cells_files/attach_1_output_13_0.jpeg} +\end{center} +\caption{output\_13\_0.jpeg}\label{fig:id1} +} +\end{figure} + +\cref{fig:id1} \cref{fig:id1} + +\hypertarget{cell-with-linked-image}{% +\subsection{Cell with Linked Image}\label{cell-with-linked-image}} + +\begin{figure}[] +\hypertarget{fig:id2}{% +\begin{center} +\adjustimage{max size={0.9\linewidth}{0.9\paperheight},width=0.5\linewidth}{nb_markdown_cells_files/logo_example.png} +\end{center} +\caption{this is a \textbf{caption}}\label{fig:id2} +} +\end{figure} + +\hypertarget{cell-with-link-to-header}{% +\subsection{Cell with Link to Header}\label{cell-with-link-to-header}} + +\cref{cell-with-link-to-header} + +\hypertarget{cell-with-math}{% +\subsection{Cell with Math}\label{cell-with-math}} + +inline: $a = b$ + +\begin{equation}a = b\label{eq:id1}\end{equation} +\begin{align*}c &= d \\ other &= e\label{eq:id2}\end{align*} + +\hypertarget{cell-with-table}{% +\subsection{Cell with Table}\label{cell-with-table}} + +\begin{longtable}[c]{@{}lll@{}} +\toprule\addlinespace +A & B & A and B +\\\addlinespace +\midrule\endhead +False & False & False +\\\addlinespace +True & False & False +\\\addlinespace +False & True & False +\\\addlinespace +True & True & True +\\\addlinespace +\bottomrule +\addlinespace +\caption{Caption. \label{tbl:id}} +\end{longtable} + +\cref{tbl:id} + +\hypertarget{references-using-notation}{% +\subsection{References Using @ +Notation}\label{references-using-notation}} + +\Cref{cell-with-link-to-header}, and multiple references +\cref{tbl:id,eq:id1} + +\end{document} + diff --git a/ipypublish/tests/test_files/nb_markdown_cells/latex_ipypublish_main.pandoc.2-2.tex b/ipypublish/tests/test_files/nb_markdown_cells/latex_ipypublish_main.pandoc.2-2.tex new file mode 100644 index 0000000..3b80875 --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/latex_ipypublish_main.pandoc.2-2.tex @@ -0,0 +1,507 @@ + + +% A latex document created by ipypublish +% outline: ipypublish.templates.outline_schemas/latex_outline.latex.j2 +% with segments: +% - standard-standard_packages: with standard nbconvert packages +% - standard-standard_definitions: with standard nbconvert definitions +% - ipypublish-doc_article: with the main ipypublish article setup +% - ipypublish-front_pages: with the main ipypublish title and contents page setup +% - ipypublish-biblio_natbib: with the main ipypublish bibliography +% - ipypublish-contents_output: with the main ipypublish content +% - ipypublish-contents_framed_code: with the input code wrapped and framed +% +%%%%%%%%%%%% DOCCLASS + +\documentclass[10pt,parskip=half, +toc=sectionentrywithdots, +bibliography=totocnumbered, +captions=tableheading,numbers=noendperiod]{scrartcl} + +%%%%%%%%%%%% + +%%%%%%%%%%%% PACKAGES + +\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[table]{xcolor} % Allow colors to be defined +\usepackage{enumerate} % Needed for markdown enumerations to work +\usepackage{amsmath} % Equations +\usepackage{amssymb} % Equations +\usepackage{textcomp} % defines textquotesingle +% Hack from http://tex.stackexchange.com/a/47451/13684: +\AtBeginDocument{% + \def\PYZsq{\textquotesingle}% Upright quotes in Pygmentized code +} +\usepackage{upquote} % Upright quotes for verbatim code +\usepackage{eurosym} % defines \euro +\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 +% 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.) +\usepackage{hyperref} +\usepackage{longtable} % longtable support required by pandoc >1.10 +\usepackage{booktabs} % table support for pandoc > 1.12.2 +\usepackage[inline]{enumitem} % IRkernel/repr support (it uses the enumerate* environment) +\usepackage[normalem]{ulem} % ulem is needed to support strikethroughs (\sout) + % normalem makes italics be italics, not underlines + +\usepackage{translations} +\usepackage{microtype} % improves the spacing between words and letters +\usepackage{placeins} % placement of figures +% could use \usepackage[section]{placeins} but placing in subsection in command section +% Places the float at precisely the location in the LaTeX code (with H) +\usepackage{float} +\usepackage[colorinlistoftodos,obeyFinal,textwidth=.8in]{todonotes} % to mark to-dos +% number figures, tables and equations by section +% fix for new versions of texlive (see https://tex.stackexchange.com/a/425603/107738) +\let\counterwithout\relax +\let\counterwithin\relax +\usepackage{chngcntr} +% header/footer +\usepackage[footsepline=0.25pt]{scrlayer-scrpage} + +% bibliography formatting +\usepackage[numbers, square, super, sort&compress]{natbib} +% hyperlink doi's +\usepackage{doi} + + % define a code float + \usepackage{newfloat} % to define a new float types + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Code]{codecell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{textcell} + \DeclareFloatingEnvironment[ + fileext=frm,placement={!ht}, + within=section,name=Text]{errorcell} + + \usepackage{listings} % a package for wrapping code in a box + \usepackage[framemethod=tikz]{mdframed} % to fram code + +%%%%%%%%%%%% + +%%%%%%%%%%%% DEFINITIONS + +% Pygments definitions + +\makeatletter +\def\PY@reset{\let\PY@it=\relax \let\PY@bf=\relax% + \let\PY@ul=\relax \let\PY@tc=\relax% + \let\PY@bc=\relax \let\PY@ff=\relax} +\def\PY@tok#1{\csname PY@tok@#1\endcsname} +\def\PY@toks#1+{\ifx\relax#1\empty\else% + \PY@tok{#1}\expandafter\PY@toks\fi} +\def\PY@do#1{\PY@bc{\PY@tc{\PY@ul{% + \PY@it{\PY@bf{\PY@ff{#1}}}}}}} +\def\PY#1#2{\PY@reset\PY@toks#1+\relax+\PY@do{#2}} + +\expandafter\def\csname PY@tok@w\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.73,0.73}{##1}}} +\expandafter\def\csname PY@tok@c\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.74,0.48,0.00}{##1}}} +\expandafter\def\csname PY@tok@k\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kt\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.69,0.00,0.25}{##1}}} +\expandafter\def\csname PY@tok@o\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@ow\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}} +\expandafter\def\csname PY@tok@nb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@nf\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@nc\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@nn\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@ne\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.82,0.25,0.23}{##1}}} +\expandafter\def\csname PY@tok@nv\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@no\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.53,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@nl\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.63,0.63,0.00}{##1}}} +\expandafter\def\csname PY@tok@ni\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.60,0.60,0.60}{##1}}} +\expandafter\def\csname PY@tok@na\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.49,0.56,0.16}{##1}}} +\expandafter\def\csname PY@tok@nt\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@nd\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.67,0.13,1.00}{##1}}} +\expandafter\def\csname PY@tok@s\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sd\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@si\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}} +\expandafter\def\csname PY@tok@se\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.13}{##1}}} +\expandafter\def\csname PY@tok@sr\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.40,0.53}{##1}}} +\expandafter\def\csname PY@tok@ss\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@sx\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@m\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@gh\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@gu\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.50,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@gd\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.63,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@gi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.63,0.00}{##1}}} +\expandafter\def\csname PY@tok@gr\endcsname{\def\PY@tc##1{\textcolor[rgb]{1.00,0.00,0.00}{##1}}} +\expandafter\def\csname PY@tok@ge\endcsname{\let\PY@it=\textit} +\expandafter\def\csname PY@tok@gs\endcsname{\let\PY@bf=\textbf} +\expandafter\def\csname PY@tok@gp\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,0.50}{##1}}} +\expandafter\def\csname PY@tok@go\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.53,0.53,0.53}{##1}}} +\expandafter\def\csname PY@tok@gt\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.27,0.87}{##1}}} +\expandafter\def\csname PY@tok@err\endcsname{\def\PY@bc##1{\setlength{\fboxsep}{0pt}\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{\strut ##1}}} +\expandafter\def\csname PY@tok@kc\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kd\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kn\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@kr\endcsname{\let\PY@bf=\textbf\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@bp\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.50,0.00}{##1}}} +\expandafter\def\csname PY@tok@fm\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.00,0.00,1.00}{##1}}} +\expandafter\def\csname PY@tok@vc\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vg\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@vm\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.10,0.09,0.49}{##1}}} +\expandafter\def\csname PY@tok@sa\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sc\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@dl\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@s2\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@sh\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@s1\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.73,0.13,0.13}{##1}}} +\expandafter\def\csname PY@tok@mb\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mf\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mh\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mi\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@il\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@mo\endcsname{\def\PY@tc##1{\textcolor[rgb]{0.40,0.40,0.40}{##1}}} +\expandafter\def\csname PY@tok@ch\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cm\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cpf\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@c1\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} +\expandafter\def\csname PY@tok@cs\endcsname{\let\PY@it=\textit\def\PY@tc##1{\textcolor[rgb]{0.25,0.50,0.50}{##1}}} + +\def\PYZbs{\char`\\} +\def\PYZus{\char`\_} +\def\PYZob{\char`\{} +\def\PYZcb{\char`\}} +\def\PYZca{\char`\^} +\def\PYZam{\char`\&} +\def\PYZlt{\char`\<} +\def\PYZgt{\char`\>} +\def\PYZsh{\char`\#} +\def\PYZpc{\char`\%} +\def\PYZdl{\char`\$} +\def\PYZhy{\char`\-} +\def\PYZsq{\char`\'} +\def\PYZdq{\char`\"} +\def\PYZti{\char`\~} +% for compatibility with earlier versions +\def\PYZat{@} +\def\PYZlb{[} +\def\PYZrb{]} +\makeatother + +% ANSI colors +\definecolor{ansi-black}{HTML}{3E424D} +\definecolor{ansi-black-intense}{HTML}{282C36} +\definecolor{ansi-red}{HTML}{E75C58} +\definecolor{ansi-red-intense}{HTML}{B22B31} +\definecolor{ansi-green}{HTML}{00A250} +\definecolor{ansi-green-intense}{HTML}{007427} +\definecolor{ansi-yellow}{HTML}{DDB62B} +\definecolor{ansi-yellow-intense}{HTML}{B27D12} +\definecolor{ansi-blue}{HTML}{208FFB} +\definecolor{ansi-blue-intense}{HTML}{0065CA} +\definecolor{ansi-magenta}{HTML}{D160C4} +\definecolor{ansi-magenta-intense}{HTML}{A03196} +\definecolor{ansi-cyan}{HTML}{60C6C8} +\definecolor{ansi-cyan-intense}{HTML}{258F8F} +\definecolor{ansi-white}{HTML}{C5C1B4} +\definecolor{ansi-white-intense}{HTML}{A1A6B2} + +% commands and environments needed by pandoc snippets +% extracted from the output of `pandoc -s` +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} +% Add ',fontsize=\small' for more characters per line +\newenvironment{Shaded}{}{} +\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}} +\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}} +\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}} +\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}} +\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}} +\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}} +\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}} +\newcommand{\RegionMarkerTok}[1]{{#1}} +\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}} +\newcommand{\NormalTok}[1]{{#1}} + +% Additional commands for more recent versions of Pandoc +\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{{#1}}} +\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}} +\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{{#1}}} +\newcommand{\ImportTok}[1]{{#1}} +\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{{#1}}}} +\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{{#1}}} +\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}} +\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{{#1}}} +\newcommand{\BuiltInTok}[1]{{#1}} +\newcommand{\ExtensionTok}[1]{{#1}} +\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{{#1}}} +\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{{#1}}} +\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} +\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}} + +% Define a nice break command that doesn't care if a line doesn't already +% exist. +\def\br{\hspace*{\fill} \\* } + +% Math Jax compatability definitions +\def\gt{>} +\def\lt{<} + +\setcounter{secnumdepth}{5} + +% Colors for the hyperref package +\definecolor{urlcolor}{rgb}{0,.145,.698} +\definecolor{linkcolor}{rgb}{.71,0.21,0.01} +\definecolor{citecolor}{rgb}{.12,.54,.11} + +\DeclareTranslationFallback{Author}{Author} +\DeclareTranslation{Portuges}{Author}{Autor} + +\DeclareTranslationFallback{List of Codes}{List of Codes} +\DeclareTranslation{Catalan}{List of Codes}{Llista de Codis} +\DeclareTranslation{Danish}{List of Codes}{Liste over Koder} +\DeclareTranslation{German}{List of Codes}{Liste der Codes} +\DeclareTranslation{Spanish}{List of Codes}{Lista de C\'{o}digos} +\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} + +\DeclareTranslationFallback{Supervisors}{Supervisors} +\DeclareTranslation{Catalan}{Supervisors}{Supervisors} +\DeclareTranslation{Danish}{Supervisors}{Vejledere} +\DeclareTranslation{German}{Supervisors}{Vorgesetzten} +\DeclareTranslation{Spanish}{Supervisors}{Supervisores} +\DeclareTranslation{French}{Supervisors}{Superviseurs} +\DeclareTranslation{Italian}{Supervisors}{Le autorit\`{a} di vigilanza} +\DeclareTranslation{Dutch}{Supervisors}{supervisors} +\DeclareTranslation{Portuguese}{Supervisors}{Supervisores} + +\definecolor{codegreen}{rgb}{0,0.6,0} +\definecolor{codegray}{rgb}{0.5,0.5,0.5} +\definecolor{codepurple}{rgb}{0.58,0,0.82} +\definecolor{backcolour}{rgb}{0.95,0.95,0.95} + +\lstdefinestyle{mystyle}{ + commentstyle=\color{codegreen}, + keywordstyle=\color{magenta}, + numberstyle=\tiny\color{codegray}, + stringstyle=\color{codepurple}, + basicstyle=\ttfamily, + breakatwhitespace=false, + keepspaces=true, + numbers=left, + numbersep=10pt, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + breaklines=true, + literate={\-}{}{0\discretionary{-}{}{-}}, + postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space}, +} + +\lstset{style=mystyle} + +\surroundwithmdframed[ + hidealllines=true, + backgroundcolor=backcolour, + innerleftmargin=0pt, + innerrightmargin=0pt, + innertopmargin=0pt, + innerbottommargin=0pt]{lstlisting} + +%%%%%%%%%%%% + +%%%%%%%%%%%% MARGINS + + % Used to adjust the document margins +\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 +%%%%%%%%%%%% + +%%%%%%%%%%%% COMMANDS + +% ensure new section starts on new page +\addtokomafont{section}{\clearpage} + +% Prevent overflowing lines due to hard-to-break entities +\sloppy + +% Setup hyperref package +\hypersetup{ + breaklinks=true, % so long urls are correctly broken across lines + colorlinks=true, + urlcolor=urlcolor, + linkcolor=linkcolor, + citecolor=citecolor, + } + +% ensure figures are placed within subsections +\makeatletter +\AtBeginDocument{% + \expandafter\renewcommand\expandafter\subsection\expandafter + {\expandafter\@fb@secFB\subsection}% + \newcommand\@fb@secFB{\FloatBarrier + \gdef\@fb@afterHHook{\@fb@topbarrier \gdef\@fb@afterHHook{}}}% + \g@addto@macro\@afterheading{\@fb@afterHHook}% + \gdef\@fb@afterHHook{}% +} +\makeatother + +% number figures, tables and equations by section +\counterwithout{figure}{section} +\counterwithout{table}{section} +\counterwithout{equation}{section} +\makeatletter +\@addtoreset{table}{section} +\@addtoreset{figure}{section} +\@addtoreset{equation}{section} +\makeatother +\renewcommand\thetable{\thesection.\arabic{table}} +\renewcommand\thefigure{\thesection.\arabic{figure}} +\renewcommand\theequation{\thesection.\arabic{equation}} + + % set global options for float placement + \makeatletter + \providecommand*\setfloatlocations[2]{\@namedef{fps@#1}{#2}} + \makeatother + +% align captions to left (indented) +\captionsetup{justification=raggedright, +singlelinecheck=false,format=hang,labelfont={it,bf}} + +% shift footer down so space between separation line +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.odd} +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.even} +\ModifyLayer[addvoffset=.6ex]{scrheadings.foot.oneside} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.odd} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.even} +\ModifyLayer[addvoffset=.6ex]{plain.scrheadings.foot.oneside} +\pagestyle{scrheadings} +\clearscrheadfoot{} +\ifoot{\leftmark} +\renewcommand{\sectionmark}[1]{\markleft{\thesection\ #1}} +\ofoot{\pagemark} +\cfoot{} + +%%%%%%%%%%%% + +%%%%%%%%%%%% FINAL HEADER MATERIAL + +% clereref must be loaded after anything that changes the referencing system +\usepackage{cleveref} +\creflabelformat{equation}{#2#1#3} + +% make the code float work with cleverref +\crefname{codecell}{code}{codes} +\Crefname{codecell}{code}{codes} +% make the text float work with cleverref +\crefname{textcell}{text}{texts} +\Crefname{textcell}{text}{texts} +% make the text float work with cleverref +\crefname{errorcell}{error}{errors} +\Crefname{errorcell}{error}{errors} +%%%%%%%%%%%% + +\begin{document} + + \title{Notebook} + \date{\today} + \maketitle + + \begingroup + \let\cleardoublepage\relax + \let\clearpage\relax + \endgroup + +\hypertarget{notebook-to-test-markdown-cells}{% +\section{Notebook to Test Markdown +Cells}\label{notebook-to-test-markdown-cells}} + +\hypertarget{cell-with-attached-image}{% +\subsection{Cell with Attached Image}\label{cell-with-attached-image}} + +\begin{figure}[H] +\hypertarget{fig:id1}{% +\begin{center} +\adjustimage{max size={0.9\linewidth}{0.9\paperheight},}{nb_markdown_cells_files/attach_1_output_13_0.jpeg} +\end{center} +\caption{output\_13\_0.jpeg}\label{fig:id1} +} +\end{figure} + +\cref{fig:id1} \cref{fig:id1} + +\hypertarget{cell-with-linked-image}{% +\subsection{Cell with Linked Image}\label{cell-with-linked-image}} + +\begin{figure}[] +\hypertarget{fig:id2}{% +\begin{center} +\adjustimage{max size={0.9\linewidth}{0.9\paperheight},width=0.5\linewidth}{nb_markdown_cells_files/logo_example.png} +\end{center} +\caption{this is a \textbf{caption}}\label{fig:id2} +} +\end{figure} + +\hypertarget{cell-with-link-to-header}{% +\subsection{Cell with Link to Header}\label{cell-with-link-to-header}} + +\cref{cell-with-link-to-header} + +\hypertarget{cell-with-math}{% +\subsection{Cell with Math}\label{cell-with-math}} + +inline: \(a = b\) + +\begin{equation}a = b\label{eq:id1}\end{equation} +\begin{align*}c &= d \\ other &= e\label{eq:id2}\end{align*} + +\hypertarget{cell-with-table}{% +\subsection{Cell with Table}\label{cell-with-table}} + +\begin{longtable}[]{@{}lll@{}} +\caption{Caption. \label{tbl:id}}\tabularnewline +\toprule +A & B & A and B\tabularnewline +\midrule +\endfirsthead +\toprule +A & B & A and B\tabularnewline +\midrule +\endhead +False & False & False\tabularnewline +True & False & False\tabularnewline +False & True & False\tabularnewline +True & True & True\tabularnewline +\bottomrule +\end{longtable} + +\cref{tbl:id} + +\hypertarget{references-using-notation}{% +\subsection{References Using @ +Notation}\label{references-using-notation}} + +\Cref{cell-with-link-to-header}, and multiple references +\cref{tbl:id,eq:id1} + +\end{document} + diff --git a/ipypublish/tests/test_files/nb_markdown_cells/logo_example.png b/ipypublish/tests/test_files/nb_markdown_cells/logo_example.png new file mode 100644 index 0000000..a52d9d4 Binary files /dev/null and b/ipypublish/tests/test_files/nb_markdown_cells/logo_example.png differ diff --git a/ipypublish/tests/test_files/nb_markdown_cells/nb_markdown_cells.ipynb b/ipypublish/tests/test_files/nb_markdown_cells/nb_markdown_cells.ipynb new file mode 100644 index 0000000..39b7828 --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/nb_markdown_cells.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook to Test Markdown Cells" + ] + }, + { + "attachments": { + "output_13_0.jpeg": { + "image/jpeg": "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////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0EAAAAAAAAAgApOEJJTQQCAAAAAACGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4QklNBDAAAAAAAEMBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBADhCSU0ELQAAAAAABgABAAAANjhCSU0ECAAAAAAATAAAAAEAAAJAAAACQAAAAAwAAAAAAQAAQgAB//+cAAAAACJAAQAAH8AB///KQAD//84AAP///EAAAAAAAAAAAC5AAP//x4AB///9gAE4QklNBB4AAAAAAAQAAAAAOEJJTQQaAAAAAANjAAAABgAAAAAAAAAAAAAA/gAAAXIAAAAXADEAMgAuADEANQAtAGgAbwBtAGUAcABhAGcAZQAtAGMAdQByAGEAdABpAG8AbgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABcgAAAP4AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAQAAAAAAAG51bGwAAAACAAAABmJvdW5kc09iamMAAAABAAAAAAAAUmN0MQAAAAQAAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAAAABCdG9tbG9uZwAAAP4AAAAAUmdodGxvbmcAAAFyAAAABnNsaWNlc1ZsTHMAAAABT2JqYwAAAAEAAAAAAAVzbGljZQAAABIAAAAHc2xpY2VJRGxvbmcAAAAAAAAAB2dyb3VwSURsb25nAAAAAAAAAAZvcmlnaW5lbnVtAAAADEVTbGljZU9yaWdpbgAAAA1hdXRvR2VuZXJhdGVkAAAAAFR5cGVlbnVtAAAACkVTbGljZVR5cGUAAAAASW1nIAAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAAD+AAAAAFJnaHRsb25nAAABcgAAAAN1cmxURVhUAAAAAQAAAAAAAG51bGxURVhUAAAAAQAAAAAAAE1zZ2VURVhUAAAAAQAAAAAABmFsdFRhZ1RFWFQAAAABAAAAAAAOY2VsbFRleHRJc0hUTUxib29sAQAAAAhjZWxsVGV4dFRFWFQAAAABAAAAAAAJaG9yekFsaWduZW51bQAAAA9FU2xpY2VIb3J6QWxpZ24AAAAHZGVmYXVsdAAAAAl2ZXJ0QWxpZ25lbnVtAAAAD0VTbGljZVZlcnRBbGlnbgAAAAdkZWZhdWx0AAAAC2JnQ29sb3JUeXBlZW51bQAAABFFU2xpY2VCR0NvbG9yVHlwZQAAAABOb25lAAAACXRvcE91dHNldGxvbmcAAAAAAAAACmxlZnRPdXRzZXRsb25nAAAAAAAAAAxib3R0b21PdXRzZXRsb25nAAAAAAAAAAtyaWdodE91dHNldGxvbmcAAAAAADhCSU0EKAAAAAAADAAAAAI/8AAAAAAAADhCSU0EFAAAAAAABAAAANc4QklNBAwAAAAAD1gAAAABAAAAoAAAAG4AAAHgAADOQAAADzwAGAAB/9j/7QAMQWRvYmVfQ00AAf/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUTExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAG4AoAMBIgACEQEDEQH/3QAEAAr/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBAQEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEyBhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80YnlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBTUBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTVKMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/2gAMAwEAAhEDEQA/AOtCSUJwqrYUkE8JJIUnSCeEkrQkpQkkpaE6cAkwBJOgAWNn/WOqqx2PgNbkWtO19x1qa4fSZXt/pD2/ne70f+NSTCEpmoi3YSXPV5/U7dX5Dx5MDWD7mAK7TnZbQNzvVHcWf+Tb7kOIMksEx1BdRNCjTcy5m5sgj6TTyCppMR00LFJShNCKlkydJJSyZSTJKf/Q65OmCdVWwukknSUoBQycrGxKxblWsoY4w0vMbiPza2j32f2GoPU+o1dNwn5Vjd75FdFMx6lrvoVz+57fUuf/AKFi5mpt2VecrMebsizQ2HQAdq62/Rpqb+ZUxJlxYTPUmoj8fJ6D9u9PJ9nqvH7wrgf+COY//oqzjZ2JkuDKbAbDxW4Frz/Va76f/W1i41VL62vreHtfq17YLSD3Dm/SRLsNtlZBkOGrTwQRq0tP5rk3i1XnDj2BI8U31o6hZiYLMahxZfnFzN45bS2PtDm/uvt3soZ/11ccetdN6fc3HfulsB5Y2W1j+XqP/A96udQz8vqN7XWn9Pi1jGFnO5wdY/7Q5n+kf6jPUb+/WqFH1bxXUvpsLnWu9xvP093O7/zFTREK9f4MghkxwEYCPFqZSO3hF6eh9XpesXNbWG7zYSNu2N2/d9HZt/OUOm/WTofUM37DjXF93+Dc5hayw6uLKXu9zrNo/PZX6v8AgfUSxvq9h5PSKul5DrX47A0Fwdtc7adzdx/d/kJ+q/VfBvwqqMENwb8Mh+DcwGa3g+p73a2WMdZ77Pz/AFP0qjAx62Tvoeg/rLMkpE0K/l+i7dMMvZt/Plh+7c3/AKTVbWXbn49GXV6zoI3WFrRJMD6LG/y7He1Rf1rIsk01NqYCB7psdr/mM/6KbE6McsU5EEDSt3WSWbT1DMOrwyweEbfxYrtGSy8GAWPb9Kt3IH7zT+exGwslCUd/wSJlIpkVqyZOmSU//9HrgpKEqQVVsLpwlCkwNLgHaNn3HwH5ySHkvrJlm/rbcVpmvArDY/4W4C64/wBb0vs9Sy/rBk/Z+i3CTutAqaB/KPu/8Da9NjZLszIvz7NDlWvuPwe4urb/AGWbGo9eX9Xup/qt99FoDgTXadnuB9u31fT3/wBhPAqQNEiNXTeIEcQhYiTEgf3i6nRqfsnScRkfo6qGuP8Am+s7/qkH6oXX5HQm5F9jrbL7rnkvJMS72sbP5i2hQy+l1cS17S07f3XDboWqHT+nY/S8CjBxw4UVAhrnmXEkmx7nu9vu3PUZkDGV/NIg/wDStiJ1HQCxX+LwvHZdFl9Gc2p2yyy17WuGkQf/ADFc/XndWwLPR9a6h4/wbvcP7Pq72ub/AFV1uC31a553Wvd97nLXPTcTJrFeRUy1o7PaHa+SljmELjKPEGTmcfEQRIxkNNHkMf64deqAAspeAOH1NM/Op1aK363fWjMvGLhip1zjAbVSN39ZzrXWtra38+x/0F0v/NjoG8OOFXPh7gP7Td+1yv8A2XEwcfZjVMoraD7a2ho0/q/S/tonNi6YxfiBTXjjnesz9N3ms3qn2anKz8jbZawtpY1shrnNG07J+jX6vq2f8Wh9D+sWVf1BmH1DHFRyRvoe2RrEta5r93tdtWZ1R5HTsa8t3sbcLHtPBn1C3d/J3FN9W25fVOvY+Tcd9eG71HvAAa2A70qWx+/Y7+unDHH25Ejvr2r5WXPOQyiAJ/R9IH6P6Rt3rcvrXUet5PTel5TcGvBra99uwPdY923aza6f0e53/qxaf1f6rl5v2jGzqm4/VumvDbwzRjg7d6djW/m79rmWs+h/N2V/zmyvG+tH1e6j693VOmb3tyWBmZRVIshob72tYd11dmxnqVM96s/UjpedisyszMqfScltddTbZFjmsLnOc5j/AH11/uep77P6iZIQOKwRtGv3+P8ASYuI8VUev93h6PZtcHta9vDhI+aSHiOnHafAvH3OciqJjOhI7LJk6ZFT/9LroTgJJKqzsgqXXck4vQ8+8GHihzGH+XbGMz/pXK4FhfXi/wBPo1NE65WVW0jxbU1+Q7/wRtKMRZC6IuUR4vPdLZtqDWj6IgLls7F+z5l+M9sBriWjsWu97CP5O1dn0prS0fgrub0DA6lW0ZLCHsHssYdr2zyN37v8h6khmEJm9i2uZgJxA2I2fP69rWFjIY0j3bZHB8l2v1MwbcXpWRmOa5jcxwfQxx5rYDst2/R/S2Pf6bvz6UbB+pnRqLBZYx+SRqG3O3M0430tDGWbf5a280lmJYZ12Ez8ks/MCceGN67ksGLEYyFkHpo850QF2LST3En5rdb7QPALA6JaPs1EfuN/Itx7nbRDCR34VefzFt5dZBOxwMygdTfGHZ29pSY8ADdLfJwhVOt5Ibh2a8t7IR3AWxj6x5uJ0xjL8JlVjQ9rmgFpEggrf6Zj49FIZQxrKxJAZAE/nfRWB0cltFJ4ljUarH6l0jIsyOnVnLwbybLsPcWvY8+51mK90t93+iU0hZIuuwOxXZdga3HTd6+lw2fDhVGdWxbOsWdIrl99NP2i54jaz3MYKn/8K71W2LFH1yFo+zdPwMh/UH+2uu5oaxrjoH2bSXPY138mv/hLK1pdA6H+yca19xF2flONmXfyXOJ3+m13+j3Hf/xqaYcIPFof0R+1q9dHaw/5iPB7vyz/ABRkDDPssHg6fvH/AJijoDYMcvmKxTJykih//9Pr0kk6qthcLkv8YV+x3SKiYD3ZL/m1uOxv/VuXWhcR/jIqsycvp1NX06qLbRrH07Gs/wDRCfi+cX4/koXYMRZGrLpjmhjXDUd1ov6x07HysfDuva3JySBVXqZJO1m5zQW173e2v1FwuN1jqGATVfXLg07Nwgz+aXR7XsW79TsDFy7bOp5GSMnqbi5xqJa51bT7PUdV9Ld+b/o6Kv0aOTDXFKR06cPVsnPGZAG/6XF+iP8AvnsWFrhIPCF1CxrcC6w6htbj+BRmUtDQA4j4QqP1hrd+ycvY46Uv8PDxVcDUeao1xDzcPouE4Y1QkRsbo6T2XQVYwDBG1p8gf4lZ/TIbSwfyQB8gtRp4CM5Ek2y5ZG67Mm1WCZeNfJY31hxj9mOx06GdNVuN415Wb1sfqjx5fwSgakFmMnjDgdIH6rR5NH4Lo6NWtnTwHK5zo7gcSseE/lK3sez2hoOoH4J2T5j5ss9YjydSt2vMzp8h2RgVxef9cM3pee/Hv6eBSHH0XmwtdY0f4Sslj6vd+4r2P9fuhvqc+9uRjvY3dscwP3H9yu2pzmf9vegj7OSgRGwe3qakpRsi9R39P/SenxXN9a2ufdtY+PKXslWVzf1TyeodRzMzrGXT9moyqa6sCk8ilj3Wutdxv9R9n87/AIX/AAf6H010iVcOnZikbN91kk6ZJT//1OvThME6qs7ILjfrQ/1PrKGf6DFoZ83m2/8A9GtXYtk6Dk6BcFnZAyvrB1HIbqw5BqZ/VpDcVv8A55To9fJmwC5/RzfrJjPdhV2sEip02eQI2z/ZcifVn6v4HUqq8318im+h5bYKXBhDh9B1Vu31K97DuW9ViVZFLq7AHV2NLXNPcHRytdC6Rj9JwzjUFz9zvUse8+4uOn9Xa1iJzVjMQSJX+C/JjHucWh02P7zrsJ78qn12f2Lmx/oH/kVxpEKn13/kbN/4h/8A1JVeO480R+YeYc3pv80zyAhaYPftwsrprh6TJ8AtRp0E/FE7lmyfMWw09ln9YE4zx4z+RXfUAMTyqHWDGI8HuD+RKO4WQ+cPNdJFnoMLdRucI7/Sd4LcoFo8GwJALjP/AEVk9AcDjQdfc/8A6py6Kj2x2HIhSZT6j5sxl6R5B5360dRfQ2rCZSbsnK1Y17N7QB7P0dTmufZfu+gsWjpeR0/q3Tm59YY2yyi6xlpbox9uzZZV7vd7fexehfZ6H5DMh9bDbXPp2FoL2z9P07D7mbv5KyPrH9WbOr5ONk4z21XMc1lz3k61B2+WN+j6lTi93/CqTFmiAIH0g3xSamWJkeLeq4Y/m9PSHjJG8ySHNPxif++q0qpf+nY/ibBp/WO3/vyslQx2WT3+ikySZOWv/9XrgnCYJ1VZ1r8luHjXZjvo4tT7j/1trrB/0mrzjpDDsG/V51cfM6v/AOku2+tBtH1c6h6TdxNbRZ5VF9f2iz+zVuXHdLjf89fBPj8smzy25L0eG0bWxoPBXhMQqeJ9Ef69lal23idVXK+e7YY6VU63r0fO/wCIs/6ko9RdA0/IqvWy/wDZObA09Cz/AKkox3HmsA9Q83O6e0Cpmp4BWgHng8a6rM6ebDWzSNBE69led60iI8ydPyb0ZblnyDXdtVE/xVHrRIoePEHzRaftQcQQDqdZP9yq9YNgxnSJMa8D/wAySj8wWxFTGocToDopOvD3j/pSulpcCB4QuX6Hu2Oj/SP/ACrpsWdonhPzfMfNJ+SPkG62P7kdgnXuqtZO46aKy0mePDlRMMmdmjQ790td9xBV5w1PxVG2PTdu0EGfuV0TtG7R0CR5xqnR6sU+ixTKRTJ6x//ZOEJJTQQhAAAAAABdAAAAAQEAAAAPAEEAZABvAGIAZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAFwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAAgAEMAQwAgADIAMAAxADUAAAABADhCSU0EBgAAAAAABwAGAAAAAQEA/+Ea52h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMwNjcgNzkuMTU3NzQ3LCAyMDE1LzAzLzMwLTIzOjQwOjQyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdGVEYXRlPSIyMDE1LTEwLTA4VDEyOjM4OjExLTA0OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxNS0xMi0xN1QxMDo1OTo0NS0wNTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxNS0xMi0xN1QxMDo1OTo0NS0wNTowMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iVkEyNzAzIFNlcmllcyBDYWxpYnJhdGVkIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmUyYmQwYjU5LTU2YjEtNDNiZi1iN2UyLTMyNzQzYjFhOTNjMCIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk2MWFlZWE0LWU1NjUtMTE3OC04NGExLWVmNTVlNWVmOTVhZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjY1MjRlZTYyLTQwNDAtNDQ4Ni05NzMwLWNjNDgyZDBiNmRjNCI+IDxwaG90b3Nob3A6VGV4dExheWVycz4gPHJkZjpCYWc+IDxyZGY6bGkgcGhvdG9zaG9wOkxheWVyTmFtZT0iUG9wdWxhciBJbGx1c3RyYXRpb24gQ2F0ZWdvcmllcyIgcGhvdG9zaG9wOkxheWVyVGV4dD0iUG9wdWxhciBJbGx1c3RyYXRpb24gQ2F0ZWdvcmllcyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IlBvcHVsYXIgVmVjdG9yIENhdGVnb3JpZXMiIHBob3Rvc2hvcDpMYXllclRleHQ9IlBvcHVsYXIgVmVjdG9yIENhdGVnb3JpZXMiLz4gPHJkZjpsaSBwaG90b3Nob3A6TGF5ZXJOYW1lPSJQb3B1bGFyIFBob3RvIENhdGVnb3JpZXMiIHBob3Rvc2hvcDpMYXllclRleHQ9IlBvcHVsYXIgUGhvdG8gQ2F0ZWdvcmllcyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IkJpZ3N0b2NrIFBpY2tzIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSJCaWdzdG9jayBQaWNrcyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IldpbnRlciBDb2xsZWN0aW9uIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSJXaW50ZXIgQ29sbGVjdGlvbiIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IkJpZ3N0b2NrIFZpZGVvIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSJCaWdzdG9jayBWaWRlbyIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IkltYWdlcyBhbmQgVmlkZW8gZm9yIGV2ZXJ5b25lLiIgcGhvdG9zaG9wOkxheWVyVGV4dD0iSW1hZ2VzIGFuZCBWaWRlbyBmb3IgZXZlcnlvbmUuIi8+IDxyZGY6bGkgcGhvdG9zaG9wOkxheWVyTmFtZT0iT3ZlciAzMCBtaWxsaW9uIHN0b2NrIHBob3RvcywgdmlkZW9zLCBhbmQgdmVjdG9ycy4iIHBob3Rvc2hvcDpMYXllclRleHQ9Ik92ZXIgMzAgbWlsbGlvbiBzdG9jayBwaG90b3MsIHZpZGVvcywgYW5kIHZlY3RvcnMuIi8+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6VGV4dExheWVycz4gPHBob3Rvc2hvcDpEb2N1bWVudEFuY2VzdG9ycz4gPHJkZjpCYWc+IDxyZGY6bGk+MjMzOEI4RTQxMjc0MDA4QTkwQzhCRDI1NDc5NjBCQjM8L3JkZjpsaT4gPHJkZjpsaT4yREU0NDA2QzEwQjczNDIyQjRFRTZEOEIwRDMyNUZBODwvcmRmOmxpPiA8cmRmOmxpPjMxOTc2NTBFRTkzRjc5OEQ2QTJCQUYyMUFENjIyNzJCPC9yZGY6bGk+IDxyZGY6bGk+M0YyOTgxMzJFRjVERjdCRDFFN0U3OUM1MjVDMENBODY8L3JkZjpsaT4gPHJkZjpsaT41QUFBMDQ2RjFCRUQ1NTA3QjRGRTU4MkVEMTI1OTFBNDwvcmRmOmxpPiA8cmRmOmxpPjY1NjUzQUYxMEE1MUQ5MEZBMDdFQ0U2MzMyNjA4NEFBPC9yZGY6bGk+IDxyZGY6bGk+NzZFNDE3RTVFNDAxN0Q3ODU4MkI3OTFCOTYzQzlGM0I8L3JkZjpsaT4gPHJkZjpsaT44RjQwMDNDMUU1MzE4REU2MzVDODM0OTBENTE0ODMwQTwvcmRmOmxpPiA8cmRmOmxpPkExNDc0Q0I1QTEwRTM2RDQ2ODcwRjA0NDZFMDdEM0UwPC9yZGY6bGk+IDxyZGY6bGk+QTRENUQzQzczOTc4NTBERkZDQThDNjM2QTZEMkFBOUU8L3JkZjpsaT4gPHJkZjpsaT5BODE4NjdBREIyRDIwMzc2Q0FGMjNDNkM0NDU1QTYxRjwvcmRmOmxpPiA8cmRmOmxpPkMzMDkzMjZBRjJEMkUzQTE1QThBMjI0RUUwMjQ2NDQyPC9yZGY6bGk+IDxyZGY6bGk+QzRBQUJERDU3MUY5NkU5MjQ1QUYzMjU1REFDQkU5MDI8L3JkZjpsaT4gPHJkZjpsaT5DNTk3RkREMkRGMjlDRUUzQTA4OTA2REVGODIzNUZBMzwvcmRmOmxpPiA8cmRmOmxpPkQwRTQxMTkzRDBEODdBQkY2OUQzQTI3NDM1RkQ5Q0U2PC9yZGY6bGk+IDxyZGY6bGk+RDQ5M0IzODIwMDhERDAyMDA1ODk0Rjg0QUI4QUNEQTU8L3JkZjpsaT4gPHJkZjpsaT5EQjFBQzlEMjZCMUY0MEM5QTVBMTFCN0E2QURDM0ExMjwvcmRmOmxpPiA8cmRmOmxpPkU2NEU5NTJBQ0UyOTAwMkVCNjhGMUJDRDAxQjg5MTFCPC9yZGY6bGk+IDxyZGY6bGk+RTY4ODU2ODVDQkRGMjMyNEU3MTE1OEJGMjE3NkUxRkU8L3JkZjpsaT4gPHJkZjpsaT5GNUU3NTc3QTZCM0EzNDFBNTFDQUFBQzVFM0E1OUIxMTwvcmRmOmxpPiA8cmRmOmxpPkZEMUQ3RTMxQzc1MTJCRTBBODkxQ0RBQTVDNzdERTk1PC9yZGY6bGk+IDxyZGY6bGk+eG1wLmRpZDo2NDk0NmNiOC00MTFhLTRiMDAtOTk2MS1jODJkZmZjNTQ2NGM8L3JkZjpsaT4gPC9yZGY6QmFnPiA8L3Bob3Rvc2hvcDpEb2N1bWVudEFuY2VzdG9ycz4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjUyNGVlNjItNDA0MC00NDg2LTk3MzAtY2M0ODJkMGI2ZGM0IiBzdEV2dDp3aGVuPSIyMDE1LTEwLTMwVDE0OjI2OjM1LTA0OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGltYWdlL3BuZyB0byBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJjb252ZXJ0ZWQgZnJvbSBpbWFnZS9wbmcgdG8gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjQ5NDZjYjgtNDExYS00YjAwLTk5NjEtYzgyZGZmYzU0NjRjIiBzdEV2dDp3aGVuPSIyMDE1LTEwLTMwVDE0OjI2OjM1LTA0OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjM0NGY3MWMtNDhiZC00MGU4LWFhM2YtZjcyYTEwNmJjZTlhIiBzdEV2dDp3aGVuPSIyMDE1LTEyLTE3VDEwOjU5OjQ1LTA1OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvanBlZyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iZGVyaXZlZCIgc3RFdnQ6cGFyYW1ldGVycz0iY29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9qcGVnIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMmJkMGI1OS01NmIxLTQzYmYtYjdlMi0zMjc0M2IxYTkzYzAiIHN0RXZ0OndoZW49IjIwMTUtMTItMTdUMTA6NTk6NDUtMDU6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MzQ0ZjcxYy00OGJkLTQwZTgtYWEzZi1mNzJhMTA2YmNlOWEiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpjYTc1NGM2MC1iZmMwLTExNzgtYjgwYy1lNWFjMmFiZWU0MjgiIHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo2NTI0ZWU2Mi00MDQwLTQ0ODYtOTczMC1jYzQ4MmQwYjZkYzQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/Pv/iKWhJQ0NfUFJPRklMRQABAQAAKVhhcHBsAhAAAG1udHJSR0IgWFlaIAffAAkACgAPABcAGGFjc3BBUFBMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWRlc2MAAAFQAAAAc2RzY20AAAHEAAAATGNwcnQAAAIQAAAAI3d0cHQAAAI0AAAAFHJYWVoAAAJIAAAAFGdYWVoAAAJcAAAAFGJYWVoAAAJwAAAAFHJUUkMAAAKEAAAIDGFhcmcAAAqQAAAAIHZjZ3QAAAqwAAAG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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cell with Attached Image\n", + "\n", + "![output_13_0.jpeg](attachment:output_13_0.jpeg){#fig:id1 placement='H'}\n", + "\n", + "\\cref{fig:id1}\n", + "[mkdown_ref](#fig:id1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cell with Linked Image\n", + "\n", + "![this is a **caption**](logo_example.png){#fig:id2 width=50%}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cell with Link to Header" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[link_to_header](#cell-with-link-to-header)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cell with Math\n", + "\n", + "inline: $a = b$\n", + "\n", + "$$a = b$$ {#eq:id1}\n", + "$$c &= d \\\\ other &= e$$ {#eq:id2 env=align .unnumbered}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cell with Table\n", + "\n", + "A | B | A and B\n", + "------|-------|--------\n", + "False | False | False\n", + "True | False | False\n", + "False | True | False\n", + "True | True | True\n", + "\n", + "Table: Caption. {#tbl:id}\n", + "\n", + "\\cref{tbl:id}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ipub": { + "at_notation": true + } + }, + "source": [ + "## References Using @ Notation\n", + "\n", + "?@cell-with-link-to-header, and multiple references +[@tbl:id; @eq:id1]" + ] + } + ], + "metadata": { + "celltoolbar": "Edit Metadata", + "hide_input": false, + "ipub": { + "pandoc": { + "use_numref": true + } + }, + "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.8" + }, + "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/ipypublish/tests/test_files/nb_markdown_cells/nbsphinx_markdown.ipynb b/ipypublish/tests/test_files/nb_markdown_cells/nbsphinx_markdown.ipynb new file mode 100644 index 0000000..991ed9d --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/nbsphinx_markdown.ipynb @@ -0,0 +1,513 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "nbsphinx": "hidden" + }, + "source": [ + "This notebook is part of the `nbsphinx` documentation: https://nbsphinx.readthedocs.io/." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Markdown Cells\n", + "\n", + "We can use *emphasis*, **boldface**, `preformatted text`.\n", + "\n", + "> It looks like strike-out text is not supported: ~~strikethrough~~.\n", + "\n", + "* Red\n", + "* Green\n", + "* Blue\n", + "\n", + "***\n", + "\n", + "1. One\n", + "1. Two\n", + "1. Three\n", + "\n", + "Arbitrary Unicode characters should be supported, e.g. łßō.\n", + "Note, however, that this only works if your HTML browser and your LaTeX processor provide the appropriate fonts." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Equations\n", + "\n", + "Inline equations like $\\text{e}^{i\\pi} = -1$\n", + "can be created by putting a LaTeX expression between two dollar signs, like this:\n", + "`$\\text{e}^{i\\pi} = -1$`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "**Note:**\n", + "\n", + "Avoid leading and trailing spaces around math expressions, otherwise errors like the following will occur when Sphinx is running:\n", + "\n", + " ERROR: Unknown interpreted text role \"raw-latex\".\n", + "\n", + "See also the [pandoc docs](http://pandoc.org/MANUAL.html#math):\n", + "\n", + "> Anything between two `$` characters will be treated as TeX math. The opening `$` must have a non-space character immediately to its right, while the closing `$` must have a non-space character immediately to its left, and must not be followed immediately by a digit.\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Equations can also be displayed on their own line like this:\n", + "\\begin{equation}\n", + "\\int\\limits_{-\\infty}^\\infty f(x) \\delta(x - x_0) dx = f(x_0).\n", + "\\end{equation}\n", + "\n", + "This can be done by simply using one of the LaTeX math environments, like so:\n", + "\n", + "```\n", + "\\begin{equation}\n", + "\\int\\limits_{-\\infty}^\\infty f(x) \\delta(x - x_0) dx = f(x_0)\n", + "\\end{equation}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Automatic Equation Numbering\n", + "\n", + "This is not automatically enabled in Jupyter notebooks,\n", + "but you can install a notebook extension in order to enable equation numbering:\n", + "https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/equation-numbering/readme.html.\n", + "\n", + "Automatic Equation Numbering is enabled on http://nbviewer.jupyter.org/,\n", + "see e.g. the latest version of this very notebook at the link http://nbviewer.jupyter.org/github/spatialaudio/nbsphinx/blob/master/doc/markdown-cells.ipynb.\n", + "\n", + "When using `nbsphinx`, you can use the following `mathjax_config` setting in your `conf.py` file\n", + "to enable automatic equation numbering in HTML output.\n", + "In LaTeX output, the equations are numbered by default.\n", + "\n", + "```python\n", + "mathjax_config = {\n", + " 'TeX': {'equationNumbers': {'autoNumber': 'AMS', 'useLabelIds': True}},\n", + "}\n", + "```\n", + "\n", + "You can use `\\label{...}` to give a unique label to an equation:\n", + "\n", + "\\begin{equation}\n", + "\\phi = \\frac{1 + \\sqrt{5}}{2}\n", + "\\label{golden-mean}\n", + "\\end{equation}\n", + "\n", + "```\n", + "\\begin{equation}\n", + "\\phi = \\frac{1 + \\sqrt{5}}{2}\n", + "\\label{golden-mean}\n", + "\\end{equation}\n", + "```\n", + "\n", + "If automatic equation numbering is enabled,\n", + "you can later reference that equation using its label.\n", + "You can use `\\eqref{golden-mean}` for a reference with parentheses: \\eqref{golden-mean},\n", + "or `\\ref{golden-mean}` for a reference without them: \\ref{golden-mean}.\n", + "\n", + "In HTML output, these equation references only work for equations within a single HTML page.\n", + "In LaTeX output, equations from other notebooks can be referenced, e.g. \\eqref{fibonacci-recurrence}." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Manual Equation Numbering\n", + "\n", + "If you prefer to assign equation numbers (or some kind of names) manually,\n", + "you can do so with `\\tag{...}`:\n", + "\n", + "\\begin{equation}\n", + "a^2 + b^2 = c^2\n", + "\\tag{99.4}\n", + "\\label{pythagoras}\n", + "\\end{equation}\n", + "\n", + "```\n", + "\\begin{equation}\n", + "a^2 + b^2 = c^2\n", + "\\tag{99.4}\n", + "\\label{pythagoras}\n", + "\\end{equation}\n", + "```\n", + "\n", + "The above equation has the number \\ref{pythagoras}." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Citations\n", + "\n", + "According to https://nbconvert.readthedocs.io/en/latest/latex_citations.html,\n", + "`nbconvert` supports citations using a special HTML-based syntax.\n", + "`nbsphinx` supports the same syntax.\n", + "\n", + "Example: Kluyver et al. (2016).\n", + "\n", + "```html\n", + "Kluyver et al. (2016)\n", + "```\n", + "\n", + "You don't actually have to use ``,\n", + "any inline HTML tag can be used, e.g. ``:\n", + "Python: An Ecosystem for Scientific Computing.\n", + "\n", + "```html\n", + "Python: An Ecosystem for Scientific Computing\n", + "```\n", + "\n", + "You'll also have to define a list of references,\n", + "see [the section about references](a-normal-rst-file.rst#references).\n", + "\n", + "There is also a Notebook extension which may or may not be useful: https://github.com/takluyver/cite2c." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code\n", + "\n", + "We can also write code with nice syntax highlighting:\n", + "\n", + "```python3\n", + "print(\"Hello, world!\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tables\n", + "\n", + "A | B | A and B\n", + "------|-------|--------\n", + "False | False | False\n", + "True | False | False\n", + "False | True | False\n", + "True | True | True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Images\n", + "\n", + "Local image: ![Jupyter notebook icon](images/notebook_icon.png)\n", + "\n", + "Remote image: ![Python logo (remote)](https://www.python.org/static/img/python-logo-large.png)\n", + "\n", + "### SVG support for LaTeX\n", + "\n", + "LaTeX doesn't support SVG images, but there are Sphinx extensions that can be used for automatically converting SVG images for inclusion in LaTeX output.\n", + "\n", + "Just include one of the following options in the list of `extensions`\n", + "in your `conf.py` file.\n", + "\n", + "* `'sphinxcontrib.inkscapeconverter'` or `'sphinxcontrib.rsvgconverter'`:\n", + " See https://github.com/missinglinkelectronics/sphinxcontrib-svg2pdfconverter\n", + " for installation instructions.\n", + " \n", + " The external programs `inkscape` or `rsvg-convert` (Debian/Ubuntu package `librsvg2-bin`)\n", + " are needed, respectively.\n", + "\n", + "* `'sphinx.ext.imgconverter'`:\n", + " This is a built-in Sphinx extension, see\n", + " http://www.sphinx-doc.org/en/master/usage/extensions/imgconverter.html.\n", + " \n", + " This needs the external program `convert` from *ImageMagick*.\n", + "\n", + " The disadvantage of this extension is that SVGs are converted to bitmap images." + ] + }, + { + "attachments": { + "stickfigure.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHv0lEQVR42u2ae2xbZxnGf+f4OLbjOHGcNq2bpqF12tJmZGtJttK1m7Z1XMomVQyp4zYESEMwMNLEKEgIbQJNFKkIVUNogJj2B9u6cYkETBUtl5UNWDt10NGMbiU0G+klpE6aq9PYMX+c5xRjEl8SJ3WcvdKR7ePvXN7ney/P+34fvCWLW4x5eIYHCAGV+v2PcgcgBFTpewB4J/AxYDmQAKLAaeBcOQLQKgW3AymgETgL1AJHgAlgI9ADPAQcB8bLxZ1WAZ3AYaALuBdo0flrgTogLID6gDc0xlMOynuAm4DXgU9plj1Zxt4B/BM4JYAWvPL3AX8VCPnO6A5gRFayoKUFiAOvFTibzcDfgTuvphuYRbjHkPz518BAAVbTCkwCr17NQGgW6T5J+fV1BQCwRFnhcjnEgE/KnDs1s6vEB6aSauDzwN+ALeWSAquV0rqApwXG9wXGBoERBtqkfB8wCqwtJyLkEevbI8Vf1Kcf+JNA2CzljwPfBl4qRyocltIjwBdEfA4ClwA38Czwu1KhwnMpzcAJ4CsCpU5HSTE/aw4D463KMs8shtnOVP5dmv0DQH0pv6yriEonFQRvBR5QKhwS5+8W6Sm7hkgIWA2skDs9CgwCPs38oAqku4EzpQiANYOZ9qu+r1VO3ykKvAL4I/BlBbuvqyFyXkCUhW9/GHhMjG8UeAL4lUx/raK9B7gfeEXjy6Lic7o9J4CngJeB29KaHOnSJo7/jK7pFBCeAtwqXGrKR4BHRG+bs1BYj/i9o3QYeBz4XpbaIPM5PwJ+Pl8gWHnOyEOq+b+J3dCcTvl3Aw+rOHpZ8WJCDDCXbAc+I6sx8rxmXsrhNcBu7LZ2LMu4cY1ZA1xMq/ETOZSJAB8EfgbcJTf7kHoMJSHbsXt3ufw43fdDGYHzpFwns2Z4P3Yr7d/AT4CtiislIxv0go/n4ZOblBnap6gJOoEPCJCIssZBoFfl871XK/BZOdLeJqBBFd25HP2Aj4jxXcj477JYYEQz/B2gQue+IYuJUaLrA+/Ns3HRKjN+aopoH1JNMKEAekIz3kyJrwlEFMmfziOFNWN3haeLE3XAjdgd4I2lpHiuNFghJpdP6XsZODqNKV8EXtDYkjL1bGlwQn56KAcIK4HPYjc5YzmeV3J+ns0C3lCaupDjxR3SEl+I/D6XC+RLRlzYCyMjCw2A2S6MpK/wHGUBLnUXY2XInZbvWYwAFKOztOAB8CplLjoAnC0wg7rXChbYjo+Zmm4IuBnYh935jYoP7AZ+KtITw+4b9k9zj9hsXrznc22pzHMNj7xkzAcAEeBBKXsEey3QqfbukVVMihhtVDWZmR3iwNdmAsJUis8GCGMGM/8tfX9SijtVok//+4DrsbfAtE0BgEOHv1QoAPkoXygIRgGKh+Tj+1TRPZBFAQ/2XsFQxpha7LY5hQJQiPKFgJBvT3CfCp4B+Xsu3j+u42LG+RR2i8w3VzOfeV0uEPIBIKCmSBJ7IeTILGLXiK7/qixkShCjHe0GwP5dx1IUWaId7Vcy3/5dxyaNaEe7S65gKHilgJQeHgZukblGZ6m8Iy3AMezNk6+nvZhbE2Ly3zXLxJ7DqVnXF3t3GC4xVrfc0wHhsgWs0x+OeY4Dw+tvClWc+kPsYVKMKLqf0MyYafFjMg2sfMWhzDUC36vYUIe9cSqgc5PqRhVD/Lr3crm017FIC/iEzNGUifYD55dG/K7Xno/dabqNQ81bamPvuT9Sqxt500AYB8aiHe2jUiyRByAXgV+YLuOev/zywuh1dywLaRLWiUvU6DkUEYA12DvY1qp75ZMOMQt75dZBJAmMAb3V9RXjHr+LyJbQ4NaPNtyoC5ZiN0AtKXsJuxf4L/UNBoB4tKM9ofs5LpUEJvfvOpaq8LuqJ0aTrcvW+X/89pvrtgLX9nWP3mC6jKWhlb7KDICLFQN2qiW3WpbglgXHLQU4x6RTAD0nhxr//MRZ831fbO4Pb6jaZlWY16SlN2/a2GExwR5R4h7R4iFZxLA+B4GRaEf72IsHehqPd5z3N7ZWbxofTS7p7RpZ+/xjby4DjJ17mlPBsHcuCqvdQJMsy9SRAgJWRj1gxN4c47kfdJuJ8ST+kDtkVZhBDZ6cgjs4ceNK7JAbjelzQGD0K+InVrcFm04e6gsaBtuPHugJ9p4etUKNXsPtc+HxW8YUAYw9h1OzCYAXxEitjPc3/i8NJiYmOfvqMKFGHy63QWXQbaRF5NQ05Mmdli6XZJh9QhYQd9igp8qqtDymv/O3fVUrrwkYbXeFWfmOAKZl4queky1L9dkInxHtaL8Cb8/JIZ7de5qmzTVcv3sFwbB3Ng9OZXx3DnPgXNzoOzNqhNdX4QlYWO7cRelMrGDvjtzedOXJ8aEEXUf7adpcQ8vtS/HVzHpx1kg7nNxuAWYw7DXe1hbEH6rIS/l8lZnJeNcNdzc8CGB5TOojfupW+fj9o92MXZrAG7BwWQaWxyy6XZquwmPdC2sMtnUVF6z/cQFHzp0a5jffPQOpFMvXV7Ht4414A3O1pXBmMpVLFGol09YC4fVV3PLpJp77YTfJiRTx4UTJATATZQtqiTW0BLjtvtVUVLp45WAv8aEE5ShZp3VZs5/qervFV2oWMC8AAHOVm0tGTBa5vAXAYgfgP6N1EOJNty18AAAAAElFTkSuQmCC" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cell Attachments\n", + "\n", + "Images can also be embedded in the notebook itself. Just drag an image file into the Markdown cell you are just editing or copy and paste some image data from an image editor/viewer.\n", + "\n", + "The generated Markdown code will look just like a \"normal\" image link, except that it will have an `attachment:` prefix:\n", + "\n", + " ![a stick figure](attachment:stickfigure.png)\n", + "\n", + "This is a cell attachment: ![a stick figure](attachment:stickfigure.png)\n", + "\n", + "In the Jupyter Notebook, there is a speciall \"Attachments\" cell toolbar which you can use to see all attachments of a cell and delete them, if needed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## HTML Elements (HTML only)\n", + "\n", + "It is allowed to use plain HTML elements within Markdown cells.\n", + "Those elements are passed through to the HTML output and are ignored for the LaTeX output.\n", + "Below are a few examples.\n", + "\n", + "HTML5 [audio](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio) elements can be created like this:\n", + "\n", + "```html\n", + "\n", + "```\n", + "\n", + "Example:\n", + "\n", + "\n", + "\n", + "\n", + "HTML5 [video](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) elements can be created like this:\n", + "\n", + "```html\n", + "\n", + "```\n", + "\n", + "Example:\n", + "\n", + "\n", + "\n", + "The alternative text is shown in browsers that don't support those elements. The same text is also shown in Sphinx's LaTeX output.\n", + "\n", + "
\n", + "\n", + "**Note:** You can also use local files for the `
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Info/Warning Boxes\n", + "\n", + "
\n", + "\n", + "**Warning:**\n", + "\n", + "This is an *experimental feature*!\n", + "Its usage will probably change in the future or it might be removed completely!\n", + "\n", + "
\n", + "\n", + "Until there is an info/warning extension for Markdown/CommonMark (see [this issue](https://github.com/jupyter/notebook/issues/1292)), such boxes can be created by using HTML `
` elements like this:\n", + "\n", + "```html\n", + "
\n", + "\n", + "**Note:** This is a note!\n", + "\n", + "
\n", + "```\n", + "\n", + "For this to work reliably, you should obey the following guidelines:\n", + "\n", + "* The `class` attribute has to be either `\"alert alert-info\"` or `\"alert alert-warning\"`, other values will not be converted correctly.\n", + "* No further attributes are allowed.\n", + "* For compatibility with CommonMark, you should add an empty line between the `
` start tag and the beginning of the content.\n", + "\n", + "
\n", + "\n", + "**Note:**\n", + "\n", + "The text can contain further Markdown formatting.\n", + "It is even possible to have nested boxes:\n", + "\n", + "
\n", + "\n", + "... but please don't *overuse* this!\n", + "\n", + "
\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Links to Other Notebooks\n", + "\n", + "Relative links to local notebooks can be used:\n", + "[a link to a notebook in a subdirectory](subdir/a-notebook-in-a-subdir.ipynb),\n", + "[a link to an orphan notebook](orphan.ipynb)\n", + "(latter won't work in LaTeX output, because orphan pages are not included there).\n", + "\n", + "This is how a link is created in Markdown:\n", + "\n", + "```\n", + "[a link to a notebook in a subdirectory](subdir/a-notebook-in-a-subdir.ipynb)\n", + "```\n", + "\n", + "Markdown also supports *reference-style* links:\n", + "[a reference-style link][mylink],\n", + "[another version of the same link][mylink].\n", + "\n", + "[mylink]: subdir/a-notebook-in-a-subdir.ipynb\n", + "\n", + "These can be created with this syntax:\n", + "\n", + "```\n", + "[a reference-style link][mylink]\n", + "\n", + "[mylink]: subdir/a-notebook-in-a-subdir.ipynb\n", + "```\n", + "\n", + "Links to sub-sections are also possible, e.g.\n", + "[this subsection](subdir/a-notebook-in-a-subdir.ipynb#A-Sub-Section).\n", + "\n", + "This link was created with:\n", + "\n", + "```\n", + "[this subsection](subdir/a-notebook-in-a-subdir.ipynb#A-Sub-Section)\n", + "```\n", + "\n", + "You just have to remember to replace spaces with hyphens!\n", + "\n", + "BTW, links to sections of the current notebook work, too, e.g.\n", + "[beginning of this section](#Links-to-Other-Notebooks).\n", + "\n", + "This can be done, as expected, like this:\n", + "\n", + "```\n", + "[beginning of this section](#Links-to-Other-Notebooks)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Links to `*.rst` Files (and Other Sphinx Source Files)\n", + "\n", + "Links to files whose extension is in the configuration value [source_suffix](http://www.sphinx-doc.org/en/master/config.html#confval-source_suffix), will be converted to links to the generated HTML/LaTeX pages. Example: [A reStructuredText file](a-normal-rst-file.rst).\n", + "\n", + "This was created with:\n", + "\n", + "```\n", + "[A reStructuredText file](a-normal-rst-file.rst)\n", + "```\n", + "\n", + "Links to sub-sections are also possible. Example: [Sphinx Directives](a-normal-rst-file.rst#sphinx-directives-for-info-warning-boxes).\n", + "\n", + "This was created with:\n", + "\n", + "```\n", + "[Sphinx Directives](a-normal-rst-file.rst#sphinx-directives-for-info-warning-boxes)\n", + "```\n", + "\n", + "
\n", + "\n", + "**Note:**\n", + "\n", + "Sphinx section anchors are different from Jupyter section anchors!\n", + "To create a link to a subsection in an `.rst` file (or another non-notebook source file), you not only have to replace spaces with hyphens, but also slashes and some other characters.\n", + "In case of doubt, just check the target HTML page generated by Sphinx.\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Links to Local Files\n", + "\n", + "Links to local files (other than Jupyter notebooks and other Sphinx source files) are also possible, e.g. [requirements.txt](requirements.txt).\n", + "\n", + "This was simply created with:\n", + "\n", + "```\n", + "[requirements.txt](requirements.txt)\n", + "```\n", + "\n", + "The linked files are automatically copied to the HTML output directory.\n", + "For LaTeX output, links are created,\n", + "but the files are not copied to the target directory." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Links to Domain Objects\n", + "\n", + "Links to [Sphinx domain objects](http://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html) (such as a Python class or JavaScript function) are also possible. For example:\n", + "[example_python_function()](a-normal-rst-file.rst#example_python_function).\n", + "\n", + "This was created with:\n", + "\n", + "```\n", + "[example_python_function()](a-normal-rst-file.rst#example_python_function)\n", + "```\n", + "\n", + "This is especially useful for use with the Sphinx [autodoc](http://www.sphinx-doc.org/en/master/ext/autodoc.html) extension!" + ] + } + ], + "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.8" + }, + "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/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.1-12.rst b/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.1-12.rst new file mode 100644 index 0000000..a10be5b --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.1-12.rst @@ -0,0 +1,74 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +Notebook to Test Markdown Cells +=============================== + +Cell with Attached Image +------------------------ + +.. figure:: nb_markdown_cells_files/attach_1_output_13_0.jpeg + :alt: output\_13\_0.jpeg + + output\_13\_0.jpeg +:numref:`fig:id1` `mkdown\_ref <#fig:id1>`__ + +Cell with Linked Image +---------------------- + +.. figure:: nb_markdown_cells_files/logo_example.png + :alt: this is a **caption** + + this is a **caption** + +Cell with Link to Header +------------------------ + +`link\_to\_header <#cell-with-link-to-header>`__ + +Cell with Math +-------------- + +inline: :math:`a = b` + +.. math:: + :nowrap: + :label: eq:id1 + + \begin{equation}a = b\end{equation} + +.. math:: + :nowrap: + :label: eq:id2 + + \begin{align*}c &= d \\ other &= e\end{align*} + +Cell with Table +--------------- + +.. _`tbl:id`: + ++---------+---------+-----------+ +| A | B | A and B | ++=========+=========+===========+ +| False | False | False | ++---------+---------+-----------+ +| True | False | False | ++---------+---------+-----------+ +| False | True | False | ++---------+---------+-----------+ +| True | True | True | ++---------+---------+-----------+ + +Table: Caption. + +:numref:`tbl:id` + +References Using @ Notation +--------------------------- + +:numref:`cell-with-link-to-header`, and multiple references +:numref:`tbl:id` and :numref:`eq:id1` diff --git a/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.2-2.rst b/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.2-2.rst new file mode 100644 index 0000000..1c6d3cb --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.2-2.rst @@ -0,0 +1,78 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +Notebook to Test Markdown Cells +=============================== + +Cell with Attached Image +------------------------ + +.. figure:: nb_markdown_cells_files/attach_1_output_13_0.jpeg + :alt: output_13_0.jpeg + :name: fig:id1 + + output_13_0.jpeg + +:numref:`fig:id1` :numref:`fig:id1` + +Cell with Linked Image +---------------------- + +.. figure:: nb_markdown_cells_files/logo_example.png + :alt: this is a **caption** + :name: fig:id2 + :width: 50.0% + + this is a **caption** + +Cell with Link to Header +------------------------ + +:numref:`cell-with-link-to-header` + +Cell with Math +-------------- + +inline: :math:`a = b` + +.. math:: + :nowrap: + :label: eq:id1 + + \begin{equation}a = b\end{equation} + +.. math:: + :nowrap: + :label: eq:id2 + + \begin{align*}c &= d \\ other &= e\end{align*} + +Cell with Table +--------------- + +.. _`tbl:id`: + +.. table:: Caption. + + +-------+-------+---------+ + | A | B | A and B | + +=======+=======+=========+ + | False | False | False | + +-------+-------+---------+ + | True | False | False | + +-------+-------+---------+ + | False | True | False | + +-------+-------+---------+ + | True | True | True | + +-------+-------+---------+ + +:numref:`tbl:id` + +References Using @ Notation +--------------------------- + +:numref:`cell-with-link-to-header`, and multiple references +:numref:`tbl:id` and :numref:`eq:id1` diff --git a/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.2-6.rst b/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.2-6.rst new file mode 100644 index 0000000..ecc21ab --- /dev/null +++ b/ipypublish/tests/test_files/nb_markdown_cells/sphinx_ipypublish_main.pandoc.2-6.rst @@ -0,0 +1,75 @@ + +.. An html document created by ipypublish + outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 + with segments: + - nbsphinx-ipypublish-content: ipypublish sphinx content + +Notebook to Test Markdown Cells +=============================== + +Cell with Attached Image +------------------------ + +.. figure:: nb_markdown_cells_files/attach_1_output_13_0.jpeg + :alt: output_13_0.jpeg + :name: fig:id1 + + output_13_0.jpeg + +:numref:`fig:id1` :numref:`fig:id1` + +Cell with Linked Image +---------------------- + +.. figure:: nb_markdown_cells_files/logo_example.png + :alt: this is a **caption** + :name: fig:id2 + :width: 50.0% + + this is a **caption** + +Cell with Link to Header +------------------------ + +:numref:`cell-with-link-to-header` + +Cell with Math +-------------- + +inline: :math:`a = b` + +.. math:: + :nowrap: + :label: eq:id1 + + \begin{equation}a = b\end{equation} + +.. math:: + :nowrap: + :label: eq:id2 + + \begin{align*}c &= d \\ other &= e\end{align*} + +Cell with Table +--------------- + +.. _`tbl:id`: + +.. table:: Caption. + + ===== ===== ======= + A B A and B + ===== ===== ======= + False False False + True False False + False True False + True True True + ===== ===== ======= + +:numref:`tbl:id` + +References Using @ Notation +--------------------------- + +:numref:`cell-with-link-to-header`, and multiple references +:numref:`tbl:id` and :numref:`eq:id1` diff --git a/ipypublish/tests/test_frontend.py b/ipypublish/tests/test_frontend.py index b684661..7a2c7a4 100644 --- a/ipypublish/tests/test_frontend.py +++ b/ipypublish/tests/test_frontend.py @@ -22,13 +22,13 @@ def test_nbpublish_bad_exporter(temp_folder, ipynb1): def test_nbpresent_dry_run(temp_folder, ipynb1): # type: (str, pathlib.Path) -> None assert 0 == nbpresent.run([str(ipynb1), "--outpath", temp_folder, - "--dry-run", "--log-level", "debug"]) + "--dry-run", "--log-level", "debug", "-pt"]) def test_nbpublish_dry_run(temp_folder, ipynb1): # type: (str, pathlib.Path) -> None assert 0 == nbpublish.run([str(ipynb1), "--outpath", temp_folder, - "--dry-run", "--log-level", "debug"]) + "--dry-run", "--log-level", "debug", "-pt"]) def test_nbpublish_dry_run_with_external_plugin( @@ -37,7 +37,7 @@ def test_nbpublish_dry_run_with_external_plugin( assert 0 == nbpublish.run([str(ipynb1), "--outformat", str(external_export_plugin), "--outpath", temp_folder, - "--dry-run", "--log-level", "debug"]) + "--dry-run", "--log-level", "debug", "-pt"]) def test_nbpublish_dry_run_with_external_plugin_key( @@ -50,7 +50,7 @@ def test_nbpublish_dry_run_with_external_plugin_key( os.path.splitext( str(external_export_plugin.name))[0], "--outpath", temp_folder, - "--dry-run", "--log-level", "debug"]) + "--dry-run", "--log-level", "debug", "-pt"]) def test_nbpresent_list_exports(): @@ -71,7 +71,7 @@ def test_nbpublish_write(temp_folder, ipynb1): # type: (str, pathlib.Path) -> None assert 0 == nbpublish.run([str(ipynb1), "--outformat", "latex_ipypublish_main", - "--outpath", temp_folder]) + "--outpath", temp_folder, "-pt"]) assert os.path.exists(os.path.join(temp_folder, ipynb1.name.replace(".ipynb", ".tex"))) @@ -82,6 +82,6 @@ def test_nbpublish_to_pdf(temp_folder, ipynb1): assert 0 == nbpublish.run([str(ipynb1), "--outformat", "latex_ipypublish_main", "--outpath", temp_folder, - "--create-pdf"]) + "--create-pdf", "-pt"]) assert os.path.exists(os.path.join(temp_folder, ipynb1.name.replace(".ipynb", ".pdf"))) diff --git a/ipypublish/tests/test_nbexport.py b/ipypublish/tests/test_nbexport.py index 531b7d2..794384f 100644 --- a/ipypublish/tests/test_nbexport.py +++ b/ipypublish/tests/test_nbexport.py @@ -1,8 +1,8 @@ -from ipypublish.scripts import nbmerge +from ipypublish.convert import nbmerge from ipypublish.convert.config_manager import ( create_exporter_cls, str_to_jinja ) -from ipypublish.convert.main import (dict_to_config, export_notebook) +from ipypublish.convert.main import dict_to_config, IpyPubMain def test_nbexport_latex_empty(ipynb1): @@ -10,8 +10,8 @@ def test_nbexport_latex_empty(ipynb1): config = dict_to_config({'LatexExporter.template_file': "template_name"}) exporter_cls = create_exporter_cls('nbconvert.exporters.LatexExporter') nb, path = nbmerge.merge_notebooks(ipynb1) - exporter, body, resources = export_notebook(nb, exporter_cls, - config, template) + exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, + config, template) assert exporter.output_mimetype == 'text/latex' assert body == '' @@ -25,8 +25,8 @@ def test_nbexport_latex_mkdown1(ipynb1): config = dict_to_config({'LatexExporter.template_file': "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb1) exporter_cls = create_exporter_cls('nbconvert.exporters.LatexExporter') - exporter, body, resources = export_notebook(nb, exporter_cls, - config, template) + exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, + config, template) assert exporter.output_mimetype == 'text/latex' assert body.strip() == 'test123' @@ -41,8 +41,8 @@ def test_nbexport_latex_mkdown2(ipynb1): config = dict_to_config({'LatexExporter.template_file': "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb1) exporter_cls = create_exporter_cls('nbconvert.exporters.LatexExporter') - exporter, body, resources = export_notebook(nb, exporter_cls, - config, template) + 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' @@ -53,8 +53,8 @@ def test_nbexport_html_empty(ipynb1): config = dict_to_config({'HTMLExporter.template_file': "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb1) exporter_cls = create_exporter_cls('nbconvert.exporters.HTMLExporter') - exporter, body, resources = export_notebook(nb, exporter_cls, - config, template) + exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, + config, template) assert exporter.output_mimetype == 'text/html' assert body == '' @@ -69,8 +69,8 @@ def test_nbexport_html_mkdown1(ipynb1): config = dict_to_config({'HTMLExporter.template_file': "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb1) exporter_cls = create_exporter_cls('nbconvert.exporters.HTMLExporter') - exporter, body, resources = export_notebook(nb, exporter_cls, - config, template) + exporter, body, resources = IpyPubMain().export_notebook(nb, exporter_cls, + config, template) assert exporter.output_mimetype == 'text/html' assert body.strip() == 'test123' @@ -86,8 +86,8 @@ def test_nbexport_html_mkdown2(ipynb1): config = dict_to_config({'HTMLExporter.template_file': "template_name"}) nb, path = nbmerge.merge_notebooks(ipynb1) exporter_cls = create_exporter_cls('nbconvert.exporters.HTMLExporter') - exporter, body, resources = export_notebook(nb, exporter_cls, - config, template) + 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' diff --git a/ipypublish/tests/test_nbmerge.py b/ipypublish/tests/test_nbmerge.py index 318d887..465b623 100644 --- a/ipypublish/tests/test_nbmerge.py +++ b/ipypublish/tests/test_nbmerge.py @@ -1,4 +1,4 @@ -from ipypublish.scripts import nbmerge +from ipypublish.convert import nbmerge def test_nbmerge_one_notebook(ipynb1): diff --git a/ipypublish/tests/test_nbpublish.py b/ipypublish/tests/test_nbpublish.py index c3dc9f3..53ae647 100644 --- a/ipypublish/tests/test_nbpublish.py +++ b/ipypublish/tests/test_nbpublish.py @@ -1,40 +1,22 @@ import os import re import io +import sys from difflib import context_diff import pytest -from ipypublish.convert.main import publish -from ipypublish.scripts import pdfexport +from ipypublish.convert.main import IpyPubMain from ipypublish.convert.config_manager import iter_all_export_paths from ipypublish.tests import TEST_FILES_DIR -def test_pdf_export(temp_folder): - - tex_content = """ -\\documentclass{article} -\\begin{document} -hallo world -\\end{document} -""" - - 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: - f.write(tex_content) - pdfexport.export_pdf(tex_path, temp_folder) - assert os.path.exists(pdf_path) - - def test_publish_ipynb1_latex(temp_folder, ipynb1): tex_path = os.path.join(temp_folder, os.path.splitext(ipynb1.name)[0] + '.tex') - publish(ipynb1, outpath=temp_folder) + IpyPubMain(config={"IpyPubMain": {"outpath": temp_folder}})(ipynb1) assert os.path.exists(tex_path) @@ -43,7 +25,7 @@ def test_publish_folder1_latex(ipynb_folder): tex_path = os.path.join(ipynb_folder, os.path.basename(ipynb_folder) + '.tex') - publish(ipynb_folder, outpath=ipynb_folder) + IpyPubMain(config={"IpyPubMain": {"outpath": ipynb_folder}})(ipynb_folder) assert os.path.exists(tex_path) @@ -55,30 +37,55 @@ def test_publish_ipynb1_pdf(temp_folder, ipynb1): pdf_path = os.path.join(temp_folder, os.path.splitext(ipynb1.name)[0] + '.pdf') - publish(ipynb1, outpath=temp_folder, create_pdf=True) + IpyPubMain(config={"IpyPubMain": + {"outpath": temp_folder, + "default_pporder_kwargs": {"create_pdf": True}}}, + )(ipynb1) assert os.path.exists(tex_path) assert os.path.exists(pdf_path) @pytest.mark.requires_latexmk -def test_publish_with_attachments_latex(temp_folder, ipynb_with_attach): +def test_publish_markdown_cells_latex(temp_folder, nb_markdown_cells): """ test notebook containing attachments """ - ipynb = ipynb_with_attach["input_file"] - tex_file = ipynb_with_attach["latex_ipypublish_main"] + ipynb = nb_markdown_cells["input_file"] + tex_file = nb_markdown_cells["latex_ipypublish_main"] tex_path = os.path.join(temp_folder, os.path.splitext(ipynb.name)[0] + '.tex') pdf_path = os.path.join(temp_folder, os.path.splitext(ipynb.name)[0] + '.pdf') - publish(ipynb, outpath=temp_folder, create_pdf=True) + IpyPubMain(config={"IpyPubMain": + {"outpath": temp_folder, + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}}} + )(ipynb) assert os.path.exists(tex_path) assert os.path.exists(pdf_path) compare_tex_files(tex_file, tex_path) +def test_publish_markdown_cells_rst(temp_folder, nb_markdown_cells): + """ test notebook containing attachments + + """ + ipynb = nb_markdown_cells["input_file"] + rst_file = nb_markdown_cells["sphinx_ipypublish_main"] + + rst_path = os.path.join(temp_folder, + os.path.splitext(ipynb.name)[0] + '.rst') + + IpyPubMain(config={"IpyPubMain": { + "outpath": temp_folder, + "conversion": "sphinx_ipypublish_main"}} + )(ipynb) + assert os.path.exists(rst_path) + compare_rst_files(rst_file, rst_path) + + @pytest.mark.requires_latexmk def test_publish_withbib(temp_folder, ipynb_with_bib): tex_path = os.path.join(temp_folder, @@ -86,7 +93,10 @@ def test_publish_withbib(temp_folder, ipynb_with_bib): pdf_path = os.path.join(temp_folder, os.path.splitext(ipynb_with_bib.name)[0] + '.pdf') - publish(ipynb_with_bib, outpath=temp_folder, create_pdf=True) + IpyPubMain(config={"IpyPubMain": { + "outpath": temp_folder, + "default_pporder_kwargs": {"create_pdf": True}}} + )(ipynb_with_bib) assert os.path.exists(tex_path) assert os.path.exists(pdf_path) @@ -114,10 +124,12 @@ def test_publish_complex_latex(ipynb_folder_with_external): basename + '.tex') pdf_path = os.path.join(input_folder, basename + '.pdf') - publish(input_folder, - conversion='latex_ipypublish_main', - outpath=input_folder, - create_pdf=True, pdf_debug=True) + IpyPubMain(config={"IpyPubMain": + {"outpath": input_folder, + "conversion": "latex_ipypublish_main", + "default_pporder_kwargs": {"create_pdf": True}, + "default_ppconfig_kwargs": {"pdf_debug": True}}} + )(input_folder) assert os.path.exists(tex_path) assert os.path.exists(pdf_path) compare_tex_files(expected, tex_path) @@ -136,9 +148,9 @@ def test_publish_complex_html(ipynb_folder_with_external): basename = os.path.basename(input_folder) html_path = os.path.join(input_folder, basename + '.html') - publish(input_folder, - conversion='html_ipypublish_main', - outpath=input_folder) + IpyPubMain(config={"IpyPubMain": {"outpath": input_folder, + "conversion": "html_ipypublish_main"} + })(input_folder) assert os.path.exists(html_path) compare_html_files(expected, html_path) @@ -156,19 +168,44 @@ def test_publish_complex_slides(ipynb_folder_with_external): basename = os.path.basename(input_folder) html_path = os.path.join(input_folder, basename + '.slides.html') - publish(input_folder, - conversion='slides_ipypublish_main', - outpath=input_folder) + IpyPubMain(config={"IpyPubMain": + {"outpath": input_folder, + "conversion": "slides_ipypublish_main", + "default_pporder_kwargs": {"slides": True}} + })(input_folder) assert os.path.exists(html_path) compare_html_files(expected, html_path) +def test_publish_complex_rst(ipynb_folder_with_external): + """ includes: + + - internal (image) files + - external logo and bib + + """ + input_folder = ipynb_folder_with_external["input_folder"] + expected = ipynb_folder_with_external["sphinx_ipypublish_all"] + + basename = os.path.basename(input_folder) + rst_path = os.path.join(input_folder, + basename + '.rst') + + IpyPubMain(config={"IpyPubMain": {"outpath": input_folder, + "conversion": "sphinx_ipypublish_all"} + })(input_folder) + assert os.path.exists(rst_path) + compare_rst_files(expected, rst_path) + + def test_publish_ipynb1_html(temp_folder, ipynb1): html_path = os.path.join(temp_folder, os.path.splitext(ipynb1.name)[0] + '.html') - publish(ipynb1, conversion='html_ipypublish_main', outpath=temp_folder) + IpyPubMain(config={"IpyPubMain": {"outpath": temp_folder, + "conversion": "html_ipypublish_main"} + })(ipynb1) assert os.path.exists(html_path) @@ -177,8 +214,11 @@ def test_publish_ipynb1_slides(temp_folder, ipynb1): html_path = os.path.join(temp_folder, os.path.splitext( ipynb1.name)[0] + '.slides.html') - publish(ipynb1, conversion='slides_ipypublish_main', - outpath=temp_folder) + IpyPubMain(config={"IpyPubMain": + {"outpath": temp_folder, + "conversion": "slides_ipypublish_main", + "default_pporder_kwargs": {"slides": True}} + })(ipynb1) assert os.path.exists(html_path) @@ -189,12 +229,35 @@ def test_publish_ipynb1_slides(temp_folder, ipynb1): def test_publish_run_all_plugins(temp_folder, ipynb1, plugin_name, plugin_path): - outpath, exporter = publish( - ipynb1, conversion=plugin_name, outpath=temp_folder) + 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 = "") + return + + # TODO "sphinx_ipypublish_all.run" launches a webbrowser tab + + outdata = IpyPubMain(config={"IpyPubMain": {"outpath": temp_folder, + "conversion": plugin_name}} + )(ipynb1) + + exporter = outdata["exporter"] + outname = os.path.splitext(ipynb1.name)[0] + exporter.file_extension outfile = os.path.join(temp_folder, outname) testfile = os.path.join(TEST_FILES_DIR, "ipynb1_converted", plugin_name + exporter.file_extension) + + if plugin_name in ["python_with_meta_stream", "sphinx_ipypublish_all.ext"]: + return + if plugin_name in [ + "sphinx_ipypublish_all.run", "sphinx_ipypublish_main.run"]: + assert os.path.exists(os.path.join( + temp_folder, "build", "html", + os.path.splitext(ipynb1.name)[0] + ".html")) + return + assert os.path.exists(outfile), "could not find: {} for {}".format( outfile, plugin_name) assert os.path.exists(testfile), "could not find: {} for {}".format( @@ -204,7 +267,32 @@ def test_publish_run_all_plugins(temp_folder, ipynb1, compare_tex_files(testfile, outfile) elif exporter.output_mimetype == 'text/html': compare_html_files(testfile, outfile) - # TODO test rst output + elif exporter.output_mimetype == 'text/restructuredtext': + compare_rst_files(testfile, outfile) + + +def compare_rst_files(testpath, outpath): + # only compare body of html, since styles differ by + # nbconvert/pandoc version (e.g. different versions of font-awesome) + + output = [] + for path in [testpath, outpath]: + + with io.open(str(path), encoding='utf8') as fobj: + content = fobj.read() + + # python 3.5 used .jpg instead of .jpeg + content = content.replace(".jpg", ".jpeg") + + output.append(content) + + test_content, out_content = output + + # 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): @@ -229,6 +317,9 @@ def compare_html_files(testpath, outpath): script_rgx = re.compile("\\(.*)\\", re.DOTALL) content = script_rgx.sub("", content) + # remove trailing whitespace + content = "\n".join([l.rstrip() for l in content.splitlines()]) + output.append(content) test_content, out_content = output @@ -274,11 +365,15 @@ def compare_tex_files(testpath, outpath): ("\\\\expandafter\\\\def\\\\csname " "PY\\@tok\\@[0-9a-zA-Z]*\\\\endcsname[^\n]*"), re.MULTILINE) - content = pyg_rgx.sub("\", content) + content = pyg_rgx.sub(r"\", content) # also remove all space from start of lines - space_rgx = re.compile("^[\s]*", re.MULTILINE) + 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()]) + output.append(content) test_content, out_content = output diff --git a/ipypublish/tests/test_postprocessors.py b/ipypublish/tests/test_postprocessors.py new file mode 100644 index 0000000..e301765 --- /dev/null +++ b/ipypublish/tests/test_postprocessors.py @@ -0,0 +1,29 @@ +import os +from ipypublish.postprocessors.pdfexport import PDFExport +from ipypublish.postprocessors.reveal_serve import RevealServer + +def test_pdf_export(temp_folder): + + tex_content = """ +\\documentclass{article} +\\begin{document} +hallo world +\\end{document} +""" + + 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: + f.write(tex_content) + + pdfexport = PDFExport() + pdfexport.postprocess(tex_content, 'text/latex', tex_path) + + assert os.path.exists(pdf_path) + + +def test_reveal_server(): + RevealServer() + # TODO test reveal server runs correctly, + # possibly use https://github.com/eugeniy/pytest-tornado \ No newline at end of file diff --git a/ipypublish/utils.py b/ipypublish/utils.py index 4b9990a..d3ffbcd 100644 --- a/ipypublish/utils.py +++ b/ipypublish/utils.py @@ -3,9 +3,10 @@ import inspect import importlib import re +import pkg_resources from six import string_types -import yaml # TODO use ruamel.yaml instead? +import ruamel.yaml as yaml # python 2/3 compatibility try: @@ -83,7 +84,7 @@ def read_file_from_module(module_path, file_name, jtype, logger, interp_ext=False, ext_types=( ("json", (".json")), - ("yaml", (".yaml", ".yaml.j2")))): + ("yaml", (".yaml", ".yaml.j2", "yaml.tex.j2")))): """load a file situated in a python module if ``interp_ext=True``: @@ -116,3 +117,43 @@ def get_valid_filename(s): """ s = str(s).strip().replace(' ', '_') return re.sub(r'(?u)[^-\w.]', '', s) + + +def find_entry_point(name, group, logger, preferred=None): + """find an entry point by name and group + + Parameters + ---------- + name: str + name of entry point + group: str + group of entry point + preferred: str + if multiple matches are found, prefer one from this module + + """ + 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) + 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)] + if len(oentry_points) != 1: + handle_error( + "Multiple {0} plugins found for " + "{1}: {2}".format(group, name, entry_points), + pkg_resources.ResolutionError, logger) + logger.info( + "Multiple {0} plugins found for {1}, " + "defaulting to the {2} version".format(group, name, preferred)) + entry_point = oentry_points[0] + else: + entry_point = entry_points[0] + return entry_point.load() diff --git a/requirements-lock.txt b/requirements-lock.txt index 4914528..a066365 100644 --- a/requirements-lock.txt +++ b/requirements-lock.txt @@ -6,25 +6,34 @@ # bibtexparser==1.1.0 bleach==3.1.0 # via nbconvert +click==7.0 # via panflute decorator==4.3.2 # via traitlets defusedxml==0.5.0 # via nbconvert +docutils==0.14 entrypoints==0.3 # via nbconvert -future==0.17.1 # via bibtexparser +future==0.17.1 # via bibtexparser, panflute ipython-genutils==0.2.0 # via nbformat, traitlets jinja2==2.10 jsonextended==0.7.7 jsonschema==2.6.0 jupyter-core==4.4.0 # via nbconvert, nbformat +jupytext==0.8.6 markupsafe==1.1.0 # via jinja2 mistune==0.8.4 # via nbconvert +mock==2.0.0 # via jupytext nbconvert==5.4.0 nbformat==4.4.0 pandocfilters==1.4.2 # via nbconvert +panflute==1.11.2 pathlib2==2.3.3 +pbr==5.1.2 # via mock pygments==2.3.1 # via nbconvert pyparsing==2.3.1 # via bibtexparser -pyyaml==4.2b1 +pyyaml==3.13 # via jupytext, panflute +ruamel.yaml==0.15.87 +shutilwhich==1.1.0 # via panflute six==1.12.0 +testfixtures==6.5.0 # via jupytext testpath==0.4.2 # via nbconvert tornado==5.1.1 traitlets==4.3.2 diff --git a/requirements.txt b/requirements.txt index 3cdb30b..3446f23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,8 @@ bibtexparser jsonextended>=0.7 six jsonschema -pyyaml \ No newline at end of file +ruamel.yaml +panflute +docutils +jupytext +shutilwhich \ No newline at end of file diff --git a/setup.py b/setup.py index 489e86b..a9405e9 100755 --- a/setup.py +++ b/setup.py @@ -10,8 +10,6 @@ with open('requirements.txt') as f: requirements = f.read().splitlines() -with open('test_requirements.txt') as f: - test_requirements = f.read().splitlines() with io.open('README.md') as readme: readme_str = readme.read() @@ -25,10 +23,24 @@ long_description=readme_str, long_description_content_type='text/markdown', install_requires=requirements, - tests_require=test_requirements, extras_require={ - "docs": { - "sphinx" + "sphinx": { + "sphinx>=1.6", + "sphinxcontrib-bibtex" + }, + "tests": { + "pytest>=3.6", + "pytest-cov", + "coverage", + "pillow", + "nbsphinx", + "ipykernel" + }, + "science": { + "matplotlib", + "numpy", + "pandas", + "sympy" } }, license='MIT', @@ -64,7 +76,22 @@ entry_points={ 'console_scripts': [ 'nbpublish = ipypublish.frontend.nbpublish:run', - 'nbpresent = ipypublish.frontend.nbpresent: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]' ] } ) diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index 5387a4e..0000000 --- a/test_requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -pytest>=3.6 -pytest-cov -coverage -pillow -nbsphinx \ No newline at end of file