Skip to content

Commit 3634a7f

Browse files
authored
Add renderer documentation (#7)
* Add render documentation * Remove some JS specific documentation
1 parent dfdd225 commit 3634a7f

File tree

3 files changed

+157
-80
lines changed

3 files changed

+157
-80
lines changed

docs/Using_the_api.md

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,116 @@ nested_tokens[0]
202202

203203
## Renderers
204204

205+
<!-- #region -->
206+
After the token stream is generated, it's passed to a [renderer](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/renderer.py).
207+
It then plays all the tokens, passing each to a rule with the same name as token type.
205208

206-
Todo ...
209+
Renderer rules are located in `md.renderer.rules` and are simple functions
210+
with the same signature:
211+
212+
```python
213+
def function(renderer, tokens, idx, options, env):
214+
return htmlResult
215+
```
216+
<!-- #endregion -->
217+
218+
You can inject render methods into the instantiated render class.
219+
220+
```python
221+
md = MarkdownIt("commonmark")
222+
223+
def render_em_open(self, tokens, idx, options, env):
224+
return '<em class="myclass">'
225+
226+
md.add_render_rule("em_open", render_em_open)
227+
md.render("*a*")
228+
```
229+
230+
This is a slight change to the JS version, where the renderer argument is at the end.
231+
Also `add_render_rule` method is specific to Python, rather than adding directly to the `md.renderer.rules`, this ensures the method is bound to the renderer.
232+
233+
234+
You can also subclass a render and add the method there:
235+
236+
```python
237+
from markdown_it.renderer import RendererHTML
238+
239+
class MyRenderer(RendererHTML):
240+
def em_open(self, tokens, idx, options, env):
241+
return '<em class="myclass">'
242+
243+
md = MarkdownIt("commonmark", renderer_cls=MyRenderer)
244+
md.render("*a*")
245+
```
246+
247+
Plugins can support multiple render types, using the `__ouput__` attribute (this is currently a Python only feature).
248+
249+
```python
250+
from markdown_it.renderer import RendererHTML
251+
252+
class MyRenderer1(RendererHTML):
253+
__output__ = "html1"
254+
255+
class MyRenderer2(RendererHTML):
256+
__output__ = "html2"
257+
258+
def plugin(md):
259+
def render_em_open1(self, tokens, idx, options, env):
260+
return '<em class="myclass1">'
261+
def render_em_open2(self, tokens, idx, options, env):
262+
return '<em class="myclass2">'
263+
md.add_render_rule("em_open", render_em_open1, fmt="html1")
264+
md.add_render_rule("em_open", render_em_open2, fmt="html2")
265+
266+
md = MarkdownIt("commonmark", renderer_cls=MyRenderer1).use(plugin)
267+
print(md.render("*a*"))
268+
269+
md = MarkdownIt("commonmark", renderer_cls=MyRenderer2).use(plugin)
270+
print(md.render("*a*"))
271+
```
272+
273+
Here's a more concrete example; let's replace images with vimeo links to player's iframe:
274+
275+
```python
276+
import re
277+
from markdown_it import MarkdownIt
278+
279+
vimeoRE = re.compile(r'^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)')
280+
281+
def render_vimeo(self, tokens, idx, options, env):
282+
token = tokens[idx]
283+
aIndex = token.attrIndex('src')
284+
if (vimeoRE.match(token.attrs[aIndex][1])):
285+
286+
ident = vimeoRE.match(token.attrs[aIndex][1])[2]
287+
288+
return ('<div class="embed-responsive embed-responsive-16by9">\n' +
289+
' <iframe class="embed-responsive-item" src="//player.vimeo.com/video/' +
290+
ident + '"></iframe>\n' +
291+
'</div>\n')
292+
return self.image(tokens, idx, options, env)
293+
294+
md = MarkdownIt("commonmark")
295+
md.add_render_rule("image", render_vimeo)
296+
print(md.render("![](https://www.vimeo.com/123)"))
297+
```
298+
299+
Here is another example, how to add `target="_blank"` to all links:
300+
301+
```python
302+
from markdown_it import MarkdownIt
303+
304+
def render_blank_link(self, tokens, idx, options, env):
305+
aIndex = tokens[idx].attrIndex('target')
306+
if (aIndex < 0):
307+
tokens[idx].attrPush(['target', '_blank']) # add new attribute
308+
else:
309+
tokens[idx].attrs[aIndex][1] = '_blank' # replace value of existing attr
310+
311+
# pass token to default renderer.
312+
return self.renderToken(tokens, idx, options, env)
313+
314+
md = MarkdownIt("commonmark")
315+
md.add_render_rule("link_open", render_blank_link)
316+
print(md.render("[a]\n\n[a]: b"))
317+
```

docs/architecture.md

Lines changed: 45 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ The difference is simple:
5050
- There are special token objects, "inline containers", having nested tokens.
5151
sequences with inline markup (bold, italic, text, ...).
5252

53-
See [token class](https://github.com/markdown-it/markdown-it/blob/master/lib/token.js)
53+
See [token class](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/token.py)
5454
for details about each token content.
5555

5656
In total, a token stream is:
@@ -68,8 +68,8 @@ to an AST.
6868

6969
More details about tokens:
7070

71-
- [Renderer source](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
72-
- [Token source](https://github.com/markdown-it/markdown-it/blob/master/lib/token.js)
71+
- [Renderer source](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/renderer.py)
72+
- [Token source](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/token.py)
7373
- [Live demo](https://markdown-it.github.io/) - type your text and click `debug` tab.
7474

7575

@@ -95,90 +95,67 @@ and tried to do something yourself. We never reject with help to real developers
9595

9696
## Renderer
9797

98-
After token stream is generated, it's passed to a [renderer](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js).
98+
After the token stream is generated, it's passed to a [renderer](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/renderer.py).
9999
It then plays all the tokens, passing each to a rule with the same name as token type.
100100

101101
Renderer rules are located in `md.renderer.rules[name]` and are simple functions
102102
with the same signature:
103103

104-
```js
105-
function (tokens, idx, options, env, renderer) {
106-
//...
107-
return htmlResult;
108-
}
104+
```python
105+
def function(renderer, tokens, idx, options, env):
106+
return htmlResult
109107
```
110108

111109
In many cases that allows easy output change even without parser intrusion.
112110
For example, let's replace images with vimeo links to player's iframe:
113111

114-
```js
115-
var md = require('markdown-it')();
112+
```python
113+
import re
114+
md = MarkdownIt("commonmark")
116115

117-
var defaultRender = md.renderer.rules.image,
118-
vimeoRE = /^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
116+
vimeoRE = re.compile(r'^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)')
119117

120-
md.renderer.rules.image = function (tokens, idx, options, env, self) {
121-
var token = tokens[idx],
122-
aIndex = token.attrIndex('src');
118+
def render_vimeo(self, tokens, idx, options, env):
119+
token = tokens[idx]
120+
aIndex = token.attrIndex('src')
121+
if (vimeoRE.match(token.attrs[aIndex][1])):
123122

124-
if (vimeoRE.test(token.attrs[aIndex][1])) {
123+
ident = vimeoRE.match(token.attrs[aIndex][1])[2]
125124

126-
var id = token.attrs[aIndex][1].match(vimeoRE)[2];
125+
return ('<div class="embed-responsive embed-responsive-16by9">\n' +
126+
' <iframe class="embed-responsive-item" src="//player.vimeo.com/video/' +
127+
ident + '"></iframe>\n' +
128+
'</div>\n')
129+
return self.image(tokens, idx, options, env)
127130

128-
return '<div class="embed-responsive embed-responsive-16by9">\n' +
129-
' <iframe class="embed-responsive-item" src="//player.vimeo.com/video/' + id + '"></iframe>\n' +
130-
'</div>\n';
131-
}
132-
133-
// pass token to default renderer.
134-
return defaultRender(tokens, idx, options, env, self);
135-
};
131+
md = MarkdownIt("commonmark")
132+
md.add_render_rule("image", render_vimeo)
133+
print(md.render("![](https://www.vimeo.com/123)"))
136134
```
137135

138136
Here is another example, how to add `target="_blank"` to all links:
139137

140-
```js
141-
// Remember old renderer, if overridden, or proxy to default renderer
142-
var defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
143-
return self.renderToken(tokens, idx, options);
144-
};
145-
146-
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
147-
// If you are sure other plugins can't add `target` - drop check below
148-
var aIndex = tokens[idx].attrIndex('target');
149-
150-
if (aIndex < 0) {
151-
tokens[idx].attrPush(['target', '_blank']); // add new attribute
152-
} else {
153-
tokens[idx].attrs[aIndex][1] = '_blank'; // replace value of existing attr
154-
}
155-
156-
// pass token to default renderer.
157-
return defaultRender(tokens, idx, options, env, self);
158-
};
138+
```python
139+
from markdown_it import MarkdownIt
140+
141+
def render_blank_link(self, tokens, idx, options, env):
142+
aIndex = tokens[idx].attrIndex('target')
143+
if (aIndex < 0):
144+
tokens[idx].attrPush(['target', '_blank']) # add new attribute
145+
else:
146+
tokens[idx].attrs[aIndex][1] = '_blank' # replace value of existing attr
147+
148+
# pass token to default renderer.
149+
return self.renderToken(tokens, idx, options, env)
150+
151+
md = MarkdownIt("commonmark")
152+
md.add_render_rule("link_open", render_blank_link)
153+
print(md.render("[a]\n\n[a]: b"))
159154
```
160155

161156
Note, if you need to add attributes, you can do things without renderer override.
162157
For example, you can update tokens in `core` chain. That is slower, than direct
163-
renderer override, but can be more simple. Let's use
164-
[markdown-for-inline](https://github.com/markdown-it/markdown-it-for-inline) plugin
165-
to do the same thing as in previous example:
166-
167-
```js
168-
var iterator = require('markdown-it-for-inline');
169-
170-
var md = require('markdown-it')()
171-
.use(iterator, 'url_new_win', 'link_open', function (tokens, idx) {
172-
var aIndex = tokens[idx].attrIndex('target');
173-
174-
if (aIndex < 0) {
175-
tokens[idx].attrPush(['target', '_blank']);
176-
} else {
177-
tokens[idx].attrs[aIndex][1] = '_blank';
178-
}
179-
});
180-
```
181-
158+
renderer override, but can be more simple.
182159

183160
You also can write your own renderer to generate other formats than HTML, such as
184161
JSON/XML... You can even use it to generate AST.
@@ -194,9 +171,9 @@ This was mentioned in [Data flow](#data-flow), but let's repeat sequence again:
194171

195172
And somewhere between you can apply additional transformations :) . Full content
196173
of each chain can be seen on the top of
197-
[parser_core.js](https://github.com/markdown-it/markdown-it/blob/master/lib/parser_core.js),
198-
[parser_block.js](https://github.com/markdown-it/markdown-it/blob/master/lib/parser_block.js) and
199-
[parser_inline.js](https://github.com/markdown-it/markdown-it/blob/master/lib/parser_inline.js)
174+
[parser_core.py](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/parser_core.py),
175+
[parser_block.py](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/parser_block.py) and
176+
[parser_inline.py](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/parser_inline.py)
200177
files.
201178

202-
Also you can change output directly in [renderer](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) for many simple cases.
179+
Also you can change output directly in [renderer](https://github.com/ExecutableBookProject/markdown-it-py/tree/master/markdown_it/renderer.py) for many simple cases.

docs/development.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ Before continuing, make sure you've read:
1818
block and inline rules are usually faster.
1919
- Sometimes, it's enough to only modify the renderer, for example, to add
2020
header IDs or `target="_blank"` for the links.
21-
- Plugins should not require the `markdown-it` package as dependency in `package.json`.
22-
If you need access to internals, those are available via a parser instance,
23-
passed on plugin load. See properties of main class and nested objects.
2421
2. Search existing
2522
[plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin)
2623
or [rules](https://github.com/markdown-it/markdown-it/tree/master/lib),
@@ -34,14 +31,6 @@ Before continuing, make sure you've read:
3431
Such things should be discussed first on [CommonMark forum](http://talk.commonmark.org/).
3532

3633

37-
## Notes for NPM packages
38-
39-
To simplify search:
40-
41-
- add to `package.json` keywords `markdown-it` and `markdown-it-plugin` for plugins.
42-
- add keyword `markdown-it` for any other related packages.
43-
44-
4534
## FAQ
4635

4736

0 commit comments

Comments
 (0)