From 6c5e1b585c69554090439d96ed53af8387c4a2f9 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Sat, 17 Dec 2011 16:05:14 -0600 Subject: [PATCH] Initial commit --- .gitignore | 1 + LICENSE.txt | 20 +++++++++++ README.rst | 72 ++++++++++++++++++++++++++++++++++++++ converter.rb | 30 ++++++++++++++++ directives.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ rst2html.py | 39 +++++++++++++++++++++ transform.py | 40 +++++++++++++++++++++ 7 files changed, 299 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 README.rst create mode 100644 converter.rb create mode 100644 directives.py create mode 100644 rst2html.py create mode 100644 transform.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..988ac9e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2011 Greg Thornton, http://xdissent.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..ff96149 --- /dev/null +++ b/README.rst @@ -0,0 +1,72 @@ +Overview +======== + +This plugin adds `ReStructuredText`_ support to `Jekyll`_ and `Octopress`_. +It renders ReST in posts and pages, and provides a custom directives to +support Octopress-compatible syntax highlighting. + +Requirements +============ + +* Jekyll *or* Octopress >= 2.0 +* Docutils +* Pygments +* `RbST`_ + +Installation +============ + +1. Install Docutils and Pygments. + + The most convenient way is to use virtualenv_burrito: + + .. sourcecode:: console + + $ curl -s https://raw.github.com/brainsik/virtualenv-burrito/master/virtualenv-burrito.sh | bash + $ source /Users/xdissent/.venvburrito/startup.sh + $ mkvirtualenv jekyll-rst + $ pip install docutils pygments + +2. Install RbST. + + If you use `bundler`_ with Octopress, add ``gem 'RbST'`` to + your ``Gemfile`` in the ``development`` group, then run + ``bundle install``. Otherwise, ``gem install RbST``. + +3. Install the plugin. + + For Jekyll: + + .. sourcecode:: console + + $ cd + $ git submodule add https://github.com/xdissent/jekyll-rst.git _plugins/jekyll-rst + + For Octopress: + + .. sourcecode:: console + + $ cd + $ git submodule add https://github.com/xdissent/jekyll-rst.git plugins/jekyll-rst + +4. Start blogging in ReStructuredText. Any file with the ``.rst`` extension + will be parsed as ReST and rendered into HTML. + + .. note:: Be sure to activate the ``jekyll-rst`` virtualenv before generating + the site by issuing a ``workon jekyll-rst``. I suggest you follow `Harry + Marr's advice`_ and create a ``.venv`` file that will automatically + activate the ``jekyll-rst`` virtualenv when you ``cd`` into your project. + +Octopress Tips +============== + +* Use ``.. more`` in your ReST documents to indicate where Octopress's + ``excerpt`` tag should split your content for summary views. + + +.. _ReStructuredText: http://docutils.sourceforge.net/rst.html +.. _Jekyll: http://jekyllrb.com/ +.. _Octopress: http://octopress.com/ +.. _RbST: http://rubygems.org/gems/RbST +.. _bundler: http://gembundler.com/ +.. _Harry Marr's advice: http://hmarr.com/2010/jan/19/making-virtualenv-play-nice-with-git/ \ No newline at end of file diff --git a/converter.rb b/converter.rb new file mode 100644 index 0000000..64b639c --- /dev/null +++ b/converter.rb @@ -0,0 +1,30 @@ +require 'rbst' + +module Jekyll + class RestConverter < Converter + safe true + + priority :low + + def matches(ext) + ext =~ /rst/i + end + + def output_ext(ext) + ".html" + end + + def convert(content) + RbST.executables = {:html => "#{File.expand_path(File.dirname(__FILE__))}/rst2html.py"} + RbST.new(content).to_html(:part => :fragment, :initial_header_level => 2) + end + end + + module Filters + def restify(input) + site = @context.registers[:site] + converter = site.getConverterImpl(Jekyll::RestConverter) + converter.convert(input) + end + end +end \ No newline at end of file diff --git a/directives.py b/directives.py new file mode 100644 index 0000000..de3dbf6 --- /dev/null +++ b/directives.py @@ -0,0 +1,97 @@ +# Define a new directive `code-block` (aliased as `sourcecode`) that uses the +# `pygments` source highlighter to render code in color. +# +# Incorporates code from the `Pygments`_ documentation for `Using Pygments in +# ReST documents`_ and `Octopress`_. +# +# .. _Pygments: http://pygments.org/ +# .. _Using Pygments in ReST documents: http://pygments.org/docs/rstdirective/ +# .. _Octopress: http://octopress.org/ + +import re +import os +import md5 +import __main__ + +# Absolute path to pygments cache dir +PYGMENTS_CACHE_DIR = os.path.abspath(os.path.join(os.path.dirname(__main__.__file__), '../../.pygments-cache')) + +# Ensure cache dir exists +if not os.path.exists(PYGMENTS_CACHE_DIR): + os.mkdirs(PYGMENTS_CACHE_DIR) + +from pygments.formatters import HtmlFormatter + +from docutils import nodes +from docutils.parsers.rst import directives, Directive + +from pygments import highlight +from pygments.lexers import get_lexer_by_name, TextLexer + +class Pygments(Directive): + """ Source code syntax hightlighting. + """ + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + string_opts = ['title', 'url', 'caption'] + option_spec = dict([(key, directives.unchanged) for key in string_opts]) + has_content = True + + def run(self): + self.assert_has_content() + try: + lexer_name = self.arguments[0] + lexer = get_lexer_by_name(lexer_name) + except ValueError: + # no lexer found - use the text one instead of an exception + lexer_name = 'text' + lexer = TextLexer() + formatter = HtmlFormatter() + + # Construct cache filename + cache_file = None + content_text = u'\n'.join(self.content) + cache_file_name = '%s-%s.html' % (lexer_name, md5.new(content_text).hexdigest()) + cached_path = os.path.join(PYGMENTS_CACHE_DIR, cache_file_name) + + # Look for cached version, otherwise parse + if os.path.exists(cached_path): + cache_file = open(cached_path, 'r') + parsed = cache_file.read() + else: + parsed = highlight(content_text, lexer, formatter) + + # Strip pre tag and everything outside it + pres = re.compile("
(.+)<\/pre>", re.S)
+        stripped = pres.search(parsed).group(1)
+
+        # Create tabular code with line numbers
+        table = '
'
+        lined = ''
+        for idx, line in enumerate(stripped.splitlines(True)):
+            table += '%d\n' % (idx + 1)
+            lined  += '%s' % line
+        table += '
%s
' % (lexer_name, lined) + + # Add wrapper with optional caption and link + code = '
' + if self.options: + caption = ('%s' % self.options['caption']) if 'caption' in self.options else '' + title = self.options['title'] if 'title' in self.options else 'link' + link = ('%s' % (self.options['url'], title)) if 'url' in self.options else '' + + if caption or link: + code += '
%s %s
' % (caption, link) + code += '%s
' % table + + # Write cache + if cache_file is None: + cache_file = open(cached_path, 'w') + cache_file.write(parsed) + cache_file.close() + + return [nodes.raw('', code, format='html')] + +directives.register_directive('code-block', Pygments) +directives.register_directive('sourcecode', Pygments) \ No newline at end of file diff --git a/rst2html.py b/rst2html.py new file mode 100644 index 0000000..0190da0 --- /dev/null +++ b/rst2html.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +# :Author: David Goodger, the Pygments team, Guenter Milde +# :Date: $Date: $ +# :Copyright: This module has been placed in the public domain. + +# This is a merge of the `Docutils`_ `rst2html` front end with an extension +# suggestion taken from the `Pygments`_ documentation, reworked specifically +# for `Octopress`_. +# +# .. _Pygments: http://pygments.org/ +# .. _Docutils: http://docutils.sourceforge.net/ +# .. _Octopress: http://octopress.org/ + +""" +A front end to docutils, producing HTML with syntax colouring using pygments +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from transform import transform +from docutils.writers.html4css1 import Writer +from docutils.core import default_description +from directives import Pygments + +description = ('Generates (X)HTML documents from standalone reStructuredText ' + 'sources. Uses `pygments` to colorize the content of' + '"code-block" directives. Needs an adapted stylesheet' + + default_description) + +def main(): + return transform(writer=Writer(), part='html_body') + +if __name__ == '__main__': + print(main()) diff --git a/transform.py b/transform.py new file mode 100644 index 0000000..f34723f --- /dev/null +++ b/transform.py @@ -0,0 +1,40 @@ +import sys +from docutils.core import publish_parts +from optparse import OptionParser +from docutils.frontend import OptionParser as DocutilsOptionParser +from docutils.parsers.rst import Parser + +def transform(writer=None, part=None): + p = OptionParser(add_help_option=False) + + # Collect all the command line options + docutils_parser = DocutilsOptionParser(components=(writer, Parser())) + for group in docutils_parser.option_groups: + p.add_option_group(group.title, None).add_options(group.option_list) + + p.add_option('--part', default=part) + + opts, args = p.parse_args() + + settings = dict({ + 'file_insertion_enabled': False, + 'raw_enabled': False, + }, **opts.__dict__) + + if len(args) == 1: + try: + content = open(args[0], 'r').read() + except IOError: + content = args[0] + else: + content = sys.stdin.read() + + parts = publish_parts( + source=content, + settings_overrides=settings, + writer=writer, + ) + + if opts.part in parts: + return parts[opts.part] + return '' \ No newline at end of file