Explorar el Código

initial commit: img, video, include_code tags

Jake Vanderplas hace 11 años
padre
commit
57d62ce99f

+ 52 - 0
liquid_tags/Readme.md

@@ -0,0 +1,52 @@
+# 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']
+
+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']

+ 1 - 0
liquid_tags/__init__.py

@@ -0,0 +1 @@
+from .liquid_tags import *

+ 66 - 0
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
+------
+<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):
+    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
+

+ 92 - 0
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

+ 14 - 0
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)

+ 76 - 0
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)

+ 72 - 0
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