From 1ae1833864f4395818761f52504652821ba7bcbe Mon Sep 17 00:00:00 2001 From: Jake Vanderplas Date: Thu, 2 May 2013 22:47:52 -0700 Subject: [PATCH] add notebook tag --- liquid_tags/img.py | 1 - liquid_tags/include_code.py | 6 +- liquid_tags/mdx_liquid_tags.py | 3 +- liquid_tags/notebook.py | 148 +++++++++++++++++++++++++++++++++ liquid_tags/video.py | 2 - 5 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 liquid_tags/notebook.py diff --git a/liquid_tags/img.py b/liquid_tags/img.py index 68a3b1a..26039e4 100644 --- a/liquid_tags/img.py +++ b/liquid_tags/img.py @@ -36,7 +36,6 @@ ReTitleAlt = re.compile("""(?:"|')(?P[^"']+)?(?:"|')\s+(?:"|')(?P<alt>[^" @LiquidTags.register('img') def img(preprocessor, tag, markup): - markup = markup.strip() attrs = None # Parse the markup string diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py index 302d875..0d6b822 100644 --- a/liquid_tags/include_code.py +++ b/liquid_tags/include_code.py @@ -8,7 +8,7 @@ Syntax ------ {% include_code path/to/code [Title text] %} -The "path to code" is relative to the code path in +The "path to code" is relative to the code subdirectory of the content directory (TODO: allow this to be set in configs). Example @@ -37,8 +37,6 @@ FORMAT = re.compile(r"""^(?:\s+)?(?P<src>\S+)(?:\s+)?(?:(?:lang:)(?P<lang>\S+))? @LiquidTags.register('include_code') def include_code(preprocessor, tag, markup): - markup = markup.strip() - title = None lang = None src = None @@ -59,7 +57,7 @@ def include_code(preprocessor, tag, markup): code_path = os.path.join('content', code_dir, src) if not os.path.exists(code_path): - return "File {0} could not be found".format(code_path) + raise ValueError("File {0} could not be found".format(code_path)) code = open(code_path).read() diff --git a/liquid_tags/mdx_liquid_tags.py b/liquid_tags/mdx_liquid_tags.py index 3e32011..3204f87 100644 --- a/liquid_tags/mdx_liquid_tags.py +++ b/liquid_tags/mdx_liquid_tags.py @@ -9,6 +9,7 @@ Where "tag" is associated with some user-defined extension. These result in a preprocess step within markdown that produces either markdown or html. """ +import warnings import markdown import itertools import re @@ -35,7 +36,7 @@ class _LiquidTagsPreprocessor(markdown.preprocessors.Preprocessor): tag = EXTRACT_TAG.match(markup).groups()[0] markup = EXTRACT_TAG.sub('', markup, 1) if tag in self._tags: - liquid_tags[i] = self._tags[tag](self, tag, markup) + liquid_tags[i] = self._tags[tag](self, tag, markup.strip()) # add an empty string to liquid_tags so that chaining works liquid_tags.append('') diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py new file mode 100644 index 0000000..f105c57 --- /dev/null +++ b/liquid_tags/notebook.py @@ -0,0 +1,148 @@ +""" +Notebook Tag +------------ +This is a liquid-style tag to include a static html rendering of an IPython +notebook in a blog post. + +Syntax +------ +{% notebook filename.ipynb %} + +The file should be specified relative to the ``notebook`` subdirectory of the +content directory. [TODO: make this configurable]. +This will include the IPython notebook in the file. + +Details +------- +Because the conversion and formatting of notebooks is rather involved, there +are a few extra steps required for this plugin: + +- First, the plugin requires that the nbconvert package [1]_ to be in the + python path. For example, in bash, this can be set via + + >$ export PYTHONPATH=/path/to/nbconvert/ + +- After typing "make html" when using the notebook tag, a file called + ``_nb_header.html`` will be produced in the main directory. The content + of the file should be included in the header of the theme. An easy way + to accomplish this is to add the following lines within the header template + of the theme you use: + + {% if IPYNB_FORMATTING %} + {{ IPYNB_FORMATTING }} + {% endif %} + + and in your ``pelicanconf.py`` file, include the line: + + IPYNB_FORMATTING = open('_nb_header.html').read().decode('utf-8') + +[1] https://github.com/ipython/nbconvert +""" +import re +import os +from .mdx_liquid_tags import LiquidTags +from converters import ConverterBloggerHTML # part of the nbconvert package + +SYNTAX = "{% notebook /path/to/notebook.ipynb %}" +FORMAT = re.compile(r"""^(?:\s+)?(?P<src>\S+)(?:\s+)?$""") + + +def process_body(body): + body = '\n'.join(body) + + # replace the highlight tags + body = body.replace('class="highlight"', 'class="highlight-ipynb"') + + # specify <pre> tags + body = body.replace('<pre', '<pre class="ipynb"') + + # create a special div for notebook + body = '<div class="ipynb">\n\n' + body + "\n\n</div>" + + # specialize headers + for h in '123456': + body = body.replace('<h%s' % h, '<h%s class="ipynb"' % h) + + return body.split('\n') + + +def process_header(header): + header = '\n'.join(header) + + # replace the highlight tags + header = header.replace('highlight', 'highlight-ipynb') + + # specify <pre> tags + header = header.replace('html, body', '\n'.join(('pre.ipynb {', + ' color: black;', + ' background: #f7f7f7;', + ' border: 0;', + ' box-shadow: none;', + ' margin-bottom: 0;', + ' padding: 0;' + '}\n', + 'html, body'))) + + # create a special div for notebook + header = header.replace('body {', 'div.ipynb {') + + # specialize headers + header = header.replace('html, body,', + '\n'.join((('h1.ipynb h2.ipynb h3.ipynb ' + 'h4.ipynb h5.ipynb h6.ipynb {'), + 'h1.ipynb h2.ipynb ... {', + ' margin: 0;', + ' padding: 0;', + ' border: 0;', + ' font-size: 100%;', + ' font: inherit;', + ' vertical-align: baseline;', + '}\n', + 'html, body,'))) + + header = header.replace('html, body,', + '/*html, body,*/') + header = header.replace('h1, h2, h3, h4, h5, h6,', + '/*h1, h2, h3, h4, h5, h6,*/') + + return header.split('\n') + + +@LiquidTags.register('notebook') +def notebook(preprocessor, tag, markup): + match = FORMAT.search(markup) + if match: + argdict = match.groupdict() + src = argdict['src'] + else: + raise ValueError("Error processing input, " + "expected syntax: {0}".format(SYNTAX)) + + # TODO: make the notebook directory a configurable setting + nb_dir = 'notebooks' + nb_path = os.path.join('content', nb_dir, src) + url = '/{0}/{1}/{2}'.format('static', nb_dir, src) + + if not os.path.exists(nb_path): + raise ValueError("File {0} could not be found".format(nb_path)) + + # Call the notebook converter + converter = ConverterBloggerHTML(nb_path) + converter.read() + + header_lines = process_header(converter.header_body()) + + print ("\n *** Writing styles to _nb_header.html: " + "this should be included in the theme.\n") + open('_nb_header.html', 'w').write('\n'.join(header_lines).encode('utf-8')) + + body_lines = process_body(converter.main_body('\n')) + + body = preprocessor.configs.htmlStash.store('\n'.join(body_lines), + safe=True) + return body + + +#---------------------------------------------------------------------- +# This import allows image tag to be a Pelican plugin +from liquid_tags import register diff --git a/liquid_tags/video.py b/liquid_tags/video.py index 5ce441a..2bff9fb 100644 --- a/liquid_tags/video.py +++ b/liquid_tags/video.py @@ -35,8 +35,6 @@ VID_TYPEDICT = {'.mp4':"type='video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"'", @LiquidTags.register('video') def video(preprocessor, tag, markup): - markup = markup.strip() - videos = [] width = None height = None