#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Verification signature for figrecipe figures."""
import hashlib
from pathlib import Path
from typing import Any, Dict, Optional, Union
[docs]
def signature(
image_path: Optional[Union[str, Path]] = None,
recipe_path: Optional[Union[str, Path]] = None,
) -> Dict[str, Any]:
"""Compute verification signature for a saved figure.
Parameters
----------
image_path : str or Path, optional
Path to the saved image file.
recipe_path : str or Path, optional
Path to the saved recipe YAML file.
Returns
-------
dict
Signature with keys: image_hash, recipe_hash, image_path, recipe_path.
"""
result = {}
if image_path:
image_path = Path(image_path)
if image_path.exists():
result["image_hash"] = _sha256_short(image_path)
result["image_path"] = str(image_path)
if recipe_path:
recipe_path = Path(recipe_path)
if recipe_path.exists():
result["recipe_hash"] = _sha256_short(recipe_path)
result["recipe_path"] = str(recipe_path)
return result
[docs]
def caption_with_signature(
caption: str,
image_path: Optional[Union[str, Path]] = None,
recipe_path: Optional[Union[str, Path]] = None,
style: str = "short",
) -> str:
"""Append verification signature to a figure caption.
Parameters
----------
caption : str
Original caption text.
image_path : str or Path, optional
Path to the saved image.
recipe_path : str or Path, optional
Path to the saved recipe YAML.
style : str
'short' (default): hash only.
'full': includes "Generated by figrecipe" prefix.
Returns
-------
str
Caption with appended signature.
"""
sig = signature(image_path=image_path, recipe_path=recipe_path)
parts = []
if "recipe_hash" in sig:
parts.append(f"recipe: {sig['recipe_hash']}")
if "image_hash" in sig:
parts.append(f"figure: {sig['image_hash']}")
if not parts:
return caption
hash_str = ", ".join(parts)
if style == "full":
suffix = f"Generated by figrecipe ({hash_str})."
else:
suffix = f"[{hash_str}]"
return f"{caption} {suffix}"
def _sha256_short(path: Path, length: int = 8) -> str:
"""Compute truncated SHA256 hash of a file."""
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()[:length]
__all__ = ["signature", "caption_with_signature"]