Implement common markdown blocks infrastructure
This commit is contained in:
parent
bef6602cc1
commit
49c28aaa98
7 changed files with 119 additions and 9 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
73
cara/apps/calculator/markdown_tools.py
Normal file
73
cara/apps/calculator/markdown_tools.py
Normal 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
|
||||
0
cara/apps/templates/common_text.md.j2
Normal file
0
cara/apps/templates/common_text.md.j2
Normal file
30
cara/tests/apps/calculator/test_markdown_tools.py
Normal file
30
cara/tests/apps/calculator/test_markdown_tools.py
Normal 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'
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
10
setup.py
10
setup.py
|
|
@ -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/*/*/*/*/*',
|
||||
]},
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue