Skip to main content

simple_gal/
lib.rs

1//! # Simple Gal
2//!
3//! A minimal static site generator for fine art photography portfolios.
4//! Your filesystem is the data source: directories become albums, images are
5//! ordered by numeric prefix, and markdown files become pages.
6//!
7//! # Architecture: Three-Stage Pipeline
8//!
9//! Simple Gal processes content through three independent stages, each producing
10//! a JSON manifest that the next stage consumes:
11//!
12//! ```text
13//! 1. Scan      content/  →  manifest.json    (filesystem → structured data)
14//! 2. Process   manifest  →  processed/       (responsive sizes + thumbnails)
15//! 3. Generate  manifest  →  dist/            (final HTML site)
16//! ```
17//!
18//! This separation exists for three reasons:
19//!
20//! - **Debuggability**: each manifest is human-readable JSON you can inspect.
21//! - **Incremental builds**: skip stages whose inputs haven't changed.
22//! - **Testability**: each stage is a pure function from manifest to manifest,
23//!   so unit tests can exercise pipeline logic without touching the filesystem
24//!   or encoding images.
25//!
26//! # Module Map
27//!
28//! | Module | Role |
29//! |--------|------|
30//! | [`scan`] | Stage 1 — walks the content directory, extracts metadata, produces the scan manifest |
31//! | [`process`] | Stage 2 — generates responsive AVIF images and thumbnails from the scan manifest |
32//! | [`generate`] | Stage 3 — renders the final HTML site from the process manifest using Maud |
33//! | [`cache`] | Incremental build cache — skips AVIF encoding when source and params are unchanged |
34//! | [`config`] | Hierarchical `config.toml` loading, validation, merging, and CSS generation |
35//! | [`types`] | Shared types serialized between stages (`NavItem`, `Page`) |
36//! | [`naming`] | `NNN-name` filename convention parser used by all entry types |
37//! | [`metadata`] | Image metadata resolution: IPTC tags, sidecar files, filename fallback |
38//! | [`imaging`] | Pure-Rust image operations: resize, thumbnail, IPTC parsing |
39//! | [`output`] | CLI output formatting — tree-based display of pipeline results |
40//!
41//! # Design Decisions
42//!
43//! ## AVIF-Only Output
44//!
45//! All generated images are AVIF. The format has had [100% browser support since
46//! September 2023](https://caniuse.com/avif) and produces dramatically smaller
47//! files than JPEG at equivalent quality. Using a single modern format avoids the
48//! complexity of multi-format `<picture>` fallbacks and keeps the output directory
49//! clean.
50//!
51//! ## Maud Over Template Engines
52//!
53//! HTML is generated with [Maud](https://maud.lambda.xyz/), a compile-time HTML
54//! macro system, rather than Handlebars or Tera. Advantages:
55//!
56//! - **Compile-time checking**: malformed HTML is a build error, not a runtime surprise.
57//! - **Type-safe**: template variables are Rust expressions — no stringly-typed lookups.
58//! - **XSS-safe by default**: all interpolation is auto-escaped.
59//! - **Zero runtime files**: no template directory to ship or get out of sync.
60//!
61//! ## Pure-Rust Imaging (No ImageMagick, No FFmpeg)
62//!
63//! The [`imaging`] module uses the `image` crate (Lanczos3 resampling) and `rav1e`
64//! (AVIF encoding) — both pure Rust. This eliminates system dependencies entirely:
65//! no `apt install`, no Homebrew, no version conflicts. The binary is fully
66//! self-contained, which is critical to the "forever stack" premise — a user can
67//! download a single binary and it just works, on any machine, indefinitely.
68//!
69//! ## Config Cascading (Root → Group → Gallery)
70//!
71//! Configuration files at any level of the directory tree override their parent:
72//!
73//! ```text
74//! content/config.toml           ← root (overrides stock defaults)
75//! content/Travel/config.toml    ← group (overrides root)
76//! content/Travel/Japan/config.toml ← gallery (overrides group)
77//! ```
78//!
79//! Photographers want per-gallery control over aspect ratios, quality, and theme
80//! settings without repeating the entire config. The merge logic lives in
81//! [`config::SiteConfig::merge`].
82//!
83//! ## NNN-Prefix Ordering
84//!
85//! Directories and files use a numeric prefix (`001-`, `020-`, etc.) for explicit
86//! ordering. This is parsed by [`naming::parse_entry_name`]. Items without a prefix
87//! are processed but hidden from navigation — useful for work-in-progress content
88//! that should remain accessible by direct URL. The filesystem is the source of
89//! truth; no database, no front-matter, no separate ordering file.
90//!
91//! ## Stale-While-Revalidate Service Worker
92//!
93//! Every generated site is a PWA with a service worker using a stale-while-revalidate
94//! caching strategy. This gives visitors instant loads from cache while transparently
95//! fetching fresh content in the background. The cache is versioned by the build
96//! version string, so deploying a new build automatically invalidates old caches.
97//!
98//! # The "Forever Stack"
99//!
100//! Simple Gal is designed to be usable decades from now with minimal fuss. The
101//! output is plain HTML, established CSS, and ~30 lines of vanilla JavaScript.
102//! The binary has zero runtime dependencies. AVIF is an ISO standard. The generated
103//! site can be dropped on any file server — no Node, no PHP, no database. If a
104//! browser can render HTML, it can display your portfolio.
105
106pub mod cache;
107pub mod config;
108pub mod generate;
109pub mod imaging;
110pub mod metadata;
111pub mod naming;
112pub mod output;
113pub mod process;
114pub mod scan;
115pub mod types;
116
117#[cfg(test)]
118pub(crate) mod test_helpers;