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:
+71
-9
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user