23 Commits

Author SHA1 Message Date
21ea24ffa1 header style tweaks, deemphasizing it a bit 2022-06-05 21:30:49 -05:00
724a2240b2 requirements bump for latest release 2022-05-25 07:24:03 -05:00
aa6a27dd8b make the header bigger, and align header and content padding better 2022-05-17 07:57:23 -05:00
c80172cffd go back to red headers and links as normal-colored text
the new way to do the links without adding links to images is probably
dumb and/or missing some stuff, but it works and does what I want, and I
think I like the old look of the colors better, so time to try it and
see if it sticks still
2022-05-17 07:57:23 -05:00
89ea2fb87e give the header nav links an underline on hover 2022-05-17 07:57:23 -05:00
8ac5b25208 get rid of the slight recoloring of bold text 2022-05-17 07:57:23 -05:00
54b953f5ed go back to the old, balanced width alignments 2022-05-17 07:57:23 -05:00
de0641b08f tweak the two-tone backgrounds and whitespace up the header 2022-05-17 07:57:23 -05:00
cc3e311738 clarify my DCO-like stance, and provide cloning info 2022-05-17 07:52:58 -05:00
985bb93839 do a requirements bump
notable changes:

* Flask 1.1 treats redirects to itself as relative URLs now, so a spate
  of tests were updated
2022-04-19 08:41:59 -05:00
3454de17fc style links with color, not underlines
this allows me to link to images (e.g. in figures) without having a
separate override class to remove their underline, which is ugly. on
account of making links red, it was looking like too much red
(especially on the index) with headers also being red, so this makes
headers the default text color.

still not 100% committed to this but I'm trying it out
2022-04-19 08:41:59 -05:00
1fe6623587 remove .img-frame class, unnecessary with figures 2022-04-19 08:41:59 -05:00
ec042e466c scoot the footer down a bit, and give it margins like the main content's 2022-04-19 08:41:59 -05:00
6daed848d0 remove duplicate link styles
all these subclasses or whatever are the same
2022-04-19 08:41:59 -05:00
502adac89c make the pydot syntax backwards compatible with code fences
without this, the code fence parser was getting thrown off if you didn't
have the pydot extension loaded, which was thwarting backwards
compatibility. this makes the pydot bits look like an attribute to the
vanilla parser, so at least then the vanilla markdown renders as
intended
2022-04-19 08:41:59 -05:00
08305e26db pydot: handle multiple dot images in one file 2022-04-19 08:41:59 -05:00
9a65dfffaf tweaks to the content responsiveness and header padding
more readability stuff for desktop without (hopefully) affecting the
mobile experience much
2022-04-19 08:41:58 -05:00
a42ce34aad add a bit of whitespace in content text
meant to help readability slightly, in part because of what I'm screwing
around with in changing link and header colors
2022-04-19 08:41:58 -05:00
9ce8e9a1cc restyle figures and captions for more flexibility 2022-04-19 08:41:58 -05:00
d07260331b remove unused figure classes
these are being reworked and I don't think I'm going to do responsive
stuff anymore
2022-04-19 08:41:58 -05:00
2c47501589 remove unused thumbnail class
used to appear on a couple images, the styling was replaced with some
more explicit image tags and soon will be replaced again with figure
stuff
2022-04-19 08:41:58 -05:00
f15d955067 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?
2022-04-19 08:41:55 -05:00
b3c3c0de45 clean up the default markdown configuration a bit
this brings the config a bit closer to my default, and updates the
expectations of a test accordingly
2022-04-19 06:45:32 -05:00
17 changed files with 279 additions and 170 deletions

View File

@@ -1,7 +1,10 @@
# How to Contribute # How to Contribute
incorporeal-cms is a personal project seeking to implement a simpler, cleaner form of what would incorporeal-cms is a personal project seeking to implement a simpler, cleaner form of what would
commonly be called a "CMS". I appreciate any help in making incorporeal-cms better. commonly be called a "CMS". I appreciate any help in making it better.
incorporeal-cms is made available under the GNU Affero General Public License version 3, or any
later version.
## Opening Issues ## Opening Issues
@@ -10,8 +13,24 @@ Issues should be posted to my Gitea instance at
recommend starting the title with "Improvement:", "Bug:", or similar, so I can do a high level of recommend starting the title with "Improvement:", "Bug:", or similar, so I can do a high level of
prioritization. prioritization.
## Guidelines for Patches, etc. ## Contributions
I don't expect contributors to sign up for my personal Gitea in order to send contributions, but it
of course makes it easier. If you wish to go this route, please sign up at
<https://git.incorporeal.org/bss/incorporeal-cms> and fork the project. People planning on
contributing often are also welcome to request access to the project directly.
Otherwise, contact me via any means you know to reach me at, or <bss@incorporeal.org>, to discuss
your change and to tell me how to pull your changes.
### Guidelines for Patches, etc.
* Cloning
* Clone the project. I would advise using a pull-based workflow where I have access to the hosted
repository --- using my Gitea, cloning to a public GitHub, etc. --- rather than doing this over
email, but that works too if we must.
* Make your contributions in a new branch, generally off of `master`.
* Send me a pull request when you're ready, and we'll go through a code review.
* Code: * Code:
* Keep in mind that I strive for simplicity in the software. It serves files and renders * Keep in mind that I strive for simplicity in the software. It serves files and renders
Markdown, that's pretty much it. Features around that function are good; otherwise, I need Markdown, that's pretty much it. Features around that function are good; otherwise, I need
@@ -27,22 +46,31 @@ prioritization.
* Squash tiny commits if you'd like. I prefer commits that make one atomic conceptual change * Squash tiny commits if you'd like. I prefer commits that make one atomic conceptual change
that doesn't affect the rest of the code, assembling multiple of those commits into larger that doesn't affect the rest of the code, assembling multiple of those commits into larger
changes. changes.
* Follow something like [Chris Beams'](https://chris.beams.io/posts/git-commit/) post on * Follow something like [Chris Beams's post](https://chris.beams.io/posts/git-commit/) on
formatting a good commit message. formatting a good commit message.
* Please make sure your Author contact information is stable, in case I need to reach you.
* Consider cryptographically signing (`git commit -S`) your commits.
## Contributions ### Custody of Contributions
I don't expect contributors to sign up for my personal Gitea in order to send contributions, but it I do not request the copyright of contributions be assigned to me or to the project, and I require no
of course makes it easier. If you wish to go this route, please sign up at provision that I be allowed to relicense your contributions. My personal oath is to maintain
<https://git.incorporeal.org/bss/incorporeal-cms> and fork the project. People planning on inbound=outbound in my open source projects, and the expectation is authors are responsible for their
contributing often are also welcome to request access to the project directly. contributions.
Otherwise, contact me via any means you know to reach me at, or <bss@incorporeal.org>, to discuss I am following the *spirit* of the [Developer Certificate of Origin](https://developercertificate.org/),
your change and to tell me how to pull your changes. but in a simplified fashion:
### Copyright of Contributions By making a contribution to this project, you certify that:
Accepted changes remain the copyright of the original author, but please include appropriate contact 1. The contribution was created by you and you have the right to submit it under the open source license
methods in the event I choose to provide the project under a new license and need to contact you indicated in the LICENSE file; or
to approve the new license terms. Please note that the software is provided under the GNU AGPLv3 (or 2. The contribution is based upon previous work that is covered under an appropriate open source license
later). compatible with the license indicated in the LICENSE file, and you have the right to contribute that
work with or without modifications, under the terms of that same open source license; or
3. The contribution was provided directly to you by some other person who certified points 1, 2, or 3, and
you have not modified it.
In the event of point 3, your commit **must** include the Signed-off-by line(s) as a chain of custody,
via `git commit -s`. For points 1 and 2, your commit with accurate Author information doubles as direct
custody.

View File

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

View File

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

View File

@@ -12,11 +12,11 @@ body {
} }
.site-wrap-normal-width { .site-wrap-normal-width {
max-width: 70pc; max-width: 65pc;
} }
.site-wrap-double-width { .site-wrap-double-width {
max-width: 140pc; max-width: 130pc;
} }
.site-wrap { .site-wrap {
@@ -26,22 +26,7 @@ body {
margin-right: auto; margin-right: auto;
} }
a:link { a {
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; font-weight: bold;
text-decoration: none; text-decoration: none;
} }
@@ -49,8 +34,9 @@ a:active {
div.header { div.header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: 0.75em; font-size: 0.8em;
padding: 0.25em 0.5em; padding: 1rem 1rem;
padding-bottom: 0;
} }
div.header a { div.header a {
@@ -59,8 +45,12 @@ div.header a {
div.content { div.content {
font-size: 11pt; font-size: 11pt;
padding: 0 1em; padding: 0 1rem;
line-height: 1.5em; line-height: 1.6em;
}
div.content p {
margin: 1.25em 0;
} }
sup, sub { sup, sub {
@@ -79,7 +69,7 @@ footer {
color: #999; color: #999;
padding: 0 1em; padding: 0 1em;
padding-bottom: 16px; padding-bottom: 16px;
margin-top: 15px; margin-top: 30px;
} }
table { table {
@@ -135,39 +125,26 @@ img {
margin-left: 1em; margin-left: 1em;
} }
.img-frame {
padding: 5px;
}
/* For screens with width smaller than 400px */
.figure-left .figure-right {
max-width: 95%;
float: none;
margin-left: 10px;
margin-right: 10px;
}
/* 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 { figure {
max-width: 400px;
padding: 5px; padding: 5px;
margin: 10px; margin: 0;
margin-top: 0;
margin-bottom: 5px; margin-bottom: 5px;
text-align: center;
max-width: 30%;
min-width: 10em;
display: inline-block;
}
figure.right {
float: right;
margin-left: 10px;
display: block;
}
figure.left {
float: left;
margin-right: 10px;
display: block;
} }
figure img { figure img {
@@ -176,19 +153,8 @@ figure img {
} }
figcaption { figcaption {
font-family: "Times New Roman", serif;
color: #777777;
text-align: center; text-align: center;
font-style: italic; font-size: 0.9em;
line-height: 1.3em;
margin-top: 5px;
}
.thumbnail-image {
width: 150px;
height: auto;
margin: 5px;
display: inline;
} }
.footnote { .footnote {

View File

@@ -6,43 +6,33 @@ html {
} }
body { body {
background: #090909; background: #111;
}
strong {
color: #EEE;
}
.site-wrap {
background: black;
border: 1px solid #222;
border-top: none;
border-bottom: none;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
color: #B31D15; color: #B31D15;
} }
a:link, a:visited { p a, ul a, ol a {
color: #EEE; color: #DDD;
border-bottom: 1px dotted #EEE; border-bottom: 1px solid #DDD;
} }
a:hover, a:active { p a:hover, ul a:hover, ol a:hover {
color: #B31D15; color: #B31D15;
border-bottom: 1px dotted #B31D15; border-bottom: 1px solid #B31D15;
} }
div.header { div.site-wrap {
background: #222; background: black;
border-bottom: 1px solid #222;
color: #BBB;
} }
div.header a { div.header, div.header a {
color: #BBB; color: #555;
}
div.header a:hover, div.header a:active {
border-bottom: 1px solid #555;
} }
table, th, td { table, th, td {
@@ -58,7 +48,11 @@ blockquote {
border: 1px solid #222; border: 1px solid #222;
} }
.img-frame { figure {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
border: 1px solid #333; border: 1px solid #333;
} }
figcaption {
color: #AAAAAA;
}

View File

@@ -6,43 +6,33 @@ html {
} }
body { body {
background: #F6F6F6; background: #EEE;
}
strong {
color: #111;
}
.site-wrap {
background: white;
border: 1px solid #DDD;
border-top: none;
border-bottom: none;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
color: #811610; color: #811610;
} }
a:link, a:visited { p a, ul a, ol a {
color: #111; color: #222;
border-bottom: 1px dotted #111; border-bottom: 1px solid #222;
} }
a:hover, a:active { p a:hover, ul a:hover, ol a:hover {
color: #811610; color: #811610;
border-bottom: 1px dotted #811610; border-bottom: 1px solid #811610;
} }
div.header { div.site-wrap {
background: #DDD; background: white;
border-bottom: 1px solid #DDD;
color: #444;
} }
div.header a { div.header, div.header a {
color: #444; color: #AAA;
}
div.header a:hover, div.header a:active {
border-bottom: 1px solid #AAA;
} }
table, th, td { table, th, td {
@@ -58,7 +48,11 @@ blockquote {
border: 1px solid #DDD; border: 1px solid #DDD;
} }
.img-frame { figure {
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
border: 1px solid #CCC; border: 1px solid #CCC;
} }
figcaption {
color: #666666;
}

View File

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

View File

@@ -6,18 +6,18 @@
# #
attrs==21.4.0 attrs==21.4.0
# via pytest # via pytest
bandit==1.6.2 bandit==1.7.4
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
certifi==2021.10.8 certifi==2022.5.18.1
# via requests # via requests
charset-normalizer==2.0.12 charset-normalizer==2.0.12
# via requests # via requests
click==8.0.4 click==8.1.3
# via # via
# flask # flask
# pip-tools # pip-tools
# safety # safety
coverage[toml]==6.3.2 coverage[toml]==6.4
# via pytest-cov # via pytest-cov
distlib==0.3.4 distlib==0.3.4
# via virtualenv # via virtualenv
@@ -25,7 +25,7 @@ dlint==0.12.0
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
dparse==0.5.1 dparse==0.5.1
# via safety # via safety
filelock==3.6.0 filelock==3.7.0
# via # via
# tox # tox
# virtualenv # virtualenv
@@ -54,7 +54,7 @@ flake8-logging-format==0.6.0
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
flake8-mutable==1.2.0 flake8-mutable==1.2.0
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
flask==2.0.3 flask==2.1.2
# via -r requirements/requirements.in # via -r requirements/requirements.in
gitdb==4.0.9 gitdb==4.0.9
# via gitpython # via gitpython
@@ -68,9 +68,9 @@ isort==5.10.1
# via flake8-isort # via flake8-isort
itsdangerous==2.1.2 itsdangerous==2.1.2
# via flask # via flask
jinja2==3.1.0 jinja2==3.1.2
# via flask # via flask
markdown==3.3.6 markdown==3.3.7
# via -r requirements/requirements.in # via -r requirements/requirements.in
markupsafe==2.1.1 markupsafe==2.1.1
# via jinja2 # via jinja2
@@ -82,13 +82,13 @@ packaging==21.3
# pytest # pytest
# safety # safety
# tox # tox
pbr==5.8.1 pbr==5.9.0
# via stevedore # via stevedore
pep517==0.12.0 pep517==0.12.0
# via pip-tools # via pip-tools
pip-tools==6.5.1 pip-tools==6.6.2
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
platformdirs==2.5.1 platformdirs==2.5.2
# via virtualenv # via virtualenv
pluggy==1.0.0 pluggy==1.0.0
# via # via
@@ -106,11 +106,11 @@ pydot==1.4.2
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
pyflakes==2.4.0 pyflakes==2.4.0
# via flake8 # via flake8
pyparsing==3.0.7 pyparsing==3.0.9
# via # via
# packaging # packaging
# pydot # pydot
pytest==7.1.1 pytest==7.1.2
# via # via
# -r requirements/requirements-dev.in # -r requirements/requirements-dev.in
# pytest-cov # pytest-cov
@@ -126,7 +126,6 @@ safety==1.10.3
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
six==1.16.0 six==1.16.0
# via # via
# bandit
# tox # tox
# virtualenv # virtualenv
smmap==5.0.0 smmap==5.0.0
@@ -146,7 +145,7 @@ tomli==2.0.1
# coverage # coverage
# pep517 # pep517
# pytest # pytest
tox==3.24.5 tox==3.25.0
# via # via
# -r requirements/requirements-dev.in # -r requirements/requirements-dev.in
# tox-wheel # tox-wheel
@@ -156,9 +155,9 @@ urllib3==1.26.9
# via requests # via requests
versioneer==0.22 versioneer==0.22
# via -r requirements/requirements-dev.in # via -r requirements/requirements-dev.in
virtualenv==20.13.4 virtualenv==20.14.1
# via tox # via tox
werkzeug==2.0.3 werkzeug==2.1.2
# via flask # via flask
wheel==0.37.1 wheel==0.37.1
# via # via

View File

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

View File

@@ -29,6 +29,17 @@ def test_graphviz_is_rendered():
assert b'data:image/png;base64' in response.data 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(): def test_invalid_graphviz_is_not_rendered():
"""Check that invalid graphviz doesn't blow things up.""" """Check that invalid graphviz doesn't blow things up."""
app = app_with_pydot() app = app_with_pydot()
@@ -37,3 +48,16 @@ def test_invalid_graphviz_is_not_rendered():
response = client.get('/test-invalid-graphviz') response = client.get('/test-invalid-graphviz')
assert response.status_code == 500 assert response.status_code == 500
assert b'INTERNAL SERVER ERROR' in response.data 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.""" """Test that the app can serve a basic file at the index."""
response = client.get('/') response = client.get('/')
assert response.status_code == 200 assert response.status_code == 200
assert b'<h1>test index</h1>' in response.data assert b'<h1 id="test-index">test index</h1>' in response.data
def test_direct_file_that_exists(client): def test_direct_file_that_exists(client):
@@ -134,32 +134,32 @@ def test_that_page_request_redirects_to_directory(client):
""" """
response = client.get('/subdir') response = client.get('/subdir')
assert response.status_code == 301 assert response.status_code == 301
assert response.location == 'http://localhost/subdir/' assert response.location == '/subdir/'
def test_that_request_to_symlink_redirects_markdown(client): def test_that_request_to_symlink_redirects_markdown(client):
"""Test that a request to /foo redirects to /what-foo-points-at.""" """Test that a request to /foo redirects to /what-foo-points-at."""
response = client.get('/symlink-to-no-title') response = client.get('/symlink-to-no-title')
assert response.status_code == 301 assert response.status_code == 301
assert response.location == 'http://localhost/no-title' assert response.location == '/no-title'
def test_that_request_to_symlink_redirects_file(client): def test_that_request_to_symlink_redirects_file(client):
"""Test that a request to /foo.txt redirects to /what-foo-points-at.txt.""" """Test that a request to /foo.txt redirects to /what-foo-points-at.txt."""
response = client.get('/symlink-to-foo.txt') response = client.get('/symlink-to-foo.txt')
assert response.status_code == 301 assert response.status_code == 301
assert response.location == 'http://localhost/foo.txt' assert response.location == '/foo.txt'
def test_that_request_to_symlink_redirects_directory(client): def test_that_request_to_symlink_redirects_directory(client):
"""Test that a request to /foo/ redirects to /what-foo-points-at/.""" """Test that a request to /foo/ redirects to /what-foo-points-at/."""
response = client.get('/symlink-to-subdir/') response = client.get('/symlink-to-subdir/')
assert response.status_code == 301 assert response.status_code == 301
assert response.location == 'http://localhost/subdir' assert response.location == '/subdir'
# sadly, this location also redirects # sadly, this location also redirects
response = client.get('/subdir') response = client.get('/subdir')
assert response.status_code == 301 assert response.status_code == 301
assert response.location == 'http://localhost/subdir/' assert response.location == '/subdir/'
# but we do get there # but we do get there
response = client.get('/subdir/') response = client.get('/subdir/')
assert response.status_code == 200 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.""" """Test that a request to /foo/bar redirects to /what-foo-points-at/bar."""
response = client.get('/symlink-to-subdir/page-no-title') response = client.get('/symlink-to-subdir/page-no-title')
assert response.status_code == 301 assert response.status_code == 301
assert response.location == 'http://localhost/subdir/page-no-title' assert response.location == '/subdir/page-no-title'
response = client.get('/subdir/page-no-title') response = client.get('/subdir/page-no-title')
assert response.status_code == 200 assert response.status_code == 200

View File

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

View File

@@ -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)

View File

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

View File

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

View File

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