Bladeren bron

Merge pull request #157 from samyarous/master

Pelican plugin providing RST directives for twitter bootstrap partial intergration
Justin Mayer 10 jaren geleden
bovenliggende
commit
30657eca3a

+ 118 - 0
twitter_bootstrap_rst_directives/Demo.rst

@@ -0,0 +1,118 @@
+This will be turned into :abbr:`HTML (HyperText Markup Language)`.
+
+Love this music :glyph:`music` 
+
+.. role:: story_time_glyph(glyph)
+    :target: http://www.youtube.com/watch?v=5g8ykQLYnX0
+    :class: small text-info  
+
+Love this music :story_time_glyph:`music` 
+
+This is an example of code: :code:`<example>`.
+
+This is an example of kbd: :kbd:`<example>`.
+
+
+.. label-default::
+    
+    This is a default label content
+
+.. label-primary::
+    
+    This is a primary label content
+
+.. label-success::
+    
+    This is a success label content
+
+.. label-info::
+    
+    This is a info label content
+
+.. label-warning::
+    
+    This is a warning label content
+
+.. label-danger::
+    
+    This is a danger label content
+
+
+.. panel-default::
+    :title: panel default title
+    
+    This is a default panel content
+
+.. panel-primary::
+    :title: panel primary title
+    
+    This is a primary panel content
+
+.. panel-success::
+    :title: panel success title
+    
+    This is a success panel content
+
+.. panel-info::
+    :title: panel info title
+    
+    This is a info panel content
+
+.. panel-warning::
+    :title: panel warning title
+    
+    This is a warning panel content
+
+.. panel-danger::
+    :title: panel danger title
+    
+    This is a danger panel content
+
+
+.. alert-success::
+    
+    This is a success alert content
+
+.. alert-info::
+    
+    This is a info alert content
+
+.. alert-warning::
+    
+    This is a warning alert content
+
+.. alert-danger::
+    
+    This is a danger alert content
+
+        
+.. media:: http://stuffkit.com/wp-content/uploads/2012/11/Worlds-Most-Beautiful-Lady-Camilla-Belle-HD-Photos-4.jpg
+                :height: 750
+                :width: 1000
+                :scale: 20
+                :target: http://www.google.com
+                :alt: Camilla Belle
+                :position: left
+
+                .. class:: h3
+
+                    left position
+
+                This image is not mine. Credit goes to http://stuffkit.com
+                
+
+
+.. media:: http://stuffkit.com/wp-content/uploads/2012/11/Worlds-Most-Beautiful-Lady-Camilla-Belle-HD-Photos-4.jpg
+                :height: 750
+                :width: 1000
+                :scale: 20
+                :target: http://www.google.com
+                :alt: Camilla Belle
+                :position: right
+
+                .. class:: h3
+
+                    right position
+
+
+                This image is not mine. Credit goes to http://stuffkit.com

+ 46 - 0
twitter_bootstrap_rst_directives/Readme.rst

@@ -0,0 +1,46 @@
+Twitter Bootstrap Directive for restructured text
+-------------------------------------------------
+
+This plugin defines some rst directive that enable a clean usage of the twitter bootstrap CSS and Javascript components.
+
+Directives
+----------
+
+Implemented directives:
+
+    label,
+    alert,
+    panel,
+    media
+
+Implemented roles:
+
+    glyph,
+    code,
+    kbd
+
+Usage
+-----
+
+For more informations about the usage of each directive, read the corresponding class description.
+Or checkout this demo page.
+
+Dependencies
+------------
+
+In order to use this plugin, you need to use a template that supports bootstrap 3.1.1 with the glyph font setup
+correctly. Usually you should have this structure::
+
+    static
+    ├──  css
+    |     └──  bootstrap.min.css
+    ├──  font
+    |     └──  glyphicons-halflings-regular.ttf
+    └──  js
+          └──     
+
+Warning
+-------
+
+In order to support some unique features and avoid conflicts with bootstrap, this plugin will use a custom html writer which
+is modifying the traditional docutils output.

+ 4 - 0
twitter_bootstrap_rst_directives/__init__.py

@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from .bootstrap_rst_directives import *

+ 543 - 0
twitter_bootstrap_rst_directives/bootstrap_rst_directives.py

@@ -0,0 +1,543 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+"""
+Twitter Bootstrap RST directives Plugin For Pelican
+===================================================
+
+This plugin defines rst directives for different CSS and Javascript components from
+the twitter bootstrap framework.
+
+"""
+
+from uuid import uuid1
+
+from cgi import escape
+from docutils import nodes, utils
+import docutils
+from docutils.parsers import rst
+from docutils.parsers.rst import directives, roles, Directive
+from pelican import signals
+from pelican.readers import RstReader, PelicanHTMLTranslator
+
+
+
+class CleanHTMLTranslator(PelicanHTMLTranslator):
+
+    """
+        A custom HTML translator based on the Pelican HTML translator.
+        Used to clean up some components html classes that could conflict 
+        with the bootstrap CSS classes.
+        Also defines new tags that are not handleed by the current implementation of 
+        docutils.
+
+        The most obvious example is the Container component
+    """
+
+    def visit_literal(self, node):
+        classes = node.get('classes', node.get('class', []))
+        if 'code' in classes:
+            self.body.append(self.starttag(node, 'code'))
+        elif 'kbd' in classes:
+            self.body.append(self.starttag(node, 'kbd'))
+        else:
+            self.body.append(self.starttag(node, 'pre'))
+
+    def depart_literal(self, node):
+        classes = node.get('classes', node.get('class', []))
+        if 'code' in classes:
+            self.body.append('</code>\n')
+        elif 'kbd' in classes:
+            self.body.append('</kbd>\n')
+        else:
+            self.body.append('</pre>\n')
+
+    def visit_container(self, node):
+        self.body.append(self.starttag(node, 'div'))
+
+
+class CleanRSTReader(RstReader):
+
+    """
+        A custom RST reader that behaves exactly like its parent class RstReader with
+        the difference that it uses the CleanHTMLTranslator
+    """
+
+    def _get_publisher(self, source_path):
+        extra_params = {'initial_header_level': '2',
+                        'syntax_highlight': 'short',
+                        'input_encoding': 'utf-8'}
+        user_params = self.settings.get('DOCUTILS_SETTINGS')
+        if user_params:
+            extra_params.update(user_params)
+
+        pub = docutils.core.Publisher(
+            destination_class=docutils.io.StringOutput)
+        pub.set_components('standalone', 'restructuredtext', 'html')
+        pub.writer.translator_class = CleanHTMLTranslator
+        pub.process_programmatic_settings(None, extra_params, None)
+        pub.set_source(source_path=source_path)
+        pub.publish()
+        return pub
+
+
+def keyboard_role(name, rawtext, text, lineno, inliner,
+                  options={}, content=[]):
+    """
+        This function creates an inline console input block as defined in the twitter bootstrap documentation
+        overrides the default behaviour of the kbd role
+
+        *usage:*
+            :kbd:`<your code>`
+
+        *Example:*
+
+            :kbd:`<section>`
+
+        This code is not highlighted
+    """
+    new_element = nodes.literal(rawtext, text)
+    new_element.set_class('kbd')
+
+    return [new_element], []
+
+
+def code_role(name, rawtext, text, lineno, inliner,
+              options={}, content=[]):
+    """
+        This function creates an inline code block as defined in the twitter bootstrap documentation
+        overrides the default behaviour of the code role
+
+        *usage:*
+            :code:`<your code>`
+
+        *Example:*
+
+            :code:`<section>`
+
+        This code is not highlighted
+    """
+    new_element = nodes.literal(rawtext, text)
+    new_element.set_class('code')
+
+    return [new_element], []
+
+
+def glyph_role(name, rawtext, text, lineno, inliner,
+               options={}, content=[]):
+    """
+        This function defines a glyph inline role that show a glyph icon from the 
+        twitter bootstrap framework
+
+        *Usage:*
+
+            :glyph:`<glyph_name>`
+
+        *Example:*
+
+            Love this music :glyph:`music` :)
+
+        Can be subclassed to include a target
+
+        *Example:*
+
+            .. role:: story_time_glyph(glyph)
+                :target: http://www.youtube.com/watch?v=5g8ykQLYnX0
+                :class: small text-info
+
+            Love this music :story_time_glyph:`music` :)
+
+    """
+
+    target = options.get('target', None)
+    glyph_name = 'glyphicon-{}'.format(text)
+
+    if target:
+        target = utils.unescape(target)
+        new_element = nodes.reference(rawtext, ' ', refuri=target)
+    else:
+        new_element = nodes.container()
+    classes = options.setdefault('class', [])
+    classes += ['glyphicon', glyph_name]
+    for custom_class in classes:
+        new_element.set_class(custom_class)
+    return [new_element], []
+
+glyph_role.options = {
+    'target': rst.directives.unchanged,
+}
+glyph_role.content = False
+
+
+class Label(rst.Directive):
+
+    '''
+        generic Label directive class definition.
+        This class define a directive that shows 
+        bootstrap Labels around its content
+
+        *usage:*
+
+            .. label-<label-type>::
+
+                <Label content>
+
+        *example:*
+
+            .. label-default::
+
+                This is a default label content
+
+    '''
+
+    has_content = True
+    custom_class = ''
+
+    def run(self):
+        # First argument is the name of the glyph
+        label_name = 'label-{}'.format(self.custom_class)
+        # get the label content
+        text = '\n'.join(self.content)
+        # Create a new container element (div)
+        new_element = nodes.container(text)
+        # Update its content
+        self.state.nested_parse(self.content, self.content_offset,
+                                new_element)
+        # Set its custom bootstrap classes
+        new_element['classes'] += ['label ', label_name]
+        # Return one single element
+        return [new_element]
+
+
+class DefaultLabel(Label):
+
+    custom_class = 'default'
+
+
+class PrimaryLabel(Label):
+
+    custom_class = 'primary'
+
+
+class SuccessLabel(Label):
+
+    custom_class = 'success'
+
+
+class InfoLabel(Label):
+
+    custom_class = 'info'
+
+
+class WarningLabel(Label):
+
+    custom_class = 'warning'
+
+
+class DangerLabel(Label):
+
+    custom_class = 'danger'
+
+
+class Panel(rst.Directive):
+
+    """
+        generic Panel directive class definition.
+        This class define a directive that shows 
+        bootstrap Labels around its content
+
+        *usage:*
+
+            .. panel-<panel-type>:: 
+                :title: <title>
+
+                <Panel content>
+
+        *example:*
+
+            .. panel-default:: 
+                :title: panel title
+
+                This is a default panel content
+
+    """
+
+    has_content = True
+    option_spec = {
+        'title': rst.directives.unchanged,
+    }
+    custom_class = ''
+
+    def run(self):
+        # First argument is the name of the glyph
+        panel_name = 'panel-{}'.format(self.custom_class)
+        # get the label title
+        title_text = self.options.get('title', self.custom_class.title())
+        # get the label content
+        text = '\n'.join(self.content)
+        # Create the panel element
+        panel_element = nodes.container()
+        panel_element['classes'] += ['panel', panel_name]
+        # Create the panel headings
+        heading_element = nodes.container(title_text)
+        title_nodes, messages = self.state.inline_text(title_text,
+                                                       self.lineno)
+        title = nodes.paragraph(title_text, '', *title_nodes)
+        heading_element.append(title)
+        heading_element['classes'] += ['panel-heading']
+        # Create a new container element (div)
+        body_element = nodes.container(text)
+        # Update its content
+        self.state.nested_parse(self.content, self.content_offset,
+                                body_element)
+        # Set its custom bootstrap classes
+        body_element['classes'] += ['panel-body']
+        # add the heading and body to the panel
+        panel_element.append(heading_element)
+        panel_element.append(body_element)
+        # Return the panel element
+        return [panel_element]
+
+
+class DefaultPanel(Panel):
+
+    custom_class = 'default'
+
+
+class PrimaryPanel(Panel):
+
+    custom_class = 'primary'
+
+
+class SuccessPanel(Panel):
+
+    custom_class = 'success'
+
+
+class InfoPanel(Panel):
+
+    custom_class = 'info'
+
+
+class WarningPanel(Panel):
+
+    custom_class = 'warning'
+
+
+class DangerPanel(Panel):
+
+    custom_class = 'danger'
+
+
+class Alert(rst.Directive):
+
+    """
+        generic Alert directive class definition.
+        This class define a directive that shows 
+        bootstrap Labels around its content
+
+        *usage:*
+
+            .. alert-<alert-type>::
+
+                <alert content>
+
+        *example:*
+
+            .. alert-default::
+
+                This is a default alert content
+
+    """
+    has_content = True
+    custom_class = ''
+
+    def run(self):
+        # First argument is the name of the glyph
+        alert_name = 'alert-{}'.format(self.custom_class)
+        # get the label content
+        text = '\n'.join(self.content)
+        # Create a new container element (div)
+        new_element = nodes.compound(text)
+        # Update its content
+        self.state.nested_parse(self.content, self.content_offset,
+                                new_element)
+        # Recurse inside its children and change the hyperlinks classes
+        for child in new_element.traverse(include_self=False):
+            if isinstance(child, nodes.reference):
+                child.set_class('alert-link')
+        # Set its custom bootstrap classes
+        new_element['classes'] += ['alert ', alert_name]
+        # Return one single element
+        return [new_element]
+
+
+class SuccessAlert(Alert):
+
+    custom_class = 'success'
+
+
+class InfoAlert(Alert):
+
+    custom_class = 'info'
+
+
+class WarningAlert(Alert):
+
+    custom_class = 'warning'
+
+
+class DangerAlert(Alert):
+
+    custom_class = 'danger'
+
+
+class Media(rst.Directive):
+
+    '''
+        generic Media directive class definition.
+        This class define a directive that shows 
+        bootstrap media image with text according
+        to the media component on bootstrap
+
+        *usage*:
+            .. media:: <image_uri>
+                :position: <position>
+                :alt: <alt>
+                :height: <height>
+                :width: <width>
+                :scale: <scale>
+                :target: <target>
+
+                <text content>
+
+        *example*:
+            .. media:: http://stuffkit.com/wp-content/uploads/2012/11/Worlds-Most-Beautiful-Lady-Camilla-Belle-HD-Photos-4.jpg
+                :height: 750
+                :width: 1000
+                :scale: 20
+                :target: www.google.com
+                :alt: Camilla Belle
+                :position: left
+
+                This image is not mine. Credit goes to http://stuffkit.com
+
+
+
+    '''
+
+    has_content = True
+    required_arguments = 1
+
+    option_spec = {
+        'position': str,
+        'alt': rst.directives.unchanged,
+        'height': rst.directives.length_or_unitless,
+        'width': rst.directives.length_or_percentage_or_unitless,
+        'scale': rst.directives.percentage,
+        'target': rst.directives.unchanged_required,
+    }
+
+    def get_image_element(self):
+        # Get the image url
+        image_url = self.arguments[0]
+        image_reference = rst.directives.uri(image_url)
+        self.options['uri'] = image_reference
+
+        reference_node = None
+        messages = []
+        if 'target' in self.options:
+            block = rst.states.escape2null(
+                self.options['target']).splitlines()
+            block = [line for line in block]
+            target_type, data = self.state.parse_target(
+                block, self.block_text, self.lineno)
+            if target_type == 'refuri':
+                container_node = nodes.reference(refuri=data)
+            elif target_type == 'refname':
+                container_node = nodes.reference(
+                    refname=fully_normalize_name(data),
+                    name=whitespace_normalize_name(data))
+                container_node.indirect_reference_name = data
+                self.state.document.note_refname(container_node)
+            else:                           # malformed target
+                messages.append(data)       # data is a system message
+            del self.options['target']
+        else:
+            container_node = nodes.container()
+
+        # get image position
+        position = self.options.get('position', 'left')
+        position_class = 'pull-{}'.format(position)
+
+        container_node.set_class(position_class)
+
+        image_node = nodes.image(self.block_text, **self.options)
+        image_node['classes'] += ['media-object']
+
+        container_node.append(image_node)
+        return container_node
+
+    def run(self):
+        # now we get the content
+        text = '\n'.join(self.content)
+
+        # get image alternative text
+        alternative_text = self.options.get('alternative-text', '')
+
+        # get container element
+        container_element = nodes.container()
+        container_element['classes'] += ['media']
+
+        # get image element
+        image_element = self.get_image_element()
+
+        # get body element
+        body_element = nodes.container(text)
+        body_element['classes'] += ['media-body']
+        self.state.nested_parse(self.content, self.content_offset,
+                                body_element)
+
+        container_element.append(image_element)
+        container_element.append(body_element)
+        return [container_element, ]
+
+
+def register_directives():
+    rst.directives.register_directive('label-default', DefaultLabel)
+    rst.directives.register_directive('label-primary', PrimaryLabel)
+    rst.directives.register_directive('label-success', SuccessLabel)
+    rst.directives.register_directive('label-info', InfoLabel)
+    rst.directives.register_directive('label-warning', WarningLabel)
+    rst.directives.register_directive('label-danger', DangerLabel)
+
+    rst.directives.register_directive('panel-default', DefaultPanel)
+    rst.directives.register_directive('panel-primary', PrimaryPanel)
+    rst.directives.register_directive('panel-success', SuccessPanel)
+    rst.directives.register_directive('panel-info', InfoPanel)
+    rst.directives.register_directive('panel-warning', WarningPanel)
+    rst.directives.register_directive('panel-danger', DangerPanel)
+
+    rst.directives.register_directive('alert-success', SuccessAlert)
+    rst.directives.register_directive('alert-info', InfoAlert)
+    rst.directives.register_directive('alert-warning', WarningAlert)
+    rst.directives.register_directive('alert-danger', DangerAlert)
+
+    rst.directives.register_directive( 'media', Media )
+
+
+def register_roles():
+    rst.roles.register_local_role('glyph', glyph_role)
+    rst.roles.register_local_role('code', code_role)
+    rst.roles.register_local_role('kbd', keyboard_role)
+
+
+def add_reader(readers):
+    readers.reader_classes['rst'] = CleanRSTReader
+
+
+def register():
+    register_directives()
+    register_roles()
+    signals.readers_init.connect(add_reader)