standout_dispatch/lib.rs
1//! Command dispatch and orchestration for clap-based CLIs.
2//!
3//! `standout-dispatch` provides command routing, handler execution, and a hook
4//! system for CLI applications. It orchestrates the execution flow while remaining
5//! agnostic to rendering implementation.
6//!
7//! # Architecture
8//!
9//! Dispatch is an orchestration layer that manages this execution flow:
10//!
11//! ```text
12//! parsed CLI args
13//! → pre-dispatch hook (validation, setup)
14//! → handler adapter (CLI input → application call → serializable view data)
15//! → post-dispatch hook (data transformation)
16//! → render handler (view + data → string output)
17//! → post-output hook (output transformation)
18//! ```
19//!
20//! ## Design Rationale
21//!
22//! Dispatch deliberately does not own rendering or output format logic:
23//!
24//! - Handler adapters have a strict input signature (`&ArgMatches`,
25//! `&CommandContext`) and return serializable data. Reusable application
26//! behavior remains behind a CLI-free library interface.
27//!
28//! - Render handlers are pluggable callbacks provided by the consuming framework.
29//! They receive (view name, data) and return a formatted string. All rendering
30//! decisions (format, theme, template engine) live in the render handler.
31//!
32//! This separation allows:
33//! - Using dispatch without any rendering (just return data)
34//! - Using dispatch with custom renderers (not just standout-render)
35//! - Keeping format/theme/template logic out of the dispatch layer
36//!
37//! ## Render Handler Pattern
38//!
39//! The render handler is a closure that captures rendering context:
40//!
41//! ```rust,ignore
42//! // Framework (e.g., standout) creates the render handler at runtime
43//! // after parsing CLI args to determine format
44//! let format = extract_output_mode(&matches); // --output=json
45//! let theme = &config.theme;
46//!
47//! let render_handler = move |view: &str, data: &Value| {
48//! // All format/theme knowledge lives here, not in dispatch
49//! my_renderer::render(view, data, theme, format)
50//! };
51//!
52//! dispatcher.run_with_renderer(matches, render_handler);
53//! ```
54//!
55//! This pattern means dispatch calls `render_handler(view, data)` without knowing
56//! what format, theme, or template engine is being used.
57//!
58//! # State Management
59//!
60//! [`CommandContext`] provides two mechanisms for dependency injection:
61//!
62//! - **`app_state`**: Immutable, app-lifetime state (database, config, API clients).
63//! Configured at app build time, shared across all dispatches via `Rc<Extensions>`.
64//!
65//! - **`extensions`**: Mutable, per-request state. Injected by pre-dispatch hooks
66//! for request-scoped data like user sessions or request IDs.
67//!
68//! ```rust,ignore
69//! // App-level state (build time)
70//! App::builder()
71//! .app_state(Database::connect()?)
72//! .app_state(Config::load()?)
73//!
74//! // In handler
75//! fn handler(matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
76//! let db = ctx.app_state.get_required::<Database>()?; // shared
77//! let scope = ctx.extensions.get_required::<UserScope>()?; // per-request
78//! // ...
79//! }
80//! ```
81//!
82//! # Features
83//!
84//! - Command routing: Extract command paths from clap `ArgMatches`
85//! - Handler traits: [`Handler`] trait with `&mut self` for mutable state
86//! - Hook system: Pre/post dispatch and post-output hooks for cross-cutting concerns
87//! - State injection: App-level state via `app_state`, per-request state via `extensions`
88//! - Render abstraction: Pluggable render handlers via [`RenderFn`]
89//!
90//! # Usage
91//!
92//! ## Standalone (no rendering framework)
93//!
94//! ```rust,ignore
95//! use standout_dispatch::{Handler, Output, from_fn};
96//!
97//! // Simple render handler that just serializes to JSON
98//! let render = from_fn(|data, _| Ok(serde_json::to_string_pretty(data)?));
99//!
100//! Dispatcher::builder()
101//! .command("list", list_handler, render)
102//! .build()?
103//! .run(cmd, args);
104//! ```
105//!
106//! ## With standout framework
107//!
108//! The `standout` crate provides full integration with templates and themes:
109//!
110//! ```rust,ignore
111//! use standout::{App, embed_templates};
112//!
113//! App::builder()
114//! .templates(embed_templates!("src/templates"))
115//! .command("list", list_handler, "list") // template name
116//! .build()?
117//! .run(cmd, args);
118//! ```
119//!
120//! In this case, `standout` creates the render handler internally, injecting
121//! the template registry, theme, and output format from CLI args.
122
123// Core modules
124pub mod artifact;
125mod dispatch;
126mod handler;
127mod hooks;
128mod render;
129pub mod verify;
130
131// Re-export compound artifact types
132pub use artifact::{Artifact, ArtifactDestination, ArtifactReceipt, ArtifactRun};
133
134// Re-export command routing utilities
135pub use dispatch::{
136 extract_command_path, get_deepest_matches, has_subcommand, insert_default_command,
137 path_to_string, string_to_path,
138};
139
140// Re-export handler types
141pub use handler::{
142 CommandContext, ExitStatus, Extensions, ExternalFailure, FnHandler, Handler, HandlerResult,
143 IntoHandlerResult, InvalidExternalStatus, Output, OutputKind, RunError, RunErrorKind,
144 RunOutput, RunResult, SimpleFnHandler, SuccessKind,
145};
146
147// Re-export hook types
148pub use hooks::{
149 ArtifactOutput, HookError, HookPhase, Hooks, PostDispatchFn, PostOutputFn, PreDispatchFn,
150 RenderedOutput, TextOutput,
151};
152
153// Re-export render abstraction
154pub use render::{from_fn, RenderError, RenderFn};