Compare commits

..

No commits in common. "985bb9383954f0ec3773a8f8863ef4484bedc1fe" and "853a58b78b687dd9549e0a12e3eb630a0baa053d" have entirely different histories.

16 changed files with 117 additions and 206 deletions

View File

@ -32,9 +32,10 @@ class Config(object):
},
}
MARKDOWN_EXTENSIONS = ['extra', 'incorporealcms.mdx.figures', 'sane_lists', 'smarty', 'toc']
MARKDOWN_EXTENSIONS = ['extra', 'sane_lists', 'smarty', 'tables']
MARKDOWN_EXTENSION_CONFIGS = {
'extra': {
'attr_list': {},
'footnotes': {
'UNIQUE_IDS': True,
},

View File

@ -1,60 +0,0 @@
"""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<content>[^\n]*)')
caption_regex = re.compile(r'^[ ]{0,3}\|:[ ]{0,3}(?P<caption>[^\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)

View File

@ -20,17 +20,14 @@ class InlinePydot(markdown.Extension):
class InlinePydotPreprocessor(markdown.preprocessors.Preprocessor):
"""Identify dot codeblocks and run them through pydot."""
BLOCK_RE = re.compile(r'~~~{\s+pydot:(?P<filename>[^\s]+)\s+}\n(?P<content>.*?)~~~', re.DOTALL)
BLOCK_RE = re.compile(r'~~~pydot:(?P<filename>[^\s]+)\n(?P<content>.*?)~~~', re.DOTALL)
def run(self, lines):
"""Match and generate diagrams from dot code blocks."""
text = '\n'.join(lines)
out = text
for block_match in self.BLOCK_RE.finditer(text):
filename = block_match.group(1)
dot_string = block_match.group(2)
logger.debug("matched markdown block: %s", dot_string)
logger.debug("match start/end: %s/%s", block_match.start(), block_match.end())
for match in self.BLOCK_RE.finditer(text):
filename = match.group(1)
dot_string = match.group(2)
# use pydot to turn the text into pydot
graphs = pydot.graph_from_dot_data(dot_string)
@ -44,9 +41,9 @@ class InlinePydotPreprocessor(markdown.preprocessors.Preprocessor):
inline_image = f'![{filename}]({data_path})'
# replace the image in the output markdown
out = out.replace(block_match.group(0), inline_image)
text = f'{text[:match.start()]}\n{inline_image}\n{text[match.end():]}'
return out.split('\n')
return text.split('\n')
def makeExtension(*args, **kwargs):

View File

@ -12,11 +12,11 @@ body {
}
.site-wrap-normal-width {
max-width: 80pc;
max-width: 70pc;
}
.site-wrap-double-width {
max-width: 160pc;
max-width: 140pc;
}
.site-wrap {
@ -26,7 +26,22 @@ body {
margin-right: auto;
}
a {
a:link {
font-weight: bold;
text-decoration: none;
}
a:visited {
font-weight: bold;
text-decoration: none;
}
a:hover {
font-weight: bold;
text-decoration: none;
}
a:active {
font-weight: bold;
text-decoration: none;
}
@ -35,7 +50,7 @@ div.header {
display: flex;
justify-content: space-between;
font-size: 0.75em;
padding: 0.5em 1em;
padding: 0.25em 0.5em;
}
div.header a {
@ -45,18 +60,7 @@ div.header a {
div.content {
font-size: 11pt;
padding: 0 1em;
line-height: 1.6em;
}
@media only screen and (min-width: 70pc) {
div.content, footer {
margin-left: 5pc;
margin-right: 5pc;
}
}
div.content p {
margin: 1.25em 0;
line-height: 1.5em;
}
sup, sub {
@ -75,7 +79,7 @@ footer {
color: #999;
padding: 0 1em;
padding-bottom: 16px;
margin-top: 30px;
margin-top: 15px;
}
table {
@ -131,26 +135,39 @@ img {
margin-left: 1em;
}
figure {
.img-frame {
padding: 5px;
margin: 0;
margin-bottom: 5px;
text-align: center;
max-width: 30%;
min-width: 10em;
display: inline-block;
}
figure.right {
float: right;
/* For screens with width smaller than 400px */
.figure-left .figure-right {
max-width: 95%;
float: none;
margin-left: 10px;
display: block;
margin-right: 10px;
}
figure.left {
float: left;
margin-right: 10px;
display: block;
/* For larger screens */
@media only screen and (min-width: 400px) {
.figure-left {
float: left;
margin-top: 0;
margin-left: 0;
}
.figure-right {
float: right;
margin-top: 0;
margin-right: 0;
}
}
figure {
max-width: 400px;
padding: 5px;
margin: 10px;
margin-top: 0;
margin-bottom: 5px;
}
figure img {
@ -159,8 +176,19 @@ figure img {
}
figcaption {
font-family: "Times New Roman", serif;
color: #777777;
text-align: center;
font-size: 0.9em;
font-style: italic;
line-height: 1.3em;
margin-top: 5px;
}
.thumbnail-image {
width: 150px;
height: auto;
margin: 5px;
display: inline;
}
.footnote {

View File

@ -15,15 +15,24 @@ strong {
.site-wrap {
background: black;
border: 1px solid #222;
border-top: none;
border-bottom: none;
}
h1, h2, h3, h4, h5, h6 {
color: #B31D15;
}
a:link, a:visited {
color: #B31D15;
color: #EEE;
border-bottom: 1px dotted #EEE;
}
a:hover, a:active {
color: #B31D15;
border-bottom: 1px solid #B31D15;
border-bottom: 1px dotted #B31D15;
}
div.header {
@ -49,11 +58,7 @@ blockquote {
border: 1px solid #222;
}
figure {
.img-frame {
background-color: rgba(255, 255, 255, 0.1);
border: 1px solid #333;
}
figcaption {
color: #AAAAAA;
}

View File

@ -15,15 +15,24 @@ strong {
.site-wrap {
background: white;
border: 1px solid #DDD;
border-top: none;
border-bottom: none;
}
h1, h2, h3, h4, h5, h6 {
color: #811610;
}
a:link, a:visited {
color: #811610;
color: #111;
border-bottom: 1px dotted #111;
}
a:hover, a:active {
color: #811610;
border-bottom: 1px solid #B31D15;
border-bottom: 1px dotted #811610;
}
div.header {
@ -49,11 +58,7 @@ blockquote {
border: 1px solid #DDD;
}
figure {
.img-frame {
background-color: rgba(0, 0, 0, 0.1);
border: 1px solid #CCC;
}
figcaption {
color: #666666;
}

View File

@ -6,7 +6,7 @@ pytest
pytest-cov
# linting and other static code analysis
bandit
bandit==1.6.2 # pinned because 1.7.0 wasn't running right in tox
dlint
flake8
flake8-blind-except

View File

@ -6,13 +6,13 @@
#
attrs==21.4.0
# via pytest
bandit==1.7.4
bandit==1.6.2
# via -r requirements/requirements-dev.in
certifi==2021.10.8
# via requests
charset-normalizer==2.0.12
# via requests
click==8.1.2
click==8.0.4
# via
# flask
# pip-tools
@ -54,7 +54,7 @@ flake8-logging-format==0.6.0
# via -r requirements/requirements-dev.in
flake8-mutable==1.2.0
# via -r requirements/requirements-dev.in
flask==2.1.1
flask==2.0.3
# via -r requirements/requirements.in
gitdb==4.0.9
# via gitpython
@ -68,7 +68,7 @@ isort==5.10.1
# via flake8-isort
itsdangerous==2.1.2
# via flask
jinja2==3.1.1
jinja2==3.1.0
# via flask
markdown==3.3.6
# via -r requirements/requirements.in
@ -86,9 +86,9 @@ pbr==5.8.1
# via stevedore
pep517==0.12.0
# via pip-tools
pip-tools==6.6.0
pip-tools==6.5.1
# via -r requirements/requirements-dev.in
platformdirs==2.5.2
platformdirs==2.5.1
# via virtualenv
pluggy==1.0.0
# via
@ -106,7 +106,7 @@ pydot==1.4.2
# via -r requirements/requirements-dev.in
pyflakes==2.4.0
# via flake8
pyparsing==3.0.8
pyparsing==3.0.7
# via
# packaging
# pydot
@ -126,6 +126,7 @@ safety==1.10.3
# via -r requirements/requirements-dev.in
six==1.16.0
# via
# bandit
# tox
# virtualenv
smmap==5.0.0
@ -145,7 +146,7 @@ tomli==2.0.1
# coverage
# pep517
# pytest
tox==3.25.0
tox==3.24.5
# via
# -r requirements/requirements-dev.in
# tox-wheel
@ -155,9 +156,9 @@ urllib3==1.26.9
# via requests
versioneer==0.22
# via -r requirements/requirements-dev.in
virtualenv==20.14.1
virtualenv==20.13.4
# via tox
werkzeug==2.1.1
werkzeug==2.0.3
# via flask
wheel==0.37.1
# via

View File

@ -4,17 +4,17 @@
#
# pip-compile --output-file=requirements/requirements.txt requirements/requirements.in
#
click==8.1.2
click==8.0.4
# via flask
flask==2.1.1
flask==2.0.3
# via -r requirements/requirements.in
itsdangerous==2.1.2
# via flask
jinja2==3.1.1
jinja2==3.1.0
# via flask
markdown==3.3.6
# via -r requirements/requirements.in
markupsafe==2.1.1
# via jinja2
werkzeug==2.1.1
werkzeug==2.0.3
# via flask

View File

@ -29,17 +29,6 @@ def test_graphviz_is_rendered():
assert b'data:image/png;base64' in response.data
def test_two_graphviz_are_rendered():
"""Test two images are rendered."""
app = app_with_pydot()
client = app.test_client()
response = client.get('/test-two-graphviz')
assert response.status_code == 200
assert b'~~~pydot' not in response.data
assert b'data:image/png;base64' in response.data
def test_invalid_graphviz_is_not_rendered():
"""Check that invalid graphviz doesn't blow things up."""
app = app_with_pydot()
@ -48,16 +37,3 @@ 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'<figure class="right"><img alt="fancy captioned logo" src="bss-square-no-bg.png" />'
b'<figcaption>this is my cool logo!</figcaption></figure>') in response.data
assert (b'<figure><img alt="vanilla captioned logo" src="bss-square-no-bg.png" />'
b'<figcaption>this is my cool logo without an attr!</figcaption>\n</figure>') in response.data
assert (b'<figure class="left"><img alt="fancy logo" src="bss-square-no-bg.png" />'
b'<span></span></figure>') in response.data
assert b'<figure><img alt="just a logo" src="bss-square-no-bg.png" /></figure>' in response.data

View File

@ -7,7 +7,7 @@ def test_page_that_exists(client):
"""Test that the app can serve a basic file at the index."""
response = client.get('/')
assert response.status_code == 200
assert b'<h1 id="test-index">test index</h1>' in response.data
assert b'<h1>test index</h1>' in response.data
def test_direct_file_that_exists(client):
@ -134,32 +134,32 @@ def test_that_page_request_redirects_to_directory(client):
"""
response = client.get('/subdir')
assert response.status_code == 301
assert response.location == '/subdir/'
assert response.location == 'http://localhost/subdir/'
def test_that_request_to_symlink_redirects_markdown(client):
"""Test that a request to /foo redirects to /what-foo-points-at."""
response = client.get('/symlink-to-no-title')
assert response.status_code == 301
assert response.location == '/no-title'
assert response.location == 'http://localhost/no-title'
def test_that_request_to_symlink_redirects_file(client):
"""Test that a request to /foo.txt redirects to /what-foo-points-at.txt."""
response = client.get('/symlink-to-foo.txt')
assert response.status_code == 301
assert response.location == '/foo.txt'
assert response.location == 'http://localhost/foo.txt'
def test_that_request_to_symlink_redirects_directory(client):
"""Test that a request to /foo/ redirects to /what-foo-points-at/."""
response = client.get('/symlink-to-subdir/')
assert response.status_code == 301
assert response.location == '/subdir'
assert response.location == 'http://localhost/subdir'
# sadly, this location also redirects
response = client.get('/subdir')
assert response.status_code == 301
assert response.location == '/subdir/'
assert response.location == 'http://localhost/subdir/'
# but we do get there
response = client.get('/subdir/')
assert response.status_code == 200
@ -169,7 +169,7 @@ def test_that_request_to_symlink_redirects_subdirectory(client):
"""Test that a request to /foo/bar redirects to /what-foo-points-at/bar."""
response = client.get('/symlink-to-subdir/page-no-title')
assert response.status_code == 301
assert response.location == '/subdir/page-no-title'
assert response.location == 'http://localhost/subdir/page-no-title'
response = client.get('/subdir/page-no-title')
assert response.status_code == 200

View File

@ -15,10 +15,6 @@ LOGGING = {
},
},
'loggers': {
'incorporealcms.mdx': {
'level': 'DEBUG',
'handlers': ['console'],
},
'incorporealcms.pages': {
'level': 'DEBUG',
'handlers': ['console'],

View File

@ -1,13 +0,0 @@
# 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)

View File

@ -1,7 +1,7 @@
# test
test
~~~{ pydot:attack-plan }
~~~pydot:attack-plan
digraph G {
rankdir=LR
Earth

View File

@ -1,7 +1,7 @@
# test
test
~~~{ pydot:attack-plan }
~~~pydot:attack-plan
rankdir=LR
Earth
Mars

View File

@ -1,25 +0,0 @@
# test
test
~~~{ pydot:attack-plan }
digraph G {
rankdir=LR
Earth
Mars
Earth -> Mars
}
~~~
more test
~~~{ pydot:new-attack-plan }
digraph H {
rankdir=LR
Venus
Mars
Venus -> Mars
}
~~~
done