Custom renderer works.

This commit is contained in:
Frank Smit 2015-06-27 11:26:14 +02:00
parent 26d7bf0c8e
commit 259dc0acaa
4 changed files with 146 additions and 93 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ dist
*.egg-info
*.sublime-*
*.pyc
*.so

View File

@ -18,6 +18,12 @@ OUNIT = 64
MAX_NESTING = 16
def to_string(buffer):
if buffer == ffi.NULL or buffer.size == 0:
return ''
return ffi.string(buffer.data, buffer.size).decode('utf-8')
def html(text, extensions=0, render_flags=0):
ib = lib.hoedown_buffer_new(IUNIT)
ob = lib.hoedown_buffer_new(OUNIT)
@ -38,7 +44,7 @@ def html(text, extensions=0, render_flags=0):
class Markdown:
def __init__(self, renderer, extensions=0):
# NOTE: Prevent the renderer from being garbage collected
# NOTE: Prevent the renderer from being garbage collected.
self.renderer = renderer
self.extensions = extensions
@ -50,8 +56,8 @@ class Markdown:
document = lib.hoedown_document_new(self.renderer.renderer, self.extensions, MAX_NESTING);
lib.hoedown_document_render(document, ob, ib.data, ib.size);
lib.hoedown_buffer_free(ib);
lib.hoedown_document_free(document);
lib.hoedown_buffer_free(ib)
lib.hoedown_document_free(document)
try:
return ffi.string(ob.data, ob.size).decode('utf-8')
@ -61,89 +67,82 @@ class Markdown:
_callback_signatures = {
# block level callbacks - NULL skips the block
'blockcode': 'void (*blockcode)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data)',
'blockquote': 'void (*blockquote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'header': 'void (*header)(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)',
'hrule': 'void (*hrule)(hoedown_buffer *ob, const hoedown_renderer_data *data)',
'list': 'void (*list)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)',
'listitem': 'void (*listitem)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)',
'paragraph': 'void (*paragraph)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table': 'void (*table)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_header': 'void (*table_header)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_body': 'void (*table_body)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_row': 'void (*table_row)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_cell': 'void (*table_cell)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data)',
'footnotes': 'void (*footnotes)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'footnote_def': 'void (*footnote_def)(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data)',
'blockhtml': 'void (*blockhtml)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
'blockcode': 'void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data)',
'blockquote': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'header': 'void(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)',
'hrule': 'void(hoedown_buffer *ob, const hoedown_renderer_data *data)',
'list': 'void(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)',
'listitem': 'void(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)',
'paragraph': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_header': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_body': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_row': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'table_cell': 'void(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data)',
'footnotes': 'void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'footnote_def': 'void(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data)',
'blockhtml': 'void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
# span level callbacks - NULL or return 0 prints the span verbatim
'autolink': 'int (*autolink)(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data)',
'codespan': 'int (*codespan)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
'double_emphasis': 'int (*double_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'emphasis': 'int (*emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'underline': 'int (*underline)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'highlight': 'int (*highlight)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'quote': 'int (*quote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'image': 'int (*image)(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data)',
'linebreak': 'int (*linebreak)(hoedown_buffer *ob, const hoedown_renderer_data *data)',
'link': 'int (*link)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)',
'triple_emphasis': 'int (*triple_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'strikethrough': 'int (*strikethrough)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'superscript': 'int (*superscript)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'footnote_ref': 'int (*footnote_ref)(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data)',
'math': 'int (*math)(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data)',
'raw_html': 'int (*raw_html)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
'autolink': 'int(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data)',
'codespan': 'int(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
'double_emphasis': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'emphasis': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'underline': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'highlight': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'quote': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'image': 'int(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data)',
'linebreak': 'int(hoedown_buffer *ob, const hoedown_renderer_data *data)',
'link': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)',
'triple_emphasis': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'strikethrough': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'superscript': 'int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)',
'footnote_ref': 'int(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data)',
'math': 'int(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data)',
'raw_html': 'int(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
# low level callbacks - NULL copies input directly into the output
'entity': 'void (*entity)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);',
'normal_text': 'void (*normal_text)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);',
'entity': 'void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
'normal_text': 'void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)',
# miscellaneous callbacks
'doc_header': 'void (*doc_header)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data);',
'doc_footer': 'void (*doc_footer)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data);',
'doc_header': 'void(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data)',
'doc_footer': 'void(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data)',
}
class BaseRenderer:
def __init__(self):
self.renderer = lib.null_renderer_new()
self.set_callbacks()
# Use a noop method as a placeholder for render methods that are
# implemented so there's no need to check if a render method exists
# in a callback.
for attr in _callback_signatures.keys():
if not hasattr(self, attr):
setattr(self, attr, self.noop)
def __del__(self):
lib.null_renderer_free(self.renderer)
self._callbacks = {k: ffi.callback(v, getattr(self, '_w_' + k))
for k, v in _callback_signatures.items()}
self.renderer = ffi.new('hoedown_renderer *', self._callbacks)
def set_callbacks(self):
callbacks = []
for name, func in getmembers(self, predicate=ismethod):
signature = _callback_signatures.get(name)
if signature is None:
continue
wrapper = getattr(self, '_w_' + name)
callback = ffi.callback(signature, wrapper)
callbacks.append(callback)
setattr(self.renderer, name, callback)
# Prevent callbacks from being garbage collected.
self._callbacks = callbacks
def noop(self, *args, **kwargs):
return None
def _w_blockcode(self, ob, text, lang, data):
text = ffi.string(text.data, text.size).decode('utf-8')
lang = ffi.string(lang.data, lang.size).decode('utf-8')
text = to_string(text)
lang = to_string(lang)
result = self.blockcode(text, lang)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_blockquote(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.blockquote(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_header(self, ob, content, level, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
level = int(level)
result = self.header(content, level)
if result:
@ -156,7 +155,7 @@ class BaseRenderer:
# flags: LIST_ORDERED, LI_BLOCK.
def _w_list(self, ob, content, flags, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
flags = int(flags)
result = self.list(content, flags)
if result:
@ -164,57 +163,57 @@ class BaseRenderer:
# flags: LIST_ORDERED, LI_BLOCK.
def _w_listitem(self, ob, content, flags, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
flags = int(flags)
result = self.listitem(content, flags)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_paragraph(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.paragraph(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_table(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.table(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_table_header(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.table_header(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_table_body(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.table_body(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_table_row(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.table_row(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_table_cell(self, ob, content, flags, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
flags = int(flags)
result = self.table_row(content, flags)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_footnotes(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.footnotes(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_footnote_def(self, ob, content, num, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
num = int(num)
result = self.footnote_def(content, num)
if result:
@ -244,7 +243,7 @@ class BaseRenderer:
return 0
def _w_double_emphasis(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.double_emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -252,7 +251,7 @@ class BaseRenderer:
return 0
def _w_emphasis(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -260,7 +259,7 @@ class BaseRenderer:
return 0
def _w_underline(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.underline(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -268,7 +267,7 @@ class BaseRenderer:
return 0
def _w_highlight(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.highlight(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -276,7 +275,7 @@ class BaseRenderer:
return 0
def _w_quote(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.quote(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -284,9 +283,9 @@ class BaseRenderer:
return 0
def _w_image(self, ob, link, title, alt, data):
link = ffi.string(link.data, link.size).decode('utf-8')
title = ffi.string(title.data, title.size).decode('utf-8')
alt = ffi.string(alt.data, alt.size).decode('utf-8')
link = to_string(link)
title = to_string(title)
alt = to_string(alt)
result = self.image(link, title, alt)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -294,7 +293,7 @@ class BaseRenderer:
return 0
def _w_quote(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.quote(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -309,9 +308,9 @@ class BaseRenderer:
return 0
def _w_link(self, ob, content, link, title, data):
content = ffi.string(content.data, content.size).decode('utf-8')
link = ffi.string(link.data, link.size).decode('utf-8')
title = ffi.string(title.data, title.size).decode('utf-8')
content = to_string(content)
link = to_string(link)
title = to_string(title)
result = self.link(content, link, title)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -319,7 +318,7 @@ class BaseRenderer:
return 0
def _w_triple_emphasis(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.triple_emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -327,7 +326,7 @@ class BaseRenderer:
return 0
def _w_strikethrough(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.strikethrough(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -335,7 +334,7 @@ class BaseRenderer:
return 0
def _w_superscript(self, ob, content, data):
content = ffi.string(content.data, content.size).decode('utf-8')
content = to_string(content)
result = self.superscript(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -351,7 +350,7 @@ class BaseRenderer:
return 0
def _w_math(self, ob, text, displaymode, data):
text = ffi.string(text.data, text.size).decode('utf-8')
text = to_string(text)
displaymode = int(displaymode)
result = self.math(text, displaymode)
if result:
@ -360,7 +359,7 @@ class BaseRenderer:
return 0
def _w_raw_html(self, ob, text, data):
text = ffi.string(text.data, text.size).decode('utf-8')
text = to_string(text)
result = self.raw_html(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -368,13 +367,13 @@ class BaseRenderer:
return 0
def _w_entity(self, ob, text, data):
text = ffi.string(text.data, text.size).decode('utf-8')
text = to_string(text)
result = self.entity(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_normal_text(self, ob, text, data):
text = ffi.string(text.data, text.size).decode('utf-8')
text = to_string(text)
result = self.normal_text(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ -395,7 +394,19 @@ class BaseRenderer:
class HtmlRenderer(BaseRenderer):
def __init__(self, flags=0):
self.renderer = lib.hoedown_html_renderer_new(flags, 0)
self.set_callbacks()
callbacks = []
for name, signature in _callback_signatures.items():
if not hasattr(self, name):
continue
wrapper = getattr(self, '_w_' + name)
callback = ffi.callback(signature, wrapper)
callbacks.append(callback)
setattr(self.renderer, name, callback)
# Prevent garbage collection of callbacks.
self._callbacks = callbacks
def __del__(self):
lib.hoedown_html_renderer_free(self.renderer)

View File

@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
from chibitest import runner
from test_markdown import MarkdownConformanceTest_10, MarkdownConformanceTest_103
from test_renderer import CustomRendererTest
def run_tests():
runner([
MarkdownConformanceTest_10,
MarkdownConformanceTest_103,
# MarkdownConformanceTest_10,
# MarkdownConformanceTest_103,
CustomRendererTest,
])

38
tests/test_renderer.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import re
import codecs
from os import path
from glob import glob
import misaka as m
from chibitest import TestCase, ok
from test_utils import clean_html
class TestRenderer(m.BaseRenderer):
def blockcode(self, text, language):
return '[BLOCK_CODE language={1}] {0}'.format(text, language)
def paragraph(self, text):
return '[PARAGRAPH] {0}\n'.format(text)
def normal_text(self, text):
return text
class CustomRendererTest(TestCase):
name = 'Custom Renderer'
def setup(self):
self.renderer = TestRenderer()
self.render = m.Markdown(TestRenderer(), m.EXT_FENCED_CODE).render
def md(self, md, renderer):
return m.Markdown(getattr(self, renderer)).render(md)
def test_block_code(self):
supplied = '```bash\n$ :(){ :|:& };:\n```'
expected = '[BLOCK_CODE language=bash] $ :(){ :|:& };:\n'
ok(self.render(supplied)).diff(expected)