311 lines
11 KiB
Python
311 lines
11 KiB
Python
|
#!/usr/bin/env python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
# -----------------------------------------------------------------------------
|
|||
|
# Bootstrap RST
|
|||
|
# Copyright (c) 2014, Nicolas P. Rougier
|
|||
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|||
|
# -----------------------------------------------------------------------------
|
|||
|
import sys, os, re
|
|||
|
from docutils import nodes, utils
|
|||
|
from docutils.parsers.rst.directives import images
|
|||
|
from docutils.transforms import TransformError, Transform, parts
|
|||
|
from docutils.parsers.rst import Directive, directives, states, roles
|
|||
|
from docutils.nodes import fully_normalize_name, whitespace_normalize_name
|
|||
|
from docutils.parsers.rst.roles import set_classes
|
|||
|
|
|||
|
from docutils.io import StringOutput
|
|||
|
from docutils.core import Publisher
|
|||
|
|
|||
|
from pelican import signals
|
|||
|
from pelican.readers import RstReader, PelicanHTMLTranslator
|
|||
|
|
|||
|
from .roles import *
|
|||
|
from .directives import *
|
|||
|
|
|||
|
|
|||
|
class HTMLTranslator(PelicanHTMLTranslator):
|
|||
|
"""
|
|||
|
This is a translator class for the docutils system.
|
|||
|
"""
|
|||
|
|
|||
|
def visit_h1(self, node):
|
|||
|
self.body.append('<h1>%s</h1>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_h2(self, node):
|
|||
|
self.body.append('<h2>%s</h2>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_h3(self, node):
|
|||
|
self.body.append('<h3>%s</h3>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_h4(self, node):
|
|||
|
self.body.append('<h4>%s</h4>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_h5(self, node):
|
|||
|
self.body.append('<h5>%s</h5>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_h6(self, node):
|
|||
|
self.body.append('<h6>%s</h6>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_label_default(self, node):
|
|||
|
self.body.append(
|
|||
|
'<span class="label label-default">%s</span>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_label_primary(self, node):
|
|||
|
self.body.append(
|
|||
|
'<span class="label label-primary">%s</span>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_label_success(self, node):
|
|||
|
self.body.append(
|
|||
|
'<span class="label label-success">%s</span>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_label_info(self, node):
|
|||
|
self.body.append(
|
|||
|
'<span class="label label-info">%s</span>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_label_warning(self, node):
|
|||
|
self.body.append(
|
|||
|
'<span class="label label-warning">%s</span>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_label_danger(self, node):
|
|||
|
self.body.append(
|
|||
|
'<span class="label label-danger">%s</span>' % node.children[0])
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_page_row(self, node):
|
|||
|
self.body.append(self.starttag(node,'div'))
|
|||
|
|
|||
|
def depart_page_row(self, node):
|
|||
|
self.body.append('</div>\n')
|
|||
|
|
|||
|
def visit_page_column(self, node):
|
|||
|
self.body.append(self.starttag(node,'div'))
|
|||
|
|
|||
|
def depart_page_column(self, node):
|
|||
|
self.body.append('</div>\n')
|
|||
|
|
|||
|
|
|||
|
def visit_button(self, node):
|
|||
|
btn_classes = { 'primary' : 'btn-primary', 'success' : 'btn-success',
|
|||
|
'info' : 'btn-info', 'warning' : 'btn-warning',
|
|||
|
'danger' : 'btn-danger', 'link' : 'btn-link',
|
|||
|
'outline' : 'btn-outline', 'tiny' : 'btn-xs',
|
|||
|
'small' : 'btn-sm', 'large' : 'btn-lg',
|
|||
|
'block' : 'btn-block', 'active' : 'btn-active' }
|
|||
|
|
|||
|
classes = 'btn '
|
|||
|
flag = False
|
|||
|
for node_class in node['classes']:
|
|||
|
if node_class in ['primary', 'success', 'warning'
|
|||
|
'info', 'link', 'danger', 'outline']:
|
|||
|
flag = True
|
|||
|
btn_class = btn_classes.get(node_class, None)
|
|||
|
if btn_class:
|
|||
|
classes += btn_class + ' '
|
|||
|
if flag == False:
|
|||
|
classes += 'btn-default'
|
|||
|
|
|||
|
target = node['target']
|
|||
|
properties = ''
|
|||
|
|
|||
|
# Disabled
|
|||
|
if 'disabled' in node['classes']:
|
|||
|
if target:
|
|||
|
properties += ' disabled="disabled"'
|
|||
|
else:
|
|||
|
classes += ' disabled'
|
|||
|
|
|||
|
# Data toggle
|
|||
|
if 'toggle' in node['classes']:
|
|||
|
classes += ' dropdown-toggle '
|
|||
|
properties += ' data-toggle="dropdown"'
|
|||
|
if target:
|
|||
|
properties += ' role="button"'
|
|||
|
anchor = '<a href="%s" class="%s" %s>' % (target,classes,properties)
|
|||
|
self.body.append(anchor)
|
|||
|
else:
|
|||
|
properties += ' type="button"'
|
|||
|
button = '<button class="%s" %s>' % (classes,properties)
|
|||
|
self.body.append(button)
|
|||
|
|
|||
|
def depart_button(self, node):
|
|||
|
if node['target']:
|
|||
|
self.body.append('</a>\n')
|
|||
|
else:
|
|||
|
self.body.append('</button>\n')
|
|||
|
|
|||
|
|
|||
|
def visit_progress(self, node):
|
|||
|
prg_classes = { 'success' : 'progress-bar-success',
|
|||
|
'info' : 'progress-bar-info',
|
|||
|
'warning' : 'progress-bar-warning',
|
|||
|
'danger' : 'progress-bar-danger' }
|
|||
|
|
|||
|
label = node['label']
|
|||
|
classes = 'progress-bar'
|
|||
|
flag = False
|
|||
|
for nodeclass in node['classes']:
|
|||
|
flag = True
|
|||
|
classes += ' ' + prg_classes.get(nodeclass, '')
|
|||
|
if flag == False:
|
|||
|
classes += ' progress-bar-default'
|
|||
|
properties = 'role="progress-bar"'
|
|||
|
properties += ' aria-valuenow="%d"' % int(node['value'])
|
|||
|
properties += ' aria-valuemin="%d"' % int(node['value_min'])
|
|||
|
properties += ' aria-valuemax="%d"' % int(node['value_max'])
|
|||
|
properties += ' style="width: %d%%";' % int(node['value'])
|
|||
|
if 'active' in node['classes']:
|
|||
|
self.body.append('<div class="progress progress-striped active">')
|
|||
|
elif 'striped' in node['classes']:
|
|||
|
self.body.append('<div class="progress progress-striped">')
|
|||
|
else:
|
|||
|
self.body.append('<div class="progress">')
|
|||
|
self.body.append(
|
|||
|
'<div class="%s" %s>%s</div>' % (classes,properties,label))
|
|||
|
self.body.append('</div>')
|
|||
|
raise nodes.SkipNode
|
|||
|
|
|||
|
def visit_alert(self, node):
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS='alert'))
|
|||
|
if node.dismissable:
|
|||
|
self.body.append(
|
|||
|
u"""<button type="button" class="close" data-dismiss="alert" """
|
|||
|
u"""aria-hidden="true">×</button>""")
|
|||
|
|
|||
|
def depart_alert(self, node):
|
|||
|
self.body.append('</div>\n')
|
|||
|
|
|||
|
def visit_callout(self, node):
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS='bs-callout'))
|
|||
|
|
|||
|
def depart_callout(self, node):
|
|||
|
self.body.append('</div>\n')
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# overwritten
|
|||
|
def visit_definition_list(self, node):
|
|||
|
list_class = node.parent.get('list-class', [])
|
|||
|
list_class.append('docutils')
|
|||
|
list_class = ' '.join(list_class)
|
|||
|
self.body.append(self.starttag(node, 'dl', CLASS=list_class))
|
|||
|
|
|||
|
# overwritten
|
|||
|
def visit_sidebar(self, node):
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS='col-md-3 col-md-push-9'))
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS='bs-docs-sidebar hidden-print affix-top'))
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS='sidebar'))
|
|||
|
self.set_first_last(node)
|
|||
|
self.in_sidebar = True
|
|||
|
|
|||
|
# overwritten
|
|||
|
def depart_sidebar(self, node):
|
|||
|
self.body.append('</div>\n')
|
|||
|
self.body.append('</div>\n')
|
|||
|
self.body.append('</div>\n')
|
|||
|
# Opening tag for body
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS='col-md-9 col-md-pull-3'))
|
|||
|
self.in_sidebar = False
|
|||
|
|
|||
|
# overwritten : removed compact paragraph
|
|||
|
# def visit_paragraph(self, node):
|
|||
|
# if self.should_be_compact_paragraph(node):
|
|||
|
# self.context.append('')
|
|||
|
# else:
|
|||
|
# self.body.append(self.starttag(node, 'p', ''))
|
|||
|
# self.context.append('</p>\n')
|
|||
|
|
|||
|
# overwritten: remove border=1, replace docutils/table class
|
|||
|
def visit_table(self, node):
|
|||
|
self.context.append(self.compact_p)
|
|||
|
self.compact_p = True
|
|||
|
#classes = ' '.join(['docutils', self.settings.table_style]).strip()
|
|||
|
classes = ' '.join(['table', self.settings.table_style]).strip()
|
|||
|
self.body.append(self.starttag(node, 'table', CLASS=classes))
|
|||
|
|
|||
|
# overwritten : removed 'container' class
|
|||
|
def visit_container(self, node):
|
|||
|
self.body.append(self.starttag(node, 'div', CLASS=''))
|
|||
|
|
|||
|
# overwritten: get rid of <hr> tag
|
|||
|
def depart_header(self, node):
|
|||
|
start = self.context.pop()
|
|||
|
header = [self.starttag(node, 'div', CLASS='header')]
|
|||
|
header.extend(self.body[start:])
|
|||
|
header.append('\n</div>\n')
|
|||
|
self.body_prefix.extend(header)
|
|||
|
self.header.extend(header)
|
|||
|
del self.body[start:]
|
|||
|
|
|||
|
# overwritten: get rid of <hr> tag
|
|||
|
def depart_footer(self, node):
|
|||
|
start = self.context.pop()
|
|||
|
footer = [self.starttag(node, 'div', CLASS='footer')]
|
|||
|
footer.extend(self.body[start:])
|
|||
|
footer.append('\n</div>\n')
|
|||
|
self.footer.extend(footer)
|
|||
|
self.body_suffix[:0] = footer
|
|||
|
del self.body[start:]
|
|||
|
|
|||
|
# overwritten
|
|||
|
def depart_document(self, node):
|
|||
|
self.head_prefix.extend([self.doctype,
|
|||
|
self.head_prefix_template %
|
|||
|
{'lang': self.settings.language_code}])
|
|||
|
self.html_prolog.append(self.doctype)
|
|||
|
self.meta.insert(0, self.content_type % self.settings.output_encoding)
|
|||
|
self.head.insert(0, self.content_type % self.settings.output_encoding)
|
|||
|
if self.math_header:
|
|||
|
self.head.append(self.math_header)
|
|||
|
# skip content-type meta tag with interpolated charset value:
|
|||
|
self.html_head.extend(self.head[1:])
|
|||
|
# self.body_prefix.append(self.starttag(node, 'div', CLASS='document'))
|
|||
|
self.body_prefix.append(self.starttag(node, 'div', CLASS='container'))
|
|||
|
# self.body_suffix.insert(0, '</div>\n')
|
|||
|
self.fragment.extend(self.body) # self.fragment is the "naked" body
|
|||
|
self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo
|
|||
|
+ self.docinfo + self.body
|
|||
|
+ self.body_suffix[:-1])
|
|||
|
assert not self.context, 'len(context) = %s' % len(self.context)
|
|||
|
|
|||
|
|
|||
|
# -----------------------------------------------------------------------------
|
|||
|
class RSTReader(RstReader):
|
|||
|
"""
|
|||
|
A custom RST reader that behaves exactly like its parent class RstReader
|
|||
|
with the difference that it uses our HTMLTranslator
|
|||
|
"""
|
|||
|
|
|||
|
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 = Publisher(destination_class=StringOutput)
|
|||
|
pub.set_components('standalone', 'restructuredtext', 'html')
|
|||
|
pub.writer.translator_class = HTMLTranslator
|
|||
|
pub.process_programmatic_settings(None, extra_params, None)
|
|||
|
pub.set_source(source_path=source_path)
|
|||
|
pub.publish()
|
|||
|
return pub
|
|||
|
|
|||
|
|
|||
|
def add_reader(readers):
|
|||
|
readers.reader_classes['rst'] = RSTReader
|
|||
|
|
|||
|
def register():
|
|||
|
signals.readers_init.connect(add_reader)
|