From 57d62ce99f07e5a0e3d7c913018b49baaa6a5575 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas Date: Thu, 2 May 2013 08:36:54 -0700 Subject: [PATCH 01/24] initial commit: img, video, include_code tags --- liquid_tags/Readme.md | 52 +++++++++++++++++++ liquid_tags/__init__.py | 1 + liquid_tags/img.py | 66 ++++++++++++++++++++++++ liquid_tags/include_code.py | 92 ++++++++++++++++++++++++++++++++++ liquid_tags/liquid_tags.py | 14 ++++++ liquid_tags/mdx_liquid_tags.py | 76 ++++++++++++++++++++++++++++ liquid_tags/video.py | 72 ++++++++++++++++++++++++++ 7 files changed, 373 insertions(+) create mode 100644 liquid_tags/Readme.md create mode 100644 liquid_tags/__init__.py create mode 100644 liquid_tags/img.py create mode 100644 liquid_tags/include_code.py create mode 100644 liquid_tags/liquid_tags.py create mode 100644 liquid_tags/mdx_liquid_tags.py create mode 100644 liquid_tags/video.py diff --git a/liquid_tags/Readme.md b/liquid_tags/Readme.md new file mode 100644 index 0000000..30c4734 --- /dev/null +++ b/liquid_tags/Readme.md @@ -0,0 +1,52 @@ +# Liquid-style Tags +*Author: Jake Vanderplas * + +This plugin allows liquid-style tags to be inserted into markdown within +Pelican documents. Liquid uses tags bounded by ``{% ... %}``, and is used +to extend markdown in other blogging platforms such as octopress. + +This set of extensions does not actually interface with liquid, but allows +users to define their own liquid-style tags which will be inserted into +the markdown preprocessor stream. There are several built-in tags, which +can be added as follows. + +First, in your pelicanconf.py file, add the plugins you want to use: + + PLUGIN_PATH = '/path/to/pelican-plugins' + PLUGINS = ['liquid_tags.img', 'liquid_tags.video', + 'liquid_tags.include_code'] + +There are several options available + +## Image Tag +To insert a sized and labeled image in your document, enable the +``liquid_tags.video`` plugin and use the following: + +{% img [class name(s)] path/to/image [width [height]] [title text | "title text" ["alt text"]] %} + + +## Video Tag +To insert flash/HTML5-friendly video into a post, enable the +``liquid_tags.video`` plugin, and add to your document: + + {% video /url/to/video.mp4 [width] [height] [/path/to/poster.png] %} + +The width and height are in pixels, and can be optionally specified. If they +are not, then the original video size will be used. The poster is an image +which is used as a preview of the video. + +To use a video from file, make sure it's in a static directory and put in +the appropriate url. + +## Include Code +To include code from a file in your document with a link to the original +file, enable the ``liquid_tags.include_code`` plugin, and add to your +document: + + {% include_code myscript.py [Title text] %} + +The script must be in the ``code`` subdirectory of your content folder, and +in order for the resulting hyperlink to work, this directory must be listed +under the STATIC_PATHS setting, e.g.: + + STATIC_PATHS = ['images', 'code'] \ No newline at end of file diff --git a/liquid_tags/__init__.py b/liquid_tags/__init__.py new file mode 100644 index 0000000..eabcd63 --- /dev/null +++ b/liquid_tags/__init__.py @@ -0,0 +1 @@ +from .liquid_tags import * diff --git a/liquid_tags/img.py b/liquid_tags/img.py new file mode 100644 index 0000000..68a3b1a --- /dev/null +++ b/liquid_tags/img.py @@ -0,0 +1,66 @@ +""" +Image Tag +--------- +This implements a Liquid-style image tag for Pelican, +based on the octopress image tag [1]_ + +Syntax +------ +{% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | "title text" ["alt text"]] %} + +Examples +-------- +{% img /images/ninja.png Ninja Attack! %} +{% img left half http://site.com/images/ninja.png Ninja Attack! %} +{% img left half http://site.com/images/ninja.png 150 150 "Ninja Attack!" "Ninja in attack posture" %} + +Output +------ + +Ninja Attack! +Ninja in attack posture + +[1] https://github.com/imathis/octopress/blob/master/plugins/image_tag.rb +""" +import re +from .mdx_liquid_tags import LiquidTags + +SYNTAX = '{% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | "title text" ["alt text"]] %}' + +# Regular expression to match the entire syntax +ReImg = re.compile("""(?P\S.*\s+)?(?P(?:https?:\/\/|\/|\S+\/)\S+)(?:\s+(?P\d+))?(?:\s+(?P\d+))?(?P\s+.+)?""") + +# Regular expression to split the title and alt text +ReTitleAlt = re.compile("""(?:"|')(?P<title>[^"']+)?(?:"|')\s+(?:"|')(?P<alt>[^"']+)?(?:"|')""") + + +@LiquidTags.register('img') +def img(preprocessor, tag, markup): + markup = markup.strip() + attrs = None + + # Parse the markup string + match = ReImg.search(markup) + if match: + attrs = dict([(key, val.strip()) + for (key, val) in match.groupdict().iteritems() if val]) + else: + raise ValueError('Error processing input. ' + 'Expected syntax: {0}'.format(SYNTAX)) + + # Check if alt text is present -- if so, split it from title + if 'title' in attrs: + match = ReTitleAlt.search(attrs['title']) + if match: + attrs.update(match.groupdict()) + if not attrs.get('alt'): + attrs['alt'] = attrs['title'] + + # Return the formatted text + return "<img {0}>".format(' '.join('{0}="{1}"'.format(key, val) + for (key, val) in attrs.iteritems())) + +#---------------------------------------------------------------------- +# This import allows image tag to be a Pelican plugin +from liquid_tags import register + diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py new file mode 100644 index 0000000..302d875 --- /dev/null +++ b/liquid_tags/include_code.py @@ -0,0 +1,92 @@ +""" +Include Code Tag +---------------- +This implements a Liquid-style video tag for Pelican, +based on the octopress video tag [1]_ + +Syntax +------ +{% include_code path/to/code [Title text] %} + +The "path to code" is relative to the code path in +the content directory (TODO: allow this to be set in configs). + +Example +------- +{% include_code myscript.py %} + +This will import myscript.py from content/downloads/code/myscript.py +and output the contents in a syntax highlighted code block inside a figure, +with a figcaption listing the file name and download link. + +The file link will be valid only if the 'code' directory is listed +in the STATIC_PATHS setting, e.g.: + + STATIC_PATHS = ['images', 'code'] + +[1] https://github.com/imathis/octopress/blob/master/plugins/include_code.rb +""" +import re +import os +from .mdx_liquid_tags import LiquidTags + + +SYNTAX = "{% include_code /path/to/code.py [lang:python] [title] %}" +FORMAT = re.compile(r"""^(?:\s+)?(?P<src>\S+)(?:\s+)?(?:(?:lang:)(?P<lang>\S+))?(?:\s+)?(?P<title>.+)?$""") + + +@LiquidTags.register('include_code') +def include_code(preprocessor, tag, markup): + markup = markup.strip() + + title = None + lang = None + src = None + + match = FORMAT.search(markup) + if match: + argdict = match.groupdict() + title = argdict['title'] + lang = argdict['lang'] + src = argdict['src'] + + if not src: + raise ValueError("Error processing input, " + "expected syntax: {0}".format(SYNTAX)) + + # TODO: make this directory a configurable setting + code_dir = 'code' + 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) + + code = open(code_path).read() + + if title: + title = "{0} {1}".format(title, os.path.basename(src)) + else: + title = os.path.basename(src) + + url = '/{0}/{1}/{2}'.format('static', code_dir, src) + + open_tag = ("<figure class='code'>\n<figcaption><span>{title}</span> " + "<a href='{url}'>download</a></figcaption>".format(title=title, + url=url)) + close_tag = "</figure>" + + # store HTML tags in the stash. This prevents them from being + # modified by markdown. + open_tag = preprocessor.configs.htmlStash.store(open_tag, safe=True) + close_tag = preprocessor.configs.htmlStash.store(close_tag, safe=True) + + source = (open_tag + + '\n\n ' + '\n '.join(code.split('\n')) + '\n\n' + + close_tag + '\n') + + return source + + +#---------------------------------------------------------------------- +# This import allows image tag to be a Pelican plugin +from liquid_tags import register diff --git a/liquid_tags/liquid_tags.py b/liquid_tags/liquid_tags.py new file mode 100644 index 0000000..881d8ea --- /dev/null +++ b/liquid_tags/liquid_tags.py @@ -0,0 +1,14 @@ +from pelican import signals +from mdx_liquid_tags import LiquidTags +from pelican.readers import EXTENSIONS + +def addLiquidTags(gen): + if not gen.settings.get('MD_EXTENSIONS'): + MDReader = EXTENSIONS['markdown'] + gen.settings['MD_EXTENSIONS'] = MDReader.default_extensions + + if LiquidTags not in gen.settings['MD_EXTENSIONS']: + gen.settings['MD_EXTENSIONS'].append(LiquidTags()) + +def register(): + signals.initialized.connect(addLiquidTags) diff --git a/liquid_tags/mdx_liquid_tags.py b/liquid_tags/mdx_liquid_tags.py new file mode 100644 index 0000000..3e32011 --- /dev/null +++ b/liquid_tags/mdx_liquid_tags.py @@ -0,0 +1,76 @@ +""" +Markdown Extension for Liquid-style Tags +---------------------------------------- +A markdown extension to allow user-defined tags of the form:: + + {% tag arg1 arg2 ... argn %} + +Where "tag" is associated with some user-defined extension. +These result in a preprocess step within markdown that produces +either markdown or html. +""" +import markdown +import itertools +import re +import os +from functools import wraps + +# Define some regular expressions +LIQUID_TAG = re.compile(r'\{%.*?%\}') +EXTRACT_TAG = re.compile(r'(?:\s*)(\S+)(?:\s*)') + + +class _LiquidTagsPreprocessor(markdown.preprocessors.Preprocessor): + _tags = {} + def __init__(self, configs): + self.configs = configs + + def run(self, lines): + page = '\n'.join(lines) + liquid_tags = LIQUID_TAG.findall(page) + + for i, markup in enumerate(liquid_tags): + # remove {% %} + markup = markup[2:-2] + 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) + + # add an empty string to liquid_tags so that chaining works + liquid_tags.append('') + + # reconstruct string + page = ''.join(itertools.chain(*zip(LIQUID_TAG.split(page), + liquid_tags))) + + # resplit the lines + return page.split("\n") + + +class LiquidTags(markdown.Extension): + """Wrapper for MDPreprocessor""" + @classmethod + def register(cls, tag): + """Decorator to register a new include tag""" + def dec(func): + if tag in _LiquidTagsPreprocessor._tags: + warnings.warn("Enhanced Markdown: overriding tag '%s'" % tag) + _LiquidTagsPreprocessor._tags[tag] = func + return func + return dec + + def extendMarkdown(self, md, md_globals): + self.htmlStash = md.htmlStash + md.registerExtension(self) + # for the include_code preprocessor, we need to re-run the + # fenced code block preprocessor after substituting the code. + # Because the fenced code processor is run before, {% %} tags + # within equations will not be parsed as an include. + md.preprocessors.add('mdincludes', + _LiquidTagsPreprocessor(self), ">html_block") + + +def makeExtension(configs=None): + """Wrapper for a MarkDown extension""" + return LiquidTags(configs=configs) diff --git a/liquid_tags/video.py b/liquid_tags/video.py new file mode 100644 index 0000000..5ce441a --- /dev/null +++ b/liquid_tags/video.py @@ -0,0 +1,72 @@ +""" +Video Tag +--------- +This implements a Liquid-style video tag for Pelican, +based on the octopress video tag [1]_ + +Syntax +------ +{% video url/to/video [width height] [url/to/poster] %} + +Example +------- +{% video http://site.com/video.mp4 720 480 http://site.com/poster-frame.jpg %} + +Output +------ +<video width='720' height='480' preload='none' controls poster='http://site.com/poster-frame.jpg'> + <source src='http://site.com/video.mp4' type='video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"'/> +</video> + +[1] https://github.com/imathis/octopress/blob/master/plugins/video_tag.rb +""" +import os +import re +from .mdx_liquid_tags import LiquidTags + +SYNTAX = "{% video url/to/video [url/to/video] [url/to/video] [width height] [url/to/poster] %}" + +VIDEO = re.compile(r'(/\S+|https?:\S+)(\s+(/\S+|https?:\S+))?(\s+(/\S+|https?:\S+))?(\s+(\d+)\s(\d+))?(\s+(/\S+|https?:\S+))?') + +VID_TYPEDICT = {'.mp4':"type='video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"'", + '.ogv':"type='video/ogg; codecs=theora, vorbis'", + '.webm':"type='video/webm; codecs=vp8, vorbis'"} + + +@LiquidTags.register('video') +def video(preprocessor, tag, markup): + markup = markup.strip() + + videos = [] + width = None + height = None + poster = None + + match = VIDEO.search(markup) + if match: + groups = match.groups() + videos = [g for g in groups[0:6:2] if g] + width = groups[6] + height = groups[7] + poster = groups[9] + + if any(videos): + video_out = "<video width='{width}' height='{height}' preload='none' controls poster='{poster}'>".format(width=width, height=height, poster=poster) + for vid in videos: + base, ext = os.path.splitext(vid) + if ext not in VID_TYPEDICT: + raise ValueError("Unrecognized video extension: " + "{0}".format(ext)) + video_out += ("<source src='{0}' " + "{1}>".format(vid, VID_TYPEDICT[ext])) + video_out += "</video>" + else: + raise ValueError("Error processing input, " + "expected syntax: {0}".format(SYNTAX)) + + return video_out + + +#---------------------------------------------------------------------- +# This import allows image tag to be a Pelican plugin +from liquid_tags import register From 1ae1833864f4395818761f52504652821ba7bcbe Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Thu, 2 May 2013 22:47:52 -0700 Subject: [PATCH 02/24] 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<title>[^"']+)?(?:"|')\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 From 87f81ab4f5c2f6bbfad120874025541955126386 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Fri, 3 May 2013 07:22:31 -0700 Subject: [PATCH 03/24] add ~ files to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 939db29..3bb343a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc -*.log \ No newline at end of file +*.log +*~ \ No newline at end of file From a934cde4425728bf951596fbf4986815afae1352 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Fri, 3 May 2013 07:22:41 -0700 Subject: [PATCH 04/24] fix notebook formatting --- liquid_tags/notebook.py | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index f105c57..3d37f05 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -28,13 +28,13 @@ are a few extra steps required for this plugin: to accomplish this is to add the following lines within the header template of the theme you use: - {% if IPYNB_FORMATTING %} - {{ IPYNB_FORMATTING }} + {% if EXTRA_HEADER %} + {{ EXTRA_HEADER }} {% endif %} and in your ``pelicanconf.py`` file, include the line: - IPYNB_FORMATTING = open('_nb_header.html').read().decode('utf-8') + EXTRA_HEADER = open('_nb_header.html').read().decode('utf-8') [1] https://github.com/ipython/nbconvert """ @@ -51,7 +51,7 @@ def process_body(body): body = '\n'.join(body) # replace the highlight tags - body = body.replace('class="highlight"', 'class="highlight-ipynb"') + body = body.replace('highlight', 'highlight-ipynb') # specify <pre> tags body = body.replace('<pre', '<pre class="ipynb"') @@ -72,7 +72,7 @@ def process_header(header): # replace the highlight tags header = header.replace('highlight', 'highlight-ipynb') - # specify <pre> tags + # specify pre tags header = header.replace('html, body', '\n'.join(('pre.ipynb {', ' color: black;', ' background: #f7f7f7;', @@ -83,27 +83,18 @@ def process_header(header): '}\n', 'html, body'))) + # create a special div for notebook - header = header.replace('body {', 'div.ipynb {') + R = re.compile(r'^body ?{', re.MULTILINE) + header = R.sub('div.ipynb {', header) - # 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,'))) + # specify all headers + R = re.compile(r'^(h[1-6])', re.MULTILINE) + repl = lambda match: '.ipynb ' + match.groups()[0] + header = R.sub(repl, header) - header = header.replace('html, body,', - '/*html, body,*/') - header = header.replace('h1, h2, h3, h4, h5, h6,', - '/*h1, h2, h3, h4, h5, h6,*/') + # substitude ipynb class for html and body modifiers + header = header.replace('html, body', '.ipynb div,') return header.split('\n') From 790fe92e3e73a583b32eb5b11b91c53c101b6d8e Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Fri, 3 May 2013 07:36:36 -0700 Subject: [PATCH 05/24] make code & notebook directories configurable --- liquid_tags/include_code.py | 10 ++++++---- liquid_tags/liquid_tags.py | 5 ++++- liquid_tags/notebook.py | 11 ++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py index 0d6b822..e39825f 100644 --- a/liquid_tags/include_code.py +++ b/liquid_tags/include_code.py @@ -8,8 +8,11 @@ Syntax ------ {% include_code path/to/code [Title text] %} -The "path to code" is relative to the code subdirectory of -the content directory (TODO: allow this to be set in configs). +The "path to code" is specified relative to the ``code`` subdirectory of +the content directory Optionally, this subdirectory can be specified in the +config file: + + CODE_DIR = 'code' Example ------- @@ -52,8 +55,7 @@ def include_code(preprocessor, tag, markup): raise ValueError("Error processing input, " "expected syntax: {0}".format(SYNTAX)) - # TODO: make this directory a configurable setting - code_dir = 'code' + code_dir = preprocessor.configs.config['code_dir'] code_path = os.path.join('content', code_dir, src) if not os.path.exists(code_path): diff --git a/liquid_tags/liquid_tags.py b/liquid_tags/liquid_tags.py index 881d8ea..d6e2457 100644 --- a/liquid_tags/liquid_tags.py +++ b/liquid_tags/liquid_tags.py @@ -8,7 +8,10 @@ def addLiquidTags(gen): gen.settings['MD_EXTENSIONS'] = MDReader.default_extensions if LiquidTags not in gen.settings['MD_EXTENSIONS']: - gen.settings['MD_EXTENSIONS'].append(LiquidTags()) + configs = dict(code_dir=gen.settings.get('CODE_DIR', 'code'), + notebook_dir=gen.settings.get('NOTEBOOK_DIR', + 'notebooks')) + gen.settings['MD_EXTENSIONS'].append(LiquidTags(configs)) def register(): signals.initialized.connect(addLiquidTags) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 3d37f05..3582d0e 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -8,9 +8,11 @@ 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. +The file should be specified relative to the ``notebooks`` subdirectory of the +content directory. Optionally, this subdirectory can be specified in the +config file: + + NOTEBOOK_DIR = 'notebooks' Details ------- @@ -109,8 +111,7 @@ def notebook(preprocessor, tag, markup): raise ValueError("Error processing input, " "expected syntax: {0}".format(SYNTAX)) - # TODO: make the notebook directory a configurable setting - nb_dir = 'notebooks' + nb_dir = preprocessor.configs.config['notebook_dir'] nb_path = os.path.join('content', nb_dir, src) url = '/{0}/{1}/{2}'.format('static', nb_dir, src) From a6ad0e66e6ab18a65ad6ad3ccbb0ee6219999ccf Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Fri, 3 May 2013 07:42:22 -0700 Subject: [PATCH 06/24] update readme --- liquid_tags/Readme.md | 56 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/liquid_tags/Readme.md b/liquid_tags/Readme.md index 30c4734..0fffd6a 100644 --- a/liquid_tags/Readme.md +++ b/liquid_tags/Readme.md @@ -14,7 +14,7 @@ First, in your pelicanconf.py file, add the plugins you want to use: PLUGIN_PATH = '/path/to/pelican-plugins' PLUGINS = ['liquid_tags.img', 'liquid_tags.video', - 'liquid_tags.include_code'] + 'liquid_tags.include_code', 'liquid_tags.notebook'] There are several options available @@ -45,8 +45,54 @@ document: {% include_code myscript.py [Title text] %} -The script must be in the ``code`` subdirectory of your content folder, and -in order for the resulting hyperlink to work, this directory must be listed -under the STATIC_PATHS setting, e.g.: +The script must be in the ``code`` subdirectory of your content folder: +this default location can be changed by specifying - STATIC_PATHS = ['images', 'code'] \ No newline at end of file + CODE_DIR = 'code' + +within your configuration file. Additionally, in order for the resulting +hyperlink to work, this directory must be listed under the STATIC_PATHS +setting, e.g.: + + STATIC_PATHS = ['images', 'code'] + +## IPython notebooks +To insert an ipython notebook into your post, enable the +``liquid_tags.notebook`` plugin and add to your document: + + {% notebook filename.ipynb %} + +The file should be specified relative to the ``notebooks`` subdirectory of the +content directory. Optionally, this subdirectory can be specified in the +config file: + + NOTEBOOK_DIR = 'notebooks' + +Because the conversion and rendering 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/ + + The nbconvert package is still in development, so we recommend using the + most recent version. + +- 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 EXTRA_HEADER %} + {{ EXTRA_HEADER }} + {% endif %} + + and in your configuration file, include the line: + + EXTRA_HEADER = open('_nb_header.html').read().decode('utf-8') + + this will insert the proper css formatting into your document. + +[1] https://github.com/ipython/nbconvert \ No newline at end of file From c0e756209d3057f24c42ed349ffa3cbbe5dad329 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sat, 4 May 2013 06:43:19 -0700 Subject: [PATCH 07/24] change to work with new nbconvert --- liquid_tags/include_code.py | 3 ++- liquid_tags/liquid_tags.py | 4 +--- liquid_tags/notebook.py | 28 +++++++++++++++++++++------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py index e39825f..e8eddc0 100644 --- a/liquid_tags/include_code.py +++ b/liquid_tags/include_code.py @@ -55,7 +55,8 @@ def include_code(preprocessor, tag, markup): raise ValueError("Error processing input, " "expected syntax: {0}".format(SYNTAX)) - code_dir = preprocessor.configs.config['code_dir'] + settings = preprocessor.configs.config['settings'] + code_dir = settings.get('CODE_DIR', 'code') code_path = os.path.join('content', code_dir, src) if not os.path.exists(code_path): diff --git a/liquid_tags/liquid_tags.py b/liquid_tags/liquid_tags.py index d6e2457..5721cce 100644 --- a/liquid_tags/liquid_tags.py +++ b/liquid_tags/liquid_tags.py @@ -8,9 +8,7 @@ def addLiquidTags(gen): gen.settings['MD_EXTENSIONS'] = MDReader.default_extensions if LiquidTags not in gen.settings['MD_EXTENSIONS']: - configs = dict(code_dir=gen.settings.get('CODE_DIR', 'code'), - notebook_dir=gen.settings.get('NOTEBOOK_DIR', - 'notebooks')) + configs = dict(settings=gen.settings) gen.settings['MD_EXTENSIONS'].append(LiquidTags(configs)) def register(): diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 3582d0e..79bb6e9 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -43,7 +43,14 @@ are a few extra steps required for this plugin: import re import os from .mdx_liquid_tags import LiquidTags -from converters import ConverterBloggerHTML # part of the nbconvert package + +# nbconverters: part of the nbconvert package +try: + from converters import ConverterBloggerHTMLSeparate + separate_available = True +except ImportError: + from converters import ConverterBloggerHTML # requires nbconvert package + separate_available = False SYNTAX = "{% notebook /path/to/notebook.ipynb %}" FORMAT = re.compile(r"""^(?:\s+)?(?P<src>\S+)(?:\s+)?$""") @@ -111,7 +118,8 @@ def notebook(preprocessor, tag, markup): raise ValueError("Error processing input, " "expected syntax: {0}".format(SYNTAX)) - nb_dir = preprocessor.configs.config['notebook_dir'] + settings = preprocessor.configs.config['settings'] + nb_dir = settings.get('NOTEBOOK_DIR', 'notebooks') nb_path = os.path.join('content', nb_dir, src) url = '/{0}/{1}/{2}'.format('static', nb_dir, src) @@ -119,17 +127,23 @@ def notebook(preprocessor, tag, markup): raise ValueError("File {0} could not be found".format(nb_path)) # Call the notebook converter - converter = ConverterBloggerHTML(nb_path) - converter.read() + if separate_available: + converter = ConverterBloggerHTMLSeparate(nb_path) + converter.read() + + header_lines = converter.header_body() + body_lines = converter.main_body('\n') + else: + converter = ConverterBloggerHTML(nb_path) + converter.read() - header_lines = process_header(converter.header_body()) + header_lines = process_header(converter.header_body()) + body_lines = process_body(converter.main_body('\n')) 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 From d5e4d179e990a54437eab2f16ec4468f165fb0cd Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sat, 4 May 2013 07:21:10 -0700 Subject: [PATCH 08/24] add ablility to specify notebook cells --- liquid_tags/notebook.py | 61 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 79bb6e9..2d27751 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -6,7 +6,7 @@ notebook in a blog post. Syntax ------ -{% notebook filename.ipynb %} +{% notebook filename.ipynb [ cells[start:end] ]%} The file should be specified relative to the ``notebooks`` subdirectory of the content directory. Optionally, this subdirectory can be specified in the @@ -14,6 +14,9 @@ config file: NOTEBOOK_DIR = 'notebooks' +The cells[start:end] statement is optional, and can be used to specify which +block of cells from the notebook to include. + Details ------- Because the conversion and formatting of notebooks is rather involved, there @@ -52,8 +55,8 @@ except ImportError: from converters import ConverterBloggerHTML # requires nbconvert package separate_available = False -SYNTAX = "{% notebook /path/to/notebook.ipynb %}" -FORMAT = re.compile(r"""^(?:\s+)?(?P<src>\S+)(?:\s+)?$""") +SYNTAX = "{% notebook /path/to/notebook.ipynb [ cells[start:end] ] %}" +FORMAT = re.compile(r"""^(\s+)?(?P<src>\S+)(\s+)?((cells\[)(?P<start>-?[0-9]*):(?P<end>-?[0-9]*)(\]))?(\s+)?$""") def process_body(body): @@ -108,16 +111,66 @@ def process_header(header): return header.split('\n') +def strip_divs(body, start=None, end=None): + """Strip divs from the body for partial notebook insertion + + If L represents the list of parsed main divs, then this returns + the document corresponding to the divs L[start:end]. + + body should be a list of lines in the body of the html file. + """ + # TODO: this is a bit hackish. It would be better to add a PR to + # nbconvert which does this at the source. + DIV = re.compile('<div') + UNDIV = re.compile('</div') + + # remove ipynb div + body_lines = body[1:-1] + + # split divs + L = [] + count = 0 + div_start = 0 + for i, line in enumerate(body_lines): + count += len(DIV.findall(line)) + count -= len(UNDIV.findall(line)) + + if count == 0: + L.append(body_lines[div_start:i + 1]) + div_start = i + 1 + elif count < 0: + raise ValueError("parsing error: lost a tag") + + if div_start != len(body_lines): + raise ValueError("parsing error: didn't find the end of the div") + + body_lines = sum(L[start:end], []) + + return body[:1] + body_lines + body[-1:] + + @LiquidTags.register('notebook') def notebook(preprocessor, tag, markup): match = FORMAT.search(markup) if match: argdict = match.groupdict() src = argdict['src'] + start = argdict['start'] + end = argdict['end'] else: raise ValueError("Error processing input, " "expected syntax: {0}".format(SYNTAX)) + if start: + start = int(start) + else: + start = None + + if end: + end = int(end) + else: + end = None + settings = preprocessor.configs.config['settings'] nb_dir = settings.get('NOTEBOOK_DIR', 'notebooks') nb_path = os.path.join('content', nb_dir, src) @@ -144,6 +197,8 @@ def notebook(preprocessor, tag, markup): "this should be included in the theme.\n") open('_nb_header.html', 'w').write('\n'.join(header_lines).encode('utf-8')) + body_lines = strip_divs(body_lines, start, end) + body = preprocessor.configs.htmlStash.store('\n'.join(body_lines), safe=True) return body From d091f2780b80c5cf0e7f4152ac047418e4c98f1d Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sat, 4 May 2013 21:25:34 -0700 Subject: [PATCH 09/24] add ability to specify static directory --- liquid_tags/include_code.py | 3 ++- liquid_tags/notebook.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py index e8eddc0..389f364 100644 --- a/liquid_tags/include_code.py +++ b/liquid_tags/include_code.py @@ -69,7 +69,8 @@ def include_code(preprocessor, tag, markup): else: title = os.path.basename(src) - url = '/{0}/{1}/{2}'.format('static', code_dir, src) + static_dir = settings.get('STATIC_OUT_DIR', 'static') + url = '/{0}/{1}/{2}'.format(static_dir, code_dir, src) open_tag = ("<figure class='code'>\n<figcaption><span>{title}</span> " "<a href='{url}'>download</a></figcaption>".format(title=title, diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 2d27751..846294d 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -174,7 +174,6 @@ def notebook(preprocessor, tag, markup): settings = preprocessor.configs.config['settings'] nb_dir = settings.get('NOTEBOOK_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)) From e0e303adf3858bc2c7795bbc89a0024ed3249953 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sun, 5 May 2013 06:49:00 -0700 Subject: [PATCH 10/24] save notebook header only once --- liquid_tags/notebook.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 846294d..05a83a0 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -48,12 +48,8 @@ import os from .mdx_liquid_tags import LiquidTags # nbconverters: part of the nbconvert package -try: - from converters import ConverterBloggerHTMLSeparate - separate_available = True -except ImportError: - from converters import ConverterBloggerHTML # requires nbconvert package - separate_available = False +from converters import ConverterBloggerHTML # requires nbconvert package +separate_available = False SYNTAX = "{% notebook /path/to/notebook.ipynb [ cells[start:end] ] %}" FORMAT = re.compile(r"""^(\s+)?(?P<src>\S+)(\s+)?((cells\[)(?P<start>-?[0-9]*):(?P<end>-?[0-9]*)(\]))?(\s+)?$""") @@ -179,22 +175,18 @@ def notebook(preprocessor, tag, markup): raise ValueError("File {0} could not be found".format(nb_path)) # Call the notebook converter - if separate_available: - converter = ConverterBloggerHTMLSeparate(nb_path) - converter.read() - - header_lines = converter.header_body() - body_lines = converter.main_body('\n') - else: - converter = ConverterBloggerHTML(nb_path) - converter.read() + converter = ConverterBloggerHTML(nb_path) + converter.read() - header_lines = process_header(converter.header_body()) - body_lines = process_body(converter.main_body('\n')) + header_lines = process_header(converter.header_body()) + body_lines = process_body(converter.main_body('\n')) - 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')) + if not notebook.header_saved: + notebook.header_saved = True + print ("\n *** Writing styles to _nb_header.html: " + "this should be included in the theme.\n") + lines = '\n'.join(header_lines).encode('utf-8') + open('_nb_header.html', 'w').write(lines) body_lines = strip_divs(body_lines, start, end) @@ -202,6 +194,8 @@ def notebook(preprocessor, tag, markup): safe=True) return body +notebook.header_saved = False + #---------------------------------------------------------------------- # This import allows image tag to be a Pelican plugin From 76d27c287641baf200f88d054449e7733aa1fb0f Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sun, 5 May 2013 07:36:27 -0700 Subject: [PATCH 11/24] fix cell indexing issue --- liquid_tags/notebook.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 05a83a0..1f9eca8 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -140,6 +140,8 @@ def strip_divs(body, start=None, end=None): if div_start != len(body_lines): raise ValueError("parsing error: didn't find the end of the div") + L = L[1:] + body_lines = sum(L[start:end], []) return body[:1] + body_lines + body[-1:] From 5977160bb6c73d289e6365afeca74454272c3104 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sun, 5 May 2013 21:00:09 -0700 Subject: [PATCH 12/24] fix notebook cell parsing --- liquid_tags/notebook.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 1f9eca8..27b4763 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -128,6 +128,8 @@ def strip_divs(body, start=None, end=None): count = 0 div_start = 0 for i, line in enumerate(body_lines): + if not line: + continue count += len(DIV.findall(line)) count -= len(UNDIV.findall(line)) @@ -135,13 +137,13 @@ def strip_divs(body, start=None, end=None): L.append(body_lines[div_start:i + 1]) div_start = i + 1 elif count < 0: - raise ValueError("parsing error: lost a tag") + raise ValueError("Fatal: parsing error -- lost a tag") - if div_start != len(body_lines): + # check that we've parsed to the end + # the last line may be blank, so we check two conditions + if div_start not in [len(body_lines), len(body_lines) - 1]: raise ValueError("parsing error: didn't find the end of the div") - L = L[1:] - body_lines = sum(L[start:end], []) return body[:1] + body_lines + body[-1:] From ce11ec8b206ad5dc748272daa42ec70632809d58 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Tue, 7 May 2013 21:30:53 -0700 Subject: [PATCH 13/24] add literal tag for displaying {% ... %} --- liquid_tags/literal.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 liquid_tags/literal.py diff --git a/liquid_tags/literal.py b/liquid_tags/literal.py new file mode 100644 index 0000000..7c04602 --- /dev/null +++ b/liquid_tags/literal.py @@ -0,0 +1,27 @@ +""" +Literal Tag +----------- +This implements a tag that allows explicitly showing commands which would +otherwise be interpreted as a liquid tag. + +For example, the line + + {% literal video arg1 arg2 %} + +would result in the following line: + + {% video arg1 arg2 %} + +This is useful when the resulting line would be interpreted as another +liquid-style tag. +""" +from .mdx_liquid_tags import LiquidTags + +@LiquidTags.register('literal') +def literal(preprocessor, tag, markup): + return '{%% %s %%}' % markup + +#---------------------------------------------------------------------- +# This import allows image tag to be a Pelican plugin +from liquid_tags import register + From 92e448340c68041c46b3aea5609889cf90317ff6 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Thu, 9 May 2013 07:38:34 -0700 Subject: [PATCH 14/24] properly set language in include_code --- liquid_tags/include_code.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py index 389f364..185c882 100644 --- a/liquid_tags/include_code.py +++ b/liquid_tags/include_code.py @@ -6,7 +6,7 @@ based on the octopress video tag [1]_ Syntax ------ -{% include_code path/to/code [Title text] %} +{% include_code path/to/code [lang:python] [Title text] %} The "path to code" is specified relative to the ``code`` subdirectory of the content directory Optionally, this subdirectory can be specified in the @@ -82,8 +82,15 @@ def include_code(preprocessor, tag, markup): open_tag = preprocessor.configs.htmlStash.store(open_tag, safe=True) close_tag = preprocessor.configs.htmlStash.store(close_tag, safe=True) + if lang: + lang_include = ':::' + lang + '\n ' + else: + lang_include = '' + source = (open_tag - + '\n\n ' + '\n '.join(code.split('\n')) + '\n\n' + + '\n\n ' + + lang_include + + '\n '.join(code.split('\n')) + '\n\n' + close_tag + '\n') return source From 5af2ff3c81de1e5a256bc884146dd4cf543de2dc Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sat, 1 Jun 2013 10:32:15 -0700 Subject: [PATCH 15/24] make compatible with newer IPython --- liquid_tags/include_code.py | 2 ++ liquid_tags/notebook.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/liquid_tags/include_code.py b/liquid_tags/include_code.py index 185c882..f8572ca 100644 --- a/liquid_tags/include_code.py +++ b/liquid_tags/include_code.py @@ -70,7 +70,9 @@ def include_code(preprocessor, tag, markup): title = os.path.basename(src) static_dir = settings.get('STATIC_OUT_DIR', 'static') + url = '/{0}/{1}/{2}'.format(static_dir, code_dir, src) + url = re.sub('/+', '/', url) open_tag = ("<figure class='code'>\n<figcaption><span>{title}</span> " "<a href='{url}'>download</a></figcaption>".format(title=title, diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 27b4763..c5d3f11 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -179,7 +179,7 @@ def notebook(preprocessor, tag, markup): raise ValueError("File {0} could not be found".format(nb_path)) # Call the notebook converter - converter = ConverterBloggerHTML(nb_path) + converter = ConverterBloggerHTML(infile=nb_path) converter.read() header_lines = process_header(converter.header_body()) From 2c4f75e00882c1f9e26fb51f5e38baccf1dc9e58 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Tue, 16 Jul 2013 15:00:33 -0700 Subject: [PATCH 16/24] make liquid_tags.notebook compatible with IPython 1.0 --- liquid_tags/notebook.py | 337 ++++++++++++++++++++++++---------------- 1 file changed, 205 insertions(+), 132 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index c5d3f11..eacbff8 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -17,138 +17,198 @@ config file: The cells[start:end] statement is optional, and can be used to specify which block of cells from the notebook to include. +Requirements +------------ +- The plugin requires IPython version 1.0 or above. It no longer supports the + standalone nbconvert package, which has been deprecated. + Details ------- -Because the conversion and formatting of notebooks is rather involved, there -are a few extra steps required for this plugin: +Because the notebook relies on some rather extensive custom CSS, the use of +this plugin requires additional CSS to be inserted into the blog theme. +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: -- First, the plugin requires that the nbconvert package [1]_ to be in the - python path. For example, in bash, this can be set via + {% if EXTRA_HEADER %} + {{ EXTRA_HEADER }} + {% endif %} - >$ export PYTHONPATH=/path/to/nbconvert/ +and in your ``pelicanconf.py`` file, include the line: -- 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: + EXTRA_HEADER = open('_nb_header.html').read().decode('utf-8') - {% if EXTRA_HEADER %} - {{ EXTRA_HEADER }} - {% endif %} - - and in your ``pelicanconf.py`` file, include the line: - - EXTRA_HEADER = open('_nb_header.html').read().decode('utf-8') - -[1] https://github.com/ipython/nbconvert +this will insert the appropriate CSS. All efforts have been made to ensure +that this CSS will not override formats within the blog theme, but there may +still be some conflicts. """ import re import os from .mdx_liquid_tags import LiquidTags -# nbconverters: part of the nbconvert package -from converters import ConverterBloggerHTML # requires nbconvert package -separate_available = False +try: + from IPython import nbconvert +except ImportError: + raise ValueError("IPython version 1.0+ required for notebook tag") +from IPython.nbconvert.filters.highlight import _pygment_highlight +from pygments.formatters import HtmlFormatter + +from IPython.nbconvert.exporters import BasicHTMLExporter +from IPython.config import Config + +from IPython.nbformat import current as nbformat +from IPython.nbconvert.transformers import ActivatableTransformer + +from IPython.utils.traitlets import Integer +from copy import deepcopy + +from jinja2 import DictLoader + +# assume not more than ten million cells in notebook +# this shouldn't ever be a problem +MAX_NB_CELLS = 9999999 + +#---------------------------------------------------------------------- +# Some code that will be added to the header: +# Some of the following javascript/css include is adapted from +# IPython/nbconvert/templates/fullhtml.tpl, while some are custom tags +# specifically designed to make the results look good within the +# pelican-octopress theme. +JS_INCLUDE = """ +<style type="text/css"> +/* Overrides of notebook CSS for static HTML export */ +div.entry-content { + overflow: visible; + padding: 8px; +} +.input_area { + padding: 0.2em; +} + +a.heading-anchor { + white-space: normal; +} + +.rendered_html +code { + font-size: .8em; +} + +pre.ipynb { + color: black; + background: #f7f7f7; + border: none; + box-shadow: none; + margin-bottom: 0; + padding: 0; + margin: 0px; + font-size: 13px; +} + +img.anim_icon{padding:0; border:0; -webkit-box-shadow:none; -box-shadow:none} +</style> + +<script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" type="text/javascript"></script> +""" + +# This, for some reason, results in paranthetical statements being rendered +# in math mode. +DONT_USE = """ +<script type="text/javascript"> +init_mathjax = function() { + if (window.MathJax) { + // MathJax loaded + MathJax.Hub.Config({ + tex2jax: { + inlineMath: [ ['$','$'], ["\\(","\\)"] ], + displayMath: [ ['$$','$$'], ["\\[","\\]"] ] + }, + displayAlign: 'left', // Change this to 'center' to center equations. + "HTML-CSS": { + styles: {'.MathJax_Display': {"margin": 0}} + } + }); + MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + } +} +init_mathjax(); +</script> +""" + +CSS_WRAPPER = """ +<style type="text/css"> +{0} +</style> +""" + + +#---------------------------------------------------------------------- +# Create a custom transformer +class SubCell(ActivatableTransformer): + """A transformer to select a slice of the cells of a notebook""" + start = Integer(0, config=True, + help="first cell of notebook to be converted") + end = Integer(MAX_NB_CELLS, config=True, + help="last cell of notebook to be converted") + + def __call__(self, nb, resources): + nbc = deepcopy(nb) + for worksheet in nbc.worksheets : + cells = worksheet.cells[:] + end = min(len(cells), self.end) + worksheet.cells = cells[self.start:end] + return nbc, resources + +#---------------------------------------------------------------------- +# Customize the html template: +# This changes the <pre> tags in basic_html.tpl to <pre class="ipynb" +pelican_loader = DictLoader({'pelicanhtml.tpl': +""" +{%- extends 'basichtml.tpl' -%} + +{% block stream_stdout -%} +<div class="box-flex1 output_subarea output_stream output_stdout"> +<pre class="ipynb">{{output.text |ansi2html}}</pre> +</div> +{%- endblock stream_stdout %} + +{% block stream_stderr -%} +<div class="box-flex1 output_subarea output_stream output_stderr"> +<pre class="ipynb">{{output.text |ansi2html}}</pre> +</div> +{%- endblock stream_stderr %} + +{% block pyerr -%} +<div class="box-flex1 output_subarea output_pyerr"> +<pre class="ipynb">{{super()}}</pre> +</div> +{%- endblock pyerr %} + +{%- block data_text %} +<pre class="ipynb">{{output.text | ansi2html}}</pre> +{%- endblock -%} +"""}) + + +#---------------------------------------------------------------------- +# Custom highlighter: +# instead of using class='highlight', use class='highlight-ipynb' +def custom_highlighter(source, language='ipython'): + formatter = HtmlFormatter(cssclass='highlight-ipynb') + output = _pygment_highlight(source, formatter, language) + return output.replace('<pre>', '<pre class="ipynb">') + + +#---------------------------------------------------------------------- +# Below is the pelican plugin code. +# SYNTAX = "{% notebook /path/to/notebook.ipynb [ cells[start:end] ] %}" FORMAT = re.compile(r"""^(\s+)?(?P<src>\S+)(\s+)?((cells\[)(?P<start>-?[0-9]*):(?P<end>-?[0-9]*)(\]))?(\s+)?$""") -def process_body(body): - body = '\n'.join(body) - - # replace the highlight tags - body = body.replace('highlight', '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 - R = re.compile(r'^body ?{', re.MULTILINE) - header = R.sub('div.ipynb {', header) - - # specify all headers - R = re.compile(r'^(h[1-6])', re.MULTILINE) - repl = lambda match: '.ipynb ' + match.groups()[0] - header = R.sub(repl, header) - - # substitude ipynb class for html and body modifiers - header = header.replace('html, body', '.ipynb div,') - - return header.split('\n') - - -def strip_divs(body, start=None, end=None): - """Strip divs from the body for partial notebook insertion - - If L represents the list of parsed main divs, then this returns - the document corresponding to the divs L[start:end]. - - body should be a list of lines in the body of the html file. - """ - # TODO: this is a bit hackish. It would be better to add a PR to - # nbconvert which does this at the source. - DIV = re.compile('<div') - UNDIV = re.compile('</div') - - # remove ipynb div - body_lines = body[1:-1] - - # split divs - L = [] - count = 0 - div_start = 0 - for i, line in enumerate(body_lines): - if not line: - continue - count += len(DIV.findall(line)) - count -= len(UNDIV.findall(line)) - - if count == 0: - L.append(body_lines[div_start:i + 1]) - div_start = i + 1 - elif count < 0: - raise ValueError("Fatal: parsing error -- lost a tag") - - # check that we've parsed to the end - # the last line may be blank, so we check two conditions - if div_start not in [len(body_lines), len(body_lines) - 1]: - raise ValueError("parsing error: didn't find the end of the div") - - body_lines = sum(L[start:end], []) - - return body[:1] + body_lines + body[-1:] - - @LiquidTags.register('notebook') def notebook(preprocessor, tag, markup): match = FORMAT.search(markup) @@ -164,12 +224,12 @@ def notebook(preprocessor, tag, markup): if start: start = int(start) else: - start = None + start = 0 if end: end = int(end) else: - end = None + end = MAX_NB_CELLS settings = preprocessor.configs.config['settings'] nb_dir = settings.get('NOTEBOOK_DIR', 'notebooks') @@ -178,29 +238,42 @@ def notebook(preprocessor, tag, markup): if not os.path.exists(nb_path): raise ValueError("File {0} could not be found".format(nb_path)) - # Call the notebook converter - converter = ConverterBloggerHTML(infile=nb_path) - converter.read() + # Create the custom notebook converter + c = Config({'CSSHTMLHeaderTransformer': + {'enabled':True, 'highlight_class':'.highlight-ipynb'}, + 'SubCell': + {'start':start, 'end':end}}) - header_lines = process_header(converter.header_body()) - body_lines = process_body(converter.main_body('\n')) - + exporter = BasicHTMLExporter(config=c, + filters={'highlight': custom_highlighter}, + transformers=[SubCell], + extra_loaders=[pelican_loader]) + + # read and parse the notebook + nb_text = open(nb_path).read() + nb_json = nbformat.reads_json(nb_text) + (body, resources) = exporter.from_notebook_node(nb_json) + + # if we haven't already saved the header, save it here. if not notebook.header_saved: - notebook.header_saved = True print ("\n *** Writing styles to _nb_header.html: " - "this should be included in the theme.\n") - lines = '\n'.join(header_lines).encode('utf-8') - open('_nb_header.html', 'w').write(lines) + "this should be included in the theme. ***\n") - body_lines = strip_divs(body_lines, start, end) + header = '\n'.join(CSS_WRAPPER.format(css_line) + for css_line in resources['inlining']['css']) + header += JS_INCLUDE - body = preprocessor.configs.htmlStash.store('\n'.join(body_lines), - safe=True) + open('_nb_header.html', 'w').write(header) + notebook.header_saved = True + + # this will stash special characters so that they won't be transformed + # by subsequent processes. + body = preprocessor.configs.htmlStash.store(body, safe=True) return body notebook.header_saved = False #---------------------------------------------------------------------- -# This import allows image tag to be a Pelican plugin +# This import allows notebook to be a Pelican plugin from liquid_tags import register From 6f8d398bb6ea967f331ae88fc0f5453fb8e505b9 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Wed, 17 Jul 2013 19:54:35 -0700 Subject: [PATCH 17/24] address notebook comments --- liquid_tags/notebook.py | 44 +++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index eacbff8..6887298 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -67,9 +67,6 @@ from copy import deepcopy from jinja2 import DictLoader -# assume not more than ten million cells in notebook -# this shouldn't ever be a problem -MAX_NB_CELLS = 9999999 #---------------------------------------------------------------------- # Some code that will be added to the header: @@ -147,21 +144,32 @@ CSS_WRAPPER = """ #---------------------------------------------------------------------- # Create a custom transformer +class SliceIndex(Integer): + """An integer trait that accepts None""" + default_value = None + + def validate(self, obj, value): + if value is None: + return value + else: + return super(SliceIndex, self).validate(obj, value) + + class SubCell(ActivatableTransformer): """A transformer to select a slice of the cells of a notebook""" - start = Integer(0, config=True, - help="first cell of notebook to be converted") - end = Integer(MAX_NB_CELLS, config=True, - help="last cell of notebook to be converted") - - def __call__(self, nb, resources): + start = SliceIndex(0, config=True, + help="first cell of notebook to be converted") + end = SliceIndex(None, config=True, + help="last cell of notebook to be converted") + + def call(self, nb, resources): nbc = deepcopy(nb) for worksheet in nbc.worksheets : cells = worksheet.cells[:] - end = min(len(cells), self.end) - worksheet.cells = cells[self.start:end] + worksheet.cells = cells[self.start:self.end] return nbc, resources + #---------------------------------------------------------------------- # Customize the html template: # This changes the <pre> tags in basic_html.tpl to <pre class="ipynb" @@ -229,7 +237,7 @@ def notebook(preprocessor, tag, markup): if end: end = int(end) else: - end = MAX_NB_CELLS + end = None settings = preprocessor.configs.config['settings'] nb_dir = settings.get('NOTEBOOK_DIR', 'notebooks') @@ -242,7 +250,7 @@ def notebook(preprocessor, tag, markup): c = Config({'CSSHTMLHeaderTransformer': {'enabled':True, 'highlight_class':'.highlight-ipynb'}, 'SubCell': - {'start':start, 'end':end}}) + {'enabled':True, 'start':start, 'end':end}}) exporter = BasicHTMLExporter(config=c, filters={'highlight': custom_highlighter}, @@ -250,20 +258,22 @@ def notebook(preprocessor, tag, markup): extra_loaders=[pelican_loader]) # read and parse the notebook - nb_text = open(nb_path).read() + with open(nb_path) as f: + nb_text = f.read() nb_json = nbformat.reads_json(nb_text) (body, resources) = exporter.from_notebook_node(nb_json) # if we haven't already saved the header, save it here. if not notebook.header_saved: - print ("\n *** Writing styles to _nb_header.html: " - "this should be included in the theme. ***\n") + print ("\n ** Writing styles to _nb_header.html: " + "this should be included in the theme. **\n") header = '\n'.join(CSS_WRAPPER.format(css_line) for css_line in resources['inlining']['css']) header += JS_INCLUDE - open('_nb_header.html', 'w').write(header) + with open('_nb_header.html', 'w') as f: + f.write(header) notebook.header_saved = True # this will stash special characters so that they won't be transformed From 2ad21322613be99d3e806c27c19f96741701c302 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Thu, 18 Jul 2013 07:21:49 -0700 Subject: [PATCH 18/24] ActivatableTransformer -> Transformer --- liquid_tags/notebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 6887298..657ec78 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -60,7 +60,7 @@ from IPython.nbconvert.exporters import BasicHTMLExporter from IPython.config import Config from IPython.nbformat import current as nbformat -from IPython.nbconvert.transformers import ActivatableTransformer +from IPython.nbconvert.transformers import Transformer from IPython.utils.traitlets import Integer from copy import deepcopy @@ -155,7 +155,7 @@ class SliceIndex(Integer): return super(SliceIndex, self).validate(obj, value) -class SubCell(ActivatableTransformer): +class SubCell(Transformer): """A transformer to select a slice of the cells of a notebook""" start = SliceIndex(0, config=True, help="first cell of notebook to be converted") From 499ee9890ad2b37b694ac9e34a0b4454275385d9 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Tue, 6 Aug 2013 19:18:13 -0700 Subject: [PATCH 19/24] Change names for compatibility with IPython 1.0alpha --- liquid_tags/notebook.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 657ec78..7df9479 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -56,7 +56,7 @@ except ImportError: from IPython.nbconvert.filters.highlight import _pygment_highlight from pygments.formatters import HtmlFormatter -from IPython.nbconvert.exporters import BasicHTMLExporter +from IPython.nbconvert.exporters import HTMLExporter from IPython.config import Config from IPython.nbformat import current as nbformat @@ -252,10 +252,11 @@ def notebook(preprocessor, tag, markup): 'SubCell': {'enabled':True, 'start':start, 'end':end}}) - exporter = BasicHTMLExporter(config=c, - filters={'highlight': custom_highlighter}, - transformers=[SubCell], - extra_loaders=[pelican_loader]) + exporter = HTMLExporter(config=c, + template_file='basic', + filters={'highlight2html': custom_highlighter}, + transformers=[SubCell], + extra_loaders=[pelican_loader]) # read and parse the notebook with open(nb_path) as f: From c545c9e56d9fec08c53d63b748e8581e9c6400c1 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Tue, 6 Aug 2013 20:07:41 -0700 Subject: [PATCH 20/24] modify mathjax to not interfere with normal posts --- liquid_tags/notebook.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 7df9479..5b17384 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -109,19 +109,15 @@ img.anim_icon{padding:0; border:0; -webkit-box-shadow:none; -box-shadow:none} </style> <script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" type="text/javascript"></script> -""" -# This, for some reason, results in paranthetical statements being rendered -# in math mode. -DONT_USE = """ <script type="text/javascript"> init_mathjax = function() { if (window.MathJax) { // MathJax loaded MathJax.Hub.Config({ tex2jax: { - inlineMath: [ ['$','$'], ["\\(","\\)"] ], - displayMath: [ ['$$','$$'], ["\\[","\\]"] ] + inlineMath: [ ['$','$'] ], + displayMath: [ ['$$','$$'] ] }, displayAlign: 'left', // Change this to 'center' to center equations. "HTML-CSS": { From 4643ab7186739b9b318db91d2730a89acf625f4b Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Wed, 7 Aug 2013 12:05:22 -0700 Subject: [PATCH 21/24] adjust anim_icon CSS --- liquid_tags/notebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 5b17384..666a6a1 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -105,7 +105,7 @@ pre.ipynb { font-size: 13px; } -img.anim_icon{padding:0; border:0; -webkit-box-shadow:none; -box-shadow:none} +img.anim_icon{padding:0; border:0; vertical-align:middle; -webkit-box-shadow:none; -box-shadow:none} </style> <script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" type="text/javascript"></script> From 9e416a73b288ae3906b574de573fa14b54a0a849 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Tue, 20 Aug 2013 09:50:48 -0700 Subject: [PATCH 22/24] fix mathjax script --- liquid_tags/notebook.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index 666a6a1..a2ccc71 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -74,7 +74,7 @@ from jinja2 import DictLoader # IPython/nbconvert/templates/fullhtml.tpl, while some are custom tags # specifically designed to make the results look good within the # pelican-octopress theme. -JS_INCLUDE = """ +JS_INCLUDE = r""" <style type="text/css"> /* Overrides of notebook CSS for static HTML export */ div.entry-content { @@ -109,15 +109,14 @@ img.anim_icon{padding:0; border:0; vertical-align:middle; -webkit-box-shadow:non </style> <script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" type="text/javascript"></script> - <script type="text/javascript"> init_mathjax = function() { if (window.MathJax) { // MathJax loaded MathJax.Hub.Config({ tex2jax: { - inlineMath: [ ['$','$'] ], - displayMath: [ ['$$','$$'] ] + inlineMath: [ ['$','$'], ["\\(","\\)"] ], + displayMath: [ ['$$','$$'], ["\\[","\\]"] ] }, displayAlign: 'left', // Change this to 'center' to center equations. "HTML-CSS": { From e36dae6c11ca21db1871d962ab7ed0c9a349c121 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Tue, 20 Aug 2013 10:07:50 -0700 Subject: [PATCH 23/24] provide warning if IPython 2.0-dev is being used --- liquid_tags/notebook.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index a2ccc71..f859530 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -60,7 +60,11 @@ from IPython.nbconvert.exporters import HTMLExporter from IPython.config import Config from IPython.nbformat import current as nbformat -from IPython.nbconvert.transformers import Transformer + +try: + from IPython.nbconvert.transformers import Transformer +except ImportError: + raise ValueError("IPython version 2.0 is not yet supported") from IPython.utils.traitlets import Integer from copy import deepcopy From 6cd44821438c63440580f7aaa08a21847d7ff100 Mon Sep 17 00:00:00 2001 From: Jake Vanderplas <vanderplas@astro.washington.edu> Date: Sun, 25 Aug 2013 20:15:35 -0700 Subject: [PATCH 24/24] explicitly check for IPython version --- liquid_tags/notebook.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/liquid_tags/notebook.py b/liquid_tags/notebook.py index f859530..954efaf 100644 --- a/liquid_tags/notebook.py +++ b/liquid_tags/notebook.py @@ -48,11 +48,12 @@ import re import os from .mdx_liquid_tags import LiquidTags -try: - from IPython import nbconvert -except ImportError: +import IPython +if IPython.__version__.split('.')[0] != 1: raise ValueError("IPython version 1.0+ required for notebook tag") +from IPython import nbconvert + from IPython.nbconvert.filters.highlight import _pygment_highlight from pygments.formatters import HtmlFormatter