test bkp
This commit is contained in:
1 parent
6ccab0347b
commit
954a2fd6d6
18684 files changed
+601012
-190
No files matched your search
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Extension for "enhanced" blockquotes.
|
||||
|
||||
This extension deviates from Python Markdown's original blockquote extension by:
|
||||
|
||||
- not grouping consecutive block quotes together.
|
||||
- Allowing optional callout behavior that mimics GitHub or Obsidian.
|
||||
"""
|
||||
import re
|
||||
import xml.etree.ElementTree as etree
|
||||
from markdown.blockprocessors import BlockProcessor
|
||||
from markdown.treeprocessors import Treeprocessor
|
||||
from markdown import util
|
||||
from markdown import Extension, Markdown
|
||||
from markdown.blockparser import BlockParser
|
||||
from typing import Any
|
||||
|
||||
|
||||
class QuotesProcessor(BlockProcessor):
|
||||
"""Process blockquotes."""
|
||||
|
||||
RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')
|
||||
RE_CALLOUT = re.compile(r'> *\[!([\w-]+(?: *\| *[\w-]+)*)]([-+])?(.*?)(?:\n|$)')
|
||||
|
||||
def __init__(self, parser: BlockParser, config: dict[str, Any]) -> None:
|
||||
"""Initialize."""
|
||||
|
||||
super().__init__(parser)
|
||||
self.callouts = config['callouts']
|
||||
|
||||
def test(self, parent: etree.Element, block: str) -> bool:
|
||||
"""Test for block quote."""
|
||||
|
||||
return bool(self.RE.search(block)) and not util.nearing_recursion_limit()
|
||||
|
||||
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
||||
"""Create blockquote."""
|
||||
|
||||
block = blocks.pop(0)
|
||||
alert = []
|
||||
details = ''
|
||||
m = self.RE.search(block)
|
||||
if m:
|
||||
before = block[:m.start()] # Lines before blockquote
|
||||
# Pass lines before blockquote in recursively for parsing first.
|
||||
self.parser.parseBlocks(parent, [before])
|
||||
# Remove `> ` from beginning of each line.
|
||||
lines = block[m.start():].split('\n')
|
||||
if lines and self.callouts:
|
||||
m2 = None
|
||||
index = 0
|
||||
for line in lines:
|
||||
if line and line.strip() != '>':
|
||||
m2 = self.RE_CALLOUT.match(line)
|
||||
break
|
||||
index += 1
|
||||
if m2:
|
||||
alert = [x.strip() for x in m2.group(1).split('|')]
|
||||
if m2.group(2):
|
||||
details = 'open' if m2.group(2) == '+' else 'closed'
|
||||
title = m2.group(3).strip() if m2.group(3) else ''
|
||||
if not title:
|
||||
title = alert[0].title()
|
||||
lines[index] = ''
|
||||
lines.insert(index, title)
|
||||
if alert:
|
||||
alert[0] = alert[0].lower()
|
||||
block = '\n'.join([self.clean(l) for l in lines])
|
||||
|
||||
# This is a new blockquote. Create a new parent element.
|
||||
attrs = {'data-alert': ' '.join(alert), 'data-alert-collapse': details} if alert else {}
|
||||
quote = etree.SubElement(parent, 'blockquote', attrs)
|
||||
|
||||
# Recursively parse block with blockquote as parent.
|
||||
# change parser state so blockquotes embedded in lists use `p` tags
|
||||
self.parser.state.set('blockquote')
|
||||
self.parser.parseChunk(quote, block)
|
||||
self.parser.state.reset()
|
||||
|
||||
def clean(self, line: str) -> str:
|
||||
"""Remove `>` from beginning of a line."""
|
||||
|
||||
m = self.RE.match(line)
|
||||
if line.strip() == ">":
|
||||
return ""
|
||||
elif m:
|
||||
return m.group(2)
|
||||
else:
|
||||
return line
|
||||
|
||||
|
||||
class QuotesTreeprocessor(Treeprocessor):
|
||||
"""Convert "special" quotes to the common output format for Admonitions and Details."""
|
||||
|
||||
def run(self, root: etree.Element) -> etree.Element:
|
||||
"""Find and convert "special" blockquotes."""
|
||||
|
||||
for b in root.iter('blockquote'):
|
||||
if b.attrib.get('data-alert'):
|
||||
collapse = b.attrib.get('data-alert-collapse', '')
|
||||
if collapse:
|
||||
b.tag = 'details'
|
||||
child = b.find('*')
|
||||
if collapse == 'open':
|
||||
b.attrib['open'] = 'open'
|
||||
c = b.attrib.get('class', '').split(' ')
|
||||
if child is not None and child.tag.lower() == 'p':
|
||||
child.tag = 'summary'
|
||||
else:
|
||||
b.tag = 'div'
|
||||
child = b.find('*')
|
||||
c = b.attrib.get('class', '').split(' ')
|
||||
c.append('admonition')
|
||||
if child is not None and child.tag.lower() == 'p':
|
||||
c2 = child.attrib.get('class', '').split(' ')
|
||||
c2.append('admonition-title')
|
||||
child.attrib['class'] = ' '.join(_c for _c in c2 if _c)
|
||||
c.append(b.attrib.get('data-alert', ''))
|
||||
b.attrib['class'] = ' '.join(_c for _c in c if _c)
|
||||
del b.attrib['data-alert']
|
||||
del b.attrib['data-alert-collapse']
|
||||
return root
|
||||
|
||||
|
||||
class QuotesExtension(Extension):
|
||||
"""Add blockquotes extension to Markdown class."""
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Initialize."""
|
||||
|
||||
self.config = {
|
||||
'callouts': [False, "Enable GitHub/Obsidian style callouts - Default: False"]
|
||||
}
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def extendMarkdown(self, md: Markdown) -> None:
|
||||
"""Add support for blockquotes."""
|
||||
|
||||
md.registerExtension(self)
|
||||
config = self.getConfigs()
|
||||
md.parser.blockprocessors.register(QuotesProcessor(md.parser, config), "quote", 20)
|
||||
if config['callouts']:
|
||||
md.treeprocessors.register(QuotesTreeprocessor(md), 'quotes', 19.99)
|
||||
|
||||
|
||||
def makeExtension(*args: Any, **kwargs: Any) -> Extension:
|
||||
"""Return extension."""
|
||||
|
||||
return QuotesExtension(*args, **kwargs)
|
||||
Reference in new issue
Block a user