static site generator part 7? --- refactoring, more tests

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
Brian S. Stephan 2025-03-15 15:22:03 -05:00
parent 76b1800155
commit ca9e6623ff
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
2 changed files with 101 additions and 80 deletions

View File

@ -81,49 +81,72 @@ class StaticSiteGenerator(object):
cprint(f"copying files from '{source_dir}' to '{dest_dir}'", 'green') cprint(f"copying files from '{source_dir}' to '{dest_dir}'", 'green')
os.chdir(source_dir) os.chdir(source_dir)
for base_dir, subdirs, files in os.walk(source_dir): for base_dir, subdirs, files in os.walk(source_dir):
# remove the absolute path of the pages directory from the base_dir # remove the absolute path of the directory from the base_dir
base_dir = os.path.relpath(base_dir, source_dir) base_dir = os.path.relpath(base_dir, source_dir)
# create subdirs seen here for subsequent depth # create subdirs seen here for subsequent depth
for subdir in subdirs: for subdir in subdirs:
dst = os.path.join(dest_dir, base_dir, subdir) self.build_subdir_in_destination(source_dir, base_dir, subdir, dest_dir)
if os.path.islink(os.path.join(base_dir, subdir)):
# keep the link relative to the output directory
src = self.symlink_to_relative_dest(source_dir, os.path.join(base_dir, subdir))
print(f"creating directory symlink '{dst}' -> '{src}'")
os.symlink(src, dst, target_is_directory=True)
else:
print(f"creating directory '{dst}'")
try:
os.mkdir(dst)
except FileExistsError:
# already exists
pass
# process and copy files # process and copy files
for file_ in files: for file_ in files:
dst = os.path.join(dest_dir, base_dir, file_) self.build_file_in_destination(source_dir, base_dir, file_, dest_dir, convert_markdown)
if os.path.islink(os.path.join(base_dir, file_)):
# keep the link relative to the output directory
src = self.symlink_to_relative_dest(source_dir, os.path.join(base_dir, file_))
print(f"creating symlink '{dst}' -> '{src}'")
os.symlink(src, dst, target_is_directory=False)
else:
src = os.path.join(base_dir, file_)
print(f"copying file '{src}' -> '{dst}'")
shutil.copy2(src, dst)
# render markdown as HTML def build_subdir_in_destination(self, source_dir: str, base_dir: str, subdir: str, dest_dir: str) -> None:
if src.endswith('.md') and convert_markdown: """Create a subdir (which might actually be a symlink) in the output dir.
rendered_file = dst.removesuffix('.md') + '.html'
try: Args:
content = handle_markdown_file_path(src) source_dir: the absolute path of the location in the instance, contains subdir
except UnicodeDecodeError: base_dir: the relative path of the location in the instance, contains subdir
# perhaps this isn't a markdown file at all for some reason; we subdir: the subdir in the instance to replicate in the output
# copied it above so stick with tha dest_dir: the output directory to place the subdir in
cprint(f"{src} has invalid bytes! skipping", 'yellow') """
continue dst = os.path.join(dest_dir, base_dir, subdir)
with open(rendered_file, 'w') as dst_file: if os.path.islink(os.path.join(base_dir, subdir)):
dst_file.write(content) # keep the link relative to the output directory
src = self.symlink_to_relative_dest(source_dir, os.path.join(base_dir, subdir))
print(f"creating directory symlink '{dst}' -> '{src}'")
os.symlink(src, dst, target_is_directory=True)
else:
print(f"creating directory '{dst}'")
try:
os.mkdir(dst)
except FileExistsError:
# already exists
pass
def build_file_in_destination(self, source_dir: str, base_dir: str, file_: str, dest_dir: str,
convert_markdown=False) -> None:
"""Create a file (which might actually be a symlink) in the output dir.
Args:
source_dir: the absolute path of the location in the instance, contains subdir
base_dir: the relative path of the location in the instance, contains subdir
file_: the file in the instance to replicate in the output
dest_dir: the output directory to place the subdir in
"""
dst = os.path.join(dest_dir, base_dir, file_)
if os.path.islink(os.path.join(base_dir, file_)):
# keep the link relative to the output directory
src = self.symlink_to_relative_dest(source_dir, os.path.join(base_dir, file_))
print(f"creating symlink '{dst}' -> '{src}'")
os.symlink(src, dst, target_is_directory=False)
else:
src = os.path.join(base_dir, file_)
print(f"copying file '{src}' -> '{dst}'")
shutil.copy2(src, dst)
# render markdown as HTML
if src.endswith('.md') and convert_markdown:
rendered_file = dst.removesuffix('.md') + '.html'
try:
content = handle_markdown_file_path(src)
except UnicodeDecodeError:
# perhaps this isn't a markdown file at all for some reason; we
# copied it above so stick with tha
cprint(f"{src} has invalid bytes! skipping", 'yellow')
else:
with open(rendered_file, 'w') as dst_file:
dst_file.write(content)
def symlink_to_relative_dest(self, base_dir: str, source: str) -> str: def symlink_to_relative_dest(self, base_dir: str, source: str) -> str:
"""Given a symlink, make sure it points to something inside the instance and provide its real destination. """Given a symlink, make sure it points to something inside the instance and provide its real destination.

View File

@ -4,64 +4,62 @@ SPDX-FileCopyrightText: © 2021 Brian S. Stephan <bss@incorporeal.org>
SPDX-License-Identifier: AGPL-3.0-or-later SPDX-License-Identifier: AGPL-3.0-or-later
""" """
import os import os
import tempfile
from incorporealcms import create_app import pytest
from incorporealcms import init_instance
from incorporealcms.ssg import StaticSiteGenerator
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
init_instance(instance_path=os.path.join(HERE, 'instance'),
def app_with_pydot(): extra_config={'MARKDOWN_EXTENSIONS': ['incorporealcms.mdx.pydot', 'incorporealcms.mdx.figures',
"""Create the test app, including the pydot extension.""" 'attr_list']})
return create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'MARKDOWN_EXTENSIONS': ['incorporealcms.mdx.pydot']})
def test_functional_initialization():
"""Test initialization with the graphviz config."""
app = app_with_pydot()
assert app is not None
def test_graphviz_is_rendered(): def test_graphviz_is_rendered():
"""Initialize the app with the graphviz extension and ensure it does something.""" """Initialize the app with the graphviz extension and ensure it does something."""
app = app_with_pydot() with tempfile.TemporaryDirectory() as tmpdir:
client = app.test_client() src_dir = os.path.join(HERE, 'instance')
ssg = StaticSiteGenerator(src_dir, tmpdir)
os.chdir(os.path.join(src_dir, 'pages'))
response = client.get('/test-graphviz') ssg.build_file_in_destination(os.path.join(HERE, 'instance', 'pages'), '', 'test-graphviz.md', tmpdir, True)
assert response.status_code == 200 with open(os.path.join(tmpdir, 'test-graphviz.html'), 'r') as graphviz_output:
assert b'~~~pydot' not in response.data data = graphviz_output.read()
assert b'data:image/png;base64' in response.data assert 'data:image/png;base64' in data
os.chdir(HERE)
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() with tempfile.TemporaryDirectory() as tmpdir:
client = app.test_client() src_dir = os.path.join(HERE, 'instance')
ssg = StaticSiteGenerator(src_dir, tmpdir)
os.chdir(os.path.join(src_dir, 'pages'))
response = client.get('/test-invalid-graphviz') with pytest.raises(ValueError):
assert response.status_code == 500 ssg.build_file_in_destination(os.path.join(HERE, 'instance', 'pages'), '', 'test-invalid-graphviz.md',
assert b'INTERNAL SERVER ERROR' in response.data tmpdir, True)
os.chdir(HERE)
def test_figures_are_rendered(client): def test_figures_are_rendered():
"""Test that a page with my figure syntax renders as expected.""" """Test that a page with my figure syntax renders as expected."""
response = client.get('/figures') with tempfile.TemporaryDirectory() as tmpdir:
assert response.status_code == 200 src_dir = os.path.join(HERE, 'instance')
assert (b'<figure class="right"><img alt="fancy captioned logo" src="bss-square-no-bg.png" />' ssg = StaticSiteGenerator(src_dir, tmpdir)
b'<figcaption>this is my cool logo!</figcaption></figure>') in response.data os.chdir(os.path.join(src_dir, 'pages'))
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 ssg.build_file_in_destination(os.path.join(HERE, 'instance', 'pages'), '', 'figures.md', tmpdir, True)
assert (b'<figure class="left"><img alt="fancy logo" src="bss-square-no-bg.png" />' with open(os.path.join(tmpdir, 'figures.html'), 'r') as graphviz_output:
b'<span></span></figure>') in response.data data = graphviz_output.read()
assert b'<figure><img alt="just a logo" src="bss-square-no-bg.png" /></figure>' in response.data assert ('<figure class="right"><img alt="fancy captioned logo" src="bss-square-no-bg.png" />'
'<figcaption>this is my cool logo!</figcaption></figure>') in data
assert ('<figure><img alt="vanilla captioned logo" src="bss-square-no-bg.png" />'
'<figcaption>this is my cool logo without an attr!</figcaption>\n</figure>') in data
assert ('<figure class="left"><img alt="fancy logo" src="bss-square-no-bg.png" />'
'<span></span></figure>') in data
assert '<figure><img alt="just a logo" src="bss-square-no-bg.png" /></figure>' in data
os.chdir(HERE)