Initial release of the twitter bootstrap rst directives plugin
This commit is contained in:
118
twitter_bootstrap_rst_directives/Demo.rst
Normal file
118
twitter_bootstrap_rst_directives/Demo.rst
Normal 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
|
||||
46
twitter_bootstrap_rst_directives/Readme.rst
Normal file
46
twitter_bootstrap_rst_directives/Readme.rst
Normal 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.
|
||||
4
twitter_bootstrap_rst_directives/__init__.py
Normal file
4
twitter_bootstrap_rst_directives/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .bootstrap_rst_directives import *
|
||||
543
twitter_bootstrap_rst_directives/bootstrap_rst_directives.py
Normal file
543
twitter_bootstrap_rst_directives/bootstrap_rst_directives.py
Normal 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)
|
||||
Reference in New Issue
Block a user