How To: Use Only the Rendering Layer
Standout's rendering layer is fully decoupled from its CLI integration (App, Clap, Dispatch). This means you can use the template engine, theme system, and structured output logic in any context—servers, TUI apps, or even other CLI frameworks.
This decoupling allows you to maintain consistent styling and logic across different parts of your ecosystem.
When to Use This
- Adding styled output to an existing application
- Building a library that produces formatted terminal output
- Server-side rendering of CLI-style output
- Testing templates in isolation
Basic Rendering
The simplest approach—auto-detect terminal capabilities:
use standout::{render, Theme}; use console::Style; use serde::Serialize; #[derive(Serialize)] struct Report { title: String, items: Vec<String>, } fn main() -> Result<(), Box<dyn std::error::Error>> { let theme = Theme::new() .add("title", Style::new().bold().cyan()) .add("item", Style::new().green()); let data = Report { title: "Status Report".into(), items: vec!["Task A: complete".into(), "Task B: pending".into()], }; let output = render( r#"[title]{{ title }}[/title] {% for item in items %} [item]•[/item] {{ item }} {% endfor %}"#, &data, &theme, )?; println!("{}", output); Ok(()) }
Explicit Output Mode
Control ANSI code generation:
#![allow(unused)] fn main() { use standout::{render_with_output, OutputMode}; // Force ANSI codes (even when piping) let colored = render_with_output(template, &data, &theme, OutputMode::Term)?; // Force plain text (no ANSI codes) let plain = render_with_output(template, &data, &theme, OutputMode::Text)?; // Debug mode (tags as literals) let debug = render_with_output(template, &data, &theme, OutputMode::TermDebug)?; }
Auto-Dispatch: Template vs Serialization
render_auto chooses between template rendering and direct serialization:
#![allow(unused)] fn main() { use standout::render_auto; fn format_output(data: &Report, mode: OutputMode) -> Result<String, Error> { render_auto(template, data, &theme, mode) } // Term/Text/Auto: renders template format_output(&data, OutputMode::Term)?; // Json/Yaml/Xml/Csv: serializes data directly format_output(&data, OutputMode::Json)?; }
Same function, same data—output format determined by mode.
Full Control: Output Mode + Color Mode
For tests or when forcing specific behavior:
#![allow(unused)] fn main() { use standout::{render_with_mode, ColorMode}; // Force dark mode styling let dark = render_with_mode( template, &data, &theme, OutputMode::Term, ColorMode::Dark, )?; // Force light mode styling let light = render_with_mode( template, &data, &theme, OutputMode::Term, ColorMode::Light, )?; }
Building Themes Programmatically
No YAML files needed:
#![allow(unused)] fn main() { use standout::Theme; use console::{Style, Color}; let theme = Theme::new() // Simple styles .add("bold", Style::new().bold()) .add("muted", Style::new().dim()) .add("error", Style::new().red().bold()) // With specific colors .add("info", Style::new().fg(Color::Cyan)) .add("warning", Style::new().fg(Color::Yellow)) // Aliases .add("disabled", "muted") .add("inactive", "muted") .add_adaptive( "panel", Style::new().bold(), Some(Style::new().fg(Color::Black)), // Light mode Some(Style::new().fg(Color::White)), // Dark mode ); }
Theme Merging
You can layer themes using merge. This is useful for user overrides:
#![allow(unused)] fn main() { let base_theme = Theme::from_file("base.yaml")?; let user_overrides = Theme::from_file("user-config.yaml")?; // User styles overwrite base styles let final_theme = base_theme.merge(user_overrides); }
Pre-Compiled Renderer
For repeated rendering with the same templates:
#![allow(unused)] fn main() { use standout::Renderer; let theme = Theme::new() .add("title", Style::new().bold()); let mut renderer = Renderer::new(theme)?; // Register templates renderer.add_template("header", "[title]{{ title }}[/title]")?; renderer.add_template("item", " - {{ name }}: {{ value }}")?; // Render multiple times for record in records { let header = renderer.render("header", &record)?; println!("{}", header); for item in &record.items { let line = renderer.render("item", item)?; println!("{}", line); } } }
Loading Templates from Files
#![allow(unused)] fn main() { let mut renderer = Renderer::new(theme)?; // Add directory of templates renderer.add_template_dir("./templates")?; // Templates resolved by name (without extension) let output = renderer.render("report", &data)?; }
In debug builds, file-based templates are re-read on each render (hot reload).
Using Embedded Templates
For release builds, embed templates at compile time:
#![allow(unused)] fn main() { use standout::{embed_templates, Renderer, Theme}; let theme = Theme::new() .add("title", Style::new().bold()); let mut renderer = Renderer::new(theme)?; // Load all templates from the embedded source renderer.with_embedded_source(embed_templates!("src/templates")); // Render by name (with or without extension) let output = renderer.render("report", &data)?; // Includes work with extensionless names // If src/templates/_header.jinja exists, use {% include "_header" %} }
Templates are accessible by both extensionless name ("report") and with extension ("report.jinja").
Loading Themes from Embedded Styles
For production deployments, embed stylesheets:
#![allow(unused)] fn main() { use standout::{embed_styles, StylesheetRegistry, Renderer}; // Embed all .yaml files from src/styles/ let styles = embed_styles!("src/styles"); // Convert to a registry for theme lookup let mut registry: StylesheetRegistry = styles.into(); // Get a theme by name (e.g., "default" for src/styles/default.yaml) let theme = registry.get("default")?; // Use with Renderer let mut renderer = Renderer::new(theme)?; }
The relationship:
embed_styles!→EmbeddedStyles(compile-time embedding)StylesheetRegistry→ manages multiple themes, hot-reload in debugTheme→ resolved styles for a single theme, used by Renderer
Feature Support: Includes
Template includes ({% include "partial" %}) require a template registry:
| Approach | Includes | Notes |
|---|---|---|
Renderer | ✓ | Use add_template() or with_embedded_source() |
render() / render_auto() | ✗ | Takes template string, no registry |
For one-off templates without includes, use the standalone render* functions.
For multi-template projects with includes, use Renderer.
Template Validation
Catch style tag errors without producing output:
#![allow(unused)] fn main() { use standout::validate_template; let result = validate_template(template, &sample_data, &theme); match result { Ok(()) => println!("Template is valid"), Err(e) => { eprintln!("Template errors: {}", e); std::process::exit(1); } } }
Use at startup or in tests to fail fast on typos.
Context Injection
Simple Variables with render_with_vars
For adding simple key-value pairs to the template context:
#![allow(unused)] fn main() { use standout::{render_with_vars, Theme, OutputMode}; use std::collections::HashMap; let theme = Theme::new(); let mut vars = HashMap::new(); vars.insert("version", "1.0.0"); vars.insert("app_name", "MyApp"); let output = render_with_vars( "{{ name }} - {{ app_name }} v{{ version }}", &data, &theme, OutputMode::Text, vars, )?; }
This is the recommended approach for most use cases.
Full Context System
For dynamic context computed at render time:
#![allow(unused)] fn main() { use standout::{render_with_context, Theme, OutputMode}; use standout::context::{ContextRegistry, RenderContext}; use minijinja::Value; let mut context = ContextRegistry::new(); context.add_static("version", Value::from("1.0.0")); context.add_provider("timestamp", |_ctx: &RenderContext| { Value::from(chrono::Utc::now().to_rfc3339()) }); let render_ctx = RenderContext::new( OutputMode::Term, Some(80), &theme, &serde_json::to_value(&data)?, ); let output = render_with_context( template, &data, &theme, OutputMode::Term, &context, &render_ctx, )?; }
Structured Output Without Templates
For JSON/YAML output, templates are bypassed:
#![allow(unused)] fn main() { use standout::render_auto; #[derive(Serialize)] struct ApiResponse { status: String, data: Vec<Item>, } let response = ApiResponse { ... }; // Direct JSON serialization let json = render_auto("unused", &response, &theme, OutputMode::Json)?; println!("{}", json); // Direct YAML serialization let yaml = render_auto("unused", &response, &theme, OutputMode::Yaml)?; }
The template parameter is ignored for structured modes.
Minimal Example
Absolute minimum for styled output:
#![allow(unused)] fn main() { use standout::{render, Theme}; use console::Style; let theme = Theme::new().add("ok", Style::new().green()); let output = render("[ok]Success[/ok]", &(), &theme)?; println!("{}", output); }
No files, no configuration—just a theme and a template string.