123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- #!/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-warning::
- This is a warning 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)
|