-
Notifications
You must be signed in to change notification settings - Fork 5
/
lektor_rst.py
146 lines (116 loc) · 4.3 KB
/
lektor_rst.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# -*- coding: utf-8 -*-
from io import StringIO
from lektor.context import get_ctx
from lektor.pluginsystem import Plugin, get_plugin
from lektor.types import Type
from markupsafe import Markup
from weakref import ref as weakref
import datetime
import docutils.core
import docutils.writers
import docutils.writers.html4css1
import re
_re_dashes = re.compile(r"^(\s*)(---*)", re.MULTILINE)
def rst_to_html(text, extra_params, record):
ctx = get_ctx()
if ctx is None:
raise RuntimeError('Context is required for markdown rendering')
text = _re_dashes.sub(r"\1-\2", text)
try:
plugin = get_plugin('rst')
config = plugin.get_config()
settings = config.section_as_dict('docutils')
writer_name = settings.pop('writer', 'html')
extra_params.update(settings)
except:
writer_name = 'html'
Writer = docutils.writers.get_writer_class(writer_name)
pub = docutils.core.Publisher(
destination_class=docutils.io.StringOutput,
writer=Writer())
pub.set_components('standalone', 'restructuredtext', 'html')
pub.process_programmatic_settings(None, extra_params, None)
pub.set_source(
source=StringIO(text),
source_path=record.source_filename if record is not None else None)
pub.publish()
metadata = {}
for docinfo in pub.document.findall(docutils.nodes.docinfo):
for element in docinfo.children:
if element.tagname == 'field':
name_elem, body_elem = element.children
name = name_elem.astext()
value = body_elem.astext()
else:
name = element.tagname
value = element.astext()
name = name.lower()
if name == 'date':
value = datetime.datetime.strptime(value, "%Y-%m-%d %H:%M")
metadata[name] = value
parts = pub.writer.parts
body = parts['html_title'] + parts['html_subtitle'] + parts['fragment']
return body, metadata
class Rst(object):
def __init__(self, source, extra_params, record):
self.source = source
self.extra_params = extra_params
self.__record = weakref(record) if record is not None else lambda: None
self.__cached_for_ctx = None
self.__html = None
self.__meta = None
def __bool__(self):
return bool(self.source)
def __nonzero__(self):
return bool(self.source)
def __render(self):
# When the markdown instance is attached to a cached object we can
# end up in the situation where the context changed from the time
# we were put into the cache to the time where we got referenced
# by something elsewhere. In that case we need to re-process our
# markdown. For instance this affects relative links.
if self.__html is None or \
self.__cached_for_ctx != get_ctx():
self.__html, self.__meta = rst_to_html(
self.source, self.extra_params, self.__record())
self.__cached_for_ctx = get_ctx()
@property
def meta(self):
self.__render()
return self.__meta
@property
def html(self):
self.__render()
return Markup(self.__html)
def __getitem__(self, name):
return self.meta[name]
def __unicode__(self):
self.__render()
return self.__html
def __html__(self):
self.__render()
return Markup(self.__html)
class RstDescriptor(object):
def __init__(self, source, extra_params):
self.source = source
self.extra_params = extra_params
def __get__(self, obj, type=None):
if obj is None:
return self
return Rst(self.source, self.extra_params, obj)
class RstType(Type):
widget = 'multiline-text'
def __init__(self, env, options):
Type.__init__(self, env, options)
self.extra_params = {
'doctitle_xform': False,
'initial_header_level': '2',
'syntax_highlight': 'short'}
self.extra_params.update(options)
def value_from_raw(self, raw):
return RstDescriptor(raw.value or u'', self.extra_params)
class RstPlugin(Plugin):
name = "reStructuredText"
description = "Adds reStructuredText support"
def on_setup_env(self, **extra):
self.env.types['rst'] = RstType