Implement common markdown blocks infrastructure

This commit is contained in:
Philip James Elson 2021-05-03 11:33:17 +00:00 committed by Gabriella Azzopardi
parent bef6602cc1
commit 49c28aaa98
7 changed files with 119 additions and 9 deletions

View file

@ -12,6 +12,7 @@ import zlib
import jinja2
from tornado.web import Application, RequestHandler, StaticFileHandler
from . import markdown_tools
from . import model_generator
from .report_generator import ReportGenerator
from .user import AuthenticatedUser, AnonymousUser
@ -183,6 +184,10 @@ def make_app(
loader=loader,
)
template_environment.globals['common_text'] = markdown_tools.extract_rendered_markdown_blocks(
template_environment.get_template('common_text.md.j2')
)
return Application(
urls,
debug=debug,

View file

@ -6,7 +6,7 @@ from tornado.ioloop import IOLoop
from . import make_app
def configure_parser(parser):
def configure_parser(parser) -> argparse.ArgumentParser:
parser.add_argument(
"--no-debug", help="Don't enable debug mode",
action="store_false",
@ -20,8 +20,7 @@ def configure_parser(parser):
def main():
parser = argparse.ArgumentParser()
configure_parser(parser)
parser = configure_parser(argparse.ArgumentParser())
args = parser.parse_args()
theme_dir = args.theme
if theme_dir is not None:

View file

@ -0,0 +1,73 @@
import re
import jinja2
import mistune
HEADER_PATTERN = re.compile(r'\n(#+)(.*)')
def _block_headings(contents: str):
"""
Return the headings (and the start/end positions of their blocks) of
markdown, in reverse order.
Note that a block ends when the next heading is found, even if that heading
is a sub-block of the current one.
"""
all_block_headings = HEADER_PATTERN.finditer(
'\n' + contents, re.MULTILINE & re.DOTALL
)
end_pos = None
for result in list(all_block_headings)[::-1]:
heading = {
'start_pos': result.end(),
'end_pos': end_pos,
'depth': len(result[1]),
'heading': result[2].strip(),
}
end_pos = result.start()
yield heading
def extract_block(block_name: str, contents: str) -> str:
"""
Extract the given header block from the given markdown.
The result *does not* contain the children headers of the block.
"""
for block in _block_headings(contents):
if block['heading'] == block_name:
return contents[block['start_pos']: block['end_pos']]
else:
raise ValueError(f"Heading \"{ block_name }\" not found")
def extract_headings(contents: str) -> list:
"""
Extract all headers from the given markdown.
"""
headings = []
for block in _block_headings(contents):
headings.append(block['heading'])
return headings
def extract_rendered_markdown_blocks(template: jinja2.Template) -> dict:
"""
Return a dictionary of all common markdown text blocks, rendered to HTML and
uniquely identified by their headings, from the given Jinja2 template.
"""
common_text = template.render()
headings = extract_headings(common_text)
text_blocks = {}
for heading in headings:
block = extract_block(heading, common_text)
html_content = mistune.markdown(block, escape=False)
text_blocks[heading] = html_content
return text_blocks

View file

View file

@ -0,0 +1,30 @@
import textwrap
import jinja2
import pytest
import cara.apps.calculator.markdown_tools as md_tools
@pytest.fixture
def example_template():
return jinja2.Environment().from_string(textwrap.dedent("""
# A header
Some *text*
{% block using_jinja_blocks %}
# Another header
Some more **text**.
{% endblock %}
"""))
def test_extract_blocks(example_template):
blocks = md_tools.extract_rendered_markdown_blocks(example_template)
assert 'A header' in blocks
assert blocks['A header'] == '<p>Some <em>text</em></p>\n'
assert 'Another header' in blocks
assert blocks['Another header'] == '<p>Some more <strong>text</strong>.</p>\n'

View file

@ -26,6 +26,11 @@ async def test_calculator_form(http_server_client):
assert response.code == 200
async def test_user_guide(http_server_client):
resp = await http_server_client.fetch('/calculator/user-guide')
assert resp.code == 200
class TestBasicApp(tornado.testing.AsyncHTTPTestCase):
def get_app(self):
return cara.apps.calculator.make_app()

View file

@ -73,11 +73,9 @@ setup(
'all': [req for reqs in REQUIREMENTS.values() for req in reqs],
},
package_data={'cara': [
'apps/templates/*.j2',
'apps/calculator/templates/*.j2',
'apps/calculator/*',
'apps/calculator/*/*',
'apps/calculator/*/*/*',
'apps/calculator/*/*/*/*',
'apps/*/*',
'apps/*/*/*',
'apps/*/*/*/*',
'apps/*/*/*/*/*',
]},
)