Initial release of the twitter bootstrap rst directives plugin

This commit is contained in:
Samy Arous
2014-02-16 19:23:48 +01:00
parent f7178ae0e9
commit b86e17607c
4 changed files with 711 additions and 0 deletions

View File

@@ -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

View File

@@ -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.

View File

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

View File

@@ -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)