diff --git a/docs/source/dependencies.rst b/docs/source/dependencies.rst index 2dca64d1..9123f947 100644 --- a/docs/source/dependencies.rst +++ b/docs/source/dependencies.rst @@ -71,19 +71,15 @@ - Mandatory for `kikit_present` -`mistune `__ :index:`: ` |image29| |image30| - -- Mandatory for `populate` - -`QRCodeGen `__ :index:`: ` |image31| |image32| |image33| |Auto-download| +`QRCodeGen `__ :index:`: ` |image29| |image30| |image31| |Auto-download| - Mandatory for `qr_lib` -`Colorama `__ :index:`: ` |image34| |image35| |image36| +`Colorama `__ :index:`: ` |image32| |image33| |image34| - Optional to get color messages in a portable way for general use -`Git `__ :index:`: ` |image37| |image38| |Auto-download| +`Git `__ :index:`: ` |image35| |image36| |Auto-download| - Optional to: @@ -94,7 +90,7 @@ - Find commit hash and/or date for `sch_replace` - Find commit hash and/or date for `set_text_variables` -`ImageMagick `__ :index:`: ` |image39| |image40| |Auto-download| +`ImageMagick `__ :index:`: ` |image37| |image38| |Auto-download| - Optional to: @@ -104,7 +100,7 @@ - Create JPG and BMP images for `pcbdraw` - Automatically crop images for `render_3d` -`RSVG tools `__ :index:`: ` |image41| |image42| |Auto-download| +`RSVG tools `__ :index:`: ` |image39| |image40| |Auto-download| - Optional to: @@ -113,7 +109,7 @@ - Create PDF, PNG, PS and EPS formats for `pcb_print` - Create PNG, JPG and BMP images for `pcbdraw` -`Bash `__ :index:`: ` |image43| |image44| +`Bash `__ :index:`: ` |image41| |image42| - Optional to: @@ -121,27 +117,27 @@ - Run external commands to create replacement text for `sch_replace` - Run external commands to create replacement text for `set_text_variables` -`Ghostscript `__ :index:`: ` |image45| |image46| |Auto-download| +`Ghostscript `__ :index:`: ` |image43| |image44| |Auto-download| - Optional to: - Create outputs preview for `navigate_results` - Create PNG, PS and EPS formats for `pcb_print` -`numpy `__ :index:`: ` |image47| |image48| |Auto-download| +`numpy `__ :index:`: ` |image45| |image46| |Auto-download| - Optional to automatically adjust SVG margin for `pcbdraw` -`Pandoc `__ :index:`: ` |image49| |image50| +`Pandoc `__ :index:`: ` |image47| |image48| - Optional to create PDF/ODF/DOCX files for `report` - Note: In CI/CD environments: the `kicad_auto_test` docker image contains it. -`RAR `__ :index:`: ` |image51| |image52| |Auto-download| +`RAR `__ :index:`: ` |image49| |image50| |Auto-download| - Optional to compress in RAR format for `compress` -`XLSXWriter `__ :index:`: ` v1.1.2 |image53| |image54| |image55| |Auto-download| +`XLSXWriter `__ :index:`: ` v1.1.2 |image51| |image52| |image53| |Auto-download| - Optional to create XLSX files for `bom` @@ -204,57 +200,53 @@ .. |image28| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/python3-markdown2 .. |image29| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png - :target: https://pypi.org/project/mistune/ -.. |image30| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png - :target: https://packages.debian.org/stable/python3-mistune -.. |image31| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png :target: https://pypi.org/project/QRCodeGen/ -.. |image32| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/PyPI_logo_simplified-22x22.png +.. |image30| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/PyPI_logo_simplified-22x22.png :target: https://pypi.org/project/QRCodeGen/ -.. |image33| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image31| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/python3-qrcodegen -.. |image34| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png +.. |image32| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png :target: https://pypi.org/project/Colorama/ -.. |image35| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/PyPI_logo_simplified-22x22.png +.. |image33| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/PyPI_logo_simplified-22x22.png :target: https://pypi.org/project/Colorama/ -.. |image36| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image34| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/python3-colorama -.. |image37| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image35| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://git-scm.com/ -.. |image38| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image36| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/git -.. |image39| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image37| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://imagemagick.org/ -.. |image40| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image38| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/imagemagick -.. |image41| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image39| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://gitlab.gnome.org/GNOME/librsvg -.. |image42| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image40| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/librsvg2-bin -.. |image43| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image41| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://www.gnu.org/software/bash/ -.. |image44| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image42| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/bash -.. |image45| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image43| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://www.ghostscript.com/ -.. |image46| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image44| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/ghostscript -.. |image47| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png +.. |image45| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png :target: https://pypi.org/project/numpy/ -.. |image48| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image46| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/python3-numpy -.. |image49| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image47| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://pandoc.org/ -.. |image50| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image48| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/pandoc -.. |image51| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png +.. |image49| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png :target: https://www.rarlab.com/ -.. |image52| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image50| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/rar -.. |image53| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png +.. |image51| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png :target: https://pypi.org/project/XLSXWriter/ -.. |image54| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/PyPI_logo_simplified-22x22.png +.. |image52| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/PyPI_logo_simplified-22x22.png :target: https://pypi.org/project/XLSXWriter/ -.. |image55| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png +.. |image53| image:: https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png :target: https://packages.debian.org/stable/python3-xlsxwriter diff --git a/kibot/PcbDraw/mdrenderer.py b/kibot/PcbDraw/mdrenderer.py index f3d09dfb..662ca72b 100644 --- a/kibot/PcbDraw/mdrenderer.py +++ b/kibot/PcbDraw/mdrenderer.py @@ -16,7 +16,7 @@ # The following try-catch is used to support mistune 0.8.4 and 2.x try: - from mistune.renderers import BaseRenderer # type: ignore + from .mistune.renderers import BaseRenderer # type: ignore except ModuleNotFoundError: from mistune import Renderer # type: ignore BaseRenderer = Renderer diff --git a/kibot/PcbDraw/mistune/__init__.py b/kibot/PcbDraw/mistune/__init__.py new file mode 100644 index 00000000..408ed12b --- /dev/null +++ b/kibot/PcbDraw/mistune/__init__.py @@ -0,0 +1,63 @@ +from .markdown import Markdown +from .block_parser import BlockParser +from .inline_parser import InlineParser +from .renderers import AstRenderer, HTMLRenderer +from .plugins import PLUGINS +from .util import escape, escape_url, escape_html, unikey + + +def create_markdown(escape=True, hard_wrap=False, renderer=None, plugins=None): + """Create a Markdown instance based on the given condition. + + :param escape: Boolean. If using html renderer, escape html. + :param hard_wrap: Boolean. Break every new line into ``
``. + :param renderer: renderer instance or string of ``html`` and ``ast``. + :param plugins: List of plugins, string or callable. + + This method is used when you want to re-use a Markdown instance:: + + markdown = create_markdown( + escape=False, + renderer='html', + plugins=['url', 'strikethrough', 'footnotes', 'table'], + ) + # re-use markdown function + markdown('.... your text ...') + """ + if renderer is None or renderer == 'html': + renderer = HTMLRenderer(escape=escape) + elif renderer == 'ast': + renderer = AstRenderer() + + if plugins: + _plugins = [] + for p in plugins: + if isinstance(p, str): + _plugins.append(PLUGINS[p]) + else: + _plugins.append(p) + plugins = _plugins + + return Markdown(renderer, inline=InlineParser(renderer, hard_wrap=hard_wrap), plugins=plugins) + + +html = create_markdown( + escape=False, + renderer='html', + plugins=['strikethrough', 'footnotes', 'table'], +) + + +def markdown(text, escape=True, renderer=None, plugins=None): + md = create_markdown(escape=escape, renderer=renderer, plugins=plugins) + return md(text) + + +__all__ = [ + 'Markdown', 'AstRenderer', 'HTMLRenderer', + 'BlockParser', 'InlineParser', + 'escape', 'escape_url', 'escape_html', 'unikey', + 'html', 'create_markdown', 'markdown', +] + +__version__ = '2.0.4' diff --git a/kibot/PcbDraw/mistune/block_parser.py b/kibot/PcbDraw/mistune/block_parser.py new file mode 100644 index 00000000..f433fe58 --- /dev/null +++ b/kibot/PcbDraw/mistune/block_parser.py @@ -0,0 +1,366 @@ +import re +from .scanner import ScannerParser, Matcher +from .inline_parser import ESCAPE_CHAR, LINK_LABEL +from .util import unikey + +_NEW_LINES = re.compile(r'\r\n|\r') +_BLANK_LINES = re.compile(r'^ +$', re.M) + +_TRIM_4 = re.compile(r'^ {1,4}') +_EXPAND_TAB = re.compile(r'^( {0,3})\t', flags=re.M) +_INDENT_CODE_TRIM = re.compile(r'^ {1,4}', flags=re.M) +_BLOCK_QUOTE_TRIM = re.compile(r'^ {0,1}', flags=re.M) +_BLOCK_QUOTE_LEADING = re.compile(r'^ *>', flags=re.M) +_BLOCK_TAGS = { + 'address', 'article', 'aside', 'base', 'basefont', 'blockquote', + 'body', 'caption', 'center', 'col', 'colgroup', 'dd', 'details', + 'dialog', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption', + 'figure', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', + 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'iframe', + 'legend', 'li', 'link', 'main', 'menu', 'menuitem', 'meta', 'nav', + 'noframes', 'ol', 'optgroup', 'option', 'p', 'param', 'section', + 'source', 'summary', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', + 'title', 'tr', 'track', 'ul' +} +_BLOCK_HTML_RULE6 = ( + r')[\s\S]*?' + r'(?:\n{2,}|\n*$)' +) +_BLOCK_HTML_RULE7 = ( + # open tag + r'<(?!script|pre|style)([a-z][\w-]*)(?:' + r' +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"|' + r''' *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?''' + r')*? */?>(?=\s*\n)[\s\S]*?(?:\n{2,}|\n*$)|' + # close tag + r'(?=\s*\n)[\s\S]*?(?:\n{2,}|\n*$)' +) + +_PARAGRAPH_SPLIT = re.compile(r'\n{2,}') +_LIST_BULLET = re.compile(r'^ *([\*\+-]|\d+[.)])') + + +class BlockParser(ScannerParser): + scanner_cls = Matcher + + NEWLINE = re.compile(r'\n+') + DEF_LINK = re.compile( + r' {0,3}\[(' + LINK_LABEL + r')\]:(?:[ \t]*\n)?[ \t]*' + r']+)>?(?:[ \t]*\n)?' + r'(?: +["(]([^\n]+)[")])? *\n+' + ) + + AXT_HEADING = re.compile( + r' {0,3}(#{1,6})(?!#+)(?: *\n+|' + r'\s+([^\n]*?)(?:\n+|\s+?#+\s*\n+))' + ) + SETEX_HEADING = re.compile(r'([^\n]+)\n *(=|-){2,}[ \t]*\n+') + THEMATIC_BREAK = re.compile( + r' {0,3}((?:-[ \t]*){3,}|' + r'(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})\n+' + ) + + INDENT_CODE = re.compile(r'(?:\n*)(?:(?: {4}| *\t)[^\n]+\n*)+') + + FENCED_CODE = re.compile( + r'( {0,3})(`{3,}|~{3,})([^`\n]*)\n' + r'(?:|([\s\S]*?)\n)' + r'(?: {0,3}\2[~`]* *\n+|$)' + ) + BLOCK_QUOTE = re.compile( + r'(?: {0,3}>[^\n]*\n)+' + ) + LIST_START = re.compile( + r'( {0,3})([\*\+-]|\d{1,9}[.)])(?:[ \t]*|[ \t][^\n]+)\n+' + ) + + BLOCK_HTML = re.compile(( + r' {0,3}(?:' + r'<(script|pre|style)[\s>][\s\S]*?(?:[^\n]*\n+|$)|' + r'[^\n]*\n+|' + r'<\?[\s\S]*?\?>[^\n]*\n+|' + r'[^\n]*\n+|' + r'[^\n]*\n+' + r'|' + _BLOCK_HTML_RULE6 + '|' + _BLOCK_HTML_RULE7 + ')' + ), re.I) + + LIST_MAX_DEPTH = 6 + BLOCK_QUOTE_MAX_DEPTH = 6 + RULE_NAMES = ( + 'newline', 'thematic_break', + 'fenced_code', 'indent_code', + 'block_quote', 'block_html', + 'list_start', + 'axt_heading', 'setex_heading', + 'def_link', + ) + + def __init__(self): + super(BlockParser, self).__init__() + self.block_quote_rules = list(self.RULE_NAMES) + self.list_rules = list(self.RULE_NAMES) + + def parse_newline(self, m, state): + return {'type': 'newline', 'blank': True} + + def parse_thematic_break(self, m, state): + return {'type': 'thematic_break', 'blank': True} + + def parse_indent_code(self, m, state): + text = expand_leading_tab(m.group(0)) + code = _INDENT_CODE_TRIM.sub('', text) + code = code.lstrip('\n') + return self.tokenize_block_code(code, None, state) + + def parse_fenced_code(self, m, state): + info = ESCAPE_CHAR.sub(r'\1', m.group(3)) + spaces = m.group(1) + code = m.group(4) or '' + if spaces and code: + _trim_pattern = re.compile('^' + spaces, re.M) + code = _trim_pattern.sub('', code) + return self.tokenize_block_code(code + '\n', info, state) + + def tokenize_block_code(self, code, info, state): + token = {'type': 'block_code', 'raw': code} + if info: + token['params'] = (info, ) + return token + + def parse_axt_heading(self, m, state): + level = len(m.group(1)) + text = m.group(2) or '' + text = text.strip() + if set(text) == {'#'}: + text = '' + return self.tokenize_heading(text, level, state) + + def parse_setex_heading(self, m, state): + level = 1 if m.group(2) == '=' else 2 + text = m.group(1) + text = text.strip() + return self.tokenize_heading(text, level, state) + + def tokenize_heading(self, text, level, state): + return {'type': 'heading', 'text': text, 'params': (level,)} + + def get_block_quote_rules(self, depth): + if depth > self.BLOCK_QUOTE_MAX_DEPTH - 1: + rules = list(self.block_quote_rules) + rules.remove('block_quote') + return rules + return self.block_quote_rules + + def parse_block_quote(self, m, state): + depth = state.get('block_quote_depth', 0) + 1 + state['block_quote_depth'] = depth + + # normalize block quote text + text = _BLOCK_QUOTE_LEADING.sub('', m.group(0)) + text = expand_leading_tab(text) + text = _BLOCK_QUOTE_TRIM.sub('', text) + text = cleanup_lines(text) + + rules = self.get_block_quote_rules(depth) + children = self.parse(text, state, rules) + state['block_quote_depth'] = depth - 1 + return {'type': 'block_quote', 'children': children} + + def get_list_rules(self, depth): + if depth > self.LIST_MAX_DEPTH - 1: + rules = list(self.list_rules) + rules.remove('list_start') + return rules + return self.list_rules + + def parse_list_start(self, m, state, string): + items = [] + spaces = m.group(1) + marker = m.group(2) + items, pos = _find_list_items(string, m.start(), spaces, marker) + tight = '\n\n' not in ''.join(items).strip() + + ordered = len(marker) != 1 + if ordered: + start = int(marker[:-1]) + if start == 1: + start = None + else: + start = None + + list_tights = state.get('list_tights', []) + list_tights.append(tight) + state['list_tights'] = list_tights + + depth = len(list_tights) + rules = self.get_list_rules(depth) + children = [ + self.parse_list_item(item, depth, state, rules) + for item in items + ] + list_tights.pop() + params = (ordered, depth, start) + token = {'type': 'list', 'children': children, 'params': params} + return token, pos + + def parse_list_item(self, text, depth, state, rules): + text = self.normalize_list_item_text(text) + if not text: + children = [{'type': 'block_text', 'text': ''}] + else: + children = self.parse(text, state, rules) + return { + 'type': 'list_item', + 'params': (depth,), + 'children': children, + } + + @staticmethod + def normalize_list_item_text(text): + text_length = len(text) + text = _LIST_BULLET.sub('', text) + + if not text.strip(): + return '' + + space = text_length - len(text) + text = expand_leading_tab(text) + if text.startswith(' '): + text = text[1:] + space += 1 + else: + text_length = len(text) + text = _TRIM_4.sub('', text) + space += max(text_length - len(text), 1) + + # outdent + if '\n ' in text: + pattern = re.compile(r'\n {1,' + str(space) + r'}') + text = pattern.sub(r'\n', text) + return text + + def parse_block_html(self, m, state): + html = m.group(0).rstrip() + return {'type': 'block_html', 'raw': html} + + def parse_def_link(self, m, state): + key = unikey(m.group(1)) + link = m.group(2) + title = m.group(3) + if key not in state['def_links']: + state['def_links'][key] = (link, title) + + def parse_text(self, text, state): + list_tights = state.get('list_tights') + if list_tights and list_tights[-1]: + return {'type': 'block_text', 'text': text.strip()} + + tokens = [] + for s in _PARAGRAPH_SPLIT.split(text): + s = s.strip() + if s: + tokens.append({'type': 'paragraph', 'text': s}) + return tokens + + def parse(self, s, state, rules=None): + if rules is None: + rules = self.rules + + return list(self._scan(s, state, rules)) + + def render(self, tokens, inline, state): + data = self._iter_render(tokens, inline, state) + return inline.renderer.finalize(data) + + def _iter_render(self, tokens, inline, state): + for tok in tokens: + method = inline.renderer._get_method(tok['type']) + if 'blank' in tok: + yield method() + continue + + if 'children' in tok: + children = self.render(tok['children'], inline, state) + elif 'raw' in tok: + children = tok['raw'] + else: + children = inline(tok['text'], state) + params = tok.get('params') + if params: + yield method(children, *params) + else: + yield method(children) + + +def cleanup_lines(s): + s = _NEW_LINES.sub('\n', s) + s = _BLANK_LINES.sub('', s) + return s + + +def expand_leading_tab(text): + return _EXPAND_TAB.sub(_expand_tab_repl, text) + + +def _expand_tab_repl(m): + s = m.group(1) + return s + ' ' * (4 - len(s)) + + +def _create_list_item_pattern(spaces, marker): + prefix = r'( {0,' + str(len(spaces) + len(marker)) + r'})' + + if len(marker) > 1: + if marker[-1] == '.': + prefix = prefix + r'\d{0,9}\.' + else: + prefix = prefix + r'\d{0,9}\)' + else: + if marker == '*': + prefix = prefix + r'\*' + elif marker == '+': + prefix = prefix + r'\+' + else: + prefix = prefix + r'-' + + s1 = ' {' + str(len(marker) + 1) + ',}' + if len(marker) > 4: + s2 = ' {' + str(len(marker) - 4) + r',}\t' + else: + s2 = r' *\t' + return re.compile( + prefix + r'(?:[ \t]*|[ \t]+[^\n]+)\n+' + r'(?:\1(?:' + s1 + '|' + s2 + ')' + r'[^\n]+\n+)*' + ) + + +def _find_list_items(string, pos, spaces, marker): + items = [] + + if marker in {'*', '-'}: + is_hr = re.compile( + r' *((?:-[ \t]*){3,}|(?:\*[ \t]*){3,})\n+' + ) + else: + is_hr = None + + pattern = _create_list_item_pattern(spaces, marker) + while 1: + m = pattern.match(string, pos) + if not m: + break + + text = m.group(0) + if is_hr and is_hr.match(text): + break + + new_spaces = m.group(1) + if new_spaces != spaces: + spaces = new_spaces + pattern = _create_list_item_pattern(spaces, marker) + + items.append(text) + pos = m.end() + return items, pos diff --git a/kibot/PcbDraw/mistune/directives/__init__.py b/kibot/PcbDraw/mistune/directives/__init__.py new file mode 100644 index 00000000..fdc96d78 --- /dev/null +++ b/kibot/PcbDraw/mistune/directives/__init__.py @@ -0,0 +1,10 @@ +from .base import Directive +from .admonition import Admonition +from .include import DirectiveInclude +from .toc import DirectiveToc, extract_toc_items, render_toc_ul + + +__all__ = [ + 'Directive', 'Admonition', 'DirectiveInclude', + 'DirectiveToc', 'extract_toc_items', 'render_toc_ul', +] diff --git a/kibot/PcbDraw/mistune/directives/admonition.py b/kibot/PcbDraw/mistune/directives/admonition.py new file mode 100644 index 00000000..fbb5ceff --- /dev/null +++ b/kibot/PcbDraw/mistune/directives/admonition.py @@ -0,0 +1,57 @@ +from .base import Directive + + +class Admonition(Directive): + SUPPORTED_NAMES = { + "attention", "caution", "danger", "error", "hint", + "important", "note", "tip", "warning", + } + + def parse(self, block, m, state): + options = self.parse_options(m) + if options: + return { + 'type': 'block_error', + 'raw': 'Admonition has no options' + } + name = m.group('name') + title = m.group('value') + text = self.parse_text(m) + + rules = list(block.rules) + rules.remove('directive') + children = block.parse(text, state, rules) + return { + 'type': 'admonition', + 'children': children, + 'params': (name, title) + } + + def __call__(self, md): + for name in self.SUPPORTED_NAMES: + self.register_directive(md, name) + + if md.renderer.NAME == 'html': + md.renderer.register('admonition', render_html_admonition) + elif md.renderer.NAME == 'ast': + md.renderer.register('admonition', render_ast_admonition) + + +def render_html_admonition(text, name, title=""): + html = '
\n' + if not title: + title = name.capitalize() + if title: + html += '

' + title + '

\n' + if text: + html += text + return html + '
\n' + + +def render_ast_admonition(children, name, title=""): + return { + 'type': 'admonition', + 'children': children, + 'name': name, + 'title': title, + } diff --git a/kibot/PcbDraw/mistune/directives/base.py b/kibot/PcbDraw/mistune/directives/base.py new file mode 100644 index 00000000..54a3d861 --- /dev/null +++ b/kibot/PcbDraw/mistune/directives/base.py @@ -0,0 +1,99 @@ +""" + Directive Syntax + ~~~~~~~~~~~~~~~~~ + + This syntax is inspired by reStructuredText. The syntax is very powerful, + that you can define a lot of custom features by your own. + + The syntax looks like:: + + .. directive-name:: directive value + :option-key: option value + :option-key: option value + + full featured markdown text here + + :copyright: (c) Hsiaoming Yang +""" + +import re + +__all__ = ['Directive'] + + +DIRECTIVE_PATTERN = re.compile( + r'\.\.( +)(?P[a-zA-Z0-9_-]+)\:\: *(?P[^\n]*)\n+' + r'(?P(?: \1 {0,3}\:[a-zA-Z0-9_-]+\: *[^\n]*\n+)*)' + r'(?P(?: \1 {0,3}[^\n]*\n+)*)' +) + + +class Directive(object): + @staticmethod + def parse_text(m): + text = m.group('text') + if not text.strip(): + return '' + + leading = len(m.group(1)) + 2 + text = '\n'.join(line[leading:] for line in text.splitlines()) + return text.lstrip('\n') + '\n' + + @staticmethod + def parse_options(m): + text = m.group('options') + if not text.strip(): + return [] + + options = [] + for line in re.split(r'\n+', text): + line = line.strip()[1:] + if not line: + continue + i = line.find(':') + k = line[:i] + v = line[i + 1:].strip() + options.append((k, v)) + return options + + def register_directive(self, md, name): + plugin = getattr(md, '_directive', None) + if not plugin: + plugin = PluginDirective() + plugin(md) + + plugin.register_directive(name, self.parse) + + def parse(self, block, m, state): + raise NotImplementedError() + + def __call__(self, md): + raise NotImplementedError() + + +class PluginDirective(object): + def __init__(self): + self._directives = {} + + def register_directive(self, name, fn): + self._directives[name] = fn + + def parse_block_directive(self, block, m, state): + name = m.group('name') + method = self._directives.get(name) + if method: + return method(block, m, state) + + token = { + 'type': 'block_error', + 'raw': 'Unsupported directive: ' + name, + } + return token + + def __call__(self, md): + md._directive = self + md.block.register_rule( + 'directive', DIRECTIVE_PATTERN, + self.parse_block_directive + ) + md.block.rules.append('directive') diff --git a/kibot/PcbDraw/mistune/directives/include.py b/kibot/PcbDraw/mistune/directives/include.py new file mode 100644 index 00000000..3631bba5 --- /dev/null +++ b/kibot/PcbDraw/mistune/directives/include.py @@ -0,0 +1,71 @@ +import os +from mistune.markdown import preprocess +from .base import Directive + + +class DirectiveInclude(Directive): + def parse(self, block, m, state): + source_file = state.get('__file__') + if not source_file: + return { + 'type': 'block_error', + 'raw': 'Missing source file configuration', + } + + relpath = m.group('value') + options = self.parse_options(m) + + dest = os.path.join(os.path.dirname(source_file), relpath) + dest = os.path.normpath(dest) + if dest == source_file: + return { + 'type': 'block_error', + 'raw': 'Could not include self: ' + relpath, + } + + if not os.path.isfile(dest): + return { + 'type': 'block_error', + 'raw': 'Could not find file: ' + relpath, + } + + with open(dest, 'rb') as f: + content = f.read() + text = content.decode('utf-8') + + if not options: + ext = os.path.splitext(relpath)[1] + if ext in {'.md', '.markdown', '.mkd'}: + text, state = preprocess(text, {'__file__': dest}) + return block.parse(text, state) + if ext in {'.html', '.xhtml', '.htm'}: + return {'type': 'block_html', 'text': text} + + return { + 'type': 'include', + 'raw': text, + 'params': (relpath, dest, options) + } + + def __call__(self, md): + self.register_directive(md, 'include') + if md.renderer.NAME == 'html': + md.renderer.register('include', render_html_include) + + elif md.renderer.NAME == 'ast': + md.renderer.register('include', render_ast_include) + + +def render_ast_include(text, relpath, abspath=None, options=None): + return { + 'type': 'include', + 'text': text, + 'relpath': relpath, + 'abspath': abspath, + 'options': options, + } + + +def render_html_include(text, relpath, abspath=None, options=None): + html = '
\n' + text + '
\n' diff --git a/kibot/PcbDraw/mistune/directives/toc.py b/kibot/PcbDraw/mistune/directives/toc.py new file mode 100644 index 00000000..5606710b --- /dev/null +++ b/kibot/PcbDraw/mistune/directives/toc.py @@ -0,0 +1,215 @@ +""" + TOC directive + ~~~~~~~~~~~~~ + + The TOC directive syntax looks like:: + + .. toc:: Title + :depth: 3 + + "Title" and "depth" option can be empty. "depth" is an integer less + than 6, which defines the max heading level writers want to include + in TOC. +""" + +from .base import Directive + + +class DirectiveToc(Directive): + def __init__(self, depth=3): + self.depth = depth + + def parse(self, block, m, state): + title = m.group('value') + depth = None + options = self.parse_options(m) + if options: + depth = dict(options).get('depth') + if depth: + try: + depth = int(depth) + except (ValueError, TypeError): + return { + 'type': 'block_error', + 'raw': 'TOC depth MUST be integer', + } + + return {'type': 'toc', 'raw': None, 'params': (title, depth)} + + def reset_toc_state(self, md, s, state): + state['toc_depth'] = self.depth + state['toc_headings'] = [] + return s, state + + def register_plugin(self, md): + md.block.tokenize_heading = record_toc_heading + md.before_parse_hooks.append(self.reset_toc_state) + md.before_render_hooks.append(md_toc_hook) + + if md.renderer.NAME == 'html': + md.renderer.register('theading', render_html_theading) + elif md.renderer.NAME == 'ast': + md.renderer.register('theading', render_ast_theading) + + def __call__(self, md): + self.register_directive(md, 'toc') + self.register_plugin(md) + + if md.renderer.NAME == 'html': + md.renderer.register('toc', render_html_toc) + elif md.renderer.NAME == 'ast': + md.renderer.register('toc', render_ast_toc) + + +def record_toc_heading(text, level, state): + # we will use this method to replace tokenize_heading + tid = 'toc_' + str(len(state['toc_headings']) + 1) + state['toc_headings'].append((tid, text, level)) + return {'type': 'theading', 'text': text, 'params': (level, tid)} + + +def md_toc_hook(md, tokens, state): + headings = state.get('toc_headings') + if not headings: + return tokens + + # add TOC items into the given location + default_depth = state.get('toc_depth', 3) + headings = list(_cleanup_headings_text(md.inline, headings, state)) + for tok in tokens: + if tok['type'] == 'toc': + params = tok['params'] + depth = params[1] or default_depth + items = [d for d in headings if d[2] <= depth] + tok['raw'] = items + return tokens + + +def render_ast_toc(items, title, depth): + return { + 'type': 'toc', + 'items': [list(d) for d in items], + 'title': title, + 'depth': depth, + } + + +def render_ast_theading(children, level, tid): + return { + 'type': 'heading', 'children': children, + 'level': level, 'id': tid, + } + + +def render_html_toc(items, title, depth): + html = '
\n' + if title: + html += '

' + title + '

\n' + + return html + render_toc_ul(items) + '
\n' + + +def render_html_theading(text, level, tid): + tag = 'h' + str(level) + return '<' + tag + ' id="' + tid + '">' + text + '\n' + + +def extract_toc_items(md, s): + """Extract TOC headings into list structure of:: + + [ + ('toc_1', 'Introduction', 1), + ('toc_2', 'Install', 2), + ('toc_3', 'Upgrade', 2), + ('toc_4', 'License', 1), + ] + + :param md: Markdown Instance with TOC plugin. + :param s: text string. + """ + s, state = md.before_parse(s, {}) + md.block.parse(s, state) + headings = state.get('toc_headings') + if not headings: + return [] + return list(_cleanup_headings_text(md.inline, headings, state)) + + +def render_toc_ul(toc): + """Render a
    table of content HTML. The param "toc" should + be formatted into this structure:: + + [ + (toc_id, text, level), + ] + + For example:: + + [ + ('toc-intro', 'Introduction', 1), + ('toc-install', 'Install', 2), + ('toc-upgrade', 'Upgrade', 2), + ('toc-license', 'License', 1), + ] + """ + if not toc: + return '' + + s = '
      \n' + levels = [] + for k, text, level in toc: + item = '{}'.format(k, text) + if not levels: + s += '
    • ' + item + levels.append(level) + elif level == levels[-1]: + s += '
    • \n
    • ' + item + elif level > levels[-1]: + s += '\n
        \n
      • ' + item + levels.append(level) + else: + last_level = levels.pop() + while levels: + last_level = levels.pop() + if level == last_level: + s += '
      • \n
      \n
    • \n
    • ' + item + levels.append(level) + break + elif level > last_level: + s += '
    • \n
    • ' + item + levels.append(last_level) + levels.append(level) + break + else: + s += '
    • \n
    \n' + else: + levels.append(level) + s += '\n
  • ' + item + + while len(levels) > 1: + s += '
  • \n
\n' + levels.pop() + + return s + '\n\n' + + +def _cleanup_headings_text(inline, items, state): + for item in items: + text = item[1] + tokens = inline._scan(text, state, inline.rules) + text = ''.join(_inline_token_text(tok) for tok in tokens) + yield item[0], text, item[2] + + +def _inline_token_text(token): + tok_type = token[0] + if tok_type == 'inline_html': + return '' + + if len(token) == 2: + return token[1] + + if tok_type in {'image', 'link'}: + return token[2] + + return '' diff --git a/kibot/PcbDraw/mistune/inline_parser.py b/kibot/PcbDraw/mistune/inline_parser.py new file mode 100644 index 00000000..4e6bf743 --- /dev/null +++ b/kibot/PcbDraw/mistune/inline_parser.py @@ -0,0 +1,220 @@ +import re +from .scanner import ScannerParser +from .util import PUNCTUATION, ESCAPE_TEXT, escape_url, unikey + +HTML_TAGNAME = r'[A-Za-z][A-Za-z0-9-]*' +HTML_ATTRIBUTES = ( + r'(?:\s+[A-Za-z_:][A-Za-z0-9_.:-]*' + r'(?:\s*=\s*(?:[^ "\'=<>`]+|\'[^\']*?\'|"[^\"]*?"))?)*' +) +ESCAPE_CHAR = re.compile(r'\\([' + PUNCTUATION + r'])') +LINK_TEXT = r'(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?' +LINK_LABEL = r'(?:[^\\\[\]]|' + ESCAPE_TEXT + r'){0,1000}' + + +class InlineParser(ScannerParser): + ESCAPE = ESCAPE_TEXT + + #: link or email syntax:: + #: + #: + AUTO_LINK = ( + r'(?]*?|[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9]" + r'(?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?' + r'(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*)>' + ) + + #: link or image syntax:: + #: + #: [text](/link "title") + #: ![alt](/src "title") + STD_LINK = ( + r'!?\[(' + LINK_TEXT + r')\]\(\s*' + + r'(<(?:\\[<>]?|[^\s<>\\])*>|' + r'(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)' + + r'(?:\s+(' + r'''"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)''' + r'))?\s*\)' + ) + + #: Get link from references. References are defined in DEF_LINK in blocks. + #: The syntax looks like:: + #: + #: [an example][id] + #: + #: [id]: https://example.com "optional title" + REF_LINK = ( + r'!?\[(' + LINK_TEXT + r')\]' + r'\[(' + LINK_LABEL + r')\]' + ) + + #: Simple form of reference link:: + #: + #: [an example] + #: + #: [an example]: https://example.com "optional title" + REF_LINK2 = r'!?\[(' + LINK_LABEL + r')\]' + + #: emphasis and strong * or _:: + #: + #: *emphasis* **strong** + #: _emphasis_ __strong__ + ASTERISK_EMPHASIS = ( + r'(\*{1,2})(?=[^\s*])(' + r'(?:(?:(?|' # open tag + r'(?|' # close tag + r'(?|->)(?:(?!--)[\s\S])+?(?|' # comment + r'(?|' + r'(?|' # doctype + r'(?' # cdata + ) + + RULE_NAMES = ( + 'escape', 'inline_html', 'auto_link', + 'std_link', 'ref_link', 'ref_link2', + 'asterisk_emphasis', 'underscore_emphasis', + 'codespan', 'linebreak', + ) + + def __init__(self, renderer, hard_wrap=False): + super(InlineParser, self).__init__() + if hard_wrap: + #: every new line becomes
+ self.LINEBREAK = r' *\n(?!\s*$)' + self.renderer = renderer + rules = list(self.RULE_NAMES) + rules.remove('ref_link') + rules.remove('ref_link2') + self.ref_link_rules = rules + + def parse_escape(self, m, state): + text = m.group(0)[1:] + return 'text', text + + def parse_auto_link(self, m, state): + if state.get('_in_link'): + return 'text', m.group(0) + + text = m.group(1) + schemes = ('mailto:', 'http://', 'https://') + if '@' in text and not text.lower().startswith(schemes): + link = 'mailto:' + text + else: + link = text + return 'link', escape_url(link), text + + def parse_std_link(self, m, state): + line = m.group(0) + text = m.group(1) + link = ESCAPE_CHAR.sub(r'\1', m.group(2)) + if link.startswith('<') and link.endswith('>'): + link = link[1:-1] + + title = m.group(3) + if title: + title = ESCAPE_CHAR.sub(r'\1', title[1:-1]) + + if line[0] == '!': + return 'image', escape_url(link), text, title + + return self.tokenize_link(line, link, text, title, state) + + def parse_ref_link(self, m, state): + line = m.group(0) + text = m.group(1) + key = unikey(m.group(2) or text) + def_links = state.get('def_links') + if not def_links or key not in def_links: + return list(self._scan(line, state, self.ref_link_rules)) + + link, title = def_links.get(key) + link = ESCAPE_CHAR.sub(r'\1', link) + if title: + title = ESCAPE_CHAR.sub(r'\1', title) + + if line[0] == '!': + return 'image', escape_url(link), text, title + + return self.tokenize_link(line, link, text, title, state) + + def parse_ref_link2(self, m, state): + return self.parse_ref_link(m, state) + + def tokenize_link(self, line, link, text, title, state): + if state.get('_in_link'): + return 'text', line + state['_in_link'] = True + text = self.render(text, state) + state['_in_link'] = False + return 'link', escape_url(link), text, title + + def parse_asterisk_emphasis(self, m, state): + return self.tokenize_emphasis(m, state) + + def parse_underscore_emphasis(self, m, state): + return self.tokenize_emphasis(m, state) + + def tokenize_emphasis(self, m, state): + marker = m.group(1) + text = m.group(2) + if len(marker) == 1: + return 'emphasis', self.render(text, state) + return 'strong', self.render(text, state) + + def parse_codespan(self, m, state): + code = re.sub(r'[ \n]+', ' ', m.group(2).strip()) + return 'codespan', code + + def parse_linebreak(self, m, state): + return 'linebreak', + + def parse_inline_html(self, m, state): + html = m.group(0) + if html.startswith(''): + state['_in_link'] = False + return 'inline_html', html + + def parse_text(self, text, state): + return 'text', text + + def parse(self, s, state, rules=None): + if rules is None: + rules = self.rules + + tokens = ( + self.renderer._get_method(t[0])(*t[1:]) + for t in self._scan(s, state, rules) + ) + return tokens + + def render(self, s, state, rules=None): + tokens = self.parse(s, state, rules) + return self.renderer.finalize(tokens) + + def __call__(self, s, state): + return self.render(s, state) diff --git a/kibot/PcbDraw/mistune/markdown.py b/kibot/PcbDraw/mistune/markdown.py new file mode 100644 index 00000000..b0a21dc8 --- /dev/null +++ b/kibot/PcbDraw/mistune/markdown.py @@ -0,0 +1,84 @@ +from .block_parser import BlockParser, expand_leading_tab, cleanup_lines +from .inline_parser import InlineParser + + +class Markdown(object): + def __init__(self, renderer, block=None, inline=None, plugins=None): + if block is None: + block = BlockParser() + + if inline is None: + inline = InlineParser(renderer) + + self.block = block + self.inline = inline + self.renderer = inline.renderer + self.before_parse_hooks = [] + self.before_render_hooks = [] + self.after_render_hooks = [] + + if plugins: + for plugin in plugins: + plugin(self) + + def use(self, plugin): + plugin(self) + + def before_parse(self, s, state): + s, state = preprocess(s, state) + for hook in self.before_parse_hooks: + s, state = hook(self, s, state) + return s, state + + def before_render(self, tokens, state): + for hook in self.before_render_hooks: + tokens = hook(self, tokens, state) + return tokens + + def after_render(self, result, state): + for hook in self.after_render_hooks: + result = hook(self, result, state) + return result + + def parse(self, s, state=None): + if state is None: + state = {} + + s, state = self.before_parse(s, state) + tokens = self.block.parse(s, state) + tokens = self.before_render(tokens, state) + result = self.block.render(tokens, self.inline, state) + result = self.after_render(result, state) + return result + + def read(self, filepath, state=None): + if state is None: + state = {} + + state['__file__'] = filepath + with open(filepath, 'rb') as f: + s = f.read() + + return self.parse(s.decode('utf-8'), state) + + def __call__(self, s): + return self.parse(s) + + +def preprocess(s, state): + state.update({ + 'def_links': {}, + 'def_footnotes': {}, + 'footnotes': [], + }) + + if s is None: + s = '\n' + else: + s = s.replace('\u2424', '\n') + s = cleanup_lines(s) + s = expand_leading_tab(s) + if not s.endswith('\n'): + s += '\n' + + return s, state diff --git a/kibot/PcbDraw/mistune/plugins/__init__.py b/kibot/PcbDraw/mistune/plugins/__init__.py new file mode 100644 index 00000000..5de4c0ac --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/__init__.py @@ -0,0 +1,25 @@ +from .abbr import plugin_abbr +from .def_list import plugin_def_list +from .extra import plugin_strikethrough, plugin_url +from .footnotes import plugin_footnotes +from .table import plugin_table +from .task_lists import plugin_task_lists + +PLUGINS = { + "url": plugin_url, + "strikethrough": plugin_strikethrough, + "footnotes": plugin_footnotes, + "table": plugin_table, + "task_lists": plugin_task_lists, + "def_list": plugin_def_list, + "abbr": plugin_abbr, +} + +__all__ = [ + "PLUGINS", + "plugin_url", + "plugin_strikethrough", + "plugin_footnotes", + "plugin_table", + "plugin_abbr", +] diff --git a/kibot/PcbDraw/mistune/plugins/abbr.py b/kibot/PcbDraw/mistune/plugins/abbr.py new file mode 100644 index 00000000..224598a1 --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/abbr.py @@ -0,0 +1,63 @@ +import re +from ..util import escape_html + + +DEF_ABBR = re.compile( + # *[HTML]: + # *[HTML]: Hyper Text Markup Language + # *[HTML]: + # Hyper Text Markup Language + r'\*\[([^\]]+)\]:' + r'((?:[ \t]*\n(?: {3,}|\t)[^\n]+)|(?:[^\n]*))\n*' +) + + +def parse_def_abbr(block, m, state): + def_abbrs = state.get('def_abbrs', {}) + label = m.group(1) + definition = m.group(2) + def_abbrs[label] = definition.strip() + state['def_abbrs'] = def_abbrs + + +def parse_inline_abbr(inline, m, state): + def_abbrs = state['def_abbrs'] + label = m.group(0) + return 'abbr', label, def_abbrs[label] + + +def after_parse_def_abbr(md, tokens, state): + def_abbrs = state.get('def_abbrs') + if def_abbrs: + labels = list(def_abbrs.keys()) + abbr_pattern = r'|'.join(re.escape(k) for k in labels) + md.inline.register_rule('abbr', abbr_pattern, parse_inline_abbr) + md.inline.rules.append('abbr') + return tokens + + +def render_html_abbr(key, definition): + title_attribute = "" + if definition: + definition = escape_html(definition) + title_attribute = ' title="{}"'.format(definition) + + return "{key}".format( + key=key, + title_attribute=title_attribute, + ) + + +def render_ast_abbr(key, definition): + return {'type': 'abbr', 'text': key, 'definition': definition} + + +def plugin_abbr(md): + md.block.register_rule('def_abbr', DEF_ABBR, parse_def_abbr) + md.before_render_hooks.append(after_parse_def_abbr) + md.block.rules.append('def_abbr') + + if md.renderer.NAME == 'html': + md.renderer.register('abbr', render_html_abbr) + elif md.renderer.NAME == 'ast': + md.renderer.register('abbr', render_ast_abbr) diff --git a/kibot/PcbDraw/mistune/plugins/def_list.py b/kibot/PcbDraw/mistune/plugins/def_list.py new file mode 100644 index 00000000..9d5e7b14 --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/def_list.py @@ -0,0 +1,54 @@ +import re + +__all__ = ["plugin_def_list"] + +DEFINITION_LIST_PATTERN = re.compile(r"([^\n]+\n(:[ \t][^\n]+\n)+\n?)+") + + +def parse_def_list(block, m, state): + lines = m.group(0).split("\n") + definition_list_items = [] + for line in lines: + if not line: + continue + if line.strip()[0] == ":": + definition_list_items.append( + {"type": "def_list_item", "text": line[1:].strip()} + ) + else: + definition_list_items.append( + {"type": "def_list_header", "text": line.strip()} + ) + return {"type": "def_list", "children": definition_list_items} + + +def render_html_def_list(text): + return "
\n" + text + "
\n" + + +def render_html_def_list_header(text): + return "
" + text + "
\n" + + +def render_html_def_list_item(text): + return "
" + text + "
\n" + + +def render_ast_def_list_header(text): + return {"type": "def_list_header", "text": text[0]["text"]} + + +def render_ast_def_list_item(text): + return {"type": "def_list_item", "text": text[0]["text"]} + + +def plugin_def_list(md): + md.block.register_rule("def_list", DEFINITION_LIST_PATTERN, parse_def_list) + md.block.rules.append("def_list") + if md.renderer.NAME == "html": + md.renderer.register("def_list", render_html_def_list) + md.renderer.register("def_list_header", render_html_def_list_header) + md.renderer.register("def_list_item", render_html_def_list_item) + if md.renderer.NAME == "ast": + md.renderer.register("def_list_header", render_ast_def_list_header) + md.renderer.register("def_list_item", render_ast_def_list_item) diff --git a/kibot/PcbDraw/mistune/plugins/extra.py b/kibot/PcbDraw/mistune/plugins/extra.py new file mode 100644 index 00000000..34838334 --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/extra.py @@ -0,0 +1,50 @@ +from ..util import escape_url, ESCAPE_TEXT + +__all__ = ['plugin_url', 'plugin_strikethrough'] + + +#: url link like: ``https://lepture.com/`` +URL_LINK_PATTERN = r'''(https?:\/\/[^\s<]+[^<.,:;"')\]\s])''' + + +def parse_url_link(inline, m, state): + url = m.group(0) + if state.get('_in_link'): + return 'text', url + return 'link', escape_url(url) + + +def plugin_url(md): + md.inline.register_rule('url_link', URL_LINK_PATTERN, parse_url_link) + md.inline.rules.append('url_link') + + +#: strike through syntax looks like: ``~~word~~`` +STRIKETHROUGH_PATTERN = ( + r'~~(?=[^\s~])(' + r'(?:\\~|[^~])*' + r'(?:' + ESCAPE_TEXT + r'|[^\s~]))~~' +) + + +def parse_strikethrough(inline, m, state): + text = m.group(1) + return 'strikethrough', inline.render(text, state) + + +def render_html_strikethrough(text): + return '' + text + '' + + +def plugin_strikethrough(md): + md.inline.register_rule( + 'strikethrough', STRIKETHROUGH_PATTERN, parse_strikethrough) + + index = md.inline.rules.index('codespan') + if index != -1: + md.inline.rules.insert(index + 1, 'strikethrough') + else: # pragma: no cover + md.inline.rules.append('strikethrough') + + if md.renderer.NAME == 'html': + md.renderer.register('strikethrough', render_html_strikethrough) diff --git a/kibot/PcbDraw/mistune/plugins/footnotes.py b/kibot/PcbDraw/mistune/plugins/footnotes.py new file mode 100644 index 00000000..1c3d0530 --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/footnotes.py @@ -0,0 +1,149 @@ +import re +from ..inline_parser import LINK_LABEL +from ..util import unikey + +__all__ = ['plugin_footnotes'] + +#: inline footnote syntax looks like:: +#: +#: [^key] +INLINE_FOOTNOTE_PATTERN = r'\[\^(' + LINK_LABEL + r')\]' + +#: define a footnote item like:: +#: +#: [^key]: paragraph text to describe the note +DEF_FOOTNOTE = re.compile( + r'( {0,3})\[\^(' + LINK_LABEL + r')\]:[ \t]*(' + r'[^\n]*\n+' + r'(?:\1 {1,3}(?! )[^\n]*\n+)*' + r')' +) + + +def parse_inline_footnote(inline, m, state): + key = unikey(m.group(1)) + def_footnotes = state.get('def_footnotes') + if not def_footnotes or key not in def_footnotes: + return 'text', m.group(0) + + index = state.get('footnote_index', 0) + index += 1 + state['footnote_index'] = index + state['footnotes'].append(key) + return 'footnote_ref', key, index + + +def parse_def_footnote(block, m, state): + key = unikey(m.group(2)) + if key not in state['def_footnotes']: + state['def_footnotes'][key] = m.group(3) + + +def parse_footnote_item(block, k, i, state): + def_footnotes = state['def_footnotes'] + text = def_footnotes[k] + + stripped_text = text.strip() + if '\n' not in stripped_text: + children = [{'type': 'paragraph', 'text': stripped_text}] + else: + lines = text.splitlines() + for second_line in lines[1:]: + if second_line: + break + + spaces = len(second_line) - len(second_line.lstrip()) + pattern = re.compile(r'^ {' + str(spaces) + r',}', flags=re.M) + text = pattern.sub('', text) + children = block.parse_text(text, state) + if not isinstance(children, list): + children = [children] + + return { + 'type': 'footnote_item', + 'children': children, + 'params': (k, i) + } + + +def md_footnotes_hook(md, result, state): + footnotes = state.get('footnotes') + if not footnotes: + return result + + children = [ + parse_footnote_item(md.block, k, i + 1, state) + for i, k in enumerate(footnotes) + ] + tokens = [{'type': 'footnotes', 'children': children}] + output = md.block.render(tokens, md.inline, state) + return result + output + + +def render_ast_footnote_ref(key, index): + return {'type': 'footnote_ref', 'key': key, 'index': index} + + +def render_ast_footnote_item(children, key, index): + return { + 'type': 'footnote_item', + 'children': children, + 'key': key, + 'index': index, + } + + +def render_html_footnote_ref(key, index): + i = str(index) + html = '' + return html + '
' + i + '' + + +def render_html_footnotes(text): + return ( + '
\n
    \n' + + text + + '
\n
\n' + ) + + +def render_html_footnote_item(text, key, index): + i = str(index) + back = '' + + text = text.rstrip() + if text.endswith('

'): + text = text[:-4] + back + '

' + else: + text = text + back + return '
  • ' + text + '
  • \n' + + +def plugin_footnotes(md): + md.inline.register_rule( + 'footnote', + INLINE_FOOTNOTE_PATTERN, + parse_inline_footnote + ) + index = md.inline.rules.index('std_link') + if index != -1: + md.inline.rules.insert(index, 'footnote') + else: + md.inline.rules.append('footnote') + + md.block.register_rule('def_footnote', DEF_FOOTNOTE, parse_def_footnote) + index = md.block.rules.index('def_link') + if index != -1: + md.block.rules.insert(index, 'def_footnote') + else: + md.block.rules.append('def_footnote') + + if md.renderer.NAME == 'html': + md.renderer.register('footnote_ref', render_html_footnote_ref) + md.renderer.register('footnote_item', render_html_footnote_item) + md.renderer.register('footnotes', render_html_footnotes) + elif md.renderer.NAME == 'ast': + md.renderer.register('footnote_ref', render_ast_footnote_ref) + md.renderer.register('footnote_item', render_ast_footnote_item) + + md.after_render_hooks.append(md_footnotes_hook) diff --git a/kibot/PcbDraw/mistune/plugins/table.py b/kibot/PcbDraw/mistune/plugins/table.py new file mode 100644 index 00000000..f8dbce72 --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/table.py @@ -0,0 +1,162 @@ +import re + +__all__ = ['plugin_table'] + +TABLE_PATTERN = re.compile( + r' {0,3}\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*' +) +NP_TABLE_PATTERN = re.compile( + r' {0,3}(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*' +) +HEADER_SUB = re.compile(r'\| *$') +HEADER_SPLIT = re.compile(r' *\| *') +ALIGN_SPLIT = re.compile(r' *\| *') + + +def parse_table(self, m, state): + header = HEADER_SUB.sub('', m.group(1)).strip() + align = HEADER_SUB.sub('', m.group(2)) + thead, aligns = _process_table(header, align) + + text = re.sub(r'(?: *\| *)?\n$', '', m.group(3)) + rows = [] + for i, v in enumerate(text.split('\n')): + v = re.sub(r'^ *\| *| *\| *$', '', v) + rows.append(_process_row(v, aligns)) + + children = [thead, {'type': 'table_body', 'children': rows}] + return {'type': 'table', 'children': children} + + +def parse_nptable(self, m, state): + thead, aligns = _process_table(m.group(1), m.group(2)) + + text = re.sub(r'\n$', '', m.group(3)) + rows = [] + for i, v in enumerate(text.split('\n')): + rows.append(_process_row(v, aligns)) + + children = [thead, {'type': 'table_body', 'children': rows}] + return {'type': 'table', 'children': children} + + +def _process_table(header, align): + headers = HEADER_SPLIT.split(header) + aligns = ALIGN_SPLIT.split(align) + + if header.endswith('|'): + headers.append('') + + cells = [] + for i, v in enumerate(aligns): + if re.search(r'^ *-+: *$', v): + aligns[i] = 'right' + elif re.search(r'^ *:-+: *$', v): + aligns[i] = 'center' + elif re.search(r'^ *:-+ *$', v): + aligns[i] = 'left' + else: + aligns[i] = None + + if len(headers) > i: + cells.append({ + 'type': 'table_cell', + 'text': headers[i], + 'params': (aligns[i], True) + }) + + i += 1 + while i + 1 < len(headers): + cells.append({ + 'type': 'table_cell', + 'text': headers[i], + 'params': (None, True) + }) + aligns.append(None) + i += 1 + + thead = {'type': 'table_head', 'children': cells} + return thead, aligns + + +def _process_row(row, aligns): + cells = [] + for i, s in enumerate(re.split(r' *(?\n' + text + '\n' + + +def render_html_table_head(text): + return '\n\n' + text + '\n\n' + + +def render_html_table_body(text): + return '\n' + text + '\n' + + +def render_html_table_row(text): + return '\n' + text + '\n' + + +def render_html_table_cell(text, align=None, is_head=False): + if is_head: + tag = 'th' + else: + tag = 'td' + + html = ' <' + tag + if align: + html += ' style="text-align:' + align + '"' + + return html + '>' + text + '\n' + + +def render_ast_table_cell(children, align=None, is_head=False): + return { + 'type': 'table_cell', + 'children': children, + 'align': align, + 'is_head': is_head + } + + +def plugin_table(md): + md.block.register_rule('table', TABLE_PATTERN, parse_table) + md.block.register_rule('nptable', NP_TABLE_PATTERN, parse_nptable) + md.block.rules.append('table') + md.block.rules.append('nptable') + + if md.renderer.NAME == 'html': + md.renderer.register('table', render_html_table) + md.renderer.register('table_head', render_html_table_head) + md.renderer.register('table_body', render_html_table_body) + md.renderer.register('table_row', render_html_table_row) + md.renderer.register('table_cell', render_html_table_cell) + + elif md.renderer.NAME == 'ast': + md.renderer.register('table_cell', render_ast_table_cell) diff --git a/kibot/PcbDraw/mistune/plugins/task_lists.py b/kibot/PcbDraw/mistune/plugins/task_lists.py new file mode 100644 index 00000000..3094ea72 --- /dev/null +++ b/kibot/PcbDraw/mistune/plugins/task_lists.py @@ -0,0 +1,75 @@ +import re + +__all__ = ['plugin_task_lists'] + + +TASK_LIST_ITEM = re.compile(r'^(\[[ xX]\])\s+') + + +def task_lists_hook(md, tokens, state): + return _rewrite_all_list_items(tokens) + + +def render_ast_task_list_item(children, level, checked): + return { + 'type': 'task_list_item', + 'children': children, + 'level': level, + 'checked': checked, + } + + +def render_html_task_list_item(text, level, checked): + checkbox = ( + ''): + text = text.replace('

    ', '

    ' + checkbox, 1) + else: + text = checkbox + text + + return '

  • ' + text + '
  • \n' + + +def plugin_task_lists(md): + md.before_render_hooks.append(task_lists_hook) + + if md.renderer.NAME == 'html': + md.renderer.register('task_list_item', render_html_task_list_item) + elif md.renderer.NAME == 'ast': + md.renderer.register('task_list_item', render_ast_task_list_item) + + +def _rewrite_all_list_items(tokens): + for tok in tokens: + if tok['type'] == 'list_item': + _rewrite_list_item(tok) + if 'children' in tok.keys(): + _rewrite_all_list_items(tok['children']) + return tokens + + +def _rewrite_list_item(item): + children = item['children'] + if children: + first_child = children[0] + text = first_child.get('text', '') + m = TASK_LIST_ITEM.match(text) + if m: + mark = m.group(1) + first_child['text'] = text[m.end():] + + params = item['params'] + if mark == '[ ]': + params = (params[0], False) + else: + params = (params[0], True) + + item['type'] = 'task_list_item' + item['params'] = params diff --git a/kibot/PcbDraw/mistune/renderers.py b/kibot/PcbDraw/mistune/renderers.py new file mode 100644 index 00000000..18d3565a --- /dev/null +++ b/kibot/PcbDraw/mistune/renderers.py @@ -0,0 +1,220 @@ +from .util import escape, escape_html + + +class BaseRenderer(object): + NAME = 'base' + + def __init__(self): + self._methods = {} + + def register(self, name, method): + self._methods[name] = method + + def _get_method(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + method = self._methods.get(name) + if not method: + raise AttributeError('No renderer "{!r}"'.format(name)) + return method + + def finalize(self, data): + raise NotImplementedError( + 'The renderer needs to implement the finalize method.') + + +class AstRenderer(BaseRenderer): + NAME = 'ast' + + def text(self, text): + return {'type': 'text', 'text': text} + + def link(self, link, children=None, title=None): + if isinstance(children, str): + children = [{'type': 'text', 'text': children}] + return { + 'type': 'link', + 'link': link, + 'children': children, + 'title': title, + } + + def image(self, src, alt="", title=None): + return {'type': 'image', 'src': src, 'alt': alt, 'title': title} + + def codespan(self, text): + return {'type': 'codespan', 'text': text} + + def linebreak(self): + return {'type': 'linebreak'} + + def inline_html(self, html): + return {'type': 'inline_html', 'text': html} + + def heading(self, children, level): + return {'type': 'heading', 'children': children, 'level': level} + + def newline(self): + return {'type': 'newline'} + + def thematic_break(self): + return {'type': 'thematic_break'} + + def block_code(self, children, info=None): + return { + 'type': 'block_code', + 'text': children, + 'info': info + } + + def block_html(self, children): + return {'type': 'block_html', 'text': children} + + def list(self, children, ordered, level, start=None): + token = { + 'type': 'list', + 'children': children, + 'ordered': ordered, + 'level': level, + } + if start is not None: + token['start'] = start + return token + + def list_item(self, children, level): + return {'type': 'list_item', 'children': children, 'level': level} + + def _create_default_method(self, name): + def __ast(children): + return {'type': name, 'children': children} + return __ast + + def _get_method(self, name): + try: + return super(AstRenderer, self)._get_method(name) + except AttributeError: + return self._create_default_method(name) + + def finalize(self, data): + return list(data) + + +class HTMLRenderer(BaseRenderer): + NAME = 'html' + HARMFUL_PROTOCOLS = { + 'javascript:', + 'vbscript:', + 'data:', + } + + def __init__(self, escape=True, allow_harmful_protocols=None): + super(HTMLRenderer, self).__init__() + self._escape = escape + self._allow_harmful_protocols = allow_harmful_protocols + + def _safe_url(self, url): + if self._allow_harmful_protocols is None: + schemes = self.HARMFUL_PROTOCOLS + elif self._allow_harmful_protocols is True: + schemes = None + else: + allowed = set(self._allow_harmful_protocols) + schemes = self.HARMFUL_PROTOCOLS - allowed + + if schemes: + for s in schemes: + if url.lower().startswith(s): + url = '#harmful-link' + break + return url + + def text(self, text): + if self._escape: + return escape(text) + return escape_html(text) + + def link(self, link, text=None, title=None): + if text is None: + text = link + + s = '' + (text or link) + '' + + def image(self, src, alt="", title=None): + src = self._safe_url(src) + alt = escape_html(alt) + s = '' + alt + '' + + def emphasis(self, text): + return '' + text + '' + + def strong(self, text): + return '' + text + '' + + def codespan(self, text): + return '' + escape(text) + '' + + def linebreak(self): + return '
    \n' + + def inline_html(self, html): + if self._escape: + return escape(html) + return html + + def paragraph(self, text): + return '

    ' + text + '

    \n' + + def heading(self, text, level): + tag = 'h' + str(level) + return '<' + tag + '>' + text + '\n' + + def newline(self): + return '' + + def thematic_break(self): + return '
    \n' + + def block_text(self, text): + return text + + def block_code(self, code, info=None): + html = '
    ' + escape(code) + '
    \n' + + def block_quote(self, text): + return '
    \n' + text + '
    \n' + + def block_html(self, html): + if not self._escape: + return html + '\n' + return '

    ' + escape(html) + '

    \n' + + def block_error(self, html): + return '
    ' + html + '
    \n' + + def list(self, text, ordered, level, start=None): + if ordered: + html = '\n' + text + '\n' + return '
      \n' + text + '
    \n' + + def list_item(self, text, level): + return '
  • ' + text + '
  • \n' + + def finalize(self, data): + return ''.join(data) diff --git a/kibot/PcbDraw/mistune/scanner.py b/kibot/PcbDraw/mistune/scanner.py new file mode 100644 index 00000000..8237b620 --- /dev/null +++ b/kibot/PcbDraw/mistune/scanner.py @@ -0,0 +1,121 @@ +import re + +class Scanner(re.Scanner): + def iter(self, string, state, parse_text): + sc = self.scanner.scanner(string) + + pos = 0 + for match in iter(sc.search, None): + name, method = self.lexicon[match.lastindex - 1][1] + hole = string[pos:match.start()] + if hole: + yield parse_text(hole, state) + + yield method(match, state) + pos = match.end() + + hole = string[pos:] + if hole: + yield parse_text(hole, state) + + +class ScannerParser(object): + scanner_cls = Scanner + RULE_NAMES = tuple() + + def __init__(self): + self.rules = list(self.RULE_NAMES) + self.rule_methods = {} + self._cached_sc = {} + + def register_rule(self, name, pattern, method): + self.rule_methods[name] = (pattern, lambda m, state: method(self, m, state)) + + def get_rule_pattern(self, name): + if name not in self.RULE_NAMES: + return self.rule_methods[name][0] + return getattr(self, name.upper()) + + def get_rule_method(self, name): + if name not in self.RULE_NAMES: + return self.rule_methods[name][1] + return getattr(self, 'parse_' + name) + + def parse_text(self, text, state): + raise NotImplementedError + + def _scan(self, s, state, rules): + sc = self._create_scanner(rules) + for tok in sc.iter(s, state, self.parse_text): + if isinstance(tok, list): + for t in tok: + yield t + elif tok: + yield tok + + def _create_scanner(self, rules): + sc_key = '|'.join(rules) + sc = self._cached_sc.get(sc_key) + if sc: + return sc + + lexicon = [ + (self.get_rule_pattern(n), (n, self.get_rule_method(n))) + for n in rules + ] + sc = self.scanner_cls(lexicon) + self._cached_sc[sc_key] = sc + return sc + + +class Matcher(object): + PARAGRAPH_END = re.compile( + r'(?:\n{2,})|' + r'(?:\n {0,3}#{1,6})|' # axt heading + r'(?:\n {0,3}(?:`{3,}|~{3,}))|' # fenced code + r'(?:\n {0,3}>)|' # blockquote + r'(?:\n {0,3}(?:[\*\+-]|1[.)]))|' # list + r'(?:\n {0,3}<)' # block html + ) + + def __init__(self, lexicon): + self.lexicon = lexicon + + def search_pos(self, string, pos): + m = self.PARAGRAPH_END.search(string, pos) + if not m: + return None + if set(m.group(0)) == {'\n'}: + return m.end() + return m.start() + 1 + + def iter(self, string, state, parse_text): + pos = 0 + endpos = len(string) + last_end = 0 + while 1: + if pos >= endpos: + break + for rule, (name, method) in self.lexicon: + match = rule.match(string, pos) + if match is not None: + start, end = match.span() + if start > last_end: + yield parse_text(string[last_end:start], state) + + if name.endswith('_start'): + token = method(match, state, string) + yield token[0] + end = token[1] + else: + yield method(match, state) + last_end = pos = end + break + else: + found = self.search_pos(string, pos) + if found is None: + break + pos = found + + if last_end < endpos: + yield parse_text(string[last_end:], state) diff --git a/kibot/PcbDraw/mistune/util.py b/kibot/PcbDraw/mistune/util.py new file mode 100644 index 00000000..f99fe37e --- /dev/null +++ b/kibot/PcbDraw/mistune/util.py @@ -0,0 +1,41 @@ +try: + from urllib.parse import quote + import html +except ImportError: + from urllib import quote + html = None + + +PUNCTUATION = r'''\\!"#$%&'()*+,./:;<=>?@\[\]^`{}|_~-''' +ESCAPE_TEXT = r'\\[' + PUNCTUATION + ']' + + +def escape(s, quote=True): + s = s.replace("&", "&") + s = s.replace("<", "<") + s = s.replace(">", ">") + if quote: + s = s.replace('"', """) + return s + + +def escape_url(link): + safe = ( + ':/?#@' # gen-delims - '[]' (rfc3986) + '!$&()*+,;=' # sub-delims - "'" (rfc3986) + '%' # leave already-encoded octets alone + ) + + if html is None: + return quote(link.encode('utf-8'), safe=safe) + return html.escape(quote(html.unescape(link), safe=safe)) + + +def escape_html(s): + if html is not None: + return html.escape(html.unescape(s)).replace(''', "'") + return escape(s) + + +def unikey(s): + return ' '.join(s.split()).lower() diff --git a/kibot/PcbDraw/populate.py b/kibot/PcbDraw/populate.py index 77e29e18..1956e9c6 100644 --- a/kibot/PcbDraw/populate.py +++ b/kibot/PcbDraw/populate.py @@ -10,11 +10,11 @@ from copy import deepcopy from itertools import chain from typing import List, Optional, Any, Tuple, Dict -import mistune # type: ignore +from . import mistune # type: ignore # The following try-catch is used to support mistune 0.8.4 and 2.x try: - from mistune.plugins.table import plugin_table # type: ignore - from mistune.plugins.footnotes import plugin_footnotes # type: ignore + from .mistune.plugins.table import plugin_table # type: ignore + from .mistune.plugins.footnotes import plugin_footnotes # type: ignore InlineParser = mistune.inline_parser.InlineParser HTMLRenderer = mistune.renderers.HTMLRenderer except ModuleNotFoundError: diff --git a/kibot/out_populate.py b/kibot/out_populate.py index 62774961..8e263281 100644 --- a/kibot/out_populate.py +++ b/kibot/out_populate.py @@ -3,14 +3,15 @@ # Copyright (c) 2022-2023 Instituto Nacional de TecnologĂ­a Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) -""" -Dependencies: - - name: mistune - python_module: true - debian: python3-mistune - arch: python-mistune - role: mandatory -""" +# No longer a dependency, now included. +# Will be fixed when the code supports mistune 3 ... or never +# Dependencies: +# - name: mistune +# python_module: true +# debian: python3-mistune +# arch: python-mistune +# role: mandatory +# """ import os from tempfile import NamedTemporaryFile # Here we import the whole module to make monkeypatch work diff --git a/src/kibot-check b/src/kibot-check index 28cd8d4d..2f52c351 100755 --- a/src/kibot-check +++ b/src/kibot-check @@ -1303,40 +1303,6 @@ deps = '{\ "url": null,\ "url_down": null\ },\ - "mistune": {\ - "arch": "python-mistune",\ - "command": "mistune",\ - "comments": [],\ - "deb_package": "python3-mistune",\ - "downloader": null,\ - "downloader_str": null,\ - "extra_arch": null,\ - "extra_deb": null,\ - "help_option": "--version",\ - "importance": 10000,\ - "in_debian": true,\ - "is_kicad_plugin": false,\ - "is_python": true,\ - "module_name": "mistune",\ - "name": "mistune",\ - "no_cmd_line_version": false,\ - "no_cmd_line_version_old": false,\ - "output": "populate",\ - "plugin_dirs": null,\ - "pypi_name": "mistune",\ - "role": [\ - {\ - "desc": null,\ - "mandatory": true,\ - "max_version": null,\ - "output": "populate",\ - "version": null\ - }\ - ],\ - "tests": [],\ - "url": null,\ - "url_down": null\ - },\ "numpy": {\ "arch": "python-numpy",\ "command": "numpy",\