Move callbacks, utils and constants into their own modules.

This commit is contained in:
Frank Smit 2015-10-14 22:08:01 +02:00
parent cbd9834466
commit aac40ee755
9 changed files with 589 additions and 435 deletions

View File

@ -36,24 +36,42 @@ class benchmark(object):
print('{0} is not available'.format(name))
@benchmark('mistune')
def benchmark_mistune(text):
import mistune
mistune.markdown(text)
# @benchmark('mistune')
# def benchmark_mistune(text):
# import mistune
# mistune.markdown(text)
@benchmark('misaka')
def benchmark_misaka(text):
import misaka as m
# mistune has all these features
extensions = (
m.EXT_NO_INTRA_EMPHASIS | m.EXT_FENCED_CODE | m.EXT_AUTOLINK |
m.EXT_TABLES | m.EXT_STRIKETHROUGH
)
# extensions = (
# m.EXT_NO_INTRA_EMPHASIS | m.EXT_FENCED_CODE | m.EXT_AUTOLINK |
# m.EXT_TABLES | m.EXT_STRIKETHROUGH
# )
# md = m.Markdown(m.HtmlRenderer(), extensions=extensions)
# md.render(text)
m.html(text, extensions)
m.html(text, ('no-intra-emphasis', 'fenced=code', 'autolink', 'tables', 'strikethrough'))
@benchmark('misaka_classes')
def benchmark_misaka_classes(text):
import misaka as m
# mistune has all these features
# extensions = (
# m.EXT_NO_INTRA_EMPHASIS | m.EXT_FENCED_CODE | m.EXT_AUTOLINK |
# m.EXT_TABLES | m.EXT_STRIKETHROUGH
# )
# # md = m.Markdown(m.HtmlRenderer(), extensions=extensions)
# # md.render(text)
# m.html(text, extensions)
r = m.HtmlRenderer()
p = m.Markdown(r, ('no-intra-emphasis', 'fenced=code', 'autolink', 'tables', 'strikethrough'))
p(text)
# @benchmark('markdown2')
@ -90,7 +108,7 @@ def benchmark_hoep(text):
m.EXT_TABLES | m.EXT_STRIKETHROUGH | m.EXT_FOOTNOTES
)
md = m.Hoep(extensions=extensions)
md.render(text.decode('utf-8'))
md.render(text)
if __name__ == '__main__':

View File

@ -56,6 +56,7 @@ ffi.set_source(
#include "hoedown/buffer.h"
#include "hoedown/document.h"
#include "hoedown/html.h"
#include "extra.h"
""",
sources=(
'misaka/hoedown/version.c',
@ -67,6 +68,7 @@ ffi.set_source(
'misaka/hoedown/document.c',
'misaka/hoedown/buffer.c',
'misaka/hoedown/autolink.c',
'misaka/extra.c',
),
include_dirs=('misaka',))
@ -274,6 +276,12 @@ hoedown_renderer *hoedown_html_toc_renderer_new(
);
void hoedown_html_renderer_free(hoedown_renderer *renderer);
void hoedown_html_smartypants(hoedown_buffer *ob, const uint8_t *data, size_t size);
// ---------------
// --- extra.h ---
// ---------------
void *misaka_get_renderer(const hoedown_renderer_data *data);
""")

View File

@ -1,16 +1,10 @@
# -*- coding: utf-8 -*-
import sys
import operator as op
import warnings
from inspect import getmembers, ismethod
from ._hoedown import lib, ffi
try:
reduce
except NameError:
from functools import reduce
from .callbacks import python_callbacks, to_string
from .constants import *
from .utils import extension_map, html_flag_map, args_to_int, \
deprecation, to_string
__all__ = [
@ -21,7 +15,7 @@ __all__ = [
'HtmlRenderer',
'HtmlTocRenderer',
'_args_to_int',
'args_to_int',
'extension_map',
'html_flag_map',
@ -59,81 +53,17 @@ __all__ = [
]
def _set_constants():
is_int = lambda n: isinstance(n, int)
for name, value in getmembers(lib, is_int):
if not name.startswith('HOEDOWN_'):
continue
setattr(sys.modules[__name__], name[8:], value)
if not hasattr(sys.modules[__name__], 'EXT_TABLES'):
_set_constants()
extension_map = {
'tables': EXT_TABLES,
'fenced-code': EXT_FENCED_CODE,
'footnotes': EXT_FOOTNOTES,
'autolink': EXT_AUTOLINK,
'strikethrough': EXT_STRIKETHROUGH,
'underline': EXT_UNDERLINE,
'highlight': EXT_HIGHLIGHT,
'quote': EXT_QUOTE,
'superscript': EXT_SUPERSCRIPT,
'math': EXT_MATH,
'no-intra-emphasis': EXT_NO_INTRA_EMPHASIS,
'space-headers': EXT_SPACE_HEADERS,
'math-explicit': EXT_MATH_EXPLICIT,
'disable-indented-code': EXT_DISABLE_INDENTED_CODE,
}
html_flag_map = {
'skip-html': HTML_SKIP_HTML,
'escape': HTML_ESCAPE,
'hard-wrap': HTML_HARD_WRAP,
'use-xhtml': HTML_USE_XHTML,
}
IUNIT = 1024
OUNIT = 64
MAX_NESTING = 16
def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=3)
def to_string(buffer):
if buffer == ffi.NULL or buffer.size == 0:
return ''
return ffi.string(buffer.data, buffer.size).decode('utf-8')
def _args_to_int(mapping, argument):
"""
Convert list of strings to an int using a mapping.
"""
if isinstance(argument, int):
if argument == 0:
return 0
deprecation('passing extensions and flags as constants is deprecated')
return argument
elif isinstance(argument, (tuple, list)):
return reduce(op.or_, [mapping[n] for n in argument if n in mapping], 0)
raise TypeError('argument must be a list of strings or an int')
def html(text, extensions=0, render_flags=0):
"""
Convert markdown text to HTML.
"""
extensions = _args_to_int(extension_map, extensions)
render_flags = _args_to_int(html_flag_map, render_flags)
extensions = args_to_int(extension_map, extensions)
render_flags = args_to_int(html_flag_map, render_flags)
ib = lib.hoedown_buffer_new(IUNIT)
ob = lib.hoedown_buffer_new(OUNIT)
@ -183,13 +113,13 @@ def smartypants(text):
lib.hoedown_buffer_free(ob);
class Markdown:
class Markdown(object):
"""
Parses markdown text and renders it using the given renderer.
"""
def __init__(self, renderer, extensions=0):
self.renderer = renderer
self.extensions = _args_to_int(extension_map, extensions)
self.extensions = args_to_int(extension_map, extensions)
def __call__(self, text):
"""
@ -214,340 +144,21 @@ class Markdown:
lib.hoedown_buffer_free(ob);
_callback_signatures = {
# block level callbacks - NULL skips the block
'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(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(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(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:
class BaseRenderer(object):
def __init__(self):
# 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)
self.renderer = ffi.new('hoedown_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)
for name in python_callbacks.keys():
if hasattr(self, name):
setattr(self.renderer, name, python_callbacks[name])
else:
setattr(self.renderer, name, ffi.NULL)
def noop(self, *args, **kwargs):
return None
def _w_blockcode(self, ob, text, lang, data):
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 = 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 = to_string(content)
level = int(level)
result = self.header(content, level)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_hrule(self, ob, data):
result = self.hrule()
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# flags: LIST_ORDERED, LI_BLOCK.
def _w_list(self, ob, content, flags, data):
content = to_string(content)
flags = int(flags)
is_ordered = flags & LIST_ORDERED != 0
is_block = flags & LI_BLOCK != 0
result = self.list(content, is_ordered, is_block)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# flags: LIST_ORDERED, LI_BLOCK.
def _w_listitem(self, ob, content, flags, data):
content = to_string(content)
flags = int(flags)
is_ordered = flags & LIST_ORDERED != 0
is_block = flags & LI_BLOCK != 0
result = self.listitem(content, is_ordered, is_block)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_paragraph(self, ob, content, data):
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 = 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 = 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 = 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 = to_string(content)
result = self.table_row(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# flags: TABLE_ALIGNMASK, TABLE_ALIGN_LEFT, TABLE_ALIGN_RIGHT,
# TABLE_ALIGN_CENTER, TABLE_HEADER
def _w_table_cell(self, ob, content, flags, data):
content = to_string(content)
flags = int(flags)
is_header = flags & TABLE_HEADER != 0
align_bit = flags & TABLE_ALIGNMASK
if align_bit == TABLE_ALIGN_CENTER:
align = 'center'
elif align_bit == TABLE_ALIGN_LEFT:
align = 'left'
elif align_bit == TABLE_ALIGN_RIGHT:
align = 'right'
else:
align = ''
result = self.table_cell(content, align, is_header)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_footnotes(self, ob, content, data):
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 = to_string(content)
num = int(num)
result = self.footnote_def(content, num)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_blockhtml(self, ob, text, data):
text = ffi.string(text.data, text.size).decode('utf-8')
result = self.blockhtml(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_autolink(self, ob, link, type, data):
link = ffi.string(link.data, link.size).decode('utf-8')
is_email = int(type) & AUTOLINK_EMAIL != 0
result = self.autolink(link, is_email)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_codespan(self, ob, text, data):
text = ffi.string(text.data, text.size).decode('utf-8')
result = self.codespan(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_double_emphasis(self, ob, content, data):
content = to_string(content)
result = self.double_emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_emphasis(self, ob, content, data):
content = to_string(content)
result = self.emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_underline(self, ob, content, data):
content = to_string(content)
result = self.underline(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_highlight(self, ob, content, data):
content = to_string(content)
result = self.highlight(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_quote(self, ob, content, data):
content = to_string(content)
result = self.quote(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_image(self, ob, link, title, alt, data):
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'))
return 1
return 0
def _w_linebreak(self, ob, data):
result = self.linebreak()
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_link(self, ob, content, link, title, data):
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'))
return 1
return 0
def _w_triple_emphasis(self, ob, content, data):
content = to_string(content)
result = self.triple_emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_strikethrough(self, ob, content, data):
content = to_string(content)
result = self.strikethrough(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_superscript(self, ob, content, data):
content = to_string(content)
result = self.superscript(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_footnote_ref(self, ob, num, data):
num = int(num)
result = self.footnote_ref(num)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_math(self, ob, text, displaymode, data):
text = to_string(text)
displaymode = int(displaymode)
result = self.math(text, displaymode)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_raw_html(self, ob, text, data):
text = to_string(text)
result = self.raw_html(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
def _w_entity(self, ob, text, data):
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 = to_string(text)
result = self.normal_text(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_doc_header(self, ob, inline_render, data):
inline_render = int(inline_render)
result = self.doc_header(inline_render)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
def _w_doc_footer(self, ob, inline_render, data):
inline_render = int(inline_render)
result = self.doc_footer(inline_render)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# Store the render class' handle in the render data.
self._data = ffi.new('hoedown_renderer_data *')
self.renderer.opaque = self._data
ffi.cast('hoedown_renderer_data *', self.renderer.opaque).opaque = \
ffi.new_handle(self)
class HtmlRenderer(BaseRenderer):
@ -562,21 +173,16 @@ class HtmlRenderer(BaseRenderer):
by the ``Markdown`` instance.
"""
def __init__(self, flags=0, nesting_level=0):
flags = _args_to_int(html_flag_map, flags)
flags = args_to_int(html_flag_map, flags)
self.renderer = self._new_renderer(flags, nesting_level)
callbacks = []
for name, signature in _callback_signatures.items():
if not hasattr(self, name):
continue
# Store the render class' handle in the render state.
state = ffi.cast('hoedown_renderer_data *', self.renderer.opaque)
state.opaque = ffi.new_handle(self)
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
for name in python_callbacks.keys():
if hasattr(self, name):
setattr(self.renderer, name, python_callbacks[name])
def _new_renderer(self, flags, nesting_level):
return lib.hoedown_html_renderer_new(flags, nesting_level)

429
misaka/callbacks.py Normal file
View File

@ -0,0 +1,429 @@
# -*- coding: utf-8 -*-
from ._hoedown import lib, ffi
from .constants import *
from .utils import to_string
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data)')
def cb_blockcode(ob, text, lang, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = to_string(text)
lang = to_string(lang)
result = renderer.blockcode(text, lang)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_blockquote(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.blockquote(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)')
def cb_header(ob, content, level, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
level = int(level)
result = renderer.header(content, level)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_renderer_data *data)')
def cb_hrule(ob, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
result = renderer.hrule()
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# flags: LIST_ORDERED, LI_BLOCK.
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)')
def cb_list(ob, content, flags, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
flags = int(flags)
is_ordered = flags & LIST_ORDERED != 0
is_block = flags & LI_BLOCK != 0
result = renderer.list(content, is_ordered, is_block)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# flags: LIST_ORDERED, LI_BLOCK.
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)')
def cb_listitem(ob, content, flags, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
flags = int(flags)
is_ordered = flags & LIST_ORDERED != 0
is_block = flags & LI_BLOCK != 0
result = renderer.listitem(content, is_ordered, is_block)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_paragraph(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.paragraph(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_table(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.table(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_table_header(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.table_header(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_table_body(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.table_body(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_table_row(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.table_row(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
# flags: TABLE_ALIGNMASK, TABLE_ALIGN_LEFT, TABLE_ALIGN_RIGHT,
# TABLE_ALIGN_CENTER, TABLE_HEADER
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data)')
def cb_table_cell(ob, content, flags, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
flags = int(flags)
is_header = flags & TABLE_HEADER != 0
align_bit = flags & TABLE_ALIGNMASK
if align_bit == TABLE_ALIGN_CENTER:
align = 'center'
elif align_bit == TABLE_ALIGN_LEFT:
align = 'left'
elif align_bit == TABLE_ALIGN_RIGHT:
align = 'right'
else:
align = ''
result = renderer.table_cell(content, align, is_header)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_footnotes(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.footnotes(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data)')
def cb_footnote_def(ob, content, num, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
num = int(num)
result = renderer.footnote_def(content, num)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)')
def cb_blockhtml(ob, text, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = ffi.string(text.data, text.size).decode('utf-8')
result = renderer.blockhtml(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data)')
def cb_autolink(ob, link, type, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
link = ffi.string(link.data, link.size).decode('utf-8')
is_email = int(type) & AUTOLINK_EMAIL != 0
result = renderer.autolink(link, is_email)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)')
def cb_codespan(ob, text, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = ffi.string(text.data, text.size).decode('utf-8')
result = renderer.codespan(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_double_emphasis(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.double_emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_emphasis(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_underline(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.underline(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_highlight(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.highlight(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_quote(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.quote(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data)')
def cb_image(ob, link, title, alt, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
link = to_string(link)
title = to_string(title)
alt = to_string(alt)
result = renderer.image(link, title, alt)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_renderer_data *data)')
def cb_linebreak(ob, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
result = renderer.linebreak()
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)')
def cb_link(ob, content, link, title, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
link = to_string(link)
title = to_string(title)
result = renderer.link(content, link, title)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_triple_emphasis(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.triple_emphasis(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_strikethrough(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.strikethrough(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)')
def cb_superscript(ob, content, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
content = to_string(content)
result = renderer.superscript(content)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data)')
def cb_footnote_ref(ob, num, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
num = int(num)
result = renderer.footnote_ref(num)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data)')
def cb_math(ob, text, displaymode, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = to_string(text)
displaymode = int(displaymode)
result = renderer.math(text, displaymode)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('int(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)')
def cb_raw_html(ob, text, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = to_string(text)
result = renderer.raw_html(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
return 1
return 0
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)')
def cb_entity(ob, text, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = to_string(text)
result = renderer.entity(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)')
def cb_normal_text(ob, text, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
text = to_string(text)
result = renderer.normal_text(text)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data)')
def cb_doc_header(ob, inline_render, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
inline_render = int(inline_render)
result = renderer.doc_header(inline_render)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
@ffi.callback('void(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data)')
def cb_doc_footer(ob, inline_render, data):
renderer = ffi.from_handle(lib.misaka_get_renderer(data))
inline_render = int(inline_render)
result = renderer.doc_footer(inline_render)
if result:
lib.hoedown_buffer_puts(ob, result.encode('utf-8'))
python_callbacks = {
# block level callbacks - NULL skips the block
'blockcode': cb_blockcode,
'blockquote': cb_blockquote,
'header': cb_header,
'hrule': cb_hrule,
'list': cb_list,
'listitem': cb_listitem,
'paragraph': cb_paragraph,
'table': cb_table,
'table_header': cb_table_header,
'table_body': cb_table_body,
'table_row': cb_table_row,
'table_cell': cb_table_cell,
'footnotes': cb_footnotes,
'footnote_def': cb_footnote_def,
'blockhtml': cb_blockhtml,
# span level callbacks - NULL or return 0 prints the span verbatim
'autolink': cb_autolink,
'codespan': cb_codespan,
'double_emphasis': cb_double_emphasis,
'emphasis': cb_emphasis,
'underline': cb_underline,
'highlight': cb_highlight,
'quote': cb_quote,
'image': cb_image,
'linebreak': cb_linebreak,
'link': cb_link,
'triple_emphasis': cb_triple_emphasis,
'strikethrough': cb_strikethrough,
'superscript': cb_superscript,
'footnote_ref': cb_footnote_ref,
'math': cb_math,
'raw_html': cb_raw_html,
# low level callbacks - NULL copies input directly into the output
'entity': cb_entity,
'normal_text': cb_normal_text,
# miscellaneous callbacks
'doc_header': cb_doc_header,
'doc_footer': cb_doc_footer,
}

19
misaka/constants.py Normal file
View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
import sys
from inspect import getmembers
from ._hoedown import lib
def _set_constants():
is_int = lambda n: isinstance(n, int)
for name, value in getmembers(lib, is_int):
if not name.startswith('HOEDOWN_'):
continue
setattr(sys.modules[__name__], name[8:], value)
if not hasattr(sys.modules[__name__], 'EXT_TABLES'):
_set_constants()

9
misaka/extra.c Normal file
View File

@ -0,0 +1,9 @@
#include "hoedown/document.h"
#include "hoedown/html.h"
void *misaka_get_renderer(const hoedown_renderer_data *data) {
// NOTE: Cast to a "hoedown_renderer_data *", because
// the structure is assumed to have an "opaque" field.
// Otherwise this doesn't work.
return ((hoedown_renderer_data *) data->opaque)->opaque;
}

3
misaka/extra.h Normal file
View File

@ -0,0 +1,3 @@
/* extra.h - extra stuff in C */
void *misaka_get_renderer(const hoedown_renderer_data *data);

61
misaka/utils.py Normal file
View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
import operator as op
import warnings
try:
reduce
except NameError:
from functools import reduce
from ._hoedown import ffi
from .constants import *
extension_map = {
'tables': EXT_TABLES,
'fenced-code': EXT_FENCED_CODE,
'footnotes': EXT_FOOTNOTES,
'autolink': EXT_AUTOLINK,
'strikethrough': EXT_STRIKETHROUGH,
'underline': EXT_UNDERLINE,
'highlight': EXT_HIGHLIGHT,
'quote': EXT_QUOTE,
'superscript': EXT_SUPERSCRIPT,
'math': EXT_MATH,
'no-intra-emphasis': EXT_NO_INTRA_EMPHASIS,
'space-headers': EXT_SPACE_HEADERS,
'math-explicit': EXT_MATH_EXPLICIT,
'disable-indented-code': EXT_DISABLE_INDENTED_CODE,
}
html_flag_map = {
'skip-html': HTML_SKIP_HTML,
'escape': HTML_ESCAPE,
'hard-wrap': HTML_HARD_WRAP,
'use-xhtml': HTML_USE_XHTML,
}
def args_to_int(mapping, argument):
"""
Convert list of strings to an int using a mapping.
"""
if isinstance(argument, int):
if argument == 0:
return 0
deprecation('passing extensions and flags as constants is deprecated')
return argument
elif isinstance(argument, (tuple, list)):
return reduce(op.or_, [mapping[n] for n in argument if n in mapping], 0)
raise TypeError('argument must be a list of strings or an int')
def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=3)
def to_string(buffer):
if buffer == ffi.NULL or buffer.size == 0:
return ''
return ffi.string(buffer.data, buffer.size).decode('utf-8')

View File

@ -1,14 +1,15 @@
# -*- coding: utf-8 -*-
from chibitest import TestCase, ok
from misaka import _args_to_int, extension_map, \
from misaka import extension_map, \
EXT_TABLES, EXT_FENCED_CODE, EXT_FOOTNOTES
from misaka.utils import args_to_int
class ArgsToIntTest(TestCase):
def test_args(self):
expected = EXT_TABLES | EXT_FENCED_CODE | EXT_FOOTNOTES
result = _args_to_int(
result = args_to_int(
extension_map,
('tables', 'fenced-code', 'footnotes'))
@ -16,6 +17,6 @@ class ArgsToIntTest(TestCase):
def test_int(self):
expected = EXT_TABLES | EXT_FENCED_CODE | EXT_FOOTNOTES
result = _args_to_int(extension_map, expected)
result = args_to_int(extension_map, expected)
ok(result) == expected