From ca9e6623ff468e24cab889856ab4deda30ef4f10 Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Sat, 15 Mar 2025 15:22:03 -0500 Subject: [PATCH] static site generator part 7? --- refactoring, more tests Signed-off-by: Brian S. Stephan --- incorporealcms/ssg.py | 95 +++++++++++++++++++----------- tests/functional_markdown_tests.py | 86 +++++++++++++-------------- 2 files changed, 101 insertions(+), 80 deletions(-) diff --git a/incorporealcms/ssg.py b/incorporealcms/ssg.py index 2122f36..7770a59 100644 --- a/incorporealcms/ssg.py +++ b/incorporealcms/ssg.py @@ -81,49 +81,72 @@ class StaticSiteGenerator(object): cprint(f"copying files from '{source_dir}' to '{dest_dir}'", 'green') os.chdir(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) # create subdirs seen here for subsequent depth for subdir in subdirs: - dst = os.path.join(dest_dir, base_dir, subdir) - 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 + self.build_subdir_in_destination(source_dir, base_dir, subdir, dest_dir) # process and copy files for file_ in files: - 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) + self.build_file_in_destination(source_dir, base_dir, file_, dest_dir, convert_markdown) - # 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') - continue - with open(rendered_file, 'w') as dst_file: - dst_file.write(content) + def build_subdir_in_destination(self, source_dir: str, base_dir: str, subdir: str, dest_dir: str) -> None: + """Create a subdir (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 + subdir: the subdir 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, subdir) + 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 + + 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: """Given a symlink, make sure it points to something inside the instance and provide its real destination. diff --git a/tests/functional_markdown_tests.py b/tests/functional_markdown_tests.py index 42931bb..fce2465 100644 --- a/tests/functional_markdown_tests.py +++ b/tests/functional_markdown_tests.py @@ -4,64 +4,62 @@ SPDX-FileCopyrightText: © 2021 Brian S. Stephan SPDX-License-Identifier: AGPL-3.0-or-later """ 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__)) - -def app_with_pydot(): - """Create the test app, including the pydot extension.""" - 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 +init_instance(instance_path=os.path.join(HERE, 'instance'), + extra_config={'MARKDOWN_EXTENSIONS': ['incorporealcms.mdx.pydot', 'incorporealcms.mdx.figures', + 'attr_list']}) def test_graphviz_is_rendered(): """Initialize the app with the graphviz extension and ensure it does something.""" - app = app_with_pydot() - client = app.test_client() + with tempfile.TemporaryDirectory() as tmpdir: + 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') - assert response.status_code == 200 - assert b'~~~pydot' not 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 + ssg.build_file_in_destination(os.path.join(HERE, 'instance', 'pages'), '', 'test-graphviz.md', tmpdir, True) + with open(os.path.join(tmpdir, 'test-graphviz.html'), 'r') as graphviz_output: + data = graphviz_output.read() + assert 'data:image/png;base64' in data + os.chdir(HERE) def test_invalid_graphviz_is_not_rendered(): """Check that invalid graphviz doesn't blow things up.""" - app = app_with_pydot() - client = app.test_client() + with tempfile.TemporaryDirectory() as tmpdir: + 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') - assert response.status_code == 500 - assert b'INTERNAL SERVER ERROR' in response.data + with pytest.raises(ValueError): + ssg.build_file_in_destination(os.path.join(HERE, 'instance', 'pages'), '', 'test-invalid-graphviz.md', + 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.""" - response = client.get('/figures') - assert response.status_code == 200 - assert (b'
fancy captioned logo' - b'
this is my cool logo!
') in response.data - assert (b'
vanilla captioned logo' - b'
this is my cool logo without an attr!
\n
') in response.data - assert (b'
fancy logo' - b'
') in response.data - assert b'
just a logo
' in response.data + with tempfile.TemporaryDirectory() as tmpdir: + src_dir = os.path.join(HERE, 'instance') + ssg = StaticSiteGenerator(src_dir, tmpdir) + os.chdir(os.path.join(src_dir, 'pages')) + + ssg.build_file_in_destination(os.path.join(HERE, 'instance', 'pages'), '', 'figures.md', tmpdir, True) + with open(os.path.join(tmpdir, 'figures.html'), 'r') as graphviz_output: + data = graphviz_output.read() + assert ('
fancy captioned logo' + '
this is my cool logo!
') in data + assert ('
vanilla captioned logo' + '
this is my cool logo without an attr!
\n
') in data + assert ('
fancy logo' + '
') in data + assert '
just a logo
' in data + os.chdir(HERE)