From 269a0b96994491bb0fc465e7c50301dfaf5a11a0 Mon Sep 17 00:00:00 2001 From: fengjun Date: Tue, 14 Apr 2026 16:46:35 +0800 Subject: [PATCH] up --- assets/js/script.js | 153 ++++--- header.html | 3 +- scripts/__init__.py | 1 - scripts/__pycache__/__init__.cpython-314.pyc | Bin 140 -> 0 bytes .../optimize_assets.cpython-314.pyc | Bin 27104 -> 0 bytes scripts/optimize_assets.py | 427 ------------------ tests/__init__.py | 1 - tests/__pycache__/__init__.cpython-314.pyc | Bin 138 -> 0 bytes .../test_optimize_assets.cpython-314.pyc | Bin 4311 -> 0 bytes tests/test_optimize_assets.py | 43 -- 10 files changed, 81 insertions(+), 547 deletions(-) delete mode 100644 scripts/__init__.py delete mode 100644 scripts/__pycache__/__init__.cpython-314.pyc delete mode 100644 scripts/__pycache__/optimize_assets.cpython-314.pyc delete mode 100644 scripts/optimize_assets.py delete mode 100644 tests/__init__.py delete mode 100644 tests/__pycache__/__init__.cpython-314.pyc delete mode 100644 tests/__pycache__/test_optimize_assets.cpython-314.pyc delete mode 100644 tests/test_optimize_assets.py diff --git a/assets/js/script.js b/assets/js/script.js index a3e9aa3..c1f391b 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -96,62 +96,64 @@ } - // HERO SLIDER - var menu = []; - jQuery('.swiper-slide').each( function(index){ - menu.push( jQuery(this).find('.slide-inner').attr("data-text") ); - }); - var interleaveOffset = 0.5; - var swiperOptions = { - loop: true, - speed: 1000, - parallax: true, - autoplay: { - delay: 6500, - disableOnInteraction: false, - }, - watchSlidesProgress: true, - pagination: { - el: '.swiper-pagination', - clickable: true, - }, - - navigation: { - nextEl: '.swiper-button-next', - prevEl: '.swiper-button-prev', - }, - - on: { - progress: function() { - var swiper = this; - for (var i = 0; i < swiper.slides.length; i++) { - var slideProgress = swiper.slides[i].progress; - var innerOffset = swiper.width * interleaveOffset; - var innerTranslate = slideProgress * innerOffset; - swiper.slides[i].querySelector(".slide-inner").style.transform = - "translate3d(" + innerTranslate + "px, 0, 0)"; - } - }, - - touchStart: function() { - var swiper = this; - for (var i = 0; i < swiper.slides.length; i++) { - swiper.slides[i].style.transition = ""; - } - }, - - setTransition: function(speed) { - var swiper = this; - for (var i = 0; i < swiper.slides.length; i++) { - swiper.slides[i].style.transition = speed + "ms"; - swiper.slides[i].querySelector(".slide-inner").style.transition = - speed + "ms"; - } - } - } - }; - - var swiper = new Swiper(".swiper-container", swiperOptions); + // HERO SLIDER + if (typeof Swiper === "function" && jQuery(".swiper-container").length) { + var menu = []; + jQuery('.swiper-slide').each( function(index){ + menu.push( jQuery(this).find('.slide-inner').attr("data-text") ); + }); + var interleaveOffset = 0.5; + var swiperOptions = { + loop: true, + speed: 1000, + parallax: true, + autoplay: { + delay: 6500, + disableOnInteraction: false, + }, + watchSlidesProgress: true, + pagination: { + el: '.swiper-pagination', + clickable: true, + }, + + navigation: { + nextEl: '.swiper-button-next', + prevEl: '.swiper-button-prev', + }, + + on: { + progress: function() { + var swiper = this; + for (var i = 0; i < swiper.slides.length; i++) { + var slideProgress = swiper.slides[i].progress; + var innerOffset = swiper.width * interleaveOffset; + var innerTranslate = slideProgress * innerOffset; + swiper.slides[i].querySelector(".slide-inner").style.transform = + "translate3d(" + innerTranslate + "px, 0, 0)"; + } + }, + + touchStart: function() { + var swiper = this; + for (var i = 0; i < swiper.slides.length; i++) { + swiper.slides[i].style.transition = ""; + } + }, + + setTransition: function(speed) { + var swiper = this; + for (var i = 0; i < swiper.slides.length; i++) { + swiper.slides[i].style.transition = speed + "ms"; + swiper.slides[i].querySelector(".slide-inner").style.transition = + speed + "ms"; + } + } + } + }; + + var swiper = new Swiper(".swiper-container", swiperOptions); + } // DATA BACKGROUND IMAGE var sliderBgSetting = $(".slide-bg-image"); @@ -166,29 +168,34 @@ /*------------------------------------------ = HIDE PRELOADER -------------------------------------------*/ - function preloader() { - if($('.preloader').length) { - $('.preloader').delay(100).fadeOut(500, function() { - - //active wow - wow.init(); - - - }); - } + function preloader() { + if($('.preloader').length) { + $('.preloader').delay(100).fadeOut(500, function() { + + //active wow + if (wow) { + wow.init(); + } + + + }); + } } /*------------------------------------------ = WOW ANIMATION SETTING -------------------------------------------*/ - var wow = new WOW({ - boxClass: 'wow', // default - animateClass: 'animated', // default - offset: 0, // default - mobile: true, // default - live: true // default - }); + var wow = null; + if (typeof WOW === "function") { + wow = new WOW({ + boxClass: 'wow', // default + animateClass: 'animated', // default + offset: 0, // default + mobile: true, // default + live: true // default + }); + } /*------------------------------------------ diff --git a/header.html b/header.html index 7358e8c..778f5f3 100644 --- a/header.html +++ b/header.html @@ -203,6 +203,5 @@ }//微信公众号 }); - - \ No newline at end of file + diff --git a/scripts/__init__.py b/scripts/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/scripts/__pycache__/__init__.cpython-314.pyc b/scripts/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index d5b20f1eed606173f1bb4a85bb77239e7ce7aef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 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 str: - result = subprocess.run( - cmd, - check=True, - text=True, - capture_output=capture, - ) - return result.stdout.strip() if capture else "" - - -def should_keep_conversion(source_size: int, candidate_size: int) -> bool: - savings = source_size - candidate_size - if savings <= 0: - return False - if source_size < SMALL_FILE_LIMIT: - return savings >= SMALL_MIN_SAVINGS - return (savings / source_size) >= LARGE_MIN_RATIO - - -def classify_large_image(source_size: int, width: int, height: int) -> bool: - return source_size > MAX_FILE_SIZE or max(width, height) > MAX_DIMENSION - - -def image_info(path: Path) -> ImageInfo: - output = run( - ["magick", "identify", "-ping", "-format", "%w %h %k", str(path)], - capture=True, - ) - width_text, height_text, colors_text = output.split() - return ImageInfo(int(width_text), int(height_text), int(colors_text)) - - -def text_files() -> list[Path]: - return sorted( - path - for path in ROOT.rglob("*") - if path.is_file() - and path.suffix.lower() in TEXT_EXTENSIONS - and "assets_backup" not in path.parts - and ".git" not in path.parts - and "docs" not in path.parts - and "tests" not in path.parts - ) - - -def source_images() -> list[Path]: - return sorted( - path - for path in IMAGES_DIR.rglob("*") - if path.is_file() and path.suffix.lower() in SOURCE_EXTENSIONS and not path.name.startswith("._") - ) - - -def colliding_stems(paths: list[Path]) -> set[str]: - counts = Counter(path.with_suffix("").relative_to(ROOT).as_posix() for path in paths) - return {stem for stem, count in counts.items() if count > 1} - - -def build_webp_path(source: Path, duplicate_stems: set[str]) -> Path: - stem_key = source.with_suffix("").as_posix() - if not source.is_absolute(): - stem_key = source.with_suffix("").as_posix() - else: - stem_key = source.with_suffix("").relative_to(ROOT).as_posix() - if stem_key in duplicate_stems: - return source.with_name(f"{source.name}.webp") - return source.with_suffix(".webp") - - -def managed_webps() -> list[Path]: - return sorted( - path - for path in IMAGES_DIR.rglob("*.webp") - if path.is_file() and not path.name.startswith("._") - ) - - -def render_candidate(source: Path, operations: list[str], suffix: str) -> Path: - target = source.with_name(f"{source.stem}.{suffix}{source.suffix}") - run(["magick", str(source), *operations, str(target)]) - return target - - -def render_webp_candidate(source: Path, operations: list[str], suffix: str) -> Path: - target = source.with_name(f"{source.stem}.{suffix}.webp") - run(["magick", str(source), *operations, str(target)]) - return target - - -def choose_smaller_original(source: Path, info: ImageInfo) -> tuple[int, int]: - original_size = source.stat().st_size - extension = source.suffix.lower() - candidates: list[Path] = [] - try: - if extension in {".jpg", ".jpeg"}: - jpeg_profiles = [ - ["-strip", "-sampling-factor", "4:2:0", "-quality", "86", "-interlace", "Plane"], - ] - if classify_large_image(original_size, info.width, info.height): - jpeg_profiles.extend( - [ - ["-strip", "-sampling-factor", "4:2:0", "-quality", "82", "-interlace", "Plane"], - ["-strip", "-sampling-factor", "4:2:0", "-quality", "78", "-interlace", "Plane"], - ] - ) - if max(info.width, info.height) > MAX_DIMENSION: - jpeg_profiles.extend( - [ - [ - "-strip", - "-resize", - f"{MAX_DIMENSION}x{MAX_DIMENSION}>", - "-sampling-factor", - "4:2:0", - "-quality", - "84", - "-interlace", - "Plane", - ], - [ - "-strip", - "-resize", - f"{MAX_DIMENSION}x{MAX_DIMENSION}>", - "-sampling-factor", - "4:2:0", - "-quality", - "80", - "-interlace", - "Plane", - ], - ] - ) - for index, profile in enumerate(jpeg_profiles): - candidates.append(render_candidate(source, profile, f"jpg-opt-{index}")) - elif extension == ".png": - candidates.append( - render_candidate( - source, - ["-strip", "-define", "png:compression-level=9", "-define", "png:compression-filter=5"], - "png-opt", - ) - ) - elif extension == ".gif": - candidates.append(render_candidate(source, ["-strip"], "gif-opt")) - - best = min([source, *candidates], key=lambda candidate: candidate.stat().st_size) - best_size = best.stat().st_size - if best != source: - best.replace(source) - return original_size, best_size - finally: - for candidate in candidates: - if candidate.exists(): - candidate.unlink() - - -def choose_webp(source: Path, info: ImageInfo, target_path: Path) -> tuple[bool, int]: - source_size = source.stat().st_size - extension = source.suffix.lower() - candidates: list[Path] = [] - try: - if extension in {".jpg", ".jpeg"}: - profiles = [ - ["-strip", "-quality", "84", "-define", "webp:method=6"], - ["-strip", "-quality", "80", "-define", "webp:method=6"], - ] - if classify_large_image(source_size, info.width, info.height): - profiles.append(["-strip", "-quality", "76", "-define", "webp:method=6"]) - if max(info.width, info.height) > MAX_DIMENSION: - profiles.extend( - [ - ["-strip", "-resize", f"{MAX_DIMENSION}x{MAX_DIMENSION}>", "-quality", "82", "-define", "webp:method=6"], - ["-strip", "-resize", f"{MAX_DIMENSION}x{MAX_DIMENSION}>", "-quality", "78", "-define", "webp:method=6"], - ] - ) - elif extension == ".png": - if info.colors <= 256 and source_size < SMALL_FILE_LIMIT: - profiles = [ - ["-strip", "-define", "webp:lossless=true", "-define", "webp:method=6"], - ] - else: - profiles = [ - ["-strip", "-quality", "92", "-define", "webp:alpha-quality=95", "-define", "webp:method=6"], - ["-strip", "-quality", "88", "-define", "webp:alpha-quality=90", "-define", "webp:method=6"], - ] - if max(info.width, info.height) > MAX_DIMENSION: - profiles.extend( - [ - [ - "-strip", - "-resize", - f"{MAX_DIMENSION}x{MAX_DIMENSION}>", - "-quality", - "90", - "-define", - "webp:alpha-quality=92", - "-define", - "webp:method=6", - ], - [ - "-strip", - "-resize", - f"{MAX_DIMENSION}x{MAX_DIMENSION}>", - "-quality", - "86", - "-define", - "webp:alpha-quality=88", - "-define", - "webp:method=6", - ], - ] - ) - else: - profiles = [["-strip", "-define", "webp:lossless=true", "-define", "webp:method=6"]] - - for index, profile in enumerate(profiles): - candidates.append(render_webp_candidate(source, profile, f"webp-opt-{index}")) - - best = min(candidates, key=lambda candidate: candidate.stat().st_size) - best_size = best.stat().st_size - if should_keep_conversion(source_size, best_size): - best.replace(target_path) - accepted = True - else: - if target_path.exists(): - target_path.unlink() - accepted = False - return accepted, best_size - finally: - for candidate in candidates: - if candidate.exists(): - candidate.unlink() - - -def build_replacements(accepted_sources: list[Path], duplicate_stems: set[str]) -> dict[str, str]: - replacements: dict[str, str] = {} - for source in accepted_sources: - relative = source.relative_to(ROOT).as_posix() - target = build_webp_path(source, duplicate_stems).relative_to(ROOT).as_posix() - replacements[relative] = target - if relative.startswith("assets/images/"): - replacements[relative.replace("assets/images/", "../images/", 1)] = target.replace( - "assets/images/", "../images/", 1 - ) - replacements[relative.replace("assets/", "", 1)] = target.replace("assets/", "", 1) - return replacements - - -def referenced_asset_paths() -> set[str]: - references: set[str] = set() - pattern = re.compile(r"(assets/images|../images|images)/[^\"')\s>]+\.(?:png|jpg|jpeg|gif|webp|svg|ico)", re.IGNORECASE) - for path in text_files(): - content = path.read_text(encoding="utf-8") - for match in pattern.finditer(content): - ref = match.group(0) - candidates = [ROOT / ref, path.parent / ref] - for candidate in candidates: - if candidate.exists() and candidate.is_file(): - references.add(candidate.resolve().relative_to(ROOT).as_posix()) - break - return references - - -def should_prune_original(source: Path, referenced_paths: set[str], accepted_webp_paths: set[str]) -> bool: - source_key = source.as_posix() if not source.is_absolute() else source.relative_to(ROOT).as_posix() - if source_key in referenced_paths: - return False - return any( - webp_key in accepted_webp_paths - for webp_key in { - source.with_suffix(".webp").as_posix() if not source.is_absolute() else source.with_suffix(".webp").relative_to(ROOT).as_posix(), - source.with_name(f"{source.name}.webp").as_posix() if not source.is_absolute() else source.with_name(f"{source.name}.webp").relative_to(ROOT).as_posix(), - } - ) - - -def update_references(accepted_sources: list[Path], duplicate_stems: set[str]) -> int: - replacements = build_replacements(accepted_sources, duplicate_stems) - if not replacements: - return 0 - pattern = re.compile("|".join(sorted((re.escape(item) for item in replacements), key=len, reverse=True))) - changed = 0 - for path in text_files(): - original = path.read_text(encoding="utf-8") - updated = pattern.sub(lambda match: replacements[match.group(0)], original) - if updated != original: - path.write_text(updated, encoding="utf-8") - changed += 1 - return changed - - -def prune_replaced_originals(images: list[Path], duplicate_stems: set[str]) -> int: - references = referenced_asset_paths() - accepted_webp_paths = { - build_webp_path(source, duplicate_stems).relative_to(ROOT).as_posix() - for source in images - if build_webp_path(source, duplicate_stems).exists() - } - pruned = 0 - for source in images: - if should_prune_original(source, references, accepted_webp_paths): - source.unlink() - pruned += 1 - return pruned - - -def check_rewritten_webps() -> int: - missing: list[str] = [] - pattern = re.compile(r"(assets/images|../images|images)/[^\"')\s>]+\.webp", re.IGNORECASE) - for path in text_files(): - content = path.read_text(encoding="utf-8") - for match in pattern.finditer(content): - ref = match.group(0) - candidates = [ - ROOT / ref, - path.parent / ref, - ] - if not any(candidate.exists() and candidate.is_file() for candidate in candidates): - missing.append(f"{path.relative_to(ROOT)}: {ref}") - if missing: - print("\n".join(missing)) - return 1 - print("No missing rewritten webp references.") - return 0 - - -def ensure_backup_copy() -> None: - if BACKUP_DIR.exists(): - shutil.rmtree(BACKUP_DIR) - BACKUP_DIR.mkdir(parents=True, exist_ok=True) - for child in ASSETS_DIR.iterdir(): - destination = BACKUP_DIR / child.name - if child.is_dir(): - shutil.copytree(child, destination) - else: - shutil.copy2(child, destination) - - -def optimize_assets() -> int: - if shutil.which("magick") is None: - raise SystemExit("ImageMagick `magick` 未安装,无法继续处理图片。") - - ensure_backup_copy() - - images = source_images() - duplicate_stems = colliding_stems(images) - - total_before = 0 - total_after_original = 0 - accepted_sources: list[Path] = [] - accepted_count = 0 - skipped_count = 0 - - for source in images: - info = image_info(source) - before_size, after_original_size = choose_smaller_original(source, info) - total_before += before_size - total_after_original += after_original_size - accepted, _ = choose_webp(source, image_info(source), build_webp_path(source, duplicate_stems)) - if accepted: - accepted_sources.append(source) - accepted_count += 1 - else: - skipped_count += 1 - - changed_files = update_references(accepted_sources, duplicate_stems) - print(f"Backup refreshed at: {BACKUP_DIR.relative_to(ROOT)}") - print(f"Raster size before optimization: {total_before / 1024 / 1024:.2f} MB") - print(f"Raster size after original optimization: {total_after_original / 1024 / 1024:.2f} MB") - print(f"Accepted webp conversions: {accepted_count}") - print(f"Rejected webp conversions: {skipped_count}") - print(f"Updated text files: {changed_files}") - return 0 - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--check", action="store_true", help="validate rewritten webp references") - parser.add_argument("--prune-replaced", action="store_true", help="delete originals already replaced by accepted webp files") - args = parser.parse_args() - if args.check: - return check_rewritten_webps() - if args.prune_replaced: - images = source_images() - duplicate_stems = colliding_stems(images) - pruned = prune_replaced_originals(images, duplicate_stems) - print(f"Pruned replaced originals: {pruned}") - return 0 - return optimize_assets() - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/__pycache__/__init__.cpython-314.pyc b/tests/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index b1642f6e798b1e31077666236a223708fb16e745..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmdPqg#m!uY#6zj*wXXa&=#K-FuRNmsS$<0qG%}KQ@Vg>30nOqEF Nd}3x~WGrF^vH)D39q0f6 diff --git a/tests/__pycache__/test_optimize_assets.cpython-314.pyc b/tests/__pycache__/test_optimize_assets.cpython-314.pyc deleted file mode 100644 index d63fd979cfb246fabc438b0e17e062352b178053..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4311 zcmcgvO>7&-6`o!Gt!T=kTFIgk$C8@X0TYK5V6{jSHMZsa$OxjiTfq(r+k+)J631CC z*_mN2>m(FGlb+IB(@Tp2J=ixNb@VYu1~MFD>i_`)6mU;A6huK!dGGB~q%BK{TgLuF~*b~@J zoJ!0mO>#bEQuAq(p3j)f1RG^j%uS9lH+7z!&d1~QRWpg`#SEO!y6KnRWvn*B*g*!p z5A=15n^SBiZcV@%o~5Ulxr4feq7^1jd!}AYhC`LM^lSW*sI+(sRN+X~_q3Kvm$~n$ zWx-{`TNdE~U2nFb-+Mx|c(ob)UMMY_K{y)gTdExh-c)i~2A*GyXQs-IUpD^+4G(N` zo*Bz!#=ejz0)Ak83Y_LC;-4w-#tn8~^MXIXJL9DAo_WJ9is6Iu(S-L^0EqR^&l!RqnB zf?Gl`HV=WAwboz@7wnhb2S7l3kp0@K!-ibD3}(NYxP!gJv=XMRf`zNBXou;grfPU5 z9ORs=U6WPAxyFt^SqqPr>&=EJFI_AzOH*%G$|si3ib~nQDl|#njMvBkDOhBwbvev& z?gc^9ct&s@{&bgJl=^!O!gP8J#5Zefi&?3Y);*HflTp11pBU{M8(7ltzz&_wS!m#{ zPj@YXU*mEPEknfx(#^#1&%`}}j5ZiLap(FCi^-KQEVgjf4)@_klzGGRp~6|N0z=OK1VE#6?jxI%4dlx(GPzn(Kz=`$BP3_X4h)ZB-TF8@n0d(Z)eDZ5BYR zv0F~=GpF#WQ@Al+zLr@Z+ZaD_)0tD?g^``Q)_4SdsD1FW+7F^=t08Qo=f_l_eXAd# z_QxRTOCMl!1lXK)eGvhhvw91G&Qu;FU~=*w878lNl!owOhuH|QMb(b*QsXdRKUhb} z$X>N7S^yT1T__0MC{Q9BV%D)B-lP0`qR*jLg~t-^~^@$ z_)X_T&sIM$Xb9ZHylj0W^0MAS2r&?k6SU)l2__5wvDtz`;_QIwx%+&9yofJ*GT;^A zH!p!ZDFG4QuZ73y5a5#XlfNXvUy_W;_hC?oa%vKWRhS^`fZ-s)u;hEoI`)UkWsWYP;Uo%Y_O$&j zf6?~0!&b!qBUjE|tF51W!X7}Op)YK8&zWoSIn!eVn1;$@1wx@EA=7XstE5p z5IFQO1ZuJ&0?c!C_=`uup!Xl`{h=tlwo!PZhsQzKbbf!7e6G7)V=1F~b^mo=e7cTdXvPh%2@ z)3Dezc6)g8pCg^avl~;t>Ku1F$9}Uhy!c^yJ45;Sz~3kSIF{A{5brQXR1(A%BM? zz1I6GHE#z>ED0ros;Gs(-IFo@*VvP=a9tuB14r;UxSgB2oge9pmN)X#w}zkTOuXF5 zzjAB%NGJc|W+pkf|La70AWMKzdb&^?3?0si>^#hJ-e}g~as-zRxO$*R#9xLu99&8p zUj`y*a;~06lTD%c8H(po;6l{TQT!4_I0}~^xVgwG_l!}p(gs_?z{7plqpI+I&Wl!* z9wUj5N7?ZJj)Ecwk3I|Hn-73UTQ**3?$U*3D~;JPw&yHtf43e3<6R1mTMp3oYvgcb z3Y-b;(7+^#7jvQ04x~Zs!wlY^eOZZO&6ut#3At~ndH-cC3P(s|y67lmROl?i04F`T z{Lpq@L0uB3Ak#-P(;di}jm-Y4dK3Df8T2s_cN3On{fiBK&I((}lx1%+5MSk=wz41B qKRZW#$stz_ None: - self.assertFalse(should_keep_conversion(120_000, 123_000)) - - def test_accepts_materially_smaller_webp_for_large_file(self) -> None: - self.assertTrue(should_keep_conversion(500_000, 430_000)) - - def test_rejects_small_gain_below_threshold(self) -> None: - self.assertFalse(should_keep_conversion(200_000, 191_000)) - - def test_flags_large_by_filesize(self) -> None: - self.assertTrue(classify_large_image(301_000, 1200, 900)) - - def test_flags_large_by_dimensions(self) -> None: - self.assertTrue(classify_large_image(120_000, 3000, 1800)) - - def test_uses_extension_qualified_webp_name_when_stem_collides(self) -> None: - target = build_webp_path(Path("assets/images/sj.jpg"), {"assets/images/sj"}) - self.assertEqual(target.as_posix(), "assets/images/sj.jpg.webp") - - def test_prunes_original_when_webp_exists_and_original_is_unreferenced(self) -> None: - self.assertTrue( - should_prune_original( - Path("assets/images/banner.jpg"), - {"index.html", "assets/images/banner.webp"}, - {"assets/images/banner.webp"}, - ) - ) - - -if __name__ == "__main__": - unittest.main()