├── .gitignore
├── .gitmodules
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── README.rst
├── build_ffi.py
├── pedantmark
├── __init__.py
├── api.py
├── cmark_cdef.h
├── extern.c
├── extern.h
├── extern.py
├── extern_cdef.h
└── toc.py
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── bench.py
├── spec.txt
├── test_cmark.py
├── test_escape.py
├── test_renderer.py
└── test_toc.py
└── vendors
├── .gitkeep
└── unix
├── extensions
└── cmark-gfm-extensions_export.h
└── src
├── cmark-gfm_export.h
├── cmark-gfm_version.h
└── config.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # general things to ignore
2 | build/
3 | dist/
4 | *.egg-info/
5 | *.egg
6 | *.eggs
7 | *.py[cod]
8 | __pycache__/
9 | *.so
10 | *~
11 |
12 | # due to using t/nox and pytest
13 | .tox
14 | .cache
15 | .pytest_cache
16 | .coverage
17 | _cmark.c
18 | htmlcov/
19 |
20 | vendors/*/*
21 | !vendors/*/src/
22 | !vendors/*/extensions/
23 | vendors/*/src/*
24 | !vendors/*/src/*.h
25 | vendors/*/extensions/*
26 | !vendors/*/extensions/*.h
27 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "cmark-gfm"]
2 | path = cmark-gfm
3 | url = https://github.com/github/cmark-gfm.git
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: xenial
3 | language: python
4 |
5 | python:
6 | - 2.7
7 | - 3.5
8 | - 3.6
9 | - 3.7
10 |
11 | script:
12 | - python setup.py test
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018, Hsiaoming Yang
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 |
9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10 |
11 | * Neither the name of the creator nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12 |
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | include setup.py
4 | include build_ffi.py
5 |
6 | recursive-include pedantmark *.c *.h
7 | recursive-include cmark-gfm/src *.c *.h *.inc
8 | recursive-include cmark-gfm/extensions *.c *.h
9 | recursive-include vendors *.h
10 |
11 | # Include cmark licensing information
12 | include cmark-gfm/COPYING
13 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean-pyc clean-build docs
2 |
3 | clean: clean-build clean-pyc clean-docs
4 |
5 |
6 | clean-build:
7 | @rm -fr build/
8 | @rm -fr dist/
9 | @rm -fr *.egg-info
10 | @rm -f mistune.c
11 | @rm -fr cover/
12 |
13 |
14 | clean-pyc:
15 | @find . -name '*.pyc' -exec rm -f {} +
16 | @find . -name '*.pyo' -exec rm -f {} +
17 | @find . -name '*~' -exec rm -f {} +
18 | @find . -name '__pycache__' -exec rm -fr {} +
19 |
20 | clean-docs:
21 | @rm -fr docs/_build
22 |
23 | docs:
24 | @$(MAKE) -C docs html
25 |
26 | coverage:
27 | @coverage run --source=pedantmark setup.py -q nosetests
28 | @coverage html
29 |
30 | .PHONY: build
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pedantic Markdown
2 |
3 |
4 |
5 |
6 | **pedantmark** is (not only) a python binding for the GitHub's fork of CommonMark (cmark).
7 | It has also been enhanced to support custom renderers.
8 |
9 | > Only two maybes I've thought of: Strict Markdown or Pedantic Markdown. "Strict" still doesn't seem right.
10 | >
11 | > -- [John Gruber](https://twitter.com/gruber/status/507615356295200770)
12 |
13 | Ok, let's call it **pedantmark**.
14 |
15 |
16 | ## Install
17 |
18 | **pedantmark** is available in Python 2.7 and 3.5+ for Linux and Mac,
19 | Python 3.5+ for Windows. Wheels are built by [multibuild][].
20 |
21 | Install wheels by pip:
22 |
23 | $ pip install pedantmark
24 |
25 | [multibuild]: https://github.com/matthew-brett/multibuild
26 |
27 |
28 | ## Standard Usage
29 |
30 | The C source code has serval built-in renderers. The simplest interface is
31 | `pedantmark.html(text, options)`, which will render text into HTML.
32 |
33 | ```python
34 | import pedantmark
35 |
36 | text = '...'
37 | html = pedantmark.html(text, options=[pedantmark.OPT_VALIDATE_UTF8])
38 | ```
39 |
40 | The function `pedantmark.html()` accepts no extensions, but you can add
41 | extensions via `pedantmark.markdown()`:
42 |
43 | ```python
44 | import pedantmark
45 |
46 | text = '...'
47 | html = pedantmark.markdown(
48 | text,
49 | options=[pedantmark.OPT_VALIDATE_UTF8],
50 | extensions=['strikethrough', 'autolink', 'table'],
51 | renderer='html',
52 | )
53 | ```
54 |
55 | Available extensions: `table`, `autolink`, `tagfilter`, `strikethrough`.
56 | You can enable them all with a shortcut:
57 |
58 | pedantmark.markdown(..., extensions=pedantmark.EXTENSIONS)
59 |
60 | Available renderers: `html`, `xml`, `man`, `commonmark`, `plaintext`,
61 | and `latex`.
62 |
63 | ## Options
64 |
65 | Here is a full list of options:
66 |
67 | ```
68 | #: Include a `data-sourcepos` attribute on all block elements.
69 | OPT_SOURCEPOS
70 |
71 | #: Render `softbreak` elements as hard line breaks.
72 | OPT_HARDBREAKS
73 |
74 | #: Render `softbreak` elements as spaces.
75 | OPT_NOBREAKS
76 |
77 | #: Validate UTF-8 in the input before parsing, replacing illegal
78 | #: sequences with the replacement character U+FFFD.
79 | OPT_VALIDATE_UTF8
80 |
81 | #: Convert straight quotes to curly, --- to em dashes, -- to en dashes.
82 | OPT_SMART
83 |
84 | #: Use GitHub-style
tags for code blocks instead of
85 | #: .
86 | OPT_PRE_LANG
87 |
88 | #: Be liberal in interpreting inline HTML tags.
89 | OPT_LIBERAL_HTML_TAG
90 |
91 | #: Parse footnotes.
92 | OPT_FOOTNOTES
93 |
94 | #: Only parse strikethroughs if surrounded by exactly 2 tildes.
95 | OPT_STRIKETHROUGH_DOUBLE_TILDE
96 |
97 | #: Use style attributes to align table cells instead of align attributes.
98 | OPT_TABLE_PREFER_STYLE_ATTRIBUTES
99 |
100 | #: Include the remainder of the info string in code blocks in
101 | #: a separate attribute.
102 | OPT_FULL_INFO_STRING
103 |
104 | #: Allow raw HTML and unsafe links, `javascript:`, `vbscript:`, `file:`,
105 | #: and all `data:` URLs -- by default, only `image/png`, `image/gif`,
106 | #: `image/jpeg`, or `image/webp` mime types are allowed. Without this
107 | #: option, raw HTML is replaced by a placeholder HTML comment, and unsafe
108 | #: links are replaced by empty strings.
109 | OPT_UNSAFE
110 | ```
111 |
112 | ## Custom Renderer
113 |
114 | Besides the native renderers, **pedantmark** has provided you a custom renderer,
115 | which you can customize the output yourself. Here is an example of pygments code
116 | highlighting integration:
117 |
118 | ```python
119 | from pedantmark import HTMLRenderer, markdown
120 | from pygments import highlight
121 | from pygments.lexers import get_lexer_by_name
122 | from pygments.formatters import html
123 |
124 | class MyRenderer(HTMLRenderer):
125 | def code_block(self, code, lang):
126 | if lang:
127 | # everything is in bytes
128 | lang = lang.decode('utf-8')
129 | code = code.decode('utf-8')
130 | lexer = get_lexer_by_name(lang, stripall=True)
131 | formatter = html.HtmlFormatter()
132 | output = highlight(code, lexer, formatter)
133 | # return bytes
134 | return output.encode('utf-8')
135 | return super(MyRenderer, self).code_block(code, lang)
136 |
137 | text = '...'
138 | markdown(text, renderer=MyRenderer())
139 | ```
140 |
141 | The default `HTMLRenderer` has a built-in hook for code highlight, you don't need
142 | to subclass at all:
143 |
144 | ```python
145 | def add_code_highlight(code, lang):
146 | lang = lang.decode('utf-8')
147 | code = code.decode('utf-8')
148 | lexer = get_lexer_by_name(lang, stripall=True)
149 | formatter = html.HtmlFormatter()
150 | output = highlight(code, lexer, formatter)
151 | return output.encode('utf-8')
152 |
153 | text = '...'
154 | markdown(text, renderer=HTMLRenderer(highlight=add_code_highlight))
155 | ```
156 |
157 | Here is a full list of renderers:
158 |
159 | ```
160 | thematic_break(self)
161 | html_block(self, text)
162 | block_quote(self, text)
163 | code_block(self, text, lang)
164 | heading(self, text, level, index=None)
165 | paragraph(self, text)
166 | list_item(self, text)
167 | list(self, text, ordered, start=None)
168 | table_cell(self, text, tag, align=None)
169 | table_row(self, text)
170 | table_header(self, text)
171 | table(self, text)
172 | softbreak(self)
173 | linebreak(self)
174 | html_inline(self, text)
175 | text(self, text)
176 | emph(self, text)
177 | strong(self, text)
178 | strikethrough(self, text)
179 | code(self, text)
180 | link(self, url, text, title)
181 | image(self, src, alt, title)
182 | footnote_ref(self, key)
183 | footnote_item(self, text, index):
184 | footnotes(self, text)
185 | ```
186 |
187 | ## TOC
188 |
189 | To render "Table of Contents", you need to use a `MarkdownState` to record TOC
190 | contents:
191 |
192 | ```python
193 | from pedantmark import MarkdownState
194 | from pedantmark.toc import render_toc
195 |
196 | # only record level 1 ~ 3 headings
197 | state = MarkdownState(toc_level=3)
198 |
199 | markdown(text, renderer=HTMLRenderer(), state)
200 | render_toc(state.toc)
201 | ```
202 |
203 | ## Author & License
204 |
205 | This library is created by Hsiaming Yang, licensed under BSD.
206 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Pedantic Markdown
2 | =================
3 |
4 | **pedantmark** is (not only) a python binding for the GitHub's fork of CommonMark (cmark).
5 | It has also been enhanced to support custom renderers.
6 |
7 | Install
8 | -------
9 |
10 | **pedantmark** is available in Python 2.7 and 3.5+ for Linux and Mac,
11 | Python 3.5+ for Windows.
12 |
13 | Install wheels by pip::
14 |
15 | $ pip install pedantmark
16 |
17 | Usage
18 | -----
19 |
20 | A simple overview of how to use pedantmark:
21 |
22 | .. code-block:: python
23 |
24 | import pedantmark
25 |
26 | text = '...'
27 | html = pedantmark.markdown(
28 | text,
29 | options=[pedantmark.OPT_VALIDATE_UTF8],
30 | extensions=['strikethrough', 'autolink', 'table'],
31 | renderer='html',
32 | )
33 |
34 | Available extensions: ``table``, ``autolink``, ``tagfilter``, ``strikethrough``.
35 | You can enable them all with a shortcut::
36 |
37 | pedantmark.markdown(..., extensions=pedantmark.EXTENSIONS)
38 |
39 | Available renderers: ``html``, ``xml``, ``man``, ``commonmark``, ``plaintext``,
40 | ``latex`` and custom renderers.
41 |
42 | Author & License
43 | ----------------
44 |
45 | This library is created by Hsiaming Yang, licensed under BSD.
46 |
--------------------------------------------------------------------------------
/build_ffi.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import glob
3 | import cffi
4 |
5 |
6 | INCLUDE_DIRS = [
7 | 'cmark-gfm/src',
8 | 'cmark-gfm/extensions',
9 | 'pedantmark',
10 | ]
11 | SOURCES_FILES = [
12 | f for f in glob.iglob('cmark-gfm/src/*.c') if not f.endswith('main.c')
13 | ]
14 | SOURCES_FILES.extend(list(glob.iglob('cmark-gfm/extensions/*.c')))
15 | SOURCES_FILES.append('pedantmark/extern.c')
16 |
17 |
18 | def _compiler_type():
19 | import distutils.dist
20 | import distutils.ccompiler
21 | dist = distutils.dist.Distribution()
22 | dist.parse_config_files()
23 | cmd = dist.get_command_obj('build')
24 | cmd.ensure_finalized()
25 | compiler = distutils.ccompiler.new_compiler(compiler=cmd.compiler)
26 | return compiler.compiler_type
27 |
28 |
29 | COMPILER_TYPE = _compiler_type()
30 | if COMPILER_TYPE == 'unix':
31 | EXTRA_COMPILE_ARGS = ['-std=c99']
32 | INCLUDE_DIRS.append('vendors/unix/src')
33 | INCLUDE_DIRS.append('vendors/unix/extensions')
34 | elif COMPILER_TYPE == 'msvc':
35 | EXTRA_COMPILE_ARGS = ['/TP']
36 | INCLUDE_DIRS.append('vendors/windows/src')
37 | INCLUDE_DIRS.append('vendors/windows/extensions')
38 |
39 |
40 | ffi = cffi.FFI()
41 |
42 | with open('pedantmark/cmark_cdef.h', 'r') as f:
43 | CDEF_H = f.read()
44 |
45 | with open('pedantmark/extern_cdef.h', 'r') as f:
46 | CDEF_H += f.read()
47 |
48 | MODULE_H = '''
49 | #ifndef CMARK_MODULE_H
50 | #define CMARK_MODULE_H
51 |
52 | #ifdef __cplusplus
53 | extern "C" {
54 | #endif
55 |
56 | #define CMARKEXTENSIONS_STATIC_DEFINE
57 |
58 | #include "cmark-gfm.h"
59 | #include "cmark-gfm-extension_api.h"
60 | #include "cmark-gfm-core-extensions.h"
61 | #include "extern.h"
62 |
63 | #ifdef __cplusplus
64 | }
65 | #endif
66 |
67 | #endif
68 | '''
69 |
70 | ffi.cdef(CDEF_H)
71 | ffi.set_source(
72 | 'pedantmark._cmark',
73 | MODULE_H,
74 | sources=SOURCES_FILES,
75 | include_dirs=INCLUDE_DIRS,
76 | extra_compile_args=EXTRA_COMPILE_ARGS,
77 | )
78 |
79 | if __name__ == '__main__':
80 | ffi.compile(verbose=True)
81 |
--------------------------------------------------------------------------------
/pedantmark/__init__.py:
--------------------------------------------------------------------------------
1 | from .api import (
2 | markdown, html, EXTENSIONS,
3 | OPT_SOURCEPOS,
4 | OPT_HARDBREAKS,
5 | OPT_NOBREAKS,
6 | OPT_VALIDATE_UTF8,
7 | OPT_SMART,
8 | OPT_PRE_LANG,
9 | OPT_LIBERAL_HTML_TAG,
10 | OPT_FOOTNOTES,
11 | OPT_STRIKETHROUGH_DOUBLE_TILDE,
12 | OPT_TABLE_PREFER_STYLE_ATTRIBUTES,
13 | OPT_FULL_INFO_STRING,
14 | OPT_UNSAFE,
15 | )
16 | from .extern import (
17 | escape_html, escape_href,
18 | MarkdownState, HTMLRenderer,
19 | )
20 |
21 | __all__ = [
22 | 'markdown', 'html', 'EXTENSIONS',
23 | 'OPT_SOURCEPOS',
24 | 'OPT_HARDBREAKS',
25 | 'OPT_NOBREAKS',
26 | 'OPT_VALIDATE_UTF8',
27 | 'OPT_SMART',
28 | 'OPT_PRE_LANG',
29 | 'OPT_LIBERAL_HTML_TAG',
30 | 'OPT_FOOTNOTES',
31 | 'OPT_STRIKETHROUGH_DOUBLE_TILDE',
32 | 'OPT_TABLE_PREFER_STYLE_ATTRIBUTES',
33 | 'OPT_FULL_INFO_STRING',
34 | 'OPT_UNSAFE',
35 | 'escape_html', 'escape_href',
36 | 'MarkdownState', 'HTMLRenderer',
37 | ]
38 |
39 | __version__ = '0.1'
40 | __author__ = 'Hsiaoming Yang '
41 |
--------------------------------------------------------------------------------
/pedantmark/api.py:
--------------------------------------------------------------------------------
1 | from ._cmark import lib, ffi
2 |
3 | #: Include a `data-sourcepos` attribute on all block elements.
4 | OPT_SOURCEPOS = lib.CMARK_OPT_SOURCEPOS
5 |
6 | #: Render `softbreak` elements as hard line breaks.
7 | OPT_HARDBREAKS = lib.CMARK_OPT_HARDBREAKS
8 |
9 | #: Render `softbreak` elements as spaces.
10 | OPT_NOBREAKS = lib.CMARK_OPT_NOBREAKS
11 |
12 | #: Validate UTF-8 in the input before parsing, replacing illegal
13 | #: sequences with the replacement character U+FFFD.
14 | OPT_VALIDATE_UTF8 = lib.CMARK_OPT_VALIDATE_UTF8
15 |
16 | #: Convert straight quotes to curly, --- to em dashes, -- to en dashes.
17 | OPT_SMART = lib.CMARK_OPT_SMART
18 |
19 | #: Use GitHub-style tags for code blocks instead of
20 | #: .
21 | OPT_PRE_LANG = lib.CMARK_OPT_GITHUB_PRE_LANG
22 |
23 | #: Be liberal in interpreting inline HTML tags.
24 | OPT_LIBERAL_HTML_TAG = lib.CMARK_OPT_LIBERAL_HTML_TAG
25 |
26 | #: Parse footnotes.
27 | OPT_FOOTNOTES = lib.CMARK_OPT_FOOTNOTES
28 |
29 | #: Only parse strikethroughs if surrounded by exactly 2 tildes.
30 | OPT_STRIKETHROUGH_DOUBLE_TILDE = lib.CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE
31 |
32 | #: Use style attributes to align table cells instead of align attributes.
33 | OPT_TABLE_PREFER_STYLE_ATTRIBUTES = lib.CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES
34 |
35 | #: Include the remainder of the info string in code blocks in
36 | #: a separate attribute.
37 | OPT_FULL_INFO_STRING = lib.CMARK_OPT_FULL_INFO_STRING
38 |
39 | #: Allow raw HTML and unsafe links, `javascript:`, `vbscript:`, `file:`,
40 | #: and all `data:` URLs -- by default, only `image/png`, `image/gif`,
41 | #: `image/jpeg`, or `image/webp` mime types are allowed. Without this
42 | #: option, raw HTML is replaced by a placeholder HTML comment, and unsafe
43 | #: links are replaced by empty strings.
44 | OPT_UNSAFE = lib.CMARK_OPT_UNSAFE
45 |
46 | #: Available extensions
47 | EXTENSIONS = ('table', 'autolink', 'tagfilter', 'strikethrough')
48 |
49 |
50 | def markdown(text, options=None, extensions=None, renderer='html',
51 | width=0, state=None):
52 | """Parse text as markdown and render it into other format of string.
53 |
54 | :text: string of text.
55 | :options: list of options for cmark.
56 | :extensions: list of extensions.
57 | :renderer: renderer type or HTMLRenderer instance.
58 | :width: width option for renderers like man/latex.
59 | :state: MarkdownState instance for HTMLRenderer.
60 | :return: string
61 | """
62 | if options is None:
63 | options = []
64 |
65 | if extensions is None:
66 | extensions = []
67 |
68 | _options = get_options(*options)
69 | parser = lib.cmark_parser_new(_options)
70 | try:
71 | _extensions = attach_extensions(parser, *extensions)
72 |
73 | bytes_text = text.encode('utf-8')
74 | lib.cmark_parser_feed(parser, bytes_text, len(bytes_text))
75 | root = lib.cmark_parser_finish(parser)
76 |
77 | output = _render_root(
78 | renderer, root, _options, _extensions, width, state)
79 | finally:
80 | lib.cmark_parser_free(parser)
81 | return output
82 |
83 |
84 | def html(text, options=None):
85 | """Parse text as markdown and render it into HTML string.
86 |
87 | :text: string of text.
88 | :options: list of options for cmark.
89 | :return: HTML string.
90 | """
91 | if options is None:
92 | options = []
93 |
94 | _options = get_options(*options)
95 | bytes_text = text.encode('utf-8')
96 | rv = lib.cmark_markdown_to_html(bytes_text, len(bytes_text), _options)
97 | return ffi.string(rv).decode('utf-8')
98 |
99 |
100 | def attach_extensions(parser, *names):
101 | if not names:
102 | return ffi.NULL
103 |
104 | lib.cmark_gfm_core_extensions_ensure_registered()
105 | for name in names:
106 | extension = lib.cmark_find_syntax_extension(name.encode('utf-8'))
107 | if extension == ffi.NULL:
108 | raise ValueError('Invalid extensions: {!r}'.format(name))
109 | lib.cmark_parser_attach_syntax_extension(parser, extension)
110 |
111 | return lib.cmark_parser_get_syntax_extensions(parser)
112 |
113 |
114 | def get_options(*options):
115 | value = lib.CMARK_OPT_DEFAULT
116 | for opt in options:
117 | value = value | opt
118 | return value
119 |
120 |
121 | def _render_root(renderer, root, options, extensions, width, state):
122 | if callable(renderer):
123 | return renderer(root, options, state)
124 | elif renderer == 'html':
125 | output = lib.cmark_render_html(root, options, extensions)
126 | elif renderer == 'xml':
127 | output = lib.cmark_render_xml(root, options)
128 | elif renderer == 'man':
129 | output = lib.cmark_render_man(root, options, width)
130 | elif renderer == 'commonmark':
131 | output = lib.cmark_render_commonmark(root, options, width)
132 | elif renderer == 'plaintext':
133 | output = lib.cmark_render_plaintext(root, options, width)
134 | elif renderer == 'latex':
135 | output = lib.cmark_render_latex(root, options, width)
136 | else:
137 | raise ValueError('Invalid "renderer" value')
138 |
139 | return ffi.string(output).decode('utf-8')
140 |
--------------------------------------------------------------------------------
/pedantmark/cmark_cdef.h:
--------------------------------------------------------------------------------
1 | // cmark-gfm.h
2 | typedef enum {
3 | CMARK_NODE_NONE = ...
4 | } cmark_node_type;
5 |
6 | typedef struct cmark_node cmark_node;
7 | typedef struct cmark_parser cmark_parser;
8 |
9 | typedef struct cmark_mem {
10 | void *(*calloc)(size_t, size_t);
11 | void *(*realloc)(void *, size_t);
12 | void (*free)(void *);
13 | } cmark_mem;
14 |
15 | typedef void (*cmark_free_func) (cmark_mem *mem, void *user_data);
16 |
17 | typedef struct _cmark_llist {
18 | struct _cmark_llist *next;
19 | void *data;
20 | } cmark_llist;
21 |
22 | cmark_llist * cmark_llist_append (cmark_mem * mem,
23 | cmark_llist * head,
24 | void * data);
25 |
26 | void cmark_llist_free_full (cmark_mem * mem,
27 | cmark_llist * head,
28 | cmark_free_func free_func);
29 |
30 | void cmark_llist_free (cmark_mem * mem,
31 | cmark_llist * head);
32 |
33 | const char *cmark_version_string();
34 |
35 | char *cmark_markdown_to_html(const char *text, size_t len, int options);
36 | cmark_node *cmark_parse_document(const char *buffer, size_t len, int options);
37 |
38 | char *cmark_render_xml(cmark_node *root, int options);
39 | char *cmark_render_html(cmark_node *root, int options, cmark_llist *extensions);
40 | char *cmark_render_man(cmark_node *root, int options, int width);
41 | char *cmark_render_commonmark(cmark_node *root, int options, int width);
42 | char *cmark_render_plaintext(cmark_node *root, int options, int width);
43 | char *cmark_render_latex(cmark_node *root, int options, int width);
44 |
45 | cmark_parser *cmark_parser_new(int options);
46 | void cmark_parser_free(cmark_parser *parser);
47 | void cmark_parser_feed(cmark_parser *parser, const char *buffer, size_t len);
48 | cmark_node *cmark_parser_finish(cmark_parser *parser);
49 |
50 | #define CMARK_OPT_DEFAULT 0
51 | #define CMARK_OPT_SOURCEPOS ...
52 | #define CMARK_OPT_HARDBREAKS ...
53 | #define CMARK_OPT_NOBREAKS ...
54 | #define CMARK_OPT_VALIDATE_UTF8 ...
55 | #define CMARK_OPT_SMART ...
56 | #define CMARK_OPT_GITHUB_PRE_LANG ...
57 | #define CMARK_OPT_LIBERAL_HTML_TAG ...
58 | #define CMARK_OPT_FOOTNOTES ...
59 | #define CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE ...
60 | #define CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES ...
61 | #define CMARK_OPT_FULL_INFO_STRING ...
62 | #define CMARK_OPT_UNSAFE ...
63 |
64 | typedef struct cmark_syntax_extension cmark_syntax_extension;
65 |
66 | // buffer.h
67 | typedef int32_t bufsize_t;
68 | typedef struct {
69 | cmark_mem *mem;
70 | unsigned char *ptr;
71 | bufsize_t asize, size;
72 | } cmark_strbuf;
73 | void cmark_strbuf_puts(cmark_strbuf *buf, const char *string);
74 |
75 | // cmark-gfm-extension_api.h
76 | cmark_syntax_extension *cmark_find_syntax_extension(const char *name);
77 | int cmark_parser_attach_syntax_extension(cmark_parser *parser, cmark_syntax_extension *extension);
78 | cmark_llist *cmark_parser_get_syntax_extensions(cmark_parser *parser);
79 |
80 |
81 | // extensions/cmark-gfm-core-extensions.h
82 | void cmark_gfm_core_extensions_ensure_registered(void);
83 |
--------------------------------------------------------------------------------
/pedantmark/extern.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "cmark-gfm.h"
6 | #include "node.h"
7 | #include "buffer.h"
8 | #include "houdini.h"
9 | #include "syntax_extension.h"
10 | #include "scanners.h"
11 | #include "cmark-gfm-core-extensions.h"
12 | #include "extern.h"
13 |
14 | unsigned char *escape_html(const unsigned char *source, size_t len, int secure) {
15 | cmark_mem *mem = cmark_get_default_mem_allocator();
16 | cmark_strbuf buf = CMARK_BUF_INIT(mem);
17 | houdini_escape_html0(&buf, source, len, secure);
18 | unsigned char *data = buf.ptr;
19 | cmark_strbuf_free(&buf);
20 | return data;
21 | }
22 |
23 | unsigned char *escape_href(const unsigned char *source, size_t len) {
24 | cmark_mem *mem = cmark_get_default_mem_allocator();
25 | cmark_strbuf buf = CMARK_BUF_INIT(mem);
26 | houdini_escape_href(&buf, source, len);
27 | unsigned char *data = buf.ptr;
28 | cmark_strbuf_free(&buf);
29 | return data;
30 | }
31 |
32 | const char *pedant_get_node_type(cmark_node *node) {
33 | if (node == NULL) {
34 | return "NONE";
35 | }
36 |
37 | if (node->extension && node->extension->get_type_string_func) {
38 | return node->extension->get_type_string_func(node->extension, node);
39 | }
40 |
41 | switch (node->type) {
42 | case CMARK_NODE_NONE:
43 | return "none";
44 | case CMARK_NODE_DOCUMENT:
45 | return "document";
46 | case CMARK_NODE_BLOCK_QUOTE:
47 | return "block_quote";
48 | case CMARK_NODE_LIST:
49 | return "list";
50 | case CMARK_NODE_ITEM:
51 | return "list_item";
52 | case CMARK_NODE_CODE_BLOCK:
53 | return "code_block";
54 | case CMARK_NODE_HTML_BLOCK:
55 | return "html_block";
56 | case CMARK_NODE_CUSTOM_BLOCK:
57 | return "custom_block";
58 | case CMARK_NODE_PARAGRAPH:
59 | return "paragraph";
60 | case CMARK_NODE_HEADING:
61 | return "heading";
62 | case CMARK_NODE_THEMATIC_BREAK:
63 | return "thematic_break";
64 | case CMARK_NODE_TEXT:
65 | return "text";
66 | case CMARK_NODE_SOFTBREAK:
67 | return "softbreak";
68 | case CMARK_NODE_LINEBREAK:
69 | return "linebreak";
70 | case CMARK_NODE_CODE:
71 | return "code";
72 | case CMARK_NODE_HTML_INLINE:
73 | return "html_inline";
74 | case CMARK_NODE_CUSTOM_INLINE:
75 | return "custom_inline";
76 | case CMARK_NODE_EMPH:
77 | return "emph";
78 | case CMARK_NODE_STRONG:
79 | return "strong";
80 | case CMARK_NODE_LINK:
81 | return "link";
82 | case CMARK_NODE_IMAGE:
83 | return "image";
84 | case CMARK_NODE_FOOTNOTE_REFERENCE:
85 | return "footnote_ref";
86 | case CMARK_NODE_FOOTNOTE_DEFINITION:
87 | return "footnote_def";
88 | }
89 |
90 | return "_unknown";
91 | }
92 |
93 | const char *pedant_get_node_code_info(cmark_node *node) {
94 | return (const char *)node->as.code.info.data;
95 | }
96 | const int pedant_get_node_heading_level(cmark_node *node) {
97 | return node->as.heading.level;
98 | }
99 | const bool pedant_get_node_list_bullet(cmark_node *node) {
100 | cmark_list_type list_type = node->as.list.list_type;
101 | return list_type == CMARK_BULLET_LIST;
102 | }
103 | const int pedant_get_node_list_start(cmark_node *node) {
104 | return node->as.list.start;
105 | }
106 | const char *pedant_get_node_link_url(cmark_node *node) {
107 | return (const char *)node->as.link.url.data;
108 | }
109 | const char *pedant_get_node_link_title(cmark_node *node) {
110 | return (const char *)escape_html(node->as.link.title.data, node->as.link.title.len, 0);
111 | }
112 | const bool pedant_get_node_paragraph_tight(cmark_node *node) {
113 | cmark_node *parent = cmark_node_parent(node);
114 | cmark_node *grandparent = cmark_node_parent(parent);
115 | if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) {
116 | return grandparent->as.list.tight;
117 | } else {
118 | return false;
119 | }
120 | }
121 | const char *pedant_get_node_table_cell_info(cmark_node *node) {
122 | static char buffer[3];
123 | bool is_head = cmark_gfm_extensions_get_table_row_is_header(node->parent);
124 | uint8_t *alignments = cmark_gfm_extensions_get_table_alignments(node->parent->parent);
125 |
126 | if (is_head) {
127 | buffer[0] = 'h';
128 | } else {
129 | buffer[0] = 'd';
130 | }
131 |
132 | cmark_node *n;
133 | int i = 0;
134 | for (n = node->parent->first_child; n; n = n->next, ++i)
135 | if (n == node)
136 | break;
137 |
138 | buffer[1] = alignments[i];
139 | buffer[2] = '\0';
140 | return buffer;
141 | }
142 |
143 | static int S_plain_node(cmark_strbuf *buf, cmark_node *node) {
144 | if (node->first_child) {
145 | cmark_node *next = node->first_child;
146 | while (next) {
147 | S_plain_node(buf, next);
148 | next = next->next;
149 | }
150 | } else {
151 | switch (node->type) {
152 | case CMARK_NODE_TEXT:
153 | case CMARK_NODE_CODE:
154 | case CMARK_NODE_HTML_INLINE:
155 | houdini_escape_html0(buf, node->as.literal.data, node->as.literal.len, 0);
156 | break;
157 | case CMARK_NODE_LINEBREAK:
158 | case CMARK_NODE_SOFTBREAK:
159 | cmark_strbuf_putc(buf, ' ');
160 | break;
161 | }
162 | return 1;
163 | }
164 | return 1;
165 | }
166 |
167 | static char *S_flat_node(pedant_render_node_t cb, cmark_node *node, int options,
168 | bool render_plain, void *userdata) {
169 |
170 | cmark_strbuf buf = CMARK_BUF_INIT(cmark_node_mem(node));
171 |
172 | if (render_plain) {
173 | S_plain_node(&buf, node);
174 | return (char *)cmark_strbuf_detach(&buf);
175 | }
176 |
177 | char *flat_s;
178 |
179 | switch (node->type) {
180 | case CMARK_NODE_CODE:
181 | case CMARK_NODE_TEXT:
182 | cb(&buf, node, escape_html(node->as.literal.data, node->as.literal.len, 0), userdata);
183 | break;
184 | case CMARK_NODE_CODE_BLOCK:
185 | cb(&buf, node, escape_html(node->as.code.literal.data, node->as.code.literal.len, 0), userdata);
186 | break;
187 | case CMARK_NODE_HTML_BLOCK:
188 | case CMARK_NODE_HTML_INLINE:
189 | if (options & CMARK_OPT_UNSAFE) {
190 | cb(&buf, node, node->as.literal.data, userdata);
191 | } else {
192 | cb(&buf, node, escape_html(node->as.literal.data, node->as.literal.len, 0), userdata);
193 | }
194 | break;
195 | case CMARK_NODE_THEMATIC_BREAK:
196 | case CMARK_NODE_LINEBREAK:
197 | case CMARK_NODE_SOFTBREAK:
198 | cb(&buf, node, (const unsigned char *)"", userdata);
199 | break;
200 | case CMARK_NODE_FOOTNOTE_REFERENCE:
201 | cb(&buf, node, node->as.literal.data, userdata);
202 | break;
203 | default:
204 | if (node->first_child) {
205 | bool next_plain = node->type == CMARK_NODE_IMAGE;
206 | cmark_node *next = node->first_child;
207 | while (next) {
208 | flat_s = S_flat_node(cb, next, options, next_plain, userdata);
209 | cmark_strbuf_puts(&buf, flat_s);
210 | free(flat_s);
211 | next = next->next;
212 | }
213 | const unsigned char *text = (const unsigned char *)cmark_strbuf_detach(&buf);
214 | cb(&buf, node, text, userdata);
215 | }
216 | }
217 |
218 | return (char *)cmark_strbuf_detach(&buf);
219 | }
220 |
221 | char *cmark_render_pedant(pedant_render_node_t cb, cmark_node *root,
222 | int options, void *userdata) {
223 | return S_flat_node(cb, root, options, 0, userdata);
224 | }
225 |
--------------------------------------------------------------------------------
/pedantmark/extern.h:
--------------------------------------------------------------------------------
1 | #include "buffer.h"
2 |
3 | unsigned char *escape_html(const unsigned char *source, size_t length, int secure);
4 | unsigned char *escape_href(const unsigned char *source, size_t len);
5 |
6 | typedef void (*pedant_render_node_t)(cmark_strbuf *buf, cmark_node *node, const unsigned char *text, void *userdata);
7 | char *cmark_render_pedant(pedant_render_node_t cb, cmark_node *root, int options, void *userdata);
8 |
9 | const char *pedant_get_node_type(cmark_node *node);
10 | const char *pedant_get_node_code_info(cmark_node *node);
11 | const int pedant_get_node_heading_level(cmark_node *node);
12 | const bool pedant_get_node_list_bullet(cmark_node *node);
13 | const int pedant_get_node_list_start(cmark_node *node);
14 | const char *pedant_get_node_link_url(cmark_node *node);
15 | const char *pedant_get_node_link_title(cmark_node *node);
16 | const bool pedant_get_node_paragraph_tight(cmark_node *node);
17 | const char *pedant_get_node_table_cell_info(cmark_node *node);
18 |
--------------------------------------------------------------------------------
/pedantmark/extern.py:
--------------------------------------------------------------------------------
1 | from ._cmark import lib, ffi
2 |
3 |
4 | def escape_html(text, secure=False):
5 | source = text.encode('utf-8')
6 | rv = lib.escape_html(source, len(source), int(secure))
7 | return _to_s(rv)
8 |
9 |
10 | def escape_href(url):
11 | source = url.encode('utf-8')
12 | rv = lib.escape_href(source, len(source))
13 | return _to_s(rv)
14 |
15 |
16 | class MarkdownState(object):
17 | def __init__(self, toc_level=0):
18 | self.footnotes = []
19 | self.toc = []
20 | self.toc_level = toc_level
21 |
22 |
23 | class HTMLRenderer(object):
24 | BREAK_TYPES = {'thematic_break', 'linebreak', 'softbreak'}
25 | TEXT_TYPES = {
26 | 'document', 'html_block', 'block_quote', 'list_item',
27 | 'table_row', 'table_header', 'table',
28 | 'text', 'emph', 'strong', 'strikethrough',
29 | 'code', 'html_inline', 'footnote_ref',
30 | }
31 | DANGEROUS_SCHEMES = (b'javascript:', b'vbscript:')
32 |
33 | def __init__(self, highlight=None, hardbreaks=False, nobreaks=False):
34 | self._highlight = highlight
35 | self._hardbreaks = hardbreaks
36 | self._nobreaks = nobreaks
37 |
38 | def _unknown(self):
39 | return b''
40 |
41 | def _is_dangerous_url(self, url):
42 | return url.startswith(self.DANGEROUS_SCHEMES)
43 |
44 | def __call__(self, root, options, state=None):
45 | if state is None:
46 | state = MarkdownState()
47 | userdata = ffi.new_handle((self, state))
48 | output = _to_b(lib.cmark_render_pedant(
49 | lib.pedant_render_node, root, options, userdata))
50 |
51 | if state.footnotes:
52 | text = b''.join(
53 | self.footnote_item(s, i+1)
54 | for i, s in enumerate(state.footnotes)
55 | )
56 | output += self.footnotes(text)
57 | return output.decode('utf-8')
58 |
59 | def none(self):
60 | return b''
61 |
62 | def custom_block(self, text):
63 | return b'TODO'
64 |
65 | def custom_inline(self, text):
66 | return b'TODO'
67 |
68 | def document(self, text):
69 | return text
70 |
71 | def thematic_break(self):
72 | """Rendering thematic break tag like ``
``."""
73 | return b'
\n'
74 |
75 | def html_block(self, text):
76 | """Rendering block level pure html content.
77 |
78 | :param text: text content of the html snippet.
79 | """
80 | return text
81 |
82 | def block_quote(self, text):
83 | """Rendering with the given text.
84 |
85 | :param text: text content of the blockquote.
86 | """
87 | return b'' + text + b'
\n'
88 |
89 | def code_block(self, text, lang):
90 | """Rendering block level code. ``pre > code``.
91 |
92 | :param code: text content of the code block.
93 | :param lang: language of the given code.
94 | """
95 | if self._highlight and lang:
96 | return self._highlight(text, lang)
97 | out = b'' + text + b'
\n'
101 |
102 | def heading(self, text, level, index=None):
103 | """Rendering heading tags like ```` ````.
104 |
105 | :param text: rendered text content for the header.
106 | :param level: a number for the header level, for example: 1.
107 | :param index: index of this heading in TOC (if enabled).
108 | """
109 | tag = b'h' + str(level).encode('utf-8')
110 | out = b'<' + tag
111 | if index:
112 | out += b' id="toc-' + str(index).encode('utf-8') + b'"'
113 | return out + b'>' + text + b'' + tag + b'>\n'
114 |
115 | def paragraph(self, text):
116 | """Rendering paragraph tags. Like ``
``."""
117 | return b'
' + text + b'
\n'
118 |
119 | def list_item(self, text):
120 | """Rendering list item snippet. Like ````."""
121 | return b' ' + text + b' \n'
122 |
123 | def list(self, text, ordered, start=None):
124 | """Rendering list tags like ```` and ````.
125 |
126 | :param body: body contents of the list.
127 | :param ordered: whether this list is ordered or not.
128 | :param start: start property for ordered list ````.
129 | """
130 | if not ordered:
131 | return b'\n' + text + b'
\n'
132 | out = b'\n' + text + b'
\n'
136 |
137 | def table_cell(self, text, tag, align=None):
138 | """Rendering a table cell. Like ```` `` ``.
139 |
140 | :param text: content of current table cell.
141 | :param tag: tag of ``th`` or ``td``.
142 | :param align: align of current table cell.
143 | """
144 | out = b'<' + tag
145 | if align:
146 | out += b' style="text-align:' + align + b'"'
147 | return out + b'>' + text + b'' + tag + b'>\n'
148 |
149 | def table_row(self, text):
150 | """Render a row of a table.
151 |
152 | :param text: content of the table row.
153 | """
154 | return b' \n' + text + b' \n'
155 |
156 | def table_header(self, text):
157 | """Render thead row of a table.
158 |
159 | :param text: content of the table row.
160 | """
161 | return b'\n' + text + b' \n'
162 |
163 | def table(self, text):
164 | """Wrapper of the table content."""
165 | return b'\n' + text + b'
\n'
166 |
167 | def softbreak(self):
168 | """Rendering soft linebreaks depending on options."""
169 | if self._hardbreaks:
170 | return self.linebreak()
171 | if self._nobreaks:
172 | return b' '
173 | return b'\n'
174 |
175 | def linebreak(self):
176 | """Rendering line break like ``
``."""
177 | return b'
\n'
178 |
179 | def html_inline(self, text):
180 | """Rendering inline level pure html content.
181 |
182 | :param text: text content of the html snippet.
183 | """
184 | return text
185 |
186 | def text(self, text):
187 | """Rendering plain text.
188 |
189 | :param text: text content.
190 | """
191 | return text
192 |
193 | def emph(self, text):
194 | """Rendering *emphasis* text.
195 |
196 | :param text: text content for emphasis.
197 | """
198 | return b'' + text + b''
199 |
200 | def strong(self, text):
201 | """Rendering **strong** text.
202 |
203 | :param text: text content for emphasis.
204 | """
205 | return b'' + text + b''
206 |
207 | def strikethrough(self, text):
208 | """Rendering ~~strikethrough~~ text.
209 |
210 | :param text: text content for strikethrough.
211 | """
212 | return b'' + text + b''
213 |
214 | def code(self, text):
215 | """Rendering inline `code` text.
216 |
217 | :param text: text content for inline code.
218 | """
219 | return b'' + text + b'
'
220 |
221 | def link(self, url, text, title):
222 | """Rendering a given link with content and title.
223 |
224 | :param link: href link for ```` tag.
225 | :param text: text content for description.
226 | :param title: title content for `title` attribute.
227 | """
228 | if self._is_dangerous_url(url):
229 | url = b''
230 | else:
231 | # autolink url ended with \n
232 | url = url.strip()
233 | out = b'' + text + b''
237 |
238 | def image(self, src, alt, title):
239 | """Rendering a image with title and text.
240 |
241 | :param src: source link of the image.
242 | :param alt: alt text of the image.
243 | :param title: title text of the image.
244 | """
245 | if self._is_dangerous_url(src):
246 | return b''
247 | out = b'
'
253 |
254 | def footnote_ref(self, key):
255 | """Rendering the ref anchor of a footnote.
256 |
257 | :param key: identity key for the footnote.
258 | """
259 | out = b''
261 | return out + key + b''
262 |
263 | def footnote_item(self, text, index):
264 | """Rendering a footnote item.
265 |
266 | :param text: text content of the footnote.
267 | :param index: index of the footnote item.
268 | """
269 | ref_ix = str(index).encode('utf-8')
270 | out = b'- \n'
271 | backref = b' ↩'
273 | i = text.rfind(b'')
274 | if i == -1:
275 | return out + text + backref + b'
- \n'
276 | return out + text[:i] + backref + text[i:] + b'
- \n'
277 |
278 | def footnotes(self, text):
279 | """Wrapper for all footnotes.
280 |
281 | :param text: contents of all footnotes.
282 | """
283 | out = b'
\n'
284 | return out + text + b'
\n'
285 |
286 |
287 | _ALIGN_SHORTCUTS = {'l': b'left', 'c': b'center', 'r': b'right'}
288 |
289 |
290 | @ffi.def_extern()
291 | def pedant_render_node(buf, node, text, userdata):
292 | rndr, state = ffi.from_handle(userdata)
293 | node_type = _to_s(lib.pedant_get_node_type(node))
294 |
295 | result = None
296 | if node_type in rndr.TEXT_TYPES:
297 | func = getattr(rndr, node_type)
298 | result = func(_to_b(text))
299 | elif node_type in rndr.BREAK_TYPES:
300 | result = getattr(rndr, node_type)()
301 | elif node_type == 'heading':
302 | result = _render_heading(rndr, node, text, state)
303 | elif node_type == 'code_block':
304 | result = _render_code_block(rndr, node, text)
305 | elif node_type == 'list':
306 | result = _render_list(rndr, node, text)
307 | elif node_type == 'paragraph':
308 | result = _render_paragraph(rndr, node, text)
309 | elif node_type == 'link':
310 | result = _render_link(rndr, node, text)
311 | elif node_type == 'image':
312 | result = _render_link(rndr, node, text, False)
313 | elif node_type == 'footnote_def':
314 | state.footnotes.append(_to_b(text))
315 | elif node_type == 'table_cell':
316 | result = _render_table_cell(rndr, node, text)
317 | else:
318 | result = ''.format(node_type).encode('utf-8')
319 |
320 | if result:
321 | lib.cmark_strbuf_puts(buf, result)
322 |
323 |
324 | def _render_heading(rndr, node, text, state):
325 | level = lib.pedant_get_node_heading_level(node)
326 | text = _to_b(text)
327 | if level <= state.toc_level:
328 | state.toc.append((text, level))
329 | return rndr.heading(text, level, len(state.toc))
330 | return rndr.heading(text, level)
331 |
332 |
333 | def _render_table_cell(rndr, node, text):
334 | info = _to_s(lib.pedant_get_node_table_cell_info(node))
335 | if info[0] == 'h':
336 | tag = b'th'
337 | else:
338 | tag = b'td'
339 | if len(info) < 2:
340 | return rndr.table_cell(_to_b(text), tag)
341 | return rndr.table_cell(_to_b(text), tag, _ALIGN_SHORTCUTS[info[1]])
342 |
343 |
344 | def _render_paragraph(rndr, node, text):
345 | tight = lib.pedant_get_node_paragraph_tight(node)
346 | if tight:
347 | return rndr.text(_to_b(text))
348 | return rndr.paragraph(_to_b(text))
349 |
350 |
351 | def _render_code_block(rndr, node, text):
352 | info = _to_b(lib.pedant_get_node_code_info(node))
353 | if info:
354 | lang = info.split(b' ', 1)[0]
355 | else:
356 | lang = None
357 | return rndr.code_block(_to_b(text), lang)
358 |
359 |
360 | def _render_list(rndr, node, text):
361 | bullet = lib.pedant_get_node_list_bullet(node)
362 | if bullet:
363 | return rndr.list(_to_b(text), False)
364 | start = lib.pedant_get_node_list_start(node)
365 | return rndr.list(_to_b(text), True, start)
366 |
367 |
368 | def _render_link(rndr, node, text, is_link=True):
369 | url = lib.pedant_get_node_link_url(node)
370 | title = lib.pedant_get_node_link_title(node)
371 | if is_link:
372 | return rndr.link(_to_b(url), _to_b(text), _to_b(title))
373 | return rndr.image(_to_b(url), _to_b(text), _to_b(title))
374 |
375 |
376 | def _to_b(s):
377 | if s == ffi.NULL:
378 | return b''
379 | return ffi.string(s)
380 |
381 |
382 | def _to_s(s):
383 | if s == ffi.NULL:
384 | return ''
385 | return ffi.string(s).decode('utf-8')
386 |
--------------------------------------------------------------------------------
/pedantmark/extern_cdef.h:
--------------------------------------------------------------------------------
1 | unsigned char *escape_html(const unsigned char *source, size_t length, int secure);
2 | unsigned char *escape_href(const unsigned char *source, size_t len);
3 |
4 | typedef void (*pedant_render_node_t)(cmark_strbuf *buf, cmark_node *node, const unsigned char *text, void *userdata);
5 | char *cmark_render_pedant(pedant_render_node_t cb, cmark_node *root, int options, void *userdata);
6 |
7 | const char *pedant_get_node_type(cmark_node *node);
8 | const char *pedant_get_node_code_info(cmark_node *node);
9 | const int pedant_get_node_heading_level(cmark_node *node);
10 | const bool pedant_get_node_list_bullet(cmark_node *node);
11 | const int pedant_get_node_list_start(cmark_node *node);
12 | const char *pedant_get_node_link_url(cmark_node *node);
13 | const char *pedant_get_node_link_title(cmark_node *node);
14 | const bool pedant_get_node_paragraph_tight(cmark_node *node);
15 | const char *pedant_get_node_table_cell_info(cmark_node *node);
16 | extern "Python" void pedant_render_node(cmark_strbuf *, cmark_node *, const unsigned char *, void *);
17 |
--------------------------------------------------------------------------------
/pedantmark/toc.py:
--------------------------------------------------------------------------------
1 | def render_toc(toc):
2 | if not toc:
3 | return ''
4 |
5 | text, start_level = toc[0]
6 | out = '\n- ' + _render_item(text, 1)
7 | parents = [start_level]
8 |
9 | index = 2
10 | for text, level in toc[1:]:
11 | if level > parents[-1]:
12 | out += '\n
\n- '
13 | parents.append(level)
14 | elif level == parents[-1]:
15 | out += '
\n- '
16 | else:
17 | out = _outdent_item(out, parents, level)
18 | out += _render_item(text, index)
19 | index += 1
20 |
21 | return out + '
\n
\n'
22 |
23 |
24 | def _render_item(text, index):
25 | return '{}'.format(index, text.decode('utf-8'))
26 |
27 |
28 | def _outdent_item(out, parents, level):
29 | parents.pop()
30 | if not parents or level > parents[-1]:
31 | parents.append(level)
32 | return out + ' \n- '
33 | elif level == parents[-1]:
34 | return out + '
\n
\n \n- '
35 | else:
36 | return _outdent_item(out + '
\n
\n', parents, level)
37 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal=0
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import re
3 | from setuptools import setup
4 |
5 | _version_re = re.compile(r"__version__ = '(.*)'")
6 |
7 |
8 | with open('pedantmark/__init__.py', 'r') as f:
9 | version = _version_re.search(f.read()).group(1)
10 |
11 |
12 | with open('README.rst') as f:
13 | long_description = f.read()
14 |
15 |
16 | setup(
17 | name='pedantmark',
18 | version=version,
19 | description='Python binding of GitHub cmark with extensions and renderers',
20 | long_description=long_description,
21 | url='https://github.com/lepture/pedantmark',
22 | zip_safe=False,
23 | license='BSD',
24 | packages=['pedantmark'],
25 | include_package_data=True,
26 | install_requires=["cffi>=1.11.0"],
27 | setup_requires=["cffi>=1.11.0"],
28 | cffi_modules=["build_ffi.py:ffi"],
29 | tests_require=['nose'],
30 | test_suite='nose.collector',
31 | classifiers=[
32 | 'Development Status :: 4 - Beta',
33 | 'Environment :: Web Environment',
34 | 'Intended Audience :: Developers',
35 | 'License :: OSI Approved :: BSD License',
36 | 'Operating System :: OS Independent',
37 | 'Programming Language :: Python :: 2.7',
38 | 'Programming Language :: Python :: 3',
39 | 'Programming Language :: Python :: 3.5',
40 | 'Programming Language :: Python :: 3.6',
41 | 'Programming Language :: Python :: 3.7',
42 | 'Programming Language :: Python :: Implementation',
43 | 'Programming Language :: Python :: Implementation :: CPython',
44 | 'Topic :: Text Processing :: Markup',
45 | 'Topic :: Software Development :: Libraries :: Python Modules',
46 | ]
47 | )
48 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lepture/pedantmark/8643c9c9a95a911266faa3c0246e6ad9d6fc51f3/tests/__init__.py
--------------------------------------------------------------------------------
/tests/bench.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import os
4 | import time
5 | import functools
6 |
7 |
8 | class benchmark(object):
9 | suites = []
10 |
11 | def __init__(self, name):
12 | self._name = name
13 |
14 | def __call__(self, func):
15 | @functools.wraps(func)
16 | def wrapper(text, loops=1000):
17 | start = time.clock()
18 | while loops:
19 | func(text)
20 | loops -= 1
21 | end = time.clock()
22 | return end - start
23 | # register
24 | benchmark.suites.append((self._name, wrapper))
25 | return wrapper
26 |
27 | @classmethod
28 | def bench(cls, text, loops=100):
29 | print('Parsing the CommonMark spec.txt %d times...' % loops)
30 | for name, func in cls.suites:
31 | try:
32 | total = func(text, loops=loops)
33 | print('{0}: {1}'.format(name, total))
34 | except ImportError:
35 | print('{0} is not available'.format(name))
36 |
37 |
38 | @benchmark('pedantmark.html')
39 | def benchmark_pedant_html(text):
40 | import pedantmark
41 | pedantmark.html(text)
42 |
43 |
44 | @benchmark('pedantmark.markdown')
45 | def benchmark_pedant_markdown(text):
46 | import pedantmark
47 | pedantmark.markdown(
48 | text,
49 | options=[pedantmark.OPT_FOOTNOTES],
50 | extensions=['autolink', 'strikethrough', 'table'],
51 | renderer='html',
52 | )
53 |
54 |
55 | @benchmark('pedantmark.custom')
56 | def benchmark_pedant_custom(text):
57 | import pedantmark
58 | pedantmark.markdown(
59 | text,
60 | options=[pedantmark.OPT_FOOTNOTES],
61 | extensions=['autolink', 'strikethrough', 'table'],
62 | renderer=pedantmark.HTMLRenderer(),
63 | )
64 |
65 |
66 | @benchmark('misaka')
67 | def benchmark_misaka(text):
68 | import misaka as m
69 | extensions = (
70 | m.EXT_NO_INTRA_EMPHASIS | m.EXT_FENCED_CODE | m.EXT_AUTOLINK |
71 | m.EXT_TABLES | m.EXT_STRIKETHROUGH
72 | )
73 | md = m.Markdown(m.HtmlRenderer(), extensions=extensions)
74 | md(text)
75 |
76 |
77 | @benchmark('mistune')
78 | def benchmark_mistune(text):
79 | import mistune
80 | mistune.markdown(text)
81 |
82 |
83 | @benchmark('mistletoe')
84 | def benchmark_mistletoe(text):
85 | import mistletoe
86 | mistletoe.markdown(text)
87 |
88 |
89 | @benchmark('commonmark')
90 | def benchmark_commonmark(text):
91 | import commonmark
92 | commonmark.commonmark(text)
93 |
94 |
95 | @benchmark('markdown')
96 | def benchmark_markdown(text):
97 | import markdown
98 | markdown.markdown(text, extensions=['extra'])
99 |
100 |
101 | if __name__ == '__main__':
102 | root = os.path.dirname(__file__)
103 | filepath = os.path.join(root, 'spec.txt')
104 | with open(filepath, 'r') as f:
105 | text = f.read()
106 |
107 | benchmark.bench(text)
108 |
--------------------------------------------------------------------------------
/tests/test_cmark.py:
--------------------------------------------------------------------------------
1 | import pedantmark
2 |
3 |
4 | TEXT = '''
5 | # h
6 |
7 | a `b` ~c~
8 |
9 |
10 | '''
11 |
12 |
13 | def test_default_html():
14 | html = pedantmark.html(TEXT)
15 | assert 'h
' in html
16 | assert 'b
' in html
17 | assert '~c~' in html
18 | assert 'omitted' in html
19 |
20 | html = pedantmark.html(TEXT, [pedantmark.OPT_UNSAFE])
21 | assert '' in html
22 |
23 |
24 | def test_markdown_html():
25 | html = pedantmark.markdown(TEXT, renderer='html')
26 | assert html == pedantmark.html(TEXT)
27 |
28 | html = pedantmark.markdown(
29 | TEXT, renderer='html',
30 | extensions=['strikethrough']
31 | )
32 | assert 'c' in html
33 |
34 | html = pedantmark.markdown(
35 | TEXT, renderer='html',
36 | options=[pedantmark.OPT_STRIKETHROUGH_DOUBLE_TILDE],
37 | extensions=['strikethrough']
38 | )
39 | assert '~c~' in html
40 |
41 |
42 | def test_markdown_man():
43 | rv = pedantmark.markdown(TEXT, renderer='man')
44 | assert r'\f[C]b\f[]' in rv
45 |
46 |
47 | def test_markdown_latex():
48 | rv = pedantmark.markdown(TEXT, renderer='latex')
49 | assert r'\section{h}' in rv
50 |
51 |
52 | def test_markdown_plaintext():
53 | rv = pedantmark.markdown(TEXT, renderer='plaintext')
54 | assert r'a b ~c~' in rv
55 |
56 |
57 | def test_markdown_commonmark():
58 | rv = pedantmark.markdown(TEXT, renderer='commonmark')
59 | assert r'# h' in rv
60 |
61 |
62 | def test_markdown_xml():
63 | rv = pedantmark.markdown(TEXT, renderer='xml')
64 | assert '' in rv
65 |
66 |
67 | def test_markdown_invalid():
68 | try:
69 | pedantmark.markdown(TEXT, renderer='invalid')
70 | success = True
71 | except ValueError:
72 | success = False
73 | assert success is False
74 |
75 |
76 | def test_invalid_extension():
77 | try:
78 | pedantmark.markdown(TEXT, extensions=['invalid'])
79 | success = True
80 | except ValueError:
81 | success = False
82 | assert success is False
83 |
--------------------------------------------------------------------------------
/tests/test_escape.py:
--------------------------------------------------------------------------------
1 | from pedantmark import escape_html, escape_href
2 |
3 |
4 | def test_escape_html():
5 | assert escape_html('') == '<a>'
6 |
7 |
8 | def test_escape_href():
9 | assert escape_href('http://a b') == 'http://a%20b'
10 |
--------------------------------------------------------------------------------
/tests/test_renderer.py:
--------------------------------------------------------------------------------
1 | import pedantmark
2 | from pedantmark import markdown, HTMLRenderer
3 |
4 |
5 | def test_none():
6 | rv = markdown('\n', renderer=HTMLRenderer())
7 | assert rv == ''
8 |
9 |
10 | def test_strong():
11 | rv = markdown('**a**', renderer=HTMLRenderer())
12 | assert rv.strip() == 'a
'
13 |
14 |
15 | def test_emph():
16 | rv = markdown('_a_', renderer=HTMLRenderer())
17 | assert rv.strip() == 'a
'
18 |
19 |
20 | def test_strikethrough():
21 | rv = markdown('~a~', renderer=HTMLRenderer(), extensions=['strikethrough'])
22 | assert rv.strip() == 'a
'
23 |
24 |
25 | def test_code():
26 | rv = markdown('`a`', renderer=HTMLRenderer())
27 | assert rv.strip() == 'a
'
28 |
29 |
30 | def test_link():
31 | rv = markdown('[a]()', renderer=HTMLRenderer())
32 | assert rv.strip() == ''
33 |
34 | rv = markdown('[a]( "b")', renderer=HTMLRenderer())
35 | assert rv.strip() == ''
36 |
37 | rv = markdown('[a]( "b")', renderer=HTMLRenderer())
38 | assert rv.strip() == ''
39 |
40 |
41 | def test_image():
42 | rv = markdown('![a]()', renderer=HTMLRenderer())
43 | assert rv.strip() == '
'
44 |
45 | rv = markdown('', renderer=HTMLRenderer())
46 | assert rv.strip() == '
'
47 |
48 | rv = markdown('', renderer=HTMLRenderer())
49 | assert rv.strip() == ''
50 |
51 |
52 | def test_html_inline():
53 | rv = markdown('a i', renderer=HTMLRenderer())
54 | assert '<i>' in rv
55 |
56 | rv = markdown(
57 | 'a i',
58 | options=[pedantmark.OPT_UNSAFE],
59 | renderer=HTMLRenderer()
60 | )
61 | assert 'i' in rv
62 |
63 |
64 | def test_html_block():
65 | rv = markdown('\na\n', renderer=HTMLRenderer())
66 | assert '<div>' in rv
67 |
68 | rv = markdown(
69 | '\na\n',
70 | options=[pedantmark.OPT_UNSAFE],
71 | renderer=HTMLRenderer()
72 | )
73 | assert '' in rv
74 |
75 |
76 | def test_thematic_break():
77 | rv = markdown('***', renderer=HTMLRenderer())
78 | assert rv.strip() == '
'
79 |
80 |
81 | def test_linebreak():
82 | rv = markdown('a \nb\n', renderer=HTMLRenderer())
83 | assert rv.strip() == 'a
\nb
'
84 |
85 |
86 | def test_softbreak():
87 | rv = markdown('a\nb\n', renderer=HTMLRenderer())
88 | assert 'a\nb' in rv
89 |
90 | rv = markdown('a\nb\n', renderer=HTMLRenderer(hardbreaks=True))
91 | assert '
' in rv
92 |
93 | rv = markdown('a\nb\n', renderer=HTMLRenderer(nobreaks=True))
94 | assert 'a b' in rv
95 |
96 |
97 | def test_block_quote():
98 | rv = markdown('> a', renderer=HTMLRenderer())
99 | assert rv.strip() == 'a
\n
'
100 |
101 |
102 | def test_code_block():
103 | rv = markdown(' a\n', renderer=HTMLRenderer())
104 | assert rv.strip() == 'a\n
'
105 |
106 | rv = markdown('```c\na\n```', renderer=HTMLRenderer())
107 | assert rv.strip() == 'a\n
'
108 |
109 | def highlight(code, lang):
110 | return b'test'
111 |
112 | rv = markdown('```c\na\n```', renderer=HTMLRenderer(highlight))
113 | assert rv == 'test'
114 |
115 |
116 | def test_ordered_list():
117 | rv = markdown('1. a', renderer=HTMLRenderer())
118 | assert rv.strip() == '\n- a
\n
'
119 |
120 | rv = markdown('5. a', renderer=HTMLRenderer())
121 | assert rv.strip() == '\n- a
\n
'
122 |
123 |
124 | def test_unordered_list():
125 | rv = markdown('- a', renderer=HTMLRenderer())
126 | assert rv.strip() == '\n- a
\n
'
127 |
128 |
129 | def test_footnote():
130 | s = '[^1]\n\n[^1]: a'
131 | rv = markdown(
132 | s, renderer=HTMLRenderer(),
133 | options=[pedantmark.OPT_FOOTNOTES]
134 | )
135 | assert 'id="fn' in rv
136 | assert 'class="footnotes"' in rv
137 |
138 |
139 | TABLE_TEXT_1 = '''
140 | a1 | a2
141 | -- | --
142 | b1 | b2
143 | '''
144 |
145 | TABLE_TEXT_2 = '''
146 | a1 | a2
147 | :-- | :--:
148 | b1 | b2
149 | '''
150 |
151 |
152 | def test_table():
153 | rv = markdown(
154 | TABLE_TEXT_1, renderer=HTMLRenderer(),
155 | extensions=['table'],
156 | )
157 | assert '' in rv
158 |
159 | rv = markdown(
160 | TABLE_TEXT_2, renderer=HTMLRenderer(),
161 | extensions=['table'],
162 | )
163 | assert 'style=' in rv
164 |
--------------------------------------------------------------------------------
/tests/test_toc.py:
--------------------------------------------------------------------------------
1 | from pedantmark import MarkdownState, HTMLRenderer, markdown
2 | from pedantmark.toc import render_toc
3 |
4 | NORMAL_INDENT = '''
5 | # a
6 |
7 | ## b
8 |
9 | ### c
10 |
11 | #### d
12 |
13 | # e
14 | '''
15 |
16 | NORMAL_HTML = '''
17 |
29 | '''
30 |
31 | MESS_INDENT = '''
32 | ## a
33 |
34 | ###### b
35 |
36 | ### c
37 |
38 | #### d
39 |
40 | #### e
41 |
42 | # f
43 | '''
44 |
45 | MESS_HTML = '''
46 |
60 | '''
61 |
62 |
63 | def test_normal_indent():
64 | state = MarkdownState(toc_level=3)
65 | html = markdown(NORMAL_INDENT, renderer=HTMLRenderer(), state=state)
66 | # h4 has no id
67 | assert '' in html
68 | toc = render_toc(state.toc)
69 | assert toc.strip() == NORMAL_HTML.strip()
70 |
71 |
72 | def test_mess_indent():
73 | state = MarkdownState(toc_level=6)
74 | markdown(MESS_INDENT, renderer=HTMLRenderer(), state=state)
75 | toc = render_toc(state.toc)
76 | assert toc.strip() == MESS_HTML.strip()
77 |
78 |
79 | def test_none():
80 | assert render_toc([]) == ''
81 |
--------------------------------------------------------------------------------
/vendors/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lepture/pedantmark/8643c9c9a95a911266faa3c0246e6ad9d6fc51f3/vendors/.gitkeep
--------------------------------------------------------------------------------
/vendors/unix/extensions/cmark-gfm-extensions_export.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef CMARK_GFM_EXTENSIONS_EXPORT_H
3 | #define CMARK_GFM_EXTENSIONS_EXPORT_H
4 |
5 | #ifdef CMARK_GFM_EXTENSIONS_STATIC_DEFINE
6 | # define CMARK_GFM_EXTENSIONS_EXPORT
7 | # define CMARK_GFM_EXTENSIONS_NO_EXPORT
8 | #else
9 | # ifndef CMARK_GFM_EXTENSIONS_EXPORT
10 | # ifdef libcmark_gfm_extensions_EXPORTS
11 | /* We are building this library */
12 | # define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((visibility("default")))
13 | # else
14 | /* We are using this library */
15 | # define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((visibility("default")))
16 | # endif
17 | # endif
18 |
19 | # ifndef CMARK_GFM_EXTENSIONS_NO_EXPORT
20 | # define CMARK_GFM_EXTENSIONS_NO_EXPORT __attribute__((visibility("hidden")))
21 | # endif
22 | #endif
23 |
24 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED
25 | # define CMARK_GFM_EXTENSIONS_DEPRECATED __attribute__ ((__deprecated__))
26 | #endif
27 |
28 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT
29 | # define CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT CMARK_GFM_EXTENSIONS_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED
30 | #endif
31 |
32 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT
33 | # define CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT CMARK_GFM_EXTENSIONS_NO_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED
34 | #endif
35 |
36 | #if 0 /* DEFINE_NO_DEPRECATED */
37 | # ifndef CMARK_GFM_EXTENSIONS_NO_DEPRECATED
38 | # define CMARK_GFM_EXTENSIONS_NO_DEPRECATED
39 | # endif
40 | #endif
41 |
42 | #endif /* CMARK_GFM_EXTENSIONS_EXPORT_H */
43 |
--------------------------------------------------------------------------------
/vendors/unix/src/cmark-gfm_export.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef CMARK_GFM_EXPORT_H
3 | #define CMARK_GFM_EXPORT_H
4 |
5 | #ifdef CMARK_GFM_STATIC_DEFINE
6 | # define CMARK_GFM_EXPORT
7 | # define CMARK_GFM_NO_EXPORT
8 | #else
9 | # ifndef CMARK_GFM_EXPORT
10 | # ifdef libcmark_gfm_EXPORTS
11 | /* We are building this library */
12 | # define CMARK_GFM_EXPORT __attribute__((visibility("default")))
13 | # else
14 | /* We are using this library */
15 | # define CMARK_GFM_EXPORT __attribute__((visibility("default")))
16 | # endif
17 | # endif
18 |
19 | # ifndef CMARK_GFM_NO_EXPORT
20 | # define CMARK_GFM_NO_EXPORT __attribute__((visibility("hidden")))
21 | # endif
22 | #endif
23 |
24 | #ifndef CMARK_GFM_DEPRECATED
25 | # define CMARK_GFM_DEPRECATED __attribute__ ((__deprecated__))
26 | #endif
27 |
28 | #ifndef CMARK_GFM_DEPRECATED_EXPORT
29 | # define CMARK_GFM_DEPRECATED_EXPORT CMARK_GFM_EXPORT CMARK_GFM_DEPRECATED
30 | #endif
31 |
32 | #ifndef CMARK_GFM_DEPRECATED_NO_EXPORT
33 | # define CMARK_GFM_DEPRECATED_NO_EXPORT CMARK_GFM_NO_EXPORT CMARK_GFM_DEPRECATED
34 | #endif
35 |
36 | #if 0 /* DEFINE_NO_DEPRECATED */
37 | # ifndef CMARK_GFM_NO_DEPRECATED
38 | # define CMARK_GFM_NO_DEPRECATED
39 | # endif
40 | #endif
41 |
42 | #endif /* CMARK_GFM_EXPORT_H */
43 |
--------------------------------------------------------------------------------
/vendors/unix/src/cmark-gfm_version.h:
--------------------------------------------------------------------------------
1 | #ifndef CMARK_GFM_VERSION_H
2 | #define CMARK_GFM_VERSION_H
3 |
4 | #define CMARK_GFM_VERSION ((0 << 24) | (28 << 16) | (3 << 8) | 19)
5 | #define CMARK_GFM_VERSION_STRING "0.28.3.gfm.19"
6 |
7 | #endif
8 |
--------------------------------------------------------------------------------
/vendors/unix/src/config.h:
--------------------------------------------------------------------------------
1 | #ifndef CMARK_CONFIG_H
2 | #define CMARK_CONFIG_H
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #define HAVE_STDBOOL_H
9 |
10 | #ifdef HAVE_STDBOOL_H
11 | #include
12 | #elif !defined(__cplusplus)
13 | typedef char bool;
14 | #endif
15 |
16 | #define HAVE___BUILTIN_EXPECT
17 |
18 | #define HAVE___ATTRIBUTE__
19 |
20 | #ifdef HAVE___ATTRIBUTE__
21 | #define CMARK_ATTRIBUTE(list) __attribute__ (list)
22 | #else
23 | #define CMARK_ATTRIBUTE(list)
24 | #endif
25 |
26 | #ifndef CMARK_INLINE
27 | #if defined(_MSC_VER) && !defined(__cplusplus)
28 | #define CMARK_INLINE __inline
29 | #else
30 | #define CMARK_INLINE inline
31 | #endif
32 | #endif
33 |
34 | /* snprintf and vsnprintf fallbacks for MSVC before 2015,
35 | due to Valentin Milea http://stackoverflow.com/questions/2915672/
36 | */
37 |
38 | #if defined(_MSC_VER) && _MSC_VER < 1900
39 |
40 | #include
41 | #include
42 |
43 | #define snprintf c99_snprintf
44 | #define vsnprintf c99_vsnprintf
45 |
46 | CMARK_INLINE int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
47 | {
48 | int count = -1;
49 |
50 | if (size != 0)
51 | count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
52 | if (count == -1)
53 | count = _vscprintf(format, ap);
54 |
55 | return count;
56 | }
57 |
58 | CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
59 | {
60 | int count;
61 | va_list ap;
62 |
63 | va_start(ap, format);
64 | count = c99_vsnprintf(outBuf, size, format, ap);
65 | va_end(ap);
66 |
67 | return count;
68 | }
69 |
70 | #endif
71 |
72 | #ifdef __cplusplus
73 | }
74 | #endif
75 |
76 | #endif
77 |
--------------------------------------------------------------------------------