Validate tool inputs and surface tracebacks on errors

- import-photo.sh: validate \$SLUG against ^[a-z0-9-]+\$ before
  writing under content/photography/; rejects '../' and other
  surprises early. Also fail loudly with a clear message if either
  'magick' resize or 'magick mogrify -strip' returns non-zero
  (prevents shipping a file that still carries EXIF when the strip
  silently failed).
- compress-assets.sh: reject non-numeric MIN_SIZE up front (otherwise
  the comparison fails later with a cryptic arithmetic error).
- extract-dimensions.py / extract-exif.py / extract-palette.py:
  add traceback.print_exc() after the broad except so a corrupt
  image produces a stack trace alongside the one-line summary.
- extract-exif.py: switch from Pillow's deprecated _getexif() to
  the public getexif() API; pyproject allows Pillow up to 12 where
  _getexif may be removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-05-07 15:09:02 -04:00
parent 0379dda908
commit 3e76833aac
5 changed files with 21 additions and 3 deletions

View File

@ -20,6 +20,11 @@ set -euo pipefail
SITE_DIR="${1:-_site}"
MIN_SIZE="${MIN_SIZE:-1024}" # bytes
if [[ ! "$MIN_SIZE" =~ ^[0-9]+$ ]]; then
echo "compress-assets: MIN_SIZE must be a positive integer (got '$MIN_SIZE')" >&2
exit 1
fi
if [ ! -d "$SITE_DIR" ]; then
echo "compress-assets: directory '$SITE_DIR' not found" >&2
exit 1

View File

@ -99,7 +99,9 @@ def _walk_one_root(root: Path, counters: dict[str, int]) -> None:
try:
data = _read_dimensions(image)
except Exception as e: # noqa: BLE001 — keep walking
import traceback
print(f"extract-dimensions: {image}: {e}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
counters["failed"] += 1
continue

View File

@ -289,7 +289,7 @@ def _read_exif_via_pillow(image: Path) -> dict[str, Any]:
out["width"] = width
if isinstance(height, int) and height > 0:
out["height"] = height
exif = img._getexif() or {}
exif = img.getexif() or {}
except Exception: # noqa: BLE001 — corrupt EXIF should not abort the walk
return out
@ -422,7 +422,9 @@ def main() -> int:
try:
data = _read_one(image)
except Exception as e: # noqa: BLE001 — keep walking
import traceback
print(f"extract-exif: {image}: {e}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
failed += 1
continue

View File

@ -103,7 +103,9 @@ def main() -> int:
try:
palette = _extract_palette(image)
except Exception as e: # noqa: BLE001 — keep walking
import traceback
print(f"extract-palette: {image}: {e}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
failed += 1
continue

View File

@ -52,6 +52,11 @@ ORIGINAL="$1"
SLUG="$2"
shift 2
if [[ ! "$SLUG" =~ ^[a-z0-9-]+$ ]]; then
echo "import-photo: invalid slug '$SLUG' (must be lowercase a-z, 0-9, hyphens only)" >&2
exit 1
fi
TITLE=""
while [ "$#" -gt 0 ]; do
case "$1" in
@ -118,7 +123,8 @@ magick "$ORIGINAL" \
-resize '2400x2400>' \
-colorspace sRGB \
-quality 85 \
"$TARGET"
"$TARGET" \
|| { echo "import-photo: magick resize failed for $ORIGINAL$TARGET" >&2; exit 1; }
chmod 644 "$TARGET"
# ---------------------------------------------------------------------------
@ -141,7 +147,8 @@ fi
# ---------------------------------------------------------------------------
echo "import-photo: stripping EXIF from delivered file..."
magick mogrify -strip "$TARGET"
magick mogrify -strip "$TARGET" \
|| { echo "import-photo: magick mogrify -strip failed for $TARGET (EXIF NOT stripped)" >&2; exit 1; }
# ---------------------------------------------------------------------------
# Step 4: extract palette (does its own walk; idempotent on already-done photos)