-
Notifications
You must be signed in to change notification settings - Fork 315
/
Copy pathmarkdown.py
137 lines (108 loc) · 4.13 KB
/
markdown.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
import threading
from weakref import ref as weakref
import mistune
from markupsafe import Markup, escape
from werkzeug.urls import url_parse
from lektor._compat import PY2
from lektor.context import get_ctx
_markdown_cache = threading.local()
class ImprovedRenderer(mistune.Renderer):
def link(self, link, title, text):
if self.record is not None:
url = url_parse(link)
if not url.scheme:
link = self.record.url_to('!' + link,
base_url=get_ctx().base_url)
link = escape(link)
if not title:
return '<a href="%s">%s</a>' % (link, text)
title = escape(title)
return '<a href="%s" title="%s">%s</a>' % (link, title, text)
def image(self, src, title, text):
if self.record is not None:
url = url_parse(src)
if not url.scheme:
src = self.record.url_to('!' + src,
base_url=get_ctx().base_url)
src = escape(src)
text = escape(text)
if title:
title = escape(title)
return '<img src="%s" alt="%s" title="%s">' % (src, text, title)
return '<img src="%s" alt="%s">' % (src, text)
class MarkdownConfig(object):
def __init__(self):
self.options = {
'escape': False,
}
self.renderer_base = ImprovedRenderer
self.renderer_mixins = []
def make_renderer(self):
bases = tuple(self.renderer_mixins) + (self.renderer_base,)
renderer_cls = type('renderer_cls', bases, {})
return renderer_cls(**self.options)
def make_markdown(env):
cfg = MarkdownConfig()
env.plugin_controller.emit('markdown-config', config=cfg)
renderer = cfg.make_renderer()
env.plugin_controller.emit('markdown-lexer-config', config=cfg, renderer=renderer)
return mistune.Markdown(renderer, **cfg.options)
def markdown_to_html(text, record=None):
ctx = get_ctx()
if ctx is None:
raise RuntimeError('Context is required for markdown rendering')
# These markdown parsers are all terrible. Not one of them does not
# modify internal state. So since we only do one of those per thread
# we can at least cache them on a thread local.
md = getattr(_markdown_cache, 'md', None)
if md is None:
md = make_markdown(ctx.env)
_markdown_cache.md = md
meta = {}
ctx.env.plugin_controller.emit('markdown-meta-init', meta=meta,
record=record)
md.renderer.meta = meta
md.renderer.record = record
rv = md(text)
ctx.env.plugin_controller.emit('markdown-meta-postprocess', meta=meta,
record=record)
return rv, meta
class Markdown(object):
def __init__(self, source, record=None):
self.source = source
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)
__nonzero__ = __bool__
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 = markdown_to_html(
self.source, 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
if not PY2:
__str__ = __unicode__
def __html__(self):
self.__render()
return Markup(self.__html)