Compare commits
No commits in common. "master" and "v2.0.5" have entirely different histories.
29
CHANGELOG.md
29
CHANGELOG.md
@ -2,35 +2,6 @@
|
||||
|
||||
Included is a summary of changes to the project, by version. Details can be found in the commit history.
|
||||
|
||||
## v2.1.1
|
||||
|
||||
### Improvements
|
||||
|
||||
* Use the h1-as-name feature from v2.1.0 also to generate the page name in breadcrumbs. This changes the behavior on
|
||||
pages with an h1 but no Title: meta tag to have a better name, of course, but also changes the behavior on pages with
|
||||
neither a h1 nor a Title: meta tag to have a leading slash (e.g. /page-filename) where there previously was not one
|
||||
(e.g. just page-filename). This seems like an acceptable trade-off.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* With the minor breadcrumb change, a method used to finagle the breadcrumb no-name name is no longer necessary.
|
||||
|
||||
## v2.1.0
|
||||
|
||||
### Features
|
||||
|
||||
* The page title (also used in the `og:title` header) and the optional description used in the `og:description` header
|
||||
can be derived from the contents of the page content, if the markdown meta tags are not supplied. The first `h1` is
|
||||
used for the title, and the first `p` is used for the description. This is largely to save some time writing pages
|
||||
that one wants to look nice, especially in a social media card, and removes some repetition.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Requirements bumped, which led to...
|
||||
* Python 3.9 has been removed from the supported versions.
|
||||
* Added some miscellaneous unit tests and coverage changes to keep us at 95% (which only dropped for a library reason I
|
||||
don't understand).
|
||||
|
||||
## v2.0.5
|
||||
|
||||
### Features
|
||||
|
||||
@ -7,8 +7,8 @@ A lightweight static site generator for Markdown-based sites.
|
||||
Something like the following should suffice:
|
||||
|
||||
```
|
||||
% virtualenv --python=python3.10 env-py3.10
|
||||
% source env-py3.10/bin/activate
|
||||
% virtualenv --python=python3.9 env-py3.9
|
||||
% source env-py3.9/bin/activate
|
||||
% pip install -U pip
|
||||
% pip install incorporeal-cms
|
||||
% incorporealcms-build ./path/to/instance ./path/to/output/www/root
|
||||
|
||||
@ -22,7 +22,7 @@ jinja_env = Environment(
|
||||
try:
|
||||
# packaged/pip install -e . value
|
||||
from ._version import version as __version__
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError:
|
||||
# local clone value
|
||||
from setuptools_scm import get_version
|
||||
__version__ = get_version(root='..', relative_to=__file__)
|
||||
|
||||
@ -51,7 +51,7 @@ def generate_feed(feed_type: str, instance_dir: str, dest_dir: str) -> None:
|
||||
# get the actual file to parse it
|
||||
resolved_path = os.path.relpath(os.path.realpath(feed_entry_path), pages_dir)
|
||||
try:
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(pages_dir, resolved_path), pages_dir)
|
||||
content, md, page_name, page_title, mtime = parse_md(os.path.join(pages_dir, resolved_path), pages_dir)
|
||||
link = f'https://{Config.DOMAIN_NAME}{instance_resource_path_to_request_path(resolved_path)}'
|
||||
except (OSError, ValueError, TypeError):
|
||||
logger.exception("error loading/rendering markdown!")
|
||||
|
||||
@ -13,7 +13,6 @@ import os
|
||||
import re
|
||||
|
||||
import markdown
|
||||
from bs4 import BeautifulSoup
|
||||
from markupsafe import Markup
|
||||
|
||||
from incorporealcms import jinja_env
|
||||
@ -83,42 +82,11 @@ def parse_md(path: str, pages_root: str):
|
||||
logger.debug("file metadata: %s", md.Meta)
|
||||
|
||||
rel_path = os.path.relpath(path, pages_root)
|
||||
|
||||
page_name, page_description = _get_metadata_from_parsed_page(md, content, rel_path)
|
||||
page_name = get_meta_str(md, 'title') if md.Meta.get('title') else instance_resource_path_to_request_path(rel_path)
|
||||
page_title = f'{page_name} - {Config.TITLE_SUFFIX}' if page_name else Config.TITLE_SUFFIX
|
||||
logger.debug("title (potentially derived): %s", page_title)
|
||||
|
||||
return content, md, page_name, page_title, page_description, mtime
|
||||
|
||||
|
||||
def _get_metadata_from_parsed_page(md, content, path: str):
|
||||
"""Get the page name and description from a Markdown object and/or HTML output of a page.
|
||||
|
||||
Args:
|
||||
md: the parsed Markdown object, potentially including Meta tags
|
||||
content: the Markdown page content converted to HTML, to run through BeautifulSoup
|
||||
path: path of the page, to derive the name from as a fallback
|
||||
"""
|
||||
soup = BeautifulSoup(content, features='lxml')
|
||||
|
||||
# get the page title first from the markdown tags, second from the first h1, last from the path
|
||||
page_name = None
|
||||
if md.Meta.get('title'):
|
||||
page_name = get_meta_str(md, 'title')
|
||||
elif h1_tag := soup.find('h1'):
|
||||
page_name = h1_tag.string
|
||||
elif not page_name:
|
||||
page_name = instance_resource_path_to_request_path(path)
|
||||
|
||||
# get the page description from the markdown tags or first paragraph
|
||||
page_description = None
|
||||
if md.Meta.get('description'):
|
||||
page_description = get_meta_str(md, 'description')
|
||||
elif p_tag := soup.find('p'):
|
||||
if page_description := p_tag.string:
|
||||
page_description = page_description.replace('\n', ' ')
|
||||
|
||||
return page_name, page_description
|
||||
return content, md, page_name, page_title, mtime
|
||||
|
||||
|
||||
def handle_markdown_file_path(path: str, pages_root: str) -> str:
|
||||
@ -129,7 +97,7 @@ def handle_markdown_file_path(path: str, pages_root: str) -> str:
|
||||
pages_root: the absolute path to the pages/ dir, which the path should be within. necessary for
|
||||
proper resolution of resolving parent pages (which needs to know when to stop)
|
||||
"""
|
||||
content, md, page_name, page_title, page_description, mtime = parse_md(path, pages_root)
|
||||
content, md, page_name, page_title, mtime = parse_md(path, pages_root)
|
||||
relative_path = os.path.relpath(path, pages_root)
|
||||
parent_navs = generate_parent_navs(relative_path, pages_root)
|
||||
extra_footer = get_meta_str(md, 'footer') if md.Meta.get('footer') else None
|
||||
@ -143,7 +111,7 @@ def handle_markdown_file_path(path: str, pages_root: str) -> str:
|
||||
template = jinja_env.get_template(template_name)
|
||||
return template.render(title=page_title,
|
||||
config=Config,
|
||||
description=page_description,
|
||||
description=get_meta_str(md, 'description'),
|
||||
image=Config.BASE_HOST + get_meta_str(md, 'image'),
|
||||
content=content,
|
||||
base_url=Config.BASE_HOST + instance_resource_path_to_request_path(relative_path),
|
||||
@ -187,8 +155,16 @@ def generate_parent_navs(path, pages_root: str):
|
||||
try:
|
||||
with open(os.path.join(pages_root, path), 'r') as entry_file:
|
||||
entry = entry_file.read()
|
||||
content = Markup(md.convert(entry)) # nosec B704
|
||||
page_name, _ = _get_metadata_from_parsed_page(md, content, os.path.relpath(path, parent_resource_dir))
|
||||
_ = Markup(md.convert(entry)) # nosec B704
|
||||
page_name = (" ".join(md.Meta.get('title')) if md.Meta.get('title')
|
||||
else request_path_to_breadcrumb_display(request_path))
|
||||
return generate_parent_navs(parent_resource_path, pages_root) + [(page_name, request_path)]
|
||||
except FileNotFoundError:
|
||||
return generate_parent_navs(parent_resource_path, pages_root) + [(request_path, request_path)]
|
||||
|
||||
|
||||
def request_path_to_breadcrumb_display(path):
|
||||
"""Given a request path, e.g. "/foo/bar/baz/", turn it into breadcrumby text "baz"."""
|
||||
undired = path.rstrip('/')
|
||||
leaf = undired[undired.rfind('/'):]
|
||||
return leaf.strip('/')
|
||||
|
||||
@ -10,8 +10,8 @@ license = {text = "GPL-3.0-or-later"}
|
||||
authors = [
|
||||
{name = "Brian S. Stephan", email = "bss@incorporeal.org"},
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
dependencies = ["beautifulsoup4", "feedgen", "jinja2", "Markdown", "termcolor"]
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["feedgen", "jinja2", "Markdown", "termcolor"]
|
||||
dynamic = ["version"]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
|
||||
@ -6,36 +6,35 @@
|
||||
#
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.12.1
|
||||
# via httpx
|
||||
attrs==25.4.0
|
||||
attrs==25.3.0
|
||||
# via reuse
|
||||
authlib==1.6.6
|
||||
authlib==1.5.1
|
||||
# via safety
|
||||
bandit==1.9.3
|
||||
bandit==1.8.3
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
beautifulsoup4==4.14.3
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
boolean-py==5.0
|
||||
# via license-expression
|
||||
build==1.4.0
|
||||
# via pip-tools
|
||||
cachetools==6.2.6
|
||||
# via tox
|
||||
certifi==2026.1.4
|
||||
binaryornot==0.4.4
|
||||
# via reuse
|
||||
boolean-py==4.0
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
cffi==2.0.0
|
||||
# license-expression
|
||||
# reuse
|
||||
build==1.2.2.post1
|
||||
# via pip-tools
|
||||
cachetools==5.5.2
|
||||
# via tox
|
||||
certifi==2025.8.3
|
||||
# via requests
|
||||
cffi==1.17.1
|
||||
# via cryptography
|
||||
chardet==5.2.0
|
||||
# via tox
|
||||
charset-normalizer==3.4.4
|
||||
# via
|
||||
# binaryornot
|
||||
# tox
|
||||
charset-normalizer==3.4.1
|
||||
# via
|
||||
# python-debian
|
||||
# requests
|
||||
click==8.3.1
|
||||
click==8.1.8
|
||||
# via
|
||||
# nltk
|
||||
# pip-tools
|
||||
@ -44,17 +43,17 @@ click==8.3.1
|
||||
# typer
|
||||
colorama==0.4.6
|
||||
# via tox
|
||||
coverage[toml]==7.13.2
|
||||
coverage[toml]==7.7.0
|
||||
# via pytest-cov
|
||||
cryptography==46.0.4
|
||||
cryptography==44.0.2
|
||||
# via
|
||||
# authlib
|
||||
# secretstorage
|
||||
distlib==0.4.0
|
||||
distlib==0.3.9
|
||||
# via virtualenv
|
||||
dlint==0.16.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
docutils==0.22.4
|
||||
docutils==0.21.2
|
||||
# via readme-renderer
|
||||
dparse==0.6.4
|
||||
# via
|
||||
@ -62,12 +61,12 @@ dparse==0.6.4
|
||||
# safety-schemas
|
||||
feedgen==1.0.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
filelock==3.20.3
|
||||
filelock==3.16.1
|
||||
# via
|
||||
# safety
|
||||
# tox
|
||||
# virtualenv
|
||||
flake8==7.3.0
|
||||
flake8==7.1.2
|
||||
# via
|
||||
# dlint
|
||||
# flake8-builtins
|
||||
@ -79,7 +78,7 @@ flake8==7.3.0
|
||||
# incorporeal-cms (pyproject.toml)
|
||||
flake8-blind-except==0.2.1
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-builtins==3.1.0
|
||||
flake8-builtins==2.5.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-docstrings==1.7.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
@ -87,36 +86,27 @@ flake8-executable==2.1.3
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-fixme==1.1.1
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-isort==7.0.0
|
||||
flake8-isort==6.1.2
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-logging-format==2024.24.12
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-mutable==1.2.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
flake8-pyproject==1.2.4
|
||||
flake8-pyproject==1.2.3
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
h11==0.16.0
|
||||
# via httpcore
|
||||
httpcore==1.0.9
|
||||
# via httpx
|
||||
httpx==0.28.1
|
||||
# via safety
|
||||
id==1.5.0
|
||||
# via twine
|
||||
idna==3.11
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
iniconfig==2.3.0
|
||||
idna==3.10
|
||||
# via requests
|
||||
iniconfig==2.0.0
|
||||
# via pytest
|
||||
isort==7.0.0
|
||||
isort==6.0.1
|
||||
# via flake8-isort
|
||||
jaraco-classes==3.4.0
|
||||
# via keyring
|
||||
jaraco-context==6.1.0
|
||||
jaraco-context==6.0.1
|
||||
# via keyring
|
||||
jaraco-functools==4.4.0
|
||||
jaraco-functools==4.1.0
|
||||
# via keyring
|
||||
jeepney==0.9.0
|
||||
# via
|
||||
@ -127,44 +117,43 @@ jinja2==3.1.6
|
||||
# incorporeal-cms (pyproject.toml)
|
||||
# reuse
|
||||
# safety
|
||||
joblib==1.5.3
|
||||
joblib==1.4.2
|
||||
# via nltk
|
||||
keyring==25.7.0
|
||||
keyring==25.6.0
|
||||
# via twine
|
||||
librt==0.7.8
|
||||
# via mypy
|
||||
license-expression==30.4.4
|
||||
license-expression==30.4.1
|
||||
# via reuse
|
||||
lxml==6.0.2
|
||||
lxml==5.3.1
|
||||
# via feedgen
|
||||
markdown==3.10.1
|
||||
markdown==3.7
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
markdown-it-py==4.0.0
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
markupsafe==3.0.3
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
marshmallow==4.2.1
|
||||
marshmallow==3.26.1
|
||||
# via safety
|
||||
mccabe==0.7.0
|
||||
# via flake8
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
more-itertools==10.8.0
|
||||
more-itertools==10.6.0
|
||||
# via
|
||||
# jaraco-classes
|
||||
# jaraco-functools
|
||||
mypy==1.19.1
|
||||
mypy==1.15.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
mypy-extensions==1.1.0
|
||||
mypy-extensions==1.0.0
|
||||
# via mypy
|
||||
nh3==0.3.2
|
||||
nh3==0.2.21
|
||||
# via readme-renderer
|
||||
nltk==3.9.2
|
||||
nltk==3.9.1
|
||||
# via safety
|
||||
packaging==26.0
|
||||
packaging==24.2
|
||||
# via
|
||||
# build
|
||||
# dparse
|
||||
# marshmallow
|
||||
# pyproject-api
|
||||
# pytest
|
||||
# safety
|
||||
@ -172,66 +161,63 @@ packaging==26.0
|
||||
# setuptools-scm
|
||||
# tox
|
||||
# twine
|
||||
# wheel
|
||||
pathspec==1.0.4
|
||||
# via mypy
|
||||
pip-tools==7.5.2
|
||||
pbr==6.1.1
|
||||
# via stevedore
|
||||
pip-tools==7.4.1
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
platformdirs==4.5.1
|
||||
platformdirs==4.3.6
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
pluggy==1.6.0
|
||||
pluggy==1.5.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-cov
|
||||
# tox
|
||||
pycodestyle==2.14.0
|
||||
psutil==6.1.1
|
||||
# via safety
|
||||
pycodestyle==2.12.1
|
||||
# via flake8
|
||||
pycparser==3.0
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydantic==2.12.5
|
||||
pydantic==2.9.2
|
||||
# via
|
||||
# safety
|
||||
# safety-schemas
|
||||
pydantic-core==2.41.5
|
||||
pydantic-core==2.23.4
|
||||
# via pydantic
|
||||
pydocstyle==6.3.0
|
||||
# via flake8-docstrings
|
||||
pydot==4.0.1
|
||||
pydot==3.0.4
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
pyflakes==3.4.0
|
||||
pyflakes==3.2.0
|
||||
# via flake8
|
||||
pygments==2.19.2
|
||||
pygments==2.19.1
|
||||
# via
|
||||
# pytest
|
||||
# readme-renderer
|
||||
# rich
|
||||
pyparsing==3.3.2
|
||||
pyparsing==3.2.1
|
||||
# via pydot
|
||||
pyproject-api==1.10.0
|
||||
pyproject-api==1.9.0
|
||||
# via tox
|
||||
pyproject-hooks==1.2.0
|
||||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
pytest==9.0.2
|
||||
pytest==8.3.5
|
||||
# via
|
||||
# incorporeal-cms (pyproject.toml)
|
||||
# pytest-cov
|
||||
pytest-cov==7.0.0
|
||||
pytest-cov==6.0.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
python-dateutil==2.9.0.post0
|
||||
# via feedgen
|
||||
python-debian==1.0.1
|
||||
# via reuse
|
||||
python-magic==0.4.27
|
||||
# via reuse
|
||||
pyyaml==6.0.3
|
||||
pyyaml==6.0.2
|
||||
# via bandit
|
||||
readme-renderer==44.0
|
||||
# via twine
|
||||
regex==2026.1.15
|
||||
regex==2025.9.1
|
||||
# via nltk
|
||||
requests==2.32.5
|
||||
# via
|
||||
@ -241,72 +227,62 @@ requests==2.32.5
|
||||
# twine
|
||||
requests-toolbelt==1.0.0
|
||||
# via twine
|
||||
reuse==6.2.0
|
||||
reuse==5.0.2
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
rfc3986==2.0.0
|
||||
# via twine
|
||||
rich==14.3.1
|
||||
rich==13.9.4
|
||||
# via
|
||||
# bandit
|
||||
# twine
|
||||
# typer
|
||||
ruamel-yaml==0.19.1
|
||||
ruamel-yaml==0.18.10
|
||||
# via
|
||||
# safety
|
||||
# safety-schemas
|
||||
safety==3.7.0
|
||||
safety==3.3.1
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
safety-schemas==0.0.16
|
||||
safety-schemas==0.0.11
|
||||
# via safety
|
||||
secretstorage==3.5.0
|
||||
secretstorage==3.3.3
|
||||
# via keyring
|
||||
setuptools-scm==9.2.2
|
||||
setuptools-scm==8.2.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
shellingham==1.5.4
|
||||
# via typer
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
snowballstemmer==3.0.1
|
||||
snowballstemmer==2.2.0
|
||||
# via pydocstyle
|
||||
soupsieve==2.8.3
|
||||
# via beautifulsoup4
|
||||
stevedore==5.6.0
|
||||
stevedore==5.4.1
|
||||
# via bandit
|
||||
tenacity==9.1.2
|
||||
# via safety
|
||||
termcolor==3.3.0
|
||||
termcolor==2.5.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
tomlkit==0.14.0
|
||||
# via
|
||||
# reuse
|
||||
# safety
|
||||
tox==4.34.1
|
||||
tomlkit==0.13.2
|
||||
# via reuse
|
||||
tox==4.24.2
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
tqdm==4.67.1
|
||||
# via nltk
|
||||
twine==6.2.0
|
||||
twine==6.1.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
typer==0.21.1
|
||||
typer==0.15.2
|
||||
# via safety
|
||||
typing-extensions==4.15.0
|
||||
typing-extensions==4.12.2
|
||||
# via
|
||||
# beautifulsoup4
|
||||
# mypy
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# safety
|
||||
# safety-schemas
|
||||
# typer
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
urllib3==2.6.3
|
||||
urllib3==2.5.0
|
||||
# via
|
||||
# requests
|
||||
# twine
|
||||
virtualenv==20.36.1
|
||||
virtualenv==20.29.3
|
||||
# via tox
|
||||
wheel==0.46.3
|
||||
wheel==0.45.1
|
||||
# via pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
|
||||
@ -1,28 +1,22 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# This file is autogenerated by pip-compile with Python 3.12
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --output-file=requirements/requirements.txt
|
||||
#
|
||||
beautifulsoup4==4.14.3
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
feedgen==1.0.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
jinja2==3.1.6
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
lxml==6.0.2
|
||||
lxml==5.3.1
|
||||
# via feedgen
|
||||
markdown==3.10.1
|
||||
markdown==3.7
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
markupsafe==3.0.3
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
python-dateutil==2.9.0.post0
|
||||
# via feedgen
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
soupsieve==2.8.3
|
||||
# via beautifulsoup4
|
||||
termcolor==3.3.0
|
||||
termcolor==2.5.0
|
||||
# via incorporeal-cms (pyproject.toml)
|
||||
typing-extensions==4.15.0
|
||||
# via beautifulsoup4
|
||||
|
||||
@ -1 +0,0 @@
|
||||
there's just some words here but no title tag or h1
|
||||
@ -1 +0,0 @@
|
||||
there's just some words here but no title tag or h1
|
||||
@ -1 +0,0 @@
|
||||
there's just some words here but no title tag or h1
|
||||
@ -1,6 +0,0 @@
|
||||
# rambling test for inferred description
|
||||
|
||||
this is a long string of text where
|
||||
I am typing a lot over multiple lines
|
||||
|
||||
this second paragraph shouldn't be in the metadata
|
||||
@ -10,7 +10,8 @@ import pytest
|
||||
|
||||
from incorporealcms import init_instance
|
||||
from incorporealcms.markdown import (generate_parent_navs, handle_markdown_file_path,
|
||||
instance_resource_path_to_request_path, parse_md)
|
||||
instance_resource_path_to_request_path, parse_md,
|
||||
request_path_to_breadcrumb_display)
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
INSTANCE_DIR = os.path.join(HERE, 'instance')
|
||||
@ -25,21 +26,14 @@ def test_generate_page_navs_index():
|
||||
assert generate_parent_navs('index.md', PAGES_DIR) == [('example.org', '/')]
|
||||
|
||||
|
||||
def test_generate_page_navs_title_from_h1():
|
||||
"""Test that the index page has navs to the root (itself)."""
|
||||
assert generate_parent_navs('no-title.md', PAGES_DIR) == [('example.org', '/'),
|
||||
('this page doesn\'t have a title!', '/no-title')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_index():
|
||||
"""Test that dir pages have navs to the root and themselves."""
|
||||
assert generate_parent_navs('subdir/index.md', PAGES_DIR) == [('example.org', '/'), ('another page', '/subdir/')]
|
||||
assert generate_parent_navs('subdir/index.md', PAGES_DIR) == [('example.org', '/'), ('subdir', '/subdir/')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_real_page():
|
||||
"""Test that real pages have navs to the root, their parent, and themselves."""
|
||||
assert generate_parent_navs('subdir/page.md', PAGES_DIR) == [('example.org', '/'),
|
||||
('another page', '/subdir/'),
|
||||
assert generate_parent_navs('subdir/page.md', PAGES_DIR) == [('example.org', '/'), ('subdir', '/subdir/'),
|
||||
('Page', '/subdir/page')]
|
||||
|
||||
|
||||
@ -48,7 +42,7 @@ def test_generate_page_navs_subdir_with_title_parsing_real_page():
|
||||
assert generate_parent_navs('subdir-with-title/page.md', PAGES_DIR) == [
|
||||
('example.org', '/'),
|
||||
('SUB!', '/subdir-with-title/'),
|
||||
('/page', '/subdir-with-title/page')
|
||||
('page', '/subdir-with-title/page')
|
||||
]
|
||||
|
||||
|
||||
@ -57,7 +51,7 @@ def test_generate_page_navs_subdir_with_no_index():
|
||||
assert generate_parent_navs('no-index-dir/page.md', PAGES_DIR) == [
|
||||
('example.org', '/'),
|
||||
('/no-index-dir/', '/no-index-dir/'),
|
||||
('/page', '/no-index-dir/page')
|
||||
('page', '/no-index-dir/page')
|
||||
]
|
||||
|
||||
|
||||
@ -129,79 +123,51 @@ def test_instance_resource_path_to_request_path_on_subdir_and_page():
|
||||
assert instance_resource_path_to_request_path('subdir/page.md') == '/subdir/page'
|
||||
|
||||
|
||||
def test_request_path_to_breadcrumb_display_patterns():
|
||||
"""Test various conversions from request path to leaf nodes for display in the breadcrumbs."""
|
||||
assert request_path_to_breadcrumb_display('/foo') == 'foo'
|
||||
assert request_path_to_breadcrumb_display('/foo/') == 'foo'
|
||||
assert request_path_to_breadcrumb_display('/foo/bar') == 'bar'
|
||||
assert request_path_to_breadcrumb_display('/foo/bar/') == 'bar'
|
||||
assert request_path_to_breadcrumb_display('/') == ''
|
||||
|
||||
|
||||
def test_parse_md_metadata():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
content, md, page_name, page_title, page_desc, mtime = parse_md(
|
||||
os.path.join(PAGES_DIR, 'more-metadata.md'),
|
||||
PAGES_DIR
|
||||
)
|
||||
content, md, page_name, page_title, mtime = parse_md(os.path.join(PAGES_DIR, 'more-metadata.md'), PAGES_DIR)
|
||||
assert page_name == 'title for the page'
|
||||
assert page_title == 'title for the page - example.org'
|
||||
assert page_desc == 'description of this page made even longer'
|
||||
|
||||
|
||||
def test_parse_md_metadata_forced_no_title():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR, 'forced-no-title.md'), PAGES_DIR)
|
||||
content, md, page_name, page_title, mtime = parse_md(os.path.join(PAGES_DIR, 'forced-no-title.md'), PAGES_DIR)
|
||||
assert page_name == ''
|
||||
assert page_title == 'example.org'
|
||||
|
||||
|
||||
def test_parse_md_metadata_no_title_so_h1():
|
||||
def test_parse_md_metadata_no_title_so_path():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR, 'subdir/index.md'), PAGES_DIR)
|
||||
assert page_name == 'another page'
|
||||
assert page_title == 'another page - example.org'
|
||||
|
||||
|
||||
def test_parse_md_metadata_no_title_or_h1_so_path():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR, 'no-title-or-h1.md'), PAGES_DIR)
|
||||
assert page_name == '/no-title-or-h1'
|
||||
assert page_title == '/no-title-or-h1 - example.org'
|
||||
|
||||
|
||||
def test_parse_md_metadata_no_title_or_h1_so_path_dir():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR, 'no-title-subdir/index.md'),
|
||||
PAGES_DIR)
|
||||
assert page_name == '/no-title-subdir/'
|
||||
assert page_title == '/no-title-subdir/ - example.org'
|
||||
|
||||
|
||||
def test_parse_md_metadata_no_title_or_h1_so_path_dir_file():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR,
|
||||
'no-title-subdir/no-title-or-h1.md'),
|
||||
PAGES_DIR)
|
||||
assert page_name == '/no-title-subdir/no-title-or-h1'
|
||||
assert page_title == '/no-title-subdir/no-title-or-h1 - example.org'
|
||||
|
||||
|
||||
def test_parse_md_derive_description_from_p():
|
||||
"""Test that we can get a description from the first paragraph in the file."""
|
||||
content, md, page_name, page_title, page_desc, mtime = parse_md(
|
||||
os.path.join(PAGES_DIR, 'rambling.md'),
|
||||
PAGES_DIR
|
||||
)
|
||||
assert page_desc == 'this is a long string of text where I am typing a lot over multiple lines'
|
||||
content, md, page_name, page_title, mtime = parse_md(os.path.join(PAGES_DIR, 'subdir/index.md'), PAGES_DIR)
|
||||
assert page_name == '/subdir/'
|
||||
assert page_title == '/subdir/ - example.org'
|
||||
|
||||
|
||||
def test_parse_md_no_file():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
with pytest.raises(FileNotFoundError):
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR, 'nope.md'), PAGES_DIR)
|
||||
content, md, page_name, page_title, mtime = parse_md(os.path.join(PAGES_DIR, 'nope.md'), PAGES_DIR)
|
||||
|
||||
|
||||
def test_parse_md_bad_file():
|
||||
"""Test the direct results of parsing a markdown file."""
|
||||
with pytest.raises(ValueError):
|
||||
content, md, page_name, page_title, _, mtime = parse_md(os.path.join(PAGES_DIR, 'actually-a-png.md'), PAGES_DIR)
|
||||
content, md, page_name, page_title, mtime = parse_md(os.path.join(PAGES_DIR, 'actually-a-png.md'), PAGES_DIR)
|
||||
|
||||
|
||||
def test_md_extension_in_source_link_is_stripped():
|
||||
"""Test that if a foo.md file link is specified in the Markdown, it is foo in the HTML."""
|
||||
content, _, _, _, _, _ = parse_md(os.path.join(PAGES_DIR, 'file-with-md-link.md'), PAGES_DIR)
|
||||
content, _, _, _, _ = parse_md(os.path.join(PAGES_DIR, 'file-with-md-link.md'), PAGES_DIR)
|
||||
assert '<a href="foo">Foo</a>' in content
|
||||
assert '<a href="foo#anchor">Anchored Foo</a>' in content
|
||||
assert '<a href="sub/foo">Sub Foo</a>' in content
|
||||
@ -210,7 +176,7 @@ def test_md_extension_in_source_link_is_stripped():
|
||||
|
||||
def test_index_in_source_link_is_stripped():
|
||||
"""Test that if a index.md file link is specified in the Markdown, it is just the dir in the HTML."""
|
||||
content, _, _, _, _, _ = parse_md(os.path.join(PAGES_DIR, 'file-with-index.md-link.md'), PAGES_DIR)
|
||||
content, _, _, _, _ = parse_md(os.path.join(PAGES_DIR, 'file-with-index.md-link.md'), PAGES_DIR)
|
||||
assert '<a href="cool/">Cool</a>' in content
|
||||
assert '<a href="cool/#anchor">Anchored Cool</a>' in content
|
||||
assert '<a href=".">This Index</a>' in content
|
||||
|
||||
@ -129,26 +129,3 @@ def test_build_in_destination_ignores_dot_files():
|
||||
generator.build_in_destination(os.path.join(src_dir, 'pages'), tmpdir)
|
||||
|
||||
assert not os.path.exists(os.path.join(tmpdir, '.ignored-file.md'))
|
||||
|
||||
|
||||
def test_build():
|
||||
"""Test that the high level build can work against two directories."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
src_dir = os.path.join(HERE, 'instance')
|
||||
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||
generator.build()
|
||||
|
||||
assert os.path.exists(os.path.join(tmpdir, 'index.md'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'index.html'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'subdir', 'index.md'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'subdir', 'index.html'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'symlink-to-subdir'))
|
||||
assert os.path.isdir(os.path.join(tmpdir, 'symlink-to-subdir'))
|
||||
assert os.path.islink(os.path.join(tmpdir, 'symlink-to-subdir'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'media'))
|
||||
assert os.path.isdir(os.path.join(tmpdir, 'media'))
|
||||
assert not os.path.exists(os.path.join(tmpdir, '.ignored-file.md'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'feed'))
|
||||
assert os.path.isdir(os.path.join(tmpdir, 'feed'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'feed/atom'))
|
||||
assert os.path.exists(os.path.join(tmpdir, 'feed/rss'))
|
||||
|
||||
7
tox.ini
7
tox.ini
@ -5,7 +5,7 @@
|
||||
|
||||
[tox]
|
||||
isolated_build = true
|
||||
envlist = begin,py310,py311,py312,py313,coverage,security,lint,reuse
|
||||
envlist = begin,py39,py310,py311,py312,py313,coverage,security,lint,reuse
|
||||
|
||||
[testenv]
|
||||
allow_externals = pytest, coverage
|
||||
@ -21,6 +21,11 @@ deps = setuptools
|
||||
skip_install = true
|
||||
commands = coverage erase
|
||||
|
||||
[testenv:py39]
|
||||
# run pytest with coverage
|
||||
commands =
|
||||
pytest --cov-append --cov={envsitepackagesdir}/incorporealcms/ --cov-branch
|
||||
|
||||
[testenv:py310]
|
||||
# run pytest with coverage
|
||||
commands =
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user