0
|
1 import logging
|
|
2 from piecrust.plugins.base import PieCrustPlugin
|
|
3 from piecrust.formatting.base import Formatter
|
|
4
|
|
5
|
|
6 logger = logging.getLogger(__name__)
|
|
7
|
|
8
|
|
9 class HoedownPlugin(PieCrustPlugin):
|
|
10 name = 'Hoedown'
|
|
11
|
|
12 def getFormatters(self):
|
|
13 return [
|
|
14 HoedownFormatter()]
|
|
15
|
|
16
|
|
17 __piecrust_plugin__ = HoedownPlugin
|
|
18
|
|
19
|
|
20 class HoedownFormatter(Formatter):
|
|
21 FORMAT_NAMES = ['hoedown']
|
|
22 OUTPUT_FORMAT = 'html'
|
|
23
|
|
24 def __init__(self):
|
|
25 super(HoedownFormatter, self).__init__()
|
|
26 self._formatter = None
|
|
27
|
|
28 def render(self, format_name, txt):
|
|
29 assert format_name in self.FORMAT_NAMES
|
|
30 self._ensureInitialized()
|
|
31 return self._formatter(txt)
|
|
32
|
|
33 def _ensureInitialized(self):
|
|
34 if self._formatter is not None:
|
|
35 return
|
|
36
|
|
37 import misaka
|
|
38
|
|
39 # Don't show warnings once for each worker when baking, so only
|
|
40 # show them for the first. If the variable is not set, we're not
|
|
41 # baking so do show them either way.
|
|
42 show_warnings = (self.app.config.get('baker/worker_id', 0) == 0)
|
|
43
|
|
44 config = self.app.config.get('hoedown')
|
|
45 if config is None:
|
|
46 config = self.app.config.get('markdown')
|
|
47 if config is None:
|
|
48 config = {}
|
|
49 elif not isinstance(config, dict):
|
|
50 raise Exception("The `hoedown` configuration setting must be "
|
|
51 "a dictionary.")
|
|
52
|
|
53 extensions = config.get('extensions', [])
|
|
54 if isinstance(extensions, str):
|
|
55 extensions = [e.strip() for e in extensions.split(',')]
|
|
56 # Compatibility with PieCrust 1.x
|
|
57 if config.get('use_markdown_extra'):
|
|
58 extensions.append('extra')
|
|
59
|
|
60 render_flags = config.get('render_flags')
|
|
61 if render_flags is None:
|
|
62 render_flags = []
|
|
63
|
|
64 # Translate standard Markdown formatter extensions to Hoedown
|
|
65 # extension/render flags to make it easier to use Hoedown as a drop-in
|
|
66 # replacement.
|
|
67 exts = 0
|
|
68 rdrf = 0
|
|
69 other = 0
|
|
70 use_smartypants = False
|
|
71 for n in extensions:
|
|
72 # Special case for Smartypants.
|
|
73 if n.lower() in ['smarty', 'smartypants']:
|
|
74 use_smartypants = True
|
|
75 continue
|
|
76
|
|
77 # Try an extension?
|
|
78 e = getattr(misaka, 'EXT_' + n.upper(), None)
|
|
79 if e is not None:
|
|
80 exts |= e
|
|
81 continue
|
|
82
|
|
83 # Try a render flag?
|
|
84 f = getattr(misaka, 'HTML_' + n.upper(), None)
|
|
85 if f is not None:
|
|
86 rdrf |= f
|
|
87
|
|
88 # Other flag?
|
|
89 f = getattr(misaka, 'TABLE_' + n.upper(), None)
|
|
90 if f is not None:
|
|
91 other |= f
|
|
92
|
|
93 # Try translating from a Markdown extension name.
|
|
94 t = ext_translate.get(n)
|
|
95 if t is None:
|
|
96 if show_warnings:
|
|
97 logger.warning("Unknown Hoedown Markdown extension or flag: "
|
|
98 "%s" % n)
|
|
99 continue
|
|
100 if not isinstance(t, list):
|
|
101 t = [t]
|
|
102 for i in t:
|
|
103 if i.startswith('EXT_'):
|
|
104 exts |= getattr(misaka, i)
|
|
105 elif i.startswith('HTML_'):
|
|
106 rdrf |= getattr(misaka, i)
|
|
107 elif show_warnings:
|
|
108 logger.warning("Unknown Hoedown Markdown extension or flag:"
|
|
109 "%s" % n)
|
|
110 if n == 'extra' and show_warnings:
|
|
111 # Special warning for the 'extra' extension.
|
|
112 logger.warning(
|
|
113 "The 'extra' extension doesn't have a full equivalent "
|
|
114 "in Hoedown Markdown. Only 'fenced_code', 'footnotes' and "
|
|
115 "'tables' extensions will be active. "
|
|
116 "To remove this warning, replace 'extra' with those 3 "
|
|
117 "specific extensions.")
|
|
118
|
|
119 # Enable a few things by default.
|
|
120 exts |= misaka.EXT_NO_INTRA_EMPHASIS
|
|
121
|
|
122 renderer = misaka.HtmlRenderer(flags=rdrf)
|
|
123 self._formatter = misaka.Markdown(renderer, extensions=(exts | other))
|
|
124
|
|
125 if use_smartypants:
|
|
126 self._formatter = _SmartypantsFormatter(self._formatter,
|
|
127 misaka.smartypants)
|
|
128
|
|
129
|
|
130 ext_translate = {
|
|
131 'fenced_code': 'EXT_FENCED_CODE',
|
|
132 'footnotes': 'EXT_FOOTNOTES',
|
|
133 'tables': 'EXT_TABLES',
|
|
134 'nl2br': 'HTML_HARD_WRAP',
|
|
135 'toc': 'HTML_TOC',
|
|
136 'extra': ['EXT_FENCED_CODE', 'EXT_FOOTNOTES', 'EXT_TABLES']
|
|
137 }
|
|
138
|
|
139
|
|
140 class _SmartypantsFormatter:
|
|
141 def __init__(self, formatter, smartier):
|
|
142 self._fmt = formatter
|
|
143 self._sp = smartier
|
|
144
|
|
145 def __call__(self, txt):
|
|
146 return self._sp(self._fmt(txt))
|