3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.log
|
*.log
|
||||||
|
*~
|
||||||
98
liquid_tags/Readme.md
Normal file
98
liquid_tags/Readme.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Liquid-style Tags
|
||||||
|
*Author: Jake Vanderplas <jakevdp@cs.washington.edu>*
|
||||||
|
|
||||||
|
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', 'liquid_tags.notebook']
|
||||||
|
|
||||||
|
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:
|
||||||
|
this default location can be changed by specifying
|
||||||
|
|
||||||
|
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
|
||||||
1
liquid_tags/__init__.py
Normal file
1
liquid_tags/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .liquid_tags import *
|
||||||
65
liquid_tags/img.py
Normal file
65
liquid_tags/img.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
------
|
||||||
|
<img src="/images/ninja.png">
|
||||||
|
<img class="left half" src="http://site.com/images/ninja.png" title="Ninja Attack!" alt="Ninja Attack!">
|
||||||
|
<img class="left half" src="http://site.com/images/ninja.png" width="150" height="150" title="Ninja Attack!" alt="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<class>\S.*\s+)?(?P<src>(?:https?:\/\/|\/|\S+\/)\S+)(?:\s+(?P<width>\d+))?(?:\s+(?P<height>\d+))?(?P<title>\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):
|
||||||
|
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
|
||||||
|
|
||||||
103
liquid_tags/include_code.py
Normal file
103
liquid_tags/include_code.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
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 [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
|
||||||
|
config file:
|
||||||
|
|
||||||
|
CODE_DIR = 'code'
|
||||||
|
|
||||||
|
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):
|
||||||
|
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))
|
||||||
|
|
||||||
|
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):
|
||||||
|
raise ValueError("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)
|
||||||
|
|
||||||
|
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,
|
||||||
|
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)
|
||||||
|
|
||||||
|
if lang:
|
||||||
|
lang_include = ':::' + lang + '\n '
|
||||||
|
else:
|
||||||
|
lang_include = ''
|
||||||
|
|
||||||
|
source = (open_tag
|
||||||
|
+ '\n\n '
|
||||||
|
+ lang_include
|
||||||
|
+ '\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
|
||||||
15
liquid_tags/liquid_tags.py
Normal file
15
liquid_tags/liquid_tags.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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']:
|
||||||
|
configs = dict(settings=gen.settings)
|
||||||
|
gen.settings['MD_EXTENSIONS'].append(LiquidTags(configs))
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.initialized.connect(addLiquidTags)
|
||||||
27
liquid_tags/literal.py
Normal file
27
liquid_tags/literal.py
Normal file
@@ -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
|
||||||
|
|
||||||
77
liquid_tags/mdx_liquid_tags.py
Normal file
77
liquid_tags/mdx_liquid_tags.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
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 warnings
|
||||||
|
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.strip())
|
||||||
|
|
||||||
|
# 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)
|
||||||
290
liquid_tags/notebook.py
Normal file
290
liquid_tags/notebook.py
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
"""
|
||||||
|
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 [ 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
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 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:
|
||||||
|
|
||||||
|
{% 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')
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
from IPython.nbconvert.exporters import HTMLExporter
|
||||||
|
from IPython.config import Config
|
||||||
|
|
||||||
|
from IPython.nbformat import current as nbformat
|
||||||
|
|
||||||
|
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 jinja2 import DictLoader
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
# 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 = r"""
|
||||||
|
<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; 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>
|
||||||
|
<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 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(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")
|
||||||
|
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[:]
|
||||||
|
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"
|
||||||
|
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+)?$""")
|
||||||
|
|
||||||
|
|
||||||
|
@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 = 0
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if not os.path.exists(nb_path):
|
||||||
|
raise ValueError("File {0} could not be found".format(nb_path))
|
||||||
|
|
||||||
|
# Create the custom notebook converter
|
||||||
|
c = Config({'CSSHTMLHeaderTransformer':
|
||||||
|
{'enabled':True, 'highlight_class':'.highlight-ipynb'},
|
||||||
|
'SubCell':
|
||||||
|
{'enabled':True, 'start':start, 'end':end}})
|
||||||
|
|
||||||
|
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:
|
||||||
|
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")
|
||||||
|
|
||||||
|
header = '\n'.join(CSS_WRAPPER.format(css_line)
|
||||||
|
for css_line in resources['inlining']['css'])
|
||||||
|
header += JS_INCLUDE
|
||||||
|
|
||||||
|
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
|
||||||
|
# by subsequent processes.
|
||||||
|
body = preprocessor.configs.htmlStash.store(body, safe=True)
|
||||||
|
return body
|
||||||
|
|
||||||
|
notebook.header_saved = False
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
# This import allows notebook to be a Pelican plugin
|
||||||
|
from liquid_tags import register
|
||||||
70
liquid_tags/video.py
Normal file
70
liquid_tags/video.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
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):
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user