Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain/plain/assets/compile.py: 26%
46 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
1import gzip
2import hashlib
3import os
4import shutil
6from plain.runtime import settings
8from .finders import find_assets
9from .fingerprints import AssetsFingerprintsManifest
11FINGERPRINT_LENGTH = 7
13SKIP_COMPRESS_EXTENSIONS = (
14 # Images
15 "jpg",
16 "jpeg",
17 "png",
18 "gif",
19 "webp",
20 # Compressed files
21 "zip",
22 "gz",
23 "tgz",
24 "bz2",
25 "tbz",
26 "xz",
27 "br",
28 # Fonts
29 "woff",
30 "woff2",
31 # Video
32 "3gp",
33 "3gpp",
34 "asf",
35 "avi",
36 "m4v",
37 "mov",
38 "mp4",
39 "mpeg",
40 "mpg",
41 "webm",
42 "wmv",
43)
46def get_compiled_path():
47 """
48 Get the path at runtime to the compiled assets directory.
49 There's no reason currently for this to be a user-facing setting.
50 """
51 return settings.PLAIN_TEMP_PATH / "assets" / "compiled"
54def compile_assets(*, target_dir, keep_original, fingerprint, compress):
55 manifest = AssetsFingerprintsManifest()
57 for url_path, asset in find_assets().items():
58 resolved_path, compiled_paths = compile_asset(
59 asset=asset,
60 target_dir=target_dir,
61 keep_original=keep_original,
62 fingerprint=fingerprint,
63 compress=compress,
64 )
65 if resolved_path != url_path:
66 manifest[url_path] = resolved_path
68 yield url_path, resolved_path, compiled_paths
70 if manifest:
71 manifest.save()
74def compile_asset(*, asset, target_dir, keep_original, fingerprint, compress):
75 """
76 Compile an asset to multiple output paths.
77 """
78 compiled_paths = []
80 # The expected destination for the original asset
81 target_path = os.path.join(target_dir, asset.url_path)
83 # Keep track of where the final, resolved asset ends up
84 resolved_url_path = asset.url_path
86 # Make sure all the expected directories exist
87 os.makedirs(os.path.dirname(target_path), exist_ok=True)
89 base, extension = os.path.splitext(asset.url_path)
91 # First, copy the original asset over
92 if keep_original:
93 shutil.copy(asset.absolute_path, target_path)
94 compiled_paths.append(target_path)
96 if fingerprint:
97 # Fingerprint it with an md5 hash
98 # (maybe need a setting with fnmatch patterns for files to NOT fingerprint?
99 # that would allow pre-fingerprinted files to be used as-is, and keep source maps etc in tact)
100 with open(asset.absolute_path, "rb") as f:
101 content = f.read()
102 fingerprint_hash = hashlib.md5(content, usedforsecurity=False).hexdigest()[
103 :FINGERPRINT_LENGTH
104 ]
106 fingerprinted_basename = f"{base}.{fingerprint_hash}{extension}"
107 fingerprinted_path = os.path.join(target_dir, fingerprinted_basename)
108 shutil.copy(asset.absolute_path, fingerprinted_path)
109 compiled_paths.append(fingerprinted_path)
111 resolved_url_path = os.path.relpath(fingerprinted_path, target_dir)
113 if compress and extension not in SKIP_COMPRESS_EXTENSIONS:
114 for path in compiled_paths.copy():
115 gzip_path = f"{path}.gz"
116 with gzip.GzipFile(gzip_path, "wb") as f:
117 with open(path, "rb") as f2:
118 f.write(f2.read())
119 compiled_paths.append(gzip_path)
121 return resolved_url_path, compiled_paths