Minimal single-crate example
This self-contained project is a compact way to see Standout's dispatch and rendering pipeline. It keeps everything in one binary package for brevity; it is not the recommended layout for an application with reusable logic.
For production-shaped ownership, continue to the two-package worked application, where a CLI-free library owns application behavior and a binary owns all CLI concerns.
File structure
my-todo/
├── Cargo.toml
└── src/
├── main.rs
├── templates/list.jinja
└── styles/default.css
Cargo.toml
[package]
name = "my-todo"
version = "0.1.0"
edition = "2021"
[dependencies]
standout = "7"
standout-dispatch = "7" # required by code generated from #[handler]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
anyhow = "1"
src/main.rs
use clap::{CommandFactory, Parser, Subcommand}; use serde::Serialize; use standout::cli::{App, CommandContext, Dispatch, Output}; use standout::{embed_styles, embed_templates, handler}; #[derive(Parser)] #[command(name = "my-todo")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand, Dispatch)] #[dispatch(handlers = handlers)] enum Commands { /// List all todos. #[dispatch(pure)] List, } #[derive(Serialize)] struct TodoResult { todos: Vec<TodoView>, } #[derive(Serialize)] struct TodoView { title: String, status: String, } mod handlers { use super::*; // This is a CLI adapter returning view data. In a real application it // should call a CLI-free library rather than contain application behavior. #[handler] pub fn list( #[ctx] _ctx: &CommandContext, ) -> Result<Output<TodoResult>, anyhow::Error> { Ok(Output::Render(TodoResult { todos: vec![ TodoView { title: "Write documentation".into(), status: "done".into() }, TodoView { title: "Ship v1.0".into(), status: "pending".into() }, ], })) } } fn main() -> Result<(), Box<dyn std::error::Error>> { let app = App::builder() .templates(embed_templates!("src/templates")) .styles(embed_styles!("src/styles")) .default_theme("default") .commands(Commands::dispatch_config())? .build()?; app.run(Cli::command(), std::env::args()); Ok(()) }
src/templates/list.jinja
[title]My Todos[/title]
{% for todo in todos %}
[index]{{ loop.index }}.[/index] [{{ todo.status }}]{{ todo.title }}[/{{ todo.status }}]
{% endfor %}
src/styles/default.css
.title { color: cyan; font-weight: bold; }
.index { color: yellow; }
.done { color: gray; text-decoration: line-through; }
.pending { color: white; font-weight: bold; }
@media (prefers-color-scheme: light) {
.pending { color: black; }
}
Run it
cargo run -- list
cargo run -- list --output json
cargo run -- list --output text
This demonstrates command dispatch, template rendering, structured output, hot reload in debug builds, and adaptive styles. It intentionally does not teach package ownership or the testing pyramid; the production-shaped example does.