Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ test = [
"pytest-env==1.1.5",
"vcrpy==7.0.0; python_version >='3.10'",
"mock==5.2.0",
"responses==0.25.8",
]
dev = [
{include-group = "test"},
Expand Down
10 changes: 7 additions & 3 deletions ricecooker/utils/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,12 @@ def download_assets(
# if we're really stuck, just default to HTML as that is most likely if this is a redirect.
if not ext:
ext = ".html"
subpath = os.path.dirname(filename)
filename = "index{}".format(ext)

subpath = filename
# Add the existing filename in front of index.xxx, this can contain slashes and those will result
# in subdirectories created in the downloaded version. This ensures multiple instances of extensionless
# resources referenced from a page won't clobber each other.
filename = filename + "/index{}".format(ext)

os.makedirs(os.path.join(destination, subpath), exist_ok=True)

Expand Down Expand Up @@ -429,7 +433,7 @@ def js_content_middleware(content, url, **kwargs):
return content

def css_node_filter(node):
if "rel" in node:
if "rel" in node.attrs:
return "stylesheet" in node["rel"]
return node["href"].split("?")[0].strip().endswith(".css")

Expand Down
101 changes: 101 additions & 0 deletions tests/test_downloader.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
import mimetypes
import os
import re
import tempfile
import unittest
from pathlib import Path
from urllib.parse import unquote
from urllib.parse import urlparse

import responses

from ricecooker.utils import downloader

TESTCONTENT = Path(__file__).resolve().parent / "testcontent"

# Portless host used to serve the checked-in sample tree. A real host (no
# ":port") keeps the archived paths free of colons, which are illegal in
# Windows filenames - the reason the previous embedded-server version of this
# test failed on Windows.
SAMPLE_HOST = "https://example.org"


def _serve_sample_tree(request):
"""responses callback: serve files from tests/testcontent as a static site.

Maps the request URL path onto the on-disk sample tree so the fixtures
checked in for this test are served verbatim, no local web server needed.
"""
rel_path = unquote(urlparse(request.url).path).lstrip("/")
file_path = TESTCONTENT / rel_path
if not file_path.is_file():
return (404, {}, b"")
ext = file_path.suffix
if ext == ".html":
content_type = "text/html; charset=utf-8"
elif ext == "":
# Extensionless resources in this fixture are stylesheets; Google Fonts
# serves its CSS from an extensionless URL, which is what this test
# exercises.
content_type = "text/css"
else:
content_type = (
mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
)
return (200, {"Content-Type": content_type}, file_path.read_bytes())


class TestArchiver(unittest.TestCase):
def test_get_archive_filename_absolute(self):
Expand Down Expand Up @@ -70,3 +111,63 @@ def test_archive_path_as_relative_url(self):
link_filename, page_filename
)
assert rel_path == "../kolibri_1.2.3.png"


# The sample tree lives under a subject folder mirroring a real PreTeXt book:
# activecalculus.org (the page) references a stylesheet from fonts.googleapis.com
# via an extensionless URL, whose CSS in turn references a font on
# fonts.gstatic.com.
SAMPLE_ROOT = "samples/PreTeXt_book_test_manually_cleaned_urls"
# The Google Fonts stylesheet URL is extensionless; the original had a colon,
# replaced with an underscore so the fixture file checks out on Windows.
CSS_URL_PART = "css2_family_Material+Symbols+Outlined_opsz,wght,FILL,GRAD@24,400,0,0"


@responses.activate
def test_pretextbook_css_fetch():
"""A stylesheet linked via an extensionless URL is archived and rewritten.

Regression test for two bugs when archiving a PreTeXt book: the CSS
``<link>`` (matched by ``rel="stylesheet"``, not a ``.css`` extension) was
skipped, and the generated ``index.css`` for the extensionless resource was
dumped at the archive root instead of nested under the resource's own path,
which made its relative font references escape the archive directory.
"""
responses.add_callback(
responses.GET,
re.compile(re.escape(SAMPLE_HOST) + r"/.*"),
callback=_serve_sample_tree,
)

page_url = "{}/{}/activecalculus.org/single2e/sec-5-2-FTC2.html".format(
SAMPLE_HOST, SAMPLE_ROOT
)

with tempfile.TemporaryDirectory() as download_root:
archive = downloader.ArchiveDownloader(download_root)
archive.get_page(page_url)

book_dest_dir = Path(download_root) / "example.org" / SAMPLE_ROOT

# The page's stylesheet link is rewritten to the nested index.css.
page_html = (
book_dest_dir / "activecalculus.org" / "single2e" / "sec-5-2-FTC2.html"
).read_text()
assert "../../fonts.googleapis.com/" + CSS_URL_PART + "/index.css" in page_html

# The extensionless stylesheet is nested under its own path as index.css,
# not clobbered at the archive root.
index_css = book_dest_dir / "fonts.googleapis.com" / CSS_URL_PART / "index.css"
css_contents = index_css.read_text()
assert "fonts.gstatic.com/s/materialsymbolsoutlined" in css_contents

# The font referenced from the CSS is downloaded to its own nested path.
font_path = (
book_dest_dir
/ "fonts.gstatic.com"
/ "s"
/ "materialsymbolsoutlined"
/ "v290"
/ "material_symbols.woff"
)
assert font_path.stat().st_size > 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<html dir="ltr" lang="en-US">
<!--******************************************-->
<!--* Authored with PreTeXt *-->
<!--* pretextbook.org *-->
<!--* Theme: default-modern *-->
<!--* Palette: blues *-->
<!--******************************************-->
<head xmlns:book="https://ogp.me/ns/book#" xmlns:og="http://ogp.me/ns#">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<title>
AC The Second Fundamental Theorem of Calculus
</title>
<link href="" rel="preconnect"/>
<link crossorigin="sec-5-2-FTC2.html" href="" rel="preconnect"/>
<link href="../../fonts.googleapis.com/css2_family_Material+Symbols+Outlined_opsz,wght,FILL,GRAD@24,400,0,0" rel="stylesheet"/>
</head>
<body class="pretext book ignore-math">
<span aria-hidden="true" class="icon material-symbols-outlined">
</span>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 400;
src: url("../fonts.gstatic.com/s/materialsymbolsoutlined/v290/material_symbols.woff") format('woff');
}

.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-moz-font-feature-settings: 'liga';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Not really a font, just a placeholder file.
18 changes: 18 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading