Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #72 from pellenilsson/rest_html5
reST HTML5 compiler plugin
- Loading branch information
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Modified version of original reST plugin that uses an [alternative docutils writer](https://bitbucket.org/andre_felipe_dias/rst2html5) to produce HTML5 output. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
rst2html5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[Core] | ||
Name = rest_html5 | ||
Module = rest_html5 | ||
|
||
[Documentation] | ||
Author = Roberto Alsina, Pelle Nilsson | ||
Version = 1.0 | ||
Website = http://plugins.getnikola.com/#rest_html5 | ||
Description = Compile reST into HTML5 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright © 2012-2015 Roberto Alsina and others. | ||
|
||
# Permission is hereby granted, free of charge, to any | ||
# person obtaining a copy of this software and associated | ||
# documentation files (the "Software"), to deal in the | ||
# Software without restriction, including without limitation | ||
# the rights to use, copy, modify, merge, publish, | ||
# distribute, sublicense, and/or sell copies of the | ||
# Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice | ||
# shall be included in all copies or substantial portions of | ||
# the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY | ||
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | ||
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | ||
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR | ||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
from __future__ import unicode_literals | ||
import io | ||
import os | ||
import re | ||
|
||
try: | ||
import docutils.core | ||
import docutils.nodes | ||
import docutils.utils | ||
import docutils.io | ||
import docutils.readers.standalone | ||
has_docutils = True | ||
except ImportError: | ||
has_docutils = False | ||
|
||
try: | ||
import rst2html5 | ||
has_rst2html5 = True | ||
except ImportError: | ||
has_rst2html5 = False | ||
|
||
from nikola.plugin_categories import PageCompiler | ||
from nikola.utils import get_logger, makedirs, req_missing, write_metadata | ||
|
||
|
||
class CompileRestHTML5(PageCompiler): | ||
"""Compile reSt into HTML.""" | ||
|
||
name = "rest_html5" | ||
demote_headers = True | ||
logger = None | ||
|
||
def _read_extra_deps(self, post): | ||
"""Reads contents of .dep file and returns them as a list""" | ||
dep_path = post.base_path + '.dep' | ||
if os.path.isfile(dep_path): | ||
with io.open(dep_path, 'r+', encoding='utf8') as depf: | ||
deps = [l.strip() for l in depf.readlines()] | ||
return deps | ||
return [] | ||
|
||
def register_extra_dependencies(self, post): | ||
"""Adds dependency to post object to check .dep file.""" | ||
post.add_dependency(lambda: self._read_extra_deps(post), 'fragment') | ||
|
||
def compile_html(self, source, dest, is_two_file=True): | ||
"""Compile reSt into HTML.""" | ||
|
||
if not has_docutils: | ||
req_missing(['docutils'], 'build this site (compile reStructuredText)') | ||
if not has_rst2html5: | ||
req_missing(['rst2html5'], 'build this site (compile reStructuredText into HTML5)') | ||
makedirs(os.path.dirname(dest)) | ||
error_level = 100 | ||
with io.open(dest, "w+", encoding="utf8") as out_file: | ||
with io.open(source, "r", encoding="utf8") as in_file: | ||
data = in_file.read() | ||
add_ln = 0 | ||
if not is_two_file: | ||
spl = re.split('(\n\n|\r\n\r\n)', data, maxsplit=1) | ||
data = spl[-1] | ||
if len(spl) != 1: | ||
# If errors occur, this will be added to the line | ||
# number reported by docutils so the line number | ||
# matches the actual line number (off by 7 with default | ||
# metadata, could be more or less depending on the post | ||
# author). | ||
add_ln = len(spl[0].splitlines()) + 1 | ||
|
||
default_template_path = os.path.join(os.path.dirname(__file__), 'template.txt') | ||
output, error_level, deps = rst2html( | ||
data, settings_overrides={ | ||
'initial_header_level': 0, | ||
'record_dependencies': True, | ||
'stylesheet_path': None, | ||
'link_stylesheet': True, | ||
'syntax_highlight': 'short', | ||
'math_output': 'mathjax', | ||
'template': default_template_path, | ||
}, logger=self.logger, source_path=source, l_add_ln=add_ln) | ||
out_file.write(output) | ||
deps_path = dest + '.dep' | ||
if deps.list: | ||
with io.open(deps_path, "w+", encoding="utf8") as deps_file: | ||
deps_file.write('\n'.join(deps.list)) | ||
else: | ||
if os.path.isfile(deps_path): | ||
os.unlink(deps_path) | ||
if error_level < 3: | ||
return True | ||
else: | ||
return False | ||
|
||
def create_post(self, path, **kw): | ||
content = kw.pop('content', None) | ||
onefile = kw.pop('onefile', False) | ||
# is_page is not used by create_post as of now. | ||
kw.pop('is_page', False) | ||
metadata = {} | ||
metadata.update(self.default_metadata) | ||
metadata.update(kw) | ||
makedirs(os.path.dirname(path)) | ||
if not content.endswith('\n'): | ||
content += '\n' | ||
with io.open(path, "w+", encoding="utf8") as fd: | ||
if onefile: | ||
fd.write(write_metadata(metadata)) | ||
fd.write('\n') | ||
fd.write(content) | ||
|
||
def set_site(self, site): | ||
self.config_dependencies = [] | ||
for plugin_info in site.plugin_manager.getPluginsOfCategory("RestExtension"): | ||
if plugin_info.name in site.config['DISABLED_PLUGINS']: | ||
site.plugin_manager.removePluginFromCategory(plugin_info, "RestExtension") | ||
continue | ||
|
||
site.plugin_manager.activatePluginByName(plugin_info.name) | ||
self.config_dependencies.append(plugin_info.name) | ||
plugin_info.plugin_object.set_site(site) | ||
plugin_info.plugin_object.short_help = plugin_info.description | ||
|
||
self.logger = get_logger('compile_rest', site.loghandlers) | ||
if not site.debug: | ||
self.logger.level = 4 | ||
|
||
return super(CompileRestHTML5, self).set_site(site) | ||
|
||
|
||
def get_observer(settings): | ||
"""Return an observer for the docutils Reporter.""" | ||
def observer(msg): | ||
"""Report docutils/rest messages to a Nikola user. | ||
Error code mapping: | ||
+------+---------+------+----------+ | ||
| dNUM | dNAME | lNUM | lNAME | d = docutils, l = logbook | ||
+------+---------+------+----------+ | ||
| 0 | DEBUG | 1 | DEBUG | | ||
| 1 | INFO | 2 | INFO | | ||
| 2 | WARNING | 4 | WARNING | | ||
| 3 | ERROR | 5 | ERROR | | ||
| 4 | SEVERE | 6 | CRITICAL | | ||
+------+---------+------+----------+ | ||
""" | ||
errormap = {0: 1, 1: 2, 2: 4, 3: 5, 4: 6} | ||
text = docutils.nodes.Element.astext(msg) | ||
line = msg['line'] + settings['add_ln'] if 'line' in msg else 0 | ||
out = '[{source}{colon}{line}] {text}'.format( | ||
source=settings['source'], colon=(':' if line else ''), | ||
line=line, text=text) | ||
settings['logger'].log(errormap[msg['level']], out) | ||
|
||
return observer | ||
|
||
|
||
class NikolaReader(docutils.readers.standalone.Reader): | ||
|
||
def new_document(self): | ||
"""Create and return a new empty document tree (root node).""" | ||
document = docutils.utils.new_document(self.source.source_path, self.settings) | ||
document.reporter.stream = False | ||
document.reporter.attach_observer(get_observer(self.l_settings)) | ||
return document | ||
|
||
|
||
def add_node(node, visit_function=None, depart_function=None): | ||
""" | ||
Register a Docutils node class. | ||
This function is completely optional. It is a same concept as | ||
`Sphinx add_node function <http://sphinx-doc.org/ext/appapi.html#sphinx.application.Sphinx.add_node>`_. | ||
For example:: | ||
class Plugin(RestExtension): | ||
name = "rest_math" | ||
def set_site(self, site): | ||
self.site = site | ||
directives.register_directive('math', MathDirective) | ||
add_node(MathBlock, visit_Math, depart_Math) | ||
return super(Plugin, self).set_site(site) | ||
class MathDirective(Directive): | ||
def run(self): | ||
node = MathBlock() | ||
return [node] | ||
class Math(docutils.nodes.Element): pass | ||
def visit_Math(self, node): | ||
self.body.append(self.starttag(node, 'math')) | ||
def depart_Math(self, node): | ||
self.body.append('</math>') | ||
For full example, you can refer to `Microdata plugin <http://plugins.getnikola.com/#microdata>`_ | ||
""" | ||
docutils.nodes._add_node_class_names([node.__name__]) | ||
if visit_function: | ||
setattr(rst2html5.HTML5Translator, 'visit_' + node.__name__, visit_function) | ||
if depart_function: | ||
setattr(rst2html5.HTML5Translator, 'depart_' + node.__name__, depart_function) | ||
|
||
|
||
def rst2html(source, source_path=None, source_class=docutils.io.StringInput, | ||
destination_path=None, reader=None, | ||
parser=None, parser_name='restructuredtext', writer=None, | ||
writer_name='html5', settings=None, settings_spec=None, | ||
settings_overrides=None, config_section=None, | ||
enable_exit_status=None, logger=None, l_add_ln=0): | ||
""" | ||
Set up & run a `Publisher`, and return a dictionary of document parts. | ||
Dictionary keys are the names of parts, and values are Unicode strings; | ||
encoding is up to the client. For programmatic use with string I/O. | ||
For encoded string input, be sure to set the 'input_encoding' setting to | ||
the desired encoding. Set it to 'unicode' for unencoded Unicode string | ||
input. Here's how:: | ||
publish_parts(..., settings_overrides={'input_encoding': 'unicode'}) | ||
Parameters: see `publish_programmatically`. | ||
WARNING: `reader` should be None (or NikolaReader()) if you want Nikola to report | ||
reStructuredText syntax errors. | ||
""" | ||
if reader is None: | ||
reader = NikolaReader() | ||
# For our custom logging, we have special needs and special settings we | ||
# specify here. | ||
# logger a logger from Nikola | ||
# source source filename (docutils gets a string) | ||
# add_ln amount of metadata lines (see comment in compile_html above) | ||
reader.l_settings = {'logger': logger, 'source': source_path, | ||
'add_ln': l_add_ln} | ||
|
||
if writer is None: | ||
writer = rst2html5.HTML5Writer() | ||
|
||
pub = docutils.core.Publisher(reader, parser, writer, settings=settings, | ||
source_class=source_class, | ||
destination_class=docutils.io.StringOutput) | ||
pub.set_components(None, parser_name, writer_name) | ||
pub.process_programmatic_settings( | ||
settings_spec, settings_overrides, config_section) | ||
pub.set_source(source, None) | ||
pub.settings._nikola_source_path = source_path | ||
pub.set_destination(None, destination_path) | ||
pub.publish(enable_exit_status=enable_exit_status) | ||
|
||
return pub.writer.parts['body'], pub.document.reporter.max_level, pub.settings.record_dependencies |