""" Metadata Management for Scientific Gallery Generator This module handles loading, parsing, and caching of metadata for plots and folders in the gallery system. Supports YAML and JSON formats with hierarchical inheritance. Features: - Load metadata from YAML/JSON files - Hierarchical metadata inheritance from parent folders - Plot-specific metadata overrides - Metadata caching for performance """ import json import yaml from pathlib import Path from typing import Dict, Any def load_metadata_file(metadata_path: Path) -> Dict[str, Any]: """ Load metadata from a YAML or JSON file. Args: metadata_path: Path to the metadata file Returns: Dictionary containing the metadata, empty dict if file doesn't exist or can't be parsed """ if not metadata_path.exists(): return {} try: with metadata_path.open('r', encoding='utf-8') as f: suffix_lower = metadata_path.suffix.lower() if suffix_lower == '.yaml' or suffix_lower == '.yml': return yaml.safe_load(f) or {} elif metadata_path.suffix.lower() == '.json': return json.load(f) or {} else: print(f"Warning: Unknown metadata file format: " f"{metadata_path}") return {} except (yaml.YAMLError, json.JSONDecodeError, IOError) as e: print(f"Warning: Could not parse metadata file {metadata_path}: {e}") raise e def load_folder_metadata(folder_path: Path) -> Dict[str, Any]: """ Load folder-level metadata from meta.yaml, meta.json, or metadata.json. Args: folder_path: Path to the folder to check for metadata Returns: Dictionary containing the folder metadata """ # Try YAML first, then JSON (including metadata.json for backwards compatibility) for filename in ['meta.yaml', 'meta.yml', 'meta.json', 'metadata.json']: metadata_path = folder_path / filename if metadata_path.exists(): return load_metadata_file(metadata_path) return {} def merge_metadata( parent_metadata: Dict[str, Any], child_metadata: Dict[str, Any] ) -> Dict[str, Any]: """ Merge parent and child metadata, with child values overriding parent values. Args: parent_metadata: Metadata from parent folder child_metadata: Metadata from current folder Returns: Merged metadata dictionary """ merged = parent_metadata.copy() merged.update(child_metadata) return merged def resolve_metadata_for_plot( plot_path: Path, inherited_metadata: Dict[str, Any] ) -> Dict[str, Any]: """ Resolve metadata for a specific plot by merging inherited metadata with plot-specific metadata. Args: plot_path: Path to the plot file (PDF) inherited_metadata: Metadata inherited from folder hierarchy Returns: Final merged metadata for the plot """ plot_stem = plot_path.stem plot_dir = plot_path.parent # Check for plot-specific metadata files for suffix in ['.yaml', '.yml', '.json']: plot_metadata_path = plot_dir / f"{plot_stem}{suffix}" if plot_metadata_path.exists(): plot_metadata = load_metadata_file(plot_metadata_path) return merge_metadata(inherited_metadata, plot_metadata) # No plot-specific metadata found, return inherited metadata return inherited_metadata.copy() def save_metadata_cache( web_dir: Path, plot_metadata_cache: Dict[str, Dict[str, Any]] ) -> None: """ Save plot metadata cache to meta_cache.json in the web directory. Args: web_dir: Web directory where the cache file should be saved plot_metadata_cache: Dictionary mapping plot names to their metadata """ cache_path = web_dir / "meta_cache.json" try: with cache_path.open('w', encoding='utf-8') as f: json.dump(plot_metadata_cache, f, indent=2, ensure_ascii=False) print(f"Saved metadata cache: {cache_path}") except IOError as e: print(f"Warning: Could not save metadata cache {cache_path}: {e}")