# -*- coding: utf-8 -*- import sys import operator as op from inspect import getmembers, ismethod from ._hoedown import lib, ffi try: reduce except NameError: from functools import reduce __all__ = [ 'html', 'smartypants', 'Markdown', 'BaseRenderer', 'HtmlRenderer', 'HtmlTocRenderer', 'dict_to_int', 'extension_map', 'html_flag_map', 'EXT_TABLES', 'EXT_FENCED_CODE', 'EXT_FOOTNOTES', 'EXT_AUTOLINK', 'EXT_STRIKETHROUGH', 'EXT_UNDERLINE', 'EXT_HIGHLIGHT', 'EXT_QUOTE', 'EXT_SUPERSCRIPT', 'EXT_MATH', 'EXT_NO_INTRA_EMPHASIS', 'EXT_SPACE_HEADERS', 'EXT_MATH_EXPLICIT', 'EXT_DISABLE_INDENTED_CODE', 'HTML_SKIP_HTML', 'HTML_ESCAPE', 'HTML_HARD_WRAP', 'HTML_USE_XHTML', 'LIST_ORDERED', 'LI_BLOCK', 'TABLE_ALIGN_LEFT', 'TABLE_ALIGN_RIGHT', 'TABLE_ALIGN_CENTER', 'TABLE_ALIGNMASK', 'TABLE_HEADER', 'AUTOLINK_NORMAL', 'AUTOLINK_EMAIL', ] 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 to_string(buffer): if buffer == ffi.NULL or buffer.size == 0: return '' return ffi.string(buffer.data, buffer.size).decode('utf-8') def dict_to_int(mapping, argument): """ Reduce a dictionary to an integer. This function is used to reduce a dictionary (e.g. Markdown extensions, HTML render flags.) to an integer by OR'ing the values with eachother. """ if isinstance(argument, int): return argument elif isinstance(argument, (tuple, list)): return reduce(op.or_, [mapping[n] for n in argument if n in mapping]) 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. """ render_flags = dict_to_int(html_flag_map, render_flags) ib = lib.hoedown_buffer_new(IUNIT) ob = lib.hoedown_buffer_new(OUNIT) renderer = lib.hoedown_html_renderer_new(render_flags, 0) document = lib.hoedown_document_new(renderer, extensions, 16); lib.hoedown_buffer_puts(ib, text.encode('utf-8')) lib.hoedown_document_render(document, ob, ib.data, ib.size); lib.hoedown_buffer_free(ib); lib.hoedown_document_free(document); lib.hoedown_html_renderer_free(renderer); try: return to_string(ob) finally: lib.hoedown_buffer_free(ob); def smartypants(text): """ Transforms sequences of characters into HTML entities. =================================== ===================== ========= Markdown HTML Result =================================== ===================== ========= ``'s`` (s, t, m, d, re, ll, ve) ’s ’s ``"Quotes"`` “Quotes” “Quotes” ``---`` — — ``--`` – – ``...`` … … ``. . .`` … … ``(c)`` © © ``(r)`` ® ® ``(tm)`` ™ ™ ``3/4`` ¾ ¾ ``1/2`` ½ ½ ``1/4`` ¼ ¼ =================================== ===================== ========= """ byte_str = text.encode('utf-8') ob = lib.hoedown_buffer_new(OUNIT) lib.hoedown_html_smartypants(ob, byte_str, len(byte_str)) try: return to_string(ob) finally: lib.hoedown_buffer_free(ob); class Markdown: """ Parses markdown text and renders it using the given renderer. """ def __init__(self, renderer, extensions=0): self.renderer = renderer self.extensions = dict_to_int(extension_map, extensions) def __call__(self, text): """ Parses and renders markdown text. """ ib = lib.hoedown_buffer_new(IUNIT) lib.hoedown_buffer_puts(ib, text.encode('utf-8')) ob = lib.hoedown_buffer_new(OUNIT) 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) try: return to_string(ob) finally: 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: 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._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 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) result = self.list(content, flags) 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) result = self.listitem(content, flags) 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) result = self.table_cell(content, flags) 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') type = int(type) result = self.autolink(link, type) 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')) class HtmlRenderer(BaseRenderer): """ A wrapper for the HTML renderer that's included in Hoedown. ``nesting_level`` limits what's included in the table of contents. The default value is 0, no headers. An instance of the ``HtmlRenderer`` can not be shared with multiple :py:class:`Markdown` instances, because it carries state that's changed by the ``Markdown`` instance. """ def __init__(self, flags=0, nesting_level=0): flags = dict_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 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 _new_renderer(self, flags, nesting_level): return lib.hoedown_html_renderer_new(flags, nesting_level) def __del__(self): lib.hoedown_html_renderer_free(self.renderer) class HtmlTocRenderer(HtmlRenderer): """ A wrapper for the HTML table of contents renderer that's included in Hoedown. ``nesting_level`` limits what's included in the table of contents. The default value is 6, all headers. An instance of the ``HtmlTocRenderer`` can not be shared with multiple :py:class:`Markdown` instances, because it carries state that's changed by the ``Markdown`` instance. """ def __init__(self, nesting_level=6): HtmlRenderer.__init__(self, 0, nesting_level) def _new_renderer(self, flags, nesting_level): return lib.hoedown_html_toc_renderer_new(nesting_level)