24 Commits

Author SHA1 Message Date
4042932240 tone down the line-height a bit
I think the color changes and using viewport magic has helped
readability a bit
2020-12-08 18:56:49 -06:00
49ab2befb6 disable browser resize magic, do viewport magic instead 2020-12-08 18:47:02 -06:00
fbf6a81e0b use my old "square" logo as favicon
also provide a backgroundless version as a possible splash page image
2020-12-08 18:46:04 -06:00
dabf9f7544 more corrections of the link colors 2020-12-08 18:27:55 -06:00
dcf173ab61 add a test to ensure style selection works 2020-12-08 17:12:35 -06:00
d2c1c2e3ce why did I make user styles a config setting???
this moves it into the code, where it's sensible, and leaves the default
to the config
2020-12-08 16:43:20 -06:00
3fcf916317 requirements bump 2020-12-08 16:33:19 -06:00
67e1890629 increase the line height for readability(?) 2020-12-07 21:55:56 -06:00
e1cb541ea5 highlight links in light theme as in dark theme 2020-12-07 21:55:32 -06:00
93e9c8dc24 tweaks to the dark theme 2020-12-07 21:54:43 -06:00
7cf11986c5 user-selectable light and dark themes
cookies, template rendering with different CSS files via default or
request param or cookie, etc.
2020-10-30 00:19:19 -05:00
5ca483a904 configurable markdown extensions
meta is always loaded, because the code expects it
2020-10-29 23:51:58 -05:00
fe7d61e1f7 actually style the white bg beyond the viewport scroll 2020-10-25 18:05:48 -05:00
1398cfe3db put some sidebars on the site for readability 2020-10-25 17:48:19 -05:00
f63de031f6 tox updates: run py38, combine coverage, dist-as-dir 2020-10-20 16:07:49 -05:00
46bce5a0a5 recompile all requirements, add flake8-mutable 2020-10-20 16:05:17 -05:00
0af0f4e8aa tox.ini updates, use requirements-dev.txt, fix pathing 2020-06-23 13:33:15 -05:00
08896a18c1 reorganize requirements-dev.in, add dlint and flake8-fixme, bandit 2020-06-23 13:30:49 -05:00
ea7c9a1e07 let TODOs through linting, but warn about them 2020-06-22 19:09:39 -05:00
63da59efd5 enable flake8-logging-format violations 2020-06-22 18:50:13 -05:00
c7d4a1c930 add any suppressed flake8-fixme messages in the fail-open run 2020-06-22 18:49:34 -05:00
421d0e6f8e properly create the symlink to dist/ across multiple runs of tox 2020-06-22 18:48:22 -05:00
5c1fc93ff9 combine tox deps in order to unconfuse flake8-isort
with pytest not being included in the lint environment, flake8-isort
didn't know how to treat it vs. incorporealcms imports, leading to false
positives only inside tox. this makes it so that certain packages
(defined in base deps) can be imported in any/all envs, because they
show up in analyzed/imported/etc code rather than being merely tools
2020-06-22 18:48:18 -05:00
7b5f7ff00b add dlint and flake8-fixme 2020-06-20 10:48:46 -05:00
17 changed files with 360 additions and 97 deletions

View File

@@ -3,6 +3,7 @@ import logging
import os import os
from logging.config import dictConfig from logging.config import dictConfig
import markdown
from flask import Flask, request, send_from_directory from flask import Flask, request, send_from_directory
from ._version import get_versions from ._version import get_versions
@@ -31,6 +32,10 @@ def create_app(instance_path=None, test_config=None):
logger.debug("instance path: %s", app.instance_path) logger.debug("instance path: %s", app.instance_path)
# initialize markdown parser from config, but include
# extensions our app depends on, like the meta extension
app.config['md'] = markdown.Markdown(extensions=app.config['MARKDOWN_EXTENSIONS'] + ['meta'])
@app.before_request @app.before_request
def log_request(): def log_request():
logger.info("REQUEST: %s %s", request.method, request.path) logger.info("REQUEST: %s %s", request.method, request.path)

View File

@@ -32,5 +32,9 @@ class Config(object):
}, },
} }
MARKDOWN_EXTENSIONS = ['meta', 'tables']
DEFAULT_PAGE_STYLE = 'light'
TITLE_SUFFIX = 'incorporeal.org' TITLE_SUFFIX = 'incorporeal.org'
MEDIA_DIR = 'media' MEDIA_DIR = 'media'

View File

@@ -3,16 +3,14 @@ import datetime
import logging import logging
import os import os
import markdown
from flask import Blueprint, Markup, abort from flask import Blueprint, Markup, abort
from flask import current_app as app from flask import current_app as app
from flask import redirect, render_template from flask import make_response, redirect, render_template, request
from tzlocal import get_localzone from tzlocal import get_localzone
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
bp = Blueprint('pages', __name__, url_prefix='/') bp = Blueprint('pages', __name__, url_prefix='/')
md = markdown.Markdown(extensions=['meta', 'tables'])
@bp.route('/', defaults={'path': 'index'}) @bp.route('/', defaults={'path': 'index'})
@@ -34,11 +32,35 @@ def display_page(path):
logger.warning("requested path '%s' (resolved path '%s') not found!", path, resolved_path) logger.warning("requested path '%s' (resolved path '%s') not found!", path, resolved_path)
abort(404) abort(404)
else: else:
content = Markup(md.convert(entry)) content = Markup(app.config['md'].convert(entry))
logger.debug("file metadata: %s", md.Meta) logger.debug("file metadata: %s", app.config['md'].Meta)
title = " ".join(md.Meta.get('title')) if md.Meta.get('title') else "" title = " ".join(app.config['md'].Meta.get('title')) if app.config['md'].Meta.get('title') else ""
return render_template('base.html', title=title, content=content, navs=parent_navs, return render('base.html', title=title, content=content, navs=parent_navs,
mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z')) mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z'))
def render(template_name_or_list, **context):
"""Wrap Flask's render_template.
* Determine the proper site theme to use in the template and provide it.
"""
PAGE_STYLES = {
'dark': 'css/dark.css',
'light': 'css/light.css',
}
selected_style = request.args.get('style', None)
if selected_style:
user_style = selected_style
else:
user_style = request.cookies.get('user-style')
logger.debug("user style cookie: %s", user_style)
context['user_style'] = PAGE_STYLES.get(user_style, PAGE_STYLES.get(app.config['DEFAULT_PAGE_STYLE']))
resp = make_response(render_template(template_name_or_list, **context))
if selected_style:
resp.set_cookie('user-style', selected_style)
return resp
def resolve_page_file(path): def resolve_page_file(path):
@@ -86,6 +108,6 @@ def generate_parent_navs(path):
else: else:
with app.open_instance_resource(resolved_path, 'r') as entry_file: with app.open_instance_resource(resolved_path, 'r') as entry_file:
entry = entry_file.read() entry = entry_file.read()
_ = Markup(md.convert(entry)) _ = Markup(app.config['md'].convert(entry))
page_name = " ".join(md.Meta.get('title')) if md.Meta.get('title') else f'/{path}' page_name = " ".join(app.config['md'].Meta.get('title')) if app.config['md'].Meta.get('title') else f'/{path}'
return generate_parent_navs(parent_path) + [(page_name, f'/{path}')] return generate_parent_navs(parent_path) + [(page_name, f'/{path}')]

View File

@@ -0,0 +1,52 @@
html {
color: #DDD;
}
body {
background: black;
}
.site-wrap {
background: #111;
border: 1px solid #222;
border-top: none;
border-bottom: none;
}
h1, h2, h3, h4, h5, h6 {
color: #B31D15;
}
a:link, a:visited {
color: #EEE;
border-bottom: 1px dotted #EEE;
}
a:hover, a:active {
color: #B31D15;
border-bottom: 1px dotted #EEE;
}
section.nav, section.styles {
color: #BBB;
background: #222;
border-bottom: 1px solid #222;
}
section.nav a, section.styles a {
color: #BBB;
}
table, th, td {
border: 1px solid #333;
}
th {
background: #333;
}
blockquote {
background-color: rgba(120, 120, 120, 0.3);
border: 1px solid #222;
}

View File

@@ -0,0 +1,52 @@
html {
color: #222;
}
body {
background: #888;
}
.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: #111;
border-bottom: 1px dotted #111;
}
a:hover, a:active {
color: #811610;
border-bottom: 1px dotted #111;
}
section.nav, section.styles {
color: #666;
background: #EEE;
border-bottom: 1px solid #CCC;
}
section.nav a, section.styles a {
color: #666;
}
table, th, td {
border: 1px solid #ccc;
}
th {
background: #eee;
}
blockquote {
background-color: rgba(120, 120, 120, 0.1);
border: 1px solid #CCC;
}

View File

@@ -1,16 +1,22 @@
html { html {
font-family: sans-serif; font-family: sans-serif;
padding: 0; padding: 0;
padding-bottom: 16px;
color: #222;
} }
body { body {
margin: 0; margin: 0;
text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
} }
h1,h2,h3,h4,h5,h6 { .site-wrap {
color: #811610; max-width: 70pc;
min-height: 100vh;
margin: 0;
margin-left: auto;
margin-right: auto;
} }
h1 { h1 {
@@ -38,46 +44,39 @@ h6 {
} }
a:link { a:link {
color: #222;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
border-bottom: 1px dotted #222;
} }
a:visited { a:visited {
color: #222;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
border-bottom: 1px dotted #222;
} }
a:hover { a:hover {
color: #811610;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
border-bottom: 1px dotted #222;
} }
a:active { a:active {
color: #811610;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
border-bottom: 1px dotted #222;
} }
section.nav { section.nav, section.styles {
color: #666;
background: #eee;
font-size: 0.75em; font-size: 0.75em;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #444;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
} }
section.nav a { section.nav a, section.styles a {
color: #666;
border-bottom: none; border-bottom: none;
} }
section.styles {
float: right;
}
section.content { section.content {
font-size: 11pt; font-size: 11pt;
@@ -90,6 +89,7 @@ footer {
font-size: 75%; font-size: 75%;
color: #999; color: #999;
padding: 0 1em; padding: 0 1em;
padding-bottom: 16px;
margin-top: 15px; margin-top: 15px;
} }
@@ -103,11 +103,15 @@ table, th, td {
margin-bottom: 15px; margin-bottom: 15px;
} }
th {
background: #eee;
}
blockquote { blockquote {
background-color: rgba(120, 120, 120, 0.1); background-color: rgba(120, 120, 120, 0.3);
padding: 1px 10px; padding: 1px 10px;
} }
.splash {
text-align: center;
}
.img-50 {
max-width: 50%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,16 +1,24 @@
<!doctype html> <!doctype html>
<title>{{ title }}{% if title %} - {% endif %}{{ config.TITLE_SUFFIX }}</title> <title>{{ title }}{% if title %} - {% endif %}{{ config.TITLE_SUFFIX }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename=user_style) }}">
<link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}"> <link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}">
<section class="nav"> <section class="site-wrap">
{% for nav in navs %} <section class="styles">
<a href="{{ nav.1 }}">{{ nav.0 }}</a> <a href="?style=dark">[dark]</a>
{% if not loop.last %} &raquo; {% endif %} <a href="?style=light">[light]</a>
{% endfor %} </section>
<section class="nav">
{% for nav in navs %}
<a href="{{ nav.1 }}">{{ nav.0 }}</a>
{% if not loop.last %} &raquo; {% endif %}
{% endfor %}
</section>
<section class="content">
{{ content }}
</section>
<footer>
<i>Last modified: {{ mtime }}</i>
</footer>
</section> </section>
<section class="content">
{{ content }}
</section>
<footer>
<i>Last modified: {{ mtime }}</i>
</footer>

View File

@@ -1,12 +1,23 @@
-r requirements.in -r requirements.in
flake8 # flake8 and plugins, for local dev linting in vim # testing runner, test reporting, packages used during testing (e.g. requests-mock), etc.
pytest
pytest-cov
# linting and other static code analysis
bandit
dlint
flake8
flake8-blind-except flake8-blind-except
flake8-builtins flake8-builtins
flake8-docstrings flake8-docstrings
flake8-executable flake8-executable
flake8-fixme
flake8-isort flake8-isort
flake8-logging-format flake8-logging-format
flake8-mutable
# maintenance utilities and tox
pip-tools # pip-compile pip-tools # pip-compile
tox # CI stuff tox # CI stuff
tox-wheel # build wheels in tox tox-wheel # build wheels in tox

View File

@@ -5,45 +5,58 @@
# pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in # pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in
# #
appdirs==1.4.4 # via virtualenv appdirs==1.4.4 # via virtualenv
attrs==20.3.0 # via pytest
bandit==1.6.3 # via -r requirements/requirements-dev.in
click==7.1.2 # via flask, pip-tools click==7.1.2 # via flask, pip-tools
distlib==0.3.0 # via virtualenv coverage==5.3 # via pytest-cov
distlib==0.3.1 # via virtualenv
dlint==0.11.0 # via -r requirements/requirements-dev.in
filelock==3.0.12 # via tox, virtualenv filelock==3.0.12 # via tox, virtualenv
flake8-blind-except==0.1.1 # via -r requirements/requirements-dev.in flake8-blind-except==0.1.1 # via -r requirements/requirements-dev.in
flake8-builtins==1.5.3 # via -r requirements/requirements-dev.in flake8-builtins==1.5.3 # via -r requirements/requirements-dev.in
flake8-docstrings==1.5.0 # via -r requirements/requirements-dev.in flake8-docstrings==1.5.0 # via -r requirements/requirements-dev.in
flake8-executable==2.0.3 # via -r requirements/requirements-dev.in flake8-executable==2.1.0 # via -r requirements/requirements-dev.in
flake8-isort==3.0.0 # via -r requirements/requirements-dev.in flake8-fixme==1.1.1 # via -r requirements/requirements-dev.in
flake8-isort==4.0.0 # via -r requirements/requirements-dev.in
flake8-logging-format==0.6.0 # via -r requirements/requirements-dev.in flake8-logging-format==0.6.0 # via -r requirements/requirements-dev.in
flake8==3.8.3 # via -r requirements/requirements-dev.in, flake8-builtins, flake8-docstrings, flake8-executable, flake8-isort flake8-mutable==1.2.0 # via -r requirements/requirements-dev.in
flake8==3.8.4 # via -r requirements/requirements-dev.in, dlint, flake8-builtins, flake8-docstrings, flake8-executable, flake8-isort, flake8-mutable
flask==1.1.2 # via -r requirements/requirements.in flask==1.1.2 # via -r requirements/requirements.in
importlib-metadata==1.6.0 # via flake8, markdown, pluggy, tox, virtualenv gitdb==4.0.5 # via gitpython
isort[pyproject]==4.3.21 # via flake8-isort gitpython==3.1.11 # via bandit
iniconfig==1.1.1 # via pytest
isort==5.6.4 # via flake8-isort
itsdangerous==1.1.0 # via flask itsdangerous==1.1.0 # via flask
jinja2==2.11.2 # via flask jinja2==2.11.2 # via flask
markdown==3.2.2 # via -r requirements/requirements.in markdown==3.3.3 # via -r requirements/requirements.in
markupsafe==1.1.1 # via jinja2 markupsafe==1.1.1 # via jinja2
mccabe==0.6.1 # via flake8 mccabe==0.6.1 # via flake8
packaging==20.4 # via tox packaging==20.7 # via pytest, tox
pip-tools==5.2.1 # via -r requirements/requirements-dev.in pbr==5.5.1 # via stevedore
pluggy==0.13.1 # via tox pip-tools==5.4.0 # via -r requirements/requirements-dev.in
py==1.8.1 # via tox pluggy==0.13.1 # via pytest, tox
py==1.9.0 # via pytest, tox
pycodestyle==2.6.0 # via flake8 pycodestyle==2.6.0 # via flake8
pydocstyle==5.0.2 # via flake8-docstrings pydocstyle==5.1.1 # via flake8-docstrings
pyflakes==2.2.0 # via flake8 pyflakes==2.2.0 # via flake8
pyparsing==2.4.7 # via packaging pyparsing==2.4.7 # via packaging
pytz==2020.1 # via tzlocal pytest-cov==2.10.1 # via -r requirements/requirements-dev.in
six==1.15.0 # via packaging, pip-tools, tox, virtualenv pytest==6.1.2 # via -r requirements/requirements-dev.in, pytest-cov
pytz==2020.4 # via tzlocal
pyyaml==5.3.1 # via bandit
six==1.15.0 # via bandit, pip-tools, tox, virtualenv
smmap==3.0.4 # via gitdb
snowballstemmer==2.0.0 # via pydocstyle snowballstemmer==2.0.0 # via pydocstyle
testfixtures==6.14.1 # via flake8-isort stevedore==3.3.0 # via bandit
toml==0.10.1 # via isort, tox testfixtures==6.15.0 # via flake8-isort
tox-wheel==0.4.2 # via -r requirements/requirements-dev.in toml==0.10.2 # via pytest, tox
tox==3.15.2 # via -r requirements/requirements-dev.in, tox-wheel tox-wheel==0.6.0 # via -r requirements/requirements-dev.in
tox==3.20.1 # via -r requirements/requirements-dev.in, tox-wheel
tzlocal==2.1 # via -r requirements/requirements.in tzlocal==2.1 # via -r requirements/requirements.in
versioneer==0.18 # via -r requirements/requirements-dev.in versioneer==0.19 # via -r requirements/requirements-dev.in
virtualenv==20.0.23 # via tox virtualenv==20.2.2 # via tox
werkzeug==1.0.1 # via flask werkzeug==1.0.1 # via flask
wheel==0.34.2 # via tox-wheel wheel==0.36.1 # via tox-wheel
zipp==3.1.0 # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip # pip

View File

@@ -6,12 +6,10 @@
# #
click==7.1.2 # via flask click==7.1.2 # via flask
flask==1.1.2 # via -r requirements/requirements.in flask==1.1.2 # via -r requirements/requirements.in
importlib-metadata==1.6.0 # via markdown
itsdangerous==1.1.0 # via flask itsdangerous==1.1.0 # via flask
jinja2==2.11.2 # via flask jinja2==2.11.2 # via flask
markdown==3.2.2 # via -r requirements/requirements.in markdown==3.3.3 # via -r requirements/requirements.in
markupsafe==1.1.1 # via jinja2 markupsafe==1.1.1 # via jinja2
pytz==2020.1 # via tzlocal pytz==2020.4 # via tzlocal
tzlocal==2.1 # via -r requirements/requirements.in tzlocal==2.1 # via -r requirements/requirements.in
werkzeug==1.0.1 # via flask werkzeug==1.0.1 # via flask
zipp==3.1.0 # via importlib-metadata

View File

@@ -52,3 +52,24 @@ def test_that_dir_request_does_not_redirect(client):
response = client.get('/subdir/') response = client.get('/subdir/')
assert response.status_code == 200 assert response.status_code == 200
assert b'another page' in response.data assert b'another page' in response.data
def test_setting_selected_style_includes_cookie(client):
"""Test that a request with style=foo sets the cookie and renders appropriately."""
response = client.get('/')
style_cookie = next((cookie for cookie in client.cookie_jar if cookie.name == 'user-style'), None)
assert style_cookie is None
response = client.get('/?style=light')
style_cookie = next((cookie for cookie in client.cookie_jar if cookie.name == 'user-style'), None)
assert response.status_code == 200
assert b'light.css' in response.data
assert b'dark.css' not in response.data
assert style_cookie.value == 'light'
response = client.get('/?style=dark')
style_cookie = next((cookie for cookie in client.cookie_jar if cookie.name == 'user-style'), None)
assert response.status_code == 200
assert b'dark.css' in response.data
assert b'light.css' not in response.data
assert style_cookie.value == 'dark'

View File

@@ -0,0 +1,3 @@
Test page
word --- word

View File

@@ -13,6 +13,32 @@ def test_config():
assert create_app(instance_path=instance_path, test_config={"TESTING": True}).testing assert create_app(instance_path=instance_path, test_config={"TESTING": True}).testing
def test_markdown_meta_extension_always():
"""Test that the markdown meta extension is always loaded, even if not specified."""
app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'MARKDOWN_EXTENSIONS': []})
client = app.test_client()
response = client.get('/')
assert response.status_code == 200
assert b'<title>Index - incorporeal.org</title>' in response.data
def test_extra_markdown_extensions_work():
"""Test we can load more extensions via config, and that they work."""
app = create_app(instance_path=os.path.join(HERE, 'instance'))
client = app.test_client()
response = client.get('/mdash-or-triple-dash')
assert response.status_code == 200
assert b'word --- word' in response.data
app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'MARKDOWN_EXTENSIONS': ['smarty']})
client = app.test_client()
response = client.get('/mdash-or-triple-dash')
assert response.status_code == 200
assert b'word &mdash; word' in response.data
def test_title_override(): def test_title_override():
"""Test that a configuration with a specific title overrides the default.""" """Test that a configuration with a specific title overrides the default."""
instance_path = os.path.join(HERE, 'instance') instance_path = os.path.join(HERE, 'instance')

View File

@@ -1,5 +1,7 @@
"""Unit test helper methods.""" """Unit test helper methods."""
from incorporealcms.pages import generate_parent_navs, is_file_path_actually_dir_path, resolve_page_file from werkzeug.http import dump_cookie
from incorporealcms.pages import generate_parent_navs, is_file_path_actually_dir_path, render, resolve_page_file
def test_resolve_page_file_dir_to_index(): def test_resolve_page_file_dir_to_index():
@@ -74,3 +76,25 @@ def test_is_file_path_actually_dir_path_nonsense_dir_is_no(app):
"""Test that a directory request is a directory request even if the dir doesn't exist.""" """Test that a directory request is a directory request even if the dir doesn't exist."""
with app.app_context(): with app.app_context():
assert not is_file_path_actually_dir_path('/antphnathpnthapnthsnthax/') assert not is_file_path_actually_dir_path('/antphnathpnthapnthsnthax/')
def test_render_with_user_dark_theme(app):
"""Test that a request with the dark theme selected renders the dark theme."""
cookie = dump_cookie("user-style", 'dark')
with app.test_request_context(headers={'COOKIE': cookie}):
assert b'dark.css' in render('base.html').data
assert b'light.css' not in render('base.html').data
def test_render_with_user_light_theme(app):
"""Test that a request with the light theme selected renders the light theme."""
with app.test_request_context():
assert b'light.css' in render('base.html').data
assert b'dark.css' not in render('base.html').data
def test_render_with_no_user_theme(app):
"""Test that a request with no theme set renders the light theme."""
with app.test_request_context():
assert b'light.css' in render('base.html').data
assert b'dark.css' not in render('base.html').data

70
tox.ini
View File

@@ -4,7 +4,7 @@
# and then run "tox" from this directory. # and then run "tox" from this directory.
[tox] [tox]
envlist = py37,security,lint envlist = begin,py37,py38,coverage,security,lint,bundle
[testenv] [testenv]
# build a wheel and test it # build a wheel and test it
@@ -12,45 +12,59 @@ wheel = true
wheel_build_env = build wheel_build_env = build
# whitelist commands we need # whitelist commands we need
whitelist_externals = ln whitelist_externals = cp
# install everything via requirements-dev.txt, so that developer environment
# is the same as the tox environment (for ease of use/no weird gotchas in
# local dev results vs. tox results) and also to avoid ticky-tacky maintenance
# of "oh this particular env has weird results unless I install foo" --- just
# shotgun blast install everything everywhere
deps =
-rrequirements/requirements-dev.txt
[testenv:build] [testenv:build]
# require setuptools when building # require setuptools when building
deps = setuptools deps = setuptools
[testenv:begin]
# clean up potential previous coverage runs
skip_install = true
commands = coverage erase
[testenv:py37] [testenv:py37]
# run pytest and coverage # run pytest with coverage
deps =
pytest
pytest-cov
commands = commands =
pytest --cov={envsitepackagesdir}/incorporealcms/ \ pytest --cov-append --cov={envsitepackagesdir}/incorporealcms/ --cov-branch
--cov-report=term-missing --cov-branch --cov-fail-under=90
ln -sf {distdir} dist [testenv:py38]
# run pytest with coverage
commands =
pytest --cov-append --cov={envsitepackagesdir}/incorporealcms/ --cov-branch
[testenv:coverage]
# report on coverage runs from above
skip_install = true
commands =
coverage report --fail-under=95 --show-missing
[testenv:security] [testenv:security]
# run security checks # run security checks
# #
# again it seems the most valuable here to run against the packaged code # again it seems the most valuable here to run against the packaged code
deps =
bandit
commands = commands =
bandit {envsitepackagesdir}/incorporealcms/ -r bandit {envsitepackagesdir}/incorporealcms/ -r
[testenv:lint] [testenv:lint]
# run style checks # run style checks
skip_install = true
deps =
flake8
flake8-blind-except
flake8-builtins
flake8-docstrings
flake8-executable
flake8-isort
flake8-logging-format
commands = commands =
flake8 flake8
- flake8 --disable-noqa --select=E,W,F,C,D,A,G,B,I - flake8 --disable-noqa --ignore= --select=E,W,F,C,D,A,G,B,I,T,M,DUO
[testenv:bundle]
# take extra actions (build sdist, sphinx, whatever) to completely package the app
commands =
cp -r {distdir} .
python setup.py sdist
[coverage:paths] [coverage:paths]
source = source =
@@ -59,23 +73,29 @@ source =
[coverage:run] [coverage:run]
branch = True branch = True
# redundant with pytest --cov above, but this tricks the coverage.xml report into
# using the full path, otherwise files with the same name in different paths
# get clobbered. maybe appends would fix this, IDK
include =
.tox/**/incorporealcms/
omit = omit =
**/_version.py **/_version.py
[flake8] [flake8]
max-line-length = 120 enable-extensions = G,M
exclude = exclude =
.tox/ .tox/
versioneer.py versioneer.py
_version.py _version.py
instance/ instance/
extend-ignore = T101
max-complexity = 10 max-complexity = 10
max-line-length = 120
[isort] [isort]
line_length = 120 line_length = 120
# only way I could figure out how to make tests/conftest.py happy and have pytest separate from incorporealcms
# and if I let them be combined in tox runs, flake8-isort in vim would complain :(
forced_separate = incorporealcms
[pytest] [pytest]
python_files = python_files =