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)