Expand description
File-based resource loading for templates and stylesheets.
Standout supports file-based configuration for templates and stylesheets, enabling a web-app-like development workflow for CLI applications.
§Problem
CLI applications need to manage templates and stylesheets. Developers want:
- Separation of concerns - Keep templates and styles in files, not Rust code
- Accessible to non-developers - Designers can edit YAML/Jinja without Rust
- Rapid iteration - Changes visible immediately without recompilation
- Single-binary distribution - Released apps should be self-contained
These requirements create tension: development wants external files for flexibility, while release wants everything embedded for distribution.
§Solution
The file loader provides a unified system that:
- Development mode: Reads files from disk with hot reload on each access
- Release mode: Embeds all files into the binary at compile time via proc macros
§Directory Structure
Organize resources in dedicated directories:
my-app/
├── templates/
│ ├── list.jinja
│ └── report/
│ └── summary.jinja
└── styles/
├── default.yaml
└── themes/
└── dark.yaml§Name Resolution
Files are referenced by their relative path from the root, without extension:
| File Path | Resolution Name |
|---|---|
templates/list.jinja | "list" |
templates/report/summary.jinja | "report/summary" |
styles/themes/dark.yaml | "themes/dark" |
§Development Usage
Register directories and access resources by name:
use standout_render::file_loader::{FileRegistry, FileRegistryConfig};
let config = FileRegistryConfig {
extensions: &[".yaml", ".yml"],
transform: |content| Ok(content.to_string()),
};
let mut registry = FileRegistry::new(config);
registry.add_dir("./styles")?;
// Re-reads from disk each call - edits are immediately visible
let content = registry.get("themes/dark")?;§Release Embedding
For release builds, use the embedding macros to bake files into the binary:
// At compile time, walks directory and embeds all files
let styles = standout_render::embed_styles!("./styles");
// Same API - resources accessed by name
let theme = styles.get("themes/dark")?;The macros walk the directory at compile time, read each file, and generate code that registers all resources with their derived names.
See the standout_macros crate for detailed documentation on
embed_templates! and embed_styles!.
§Extension Priority
Extensions are specified in priority order. When multiple files share the same base name, the extension appearing earlier wins for extensionless lookups:
// With extensions: [".yaml", ".yml"]
// If both default.yaml and default.yml exist:
registry.get("default") // → default.yaml (higher priority)
registry.get("default.yml") // → default.yml (explicit)§Collision Detection
Cross-directory collisions (same name from different directories) cause a panic with detailed diagnostics. This catches configuration mistakes early.
Same-directory, different-extension scenarios are resolved by priority (not errors).
§Supported Resource Types
| Resource | Extensions | Transform |
|---|---|---|
| Templates | .jinja, .jinja2, .j2, .txt | Identity |
| Stylesheets | .yaml, .yml | YAML parsing |
| Custom | User-defined | User-defined |
The registry is generic over content type T, enabling consistent behavior
across all resource types with type-specific parsing via the transform function.
Structs§
- File
Registry - Generic registry for file-based resources.
- File
Registry Config - Configuration for a file registry.
- Loaded
File - A file discovered during directory walking.
Enums§
- Load
Error - Error type for file loading operations.
- Loaded
Entry - How a resource is stored—file path (dev) or content (release).
Functions§
- build_
embedded_ registry - Builds a registry map from embedded entries with extension priority handling.
- extension_
priority - Returns the extension priority for a filename (lower = higher priority).
- strip_
extension - Strips a recognized extension from a filename.
- walk_
dir - Walks a directory recursively and collects files with recognized extensions.