From f15d9550671d82861a99b66f04e5f15197bbb32d Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Tue, 29 Mar 2022 22:07:24 -0500 Subject: [PATCH] initial crack at a block-level figure parser I didn't like the other figure + figcaption parsers, they either assumed a lot about usage (e.g. images only), or they were inline parsers that either wrapped the figure in a paragraph tag (which is incorrect syntax) or did span trickery (annoying) so, this handles images and maybe other things, and does things properly with figures as their own blocks. incomplete but it works with my images, and should allow for looping (for multi-line content) in the future? --- incorporealcms/config.py | 2 +- incorporealcms/mdx/figures.py | 60 ++++++++++++++++++++++++++++++ tests/functional_markdown_tests.py | 13 +++++++ tests/instance/pages/figures.md | 13 +++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 incorporealcms/mdx/figures.py create mode 100644 tests/instance/pages/figures.md diff --git a/incorporealcms/config.py b/incorporealcms/config.py index cb443d7..5ed4258 100644 --- a/incorporealcms/config.py +++ b/incorporealcms/config.py @@ -32,7 +32,7 @@ class Config(object): }, } - MARKDOWN_EXTENSIONS = ['extra', 'sane_lists', 'smarty', 'toc'] + MARKDOWN_EXTENSIONS = ['extra', 'incorporealcms.mdx.figures', 'sane_lists', 'smarty', 'toc'] MARKDOWN_EXTENSION_CONFIGS = { 'extra': { 'footnotes': { diff --git a/incorporealcms/mdx/figures.py b/incorporealcms/mdx/figures.py new file mode 100644 index 0000000..8613a4c --- /dev/null +++ b/incorporealcms/mdx/figures.py @@ -0,0 +1,60 @@ +"""Create generic figures with captions.""" +import re +from xml.etree.ElementTree import SubElement + +import markdown + + +class FigureExtension(markdown.Extension): + """Wrap the markdown prepcoressor.""" + + def extendMarkdown(self, md): + """Add FigureBlockProcessor to the Markdown instance.""" + md.parser.blockprocessors.register(FigureBlockProcessor(md.parser), 'figure', 100) + + +class FigureBlockProcessor(markdown.blockprocessors.BlockProcessor): + """Process figures.""" + + # |> thing to put in the figure + # |: optional caption for the figure + # optional whatever else, like maybe an attr_list + figure_regex = re.compile(r'^[ ]{0,3}\|>[ ]{0,3}(?P[^\n]*)') + caption_regex = re.compile(r'^[ ]{0,3}\|:[ ]{0,3}(?P[^\n]*)') + + def test(self, parent, block): + """Determine if we should process this block.""" + lines = block.split('\n') + return bool(self.figure_regex.search(lines[0])) + + def run(self, parent, blocks): + """Replace the top block with HTML.""" + block = blocks.pop(0) + lines = block.split('\n') + + # consume line and create a figure + figure_match = self.figure_regex.search(lines[0]) + lines.pop(0) + content = figure_match.group('content') + figure = SubElement(parent, 'figure') + figure.text = content + if lines: + if caption_match := self.caption_regex.search(lines[0]): + # consume line and add the caption as a child of the figure + lines.pop(0) + caption = caption_match.group('caption') + figcaption = SubElement(figure, 'figcaption') + figcaption.text = caption + if lines: + # other lines are mysteries, might be attr_list, so re-append + # make sure there's a child to hang the rest (which is maybe an attr_list?) off of + # this is probably a bad hack + if not len(list(figure)): + SubElement(figure, 'span') + rest = '\n'.join(lines) + figure[-1].tail = f'\n{rest}' + + +def makeExtension(*args, **kwargs): + """Provide the extension to the markdown extension loader.""" + return FigureExtension(*args, **kwargs) diff --git a/tests/functional_markdown_tests.py b/tests/functional_markdown_tests.py index 2b7a65b..386730f 100644 --- a/tests/functional_markdown_tests.py +++ b/tests/functional_markdown_tests.py @@ -37,3 +37,16 @@ def test_invalid_graphviz_is_not_rendered(): response = client.get('/test-invalid-graphviz') assert response.status_code == 500 assert b'INTERNAL SERVER ERROR' in response.data + + +def test_figures_are_rendered(client): + """Test that a page with my figure syntax renders as expected.""" + response = client.get('/figures') + assert response.status_code == 200 + assert (b'
fancy captioned logo' + b'
this is my cool logo!
') in response.data + assert (b'
vanilla captioned logo' + b'
this is my cool logo without an attr!
\n
') in response.data + assert (b'
fancy logo' + b'
') in response.data + assert b'
just a logo
' in response.data diff --git a/tests/instance/pages/figures.md b/tests/instance/pages/figures.md new file mode 100644 index 0000000..e38f732 --- /dev/null +++ b/tests/instance/pages/figures.md @@ -0,0 +1,13 @@ +# test of figures + +|> ![fancy captioned logo](bss-square-no-bg.png) +|: this is my cool logo! +{: .right } + +|> ![vanilla captioned logo](bss-square-no-bg.png) +|: this is my cool logo without an attr! + +|> ![fancy logo](bss-square-no-bg.png) +{: .left } + +|> ![just a logo](bss-square-no-bg.png)