feat: Implement metadata system and reorganize project structure

 Features:
- Add comprehensive metadata system with YAML/JSON support
- Implement hierarchical metadata inheritance from parent folders
- Support plot-specific metadata overrides
- Add metadata caching for performance optimization

🗂️ Code Organization:
- Move Python orchestration code to orchestration/ folder
- Move validation utilities to tests/ folder
- Move documentation to docs/ folder
- Separate metadata functionality into dedicated module

🔧 Infrastructure:
- Add automatic asset copying to web directory
- Fix asset path resolution for nested directories
- Update template to use dynamic asset paths
- Add MetadataConfig class with inheritance options

📚 Documentation:
- Add comprehensive metadata usage guide (METADATA_USAGE.md)
- Add implementation documentation (METADATA_IMPLEMENTATION.md)
- Include example metadata files in examples/
- Add metadata validation utility script

🐛 Bug Fixes:
- Fix breadcrumb navigation and JavaScript functionality
- Resolve asset path issues in nested directories
- Update template imports for modular CSS/JS structure

This commit introduces a flexible metadata system that allows users to add
rich metadata to plots and folders using YAML or JSON files, with full
hierarchical inheritance and plot-specific overrides. The project structure
is now better organized with clear separation of concerns.
This commit is contained in:
Kylian Schmidt
2025-07-08 12:33:49 +02:00
parent 716e1c95fe
commit 72ebc5e103
13 changed files with 645 additions and 2017 deletions
+71 -9
View File
@@ -16,14 +16,22 @@ Features:
import subprocess
import shutil
from pathlib import Path
from typing import Dict, Any, Optional
from jinja2 import Environment, FileSystemLoader
from config import Config
from orchestration.config import Config
from orchestration.metadata import (
load_folder_metadata,
merge_metadata,
resolve_metadata_for_plot,
save_metadata_cache
)
CONFIG = Config.from_yaml("config.yaml")
env = Environment(loader=FileSystemLoader("."))
template = env.get_template("template.html")
template = env.get_template("templates/gallery.html")
def convert_pdf_to_png(pdf_path: Path) -> None:
@@ -80,26 +88,36 @@ def needs_update(source_file: Path, target_file: Path) -> bool:
def build_gallery(source_dir: Path, web_dir: Path,
relative_path: Path = None) -> None:
relative_path: Path = None,
inherited_metadata: Optional[Dict[str, Any]] = None) -> None:
"""
Recursively build gallery structure from source directory.
Processes all PDF files in the source directory, converts them to PNG,
copies both to the web directory, and generates index.html files with
navigation and thumbnails.
navigation and thumbnails. Now includes metadata support.
Args:
source_dir: Source directory containing PDF files
web_dir: Target web directory for gallery output
relative_path: Relative path from gallery root (for navigation)
inherited_metadata: Metadata inherited from parent directories
"""
if relative_path is None:
relative_path = Path(".")
if inherited_metadata is None:
inherited_metadata = {}
# Load folder-level metadata and merge with inherited metadata
folder_metadata = load_folder_metadata(source_dir)
current_metadata = merge_metadata(inherited_metadata, folder_metadata)
pdf_files = list(source_dir.glob("*.pdf"))
subdirs = [d for d in source_dir.iterdir() if d.is_dir()]
items = []
plot_metadata_cache = {}
for pdf_file in pdf_files:
png_file = pdf_file.with_suffix(".png")
@@ -122,18 +140,27 @@ def build_gallery(source_dir: Path, web_dir: Path,
else:
print(f"Skipping {png_file.name} (up to date)")
# Resolve metadata for this specific plot
plot_metadata = resolve_metadata_for_plot(pdf_file, current_metadata)
plot_metadata_cache[pdf_file.stem] = plot_metadata
items.append({
"name": pdf_file.stem,
"pdf_href": pdf_file.name,
"png_href": png_file.name
"png_href": png_file.name,
"metadata": plot_metadata
})
# Save metadata cache for this directory
save_metadata_cache(web_dir, plot_metadata_cache)
subdir_names = []
for subdir in subdirs:
subdir_web = web_dir / subdir.name
subdir_web.mkdir(exist_ok=True)
subdir_relative = relative_path / subdir.name
build_gallery(subdir, subdir_web, subdir_relative)
# Pass current metadata to subdirectories
build_gallery(subdir, subdir_web, subdir_relative, current_metadata)
subdir_names.append(subdir.name)
output_html = web_dir / "index.html"
@@ -152,6 +179,14 @@ def build_gallery(source_dir: Path, web_dir: Path,
"total_size_bytes": current_stats["total_size"]
}
# Calculate relative path to assets based on directory depth
if relative_path == Path("."):
assets_path = "../assets"
else:
# Count the number of directory levels to go back
depth = len(relative_path.parts)
assets_path = "../" * (depth + 1) + "assets"
with output_html.open("w") as f:
f.write(template.render(
title=title,
@@ -159,7 +194,9 @@ def build_gallery(source_dir: Path, web_dir: Path,
subdirs=subdir_names,
relpath=str(relative_path),
ui=CONFIG.ui,
stats=stats
stats=stats,
folder_metadata=current_metadata,
assets_path=assets_path
))
print(f"Generated {output_html}")
@@ -241,6 +278,20 @@ def main() -> None:
gallery_root.mkdir(parents=True, exist_ok=True)
# Copy assets folder to the gallery root
assets_src = Path("assets")
assets_dst = gallery_root.parent / "assets"
if assets_src.exists():
if assets_dst.exists():
shutil.rmtree(assets_dst)
shutil.copytree(assets_src, assets_dst)
print(f"Copied assets from {assets_src} to {assets_dst}")
else:
print(f"Warning: Assets directory {assets_src} not found")
plot_metadata_cache = {}
for source in CONFIG.sources:
source_path = Path(source.path)
@@ -270,12 +321,21 @@ def main() -> None:
else:
print(f"Skipping {source_png_path.name} (up to date)")
# Resolve metadata for this single plot
plot_metadata = resolve_metadata_for_plot(source_path, {})
plot_metadata_cache[source_path.stem] = plot_metadata
items = [{
"name": source_path.stem,
"pdf_href": pdf_name,
"png_href": png_name
"png_href": png_name,
"metadata": plot_metadata
}]
# Save metadata cache for this directory
plot_cache = {source_path.stem: plot_metadata}
save_metadata_cache(source_web_dir, plot_cache)
# Calculate statistics for single file
current_stats = calculate_directory_stats(source_web_dir)
stats = {
@@ -293,7 +353,9 @@ def main() -> None:
subdirs=[],
relpath=source.name,
ui=CONFIG.ui,
stats=stats
stats=stats,
folder_metadata={},
assets_path="../../assets"
))
print(f"Generated {output_html}")