1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
//! `osp-cli` is the library behind the `osp` CLI and REPL.
//!
//! Use it when you want one of these jobs:
//!
//! - run the full `osp` host in-process
//! - build a wrapper crate with site-specific native commands and defaults
//! - execute the small LDAP service surface without the full host
//! - render rows or run DSL pipelines in-process
//!
//! Most readers only need one of those lanes. You do not need to understand
//! the whole crate before using it.
//!
//! The crate also keeps the full `osp` product surface in one place so the
//! main concerns stay visible together: host orchestration, config resolution,
//! rendering, REPL integration, completion, plugins, and the pipeline DSL.
//! That makes rustdoc a useful architecture map after you have picked the
//! smallest surface that fits your job.
//!
//! Quick starts for the three most common library shapes:
//!
//! Full `osp`-style host with captured output:
//!
//! ```
//! use osp_cli::App;
//! use osp_cli::app::BufferedUiSink;
//!
//! let mut sink = BufferedUiSink::default();
//! let exit = App::new().run_with_sink(["osp", "--help"], &mut sink)?;
//!
//! assert_eq!(exit, 0);
//! assert!(!sink.stdout.is_empty());
//! assert!(sink.stderr.is_empty());
//! # Ok::<(), miette::Report>(())
//! ```
//!
//! Lightweight LDAP command execution plus DSL stages:
//!
//! ```
//! use osp_cli::config::RuntimeConfig;
//! use osp_cli::ports::mock::MockLdapClient;
//! use osp_cli::services::{ServiceContext, execute_line};
//!
//! let ctx = ServiceContext::new(
//! Some("oistes".to_string()),
//! MockLdapClient::default(),
//! RuntimeConfig::default(),
//! );
//! let output = execute_line(&ctx, "ldap user oistes | P uid cn")
//! .expect("service command should run");
//! let rows = output.as_rows().expect("expected row output");
//!
//! assert_eq!(rows.len(), 1);
//! assert_eq!(rows[0].get("uid").and_then(|value| value.as_str()), Some("oistes"));
//! assert!(rows[0].contains_key("cn"));
//! ```
//!
//! Rendering existing rows without bootstrapping the full host:
//!
//! ```
//! use osp_cli::core::output::OutputFormat;
//! use osp_cli::row;
//! use osp_cli::ui::{RenderSettings, render_rows};
//!
//! let rendered = render_rows(
//! &[row! { "uid" => "alice", "mail" => "alice@example.com" }],
//! &RenderSettings::test_plain(OutputFormat::Json),
//! );
//!
//! assert!(rendered.contains("\"uid\": \"alice\""));
//! assert!(rendered.contains("\"mail\": \"alice@example.com\""));
//! ```
//!
//! Building a product-specific wrapper crate:
//!
//! - keep site-specific auth, policy, and domain integrations in the wrapper
//! crate
//! - extend the command surface with [`App::with_native_commands`] or
//! [`AppBuilder::with_native_commands`]
//! - keep runtime config bootstrap aligned with
//! [`config::RuntimeDefaults`], [`config::RuntimeConfigPaths`], and
//! [`config::build_runtime_pipeline`]
//! - expose a thin product-level `run_process` or builder API on top of this
//! crate instead of forking generic host behavior
//!
//! Minimal wrapper shape:
//!
//! ```
//! use std::ffi::OsString;
//!
//! use anyhow::Result;
//! use clap::Command;
//! use osp_cli::app::BufferedUiSink;
//! use osp_cli::config::ConfigLayer;
//! use osp_cli::{
//! App, AppBuilder, NativeCommand, NativeCommandContext, NativeCommandOutcome,
//! NativeCommandRegistry,
//! };
//!
//! struct SiteStatusCommand;
//!
//! impl NativeCommand for SiteStatusCommand {
//! fn command(&self) -> Command {
//! Command::new("site-status").about("Show site-specific status")
//! }
//!
//! fn execute(
//! &self,
//! _args: &[String],
//! _context: &NativeCommandContext<'_>,
//! ) -> Result<NativeCommandOutcome> {
//! Ok(NativeCommandOutcome::Exit(0))
//! }
//! }
//!
//! fn site_registry() -> NativeCommandRegistry {
//! NativeCommandRegistry::new().with_command(SiteStatusCommand)
//! }
//!
//! fn site_defaults() -> ConfigLayer {
//! let mut defaults = ConfigLayer::default();
//! defaults.set("extensions.site.enabled", true);
//! defaults
//! }
//!
//! #[derive(Clone)]
//! struct SiteApp {
//! inner: App,
//! }
//!
//! impl SiteApp {
//! fn builder() -> AppBuilder {
//! App::builder()
//! .with_native_commands(site_registry())
//! .with_product_defaults(site_defaults())
//! }
//!
//! fn new() -> Self {
//! Self {
//! inner: Self::builder().build(),
//! }
//! }
//!
//! fn run_process<I, T>(&self, args: I) -> i32
//! where
//! I: IntoIterator<Item = T>,
//! T: Into<OsString> + Clone,
//! {
//! self.inner.run_process(args)
//! }
//! }
//!
//! let app = SiteApp::new();
//! let mut sink = BufferedUiSink::default();
//! let exit = app.inner.run_process_with_sink(["osp", "--help"], &mut sink);
//!
//! assert_eq!(exit, 0);
//! assert!(sink.stdout.contains("site-status"));
//! assert_eq!(app.run_process(["osp", "--help"]), 0);
//! ```
//!
//! If you are new here, start with one of these:
//!
//! - wrapper crate / downstream product →
//! [embedding guide](https://github.com/unioslo/osp-cli-rs/blob/main/docs/EMBEDDING.md)
//! and [`App::builder`]
//! - full in-process host → [`app`]
//! - smaller service-only integration → [`services`]
//! - rendering / formatting only → [`ui`]
//!
//! Start here depending on what you need:
//!
//! - [`app`] exists to turn the lower-level pieces into a running CLI or REPL
//! process.
//! - [`cli`] exists to model the public command-line grammar.
//! - [`config`] exists to answer what values are legal, where they came from,
//! and what finally wins.
//! - [`completion`] exists to rank suggestions without depending on terminal
//! state or editor code.
//! - [`repl`] exists to own the interactive shell boundary.
//! - [`dsl`] exists to provide the canonical document-first pipeline language.
//! - [`ui`] exists to lower structured output into terminal-facing text.
//! - [`plugin`] exists to treat external command providers as part of the same
//! command surface.
//! - [`services`] and [`ports`] exist for smaller embeddable integrations that
//! do not want the whole host stack.
//!
//! # Feature Flags
//!
//! - `clap` (enabled by default): exposes the clap conversion helpers such as
//! [`crate::core::command_def::CommandDef::from_clap`],
//! [`crate::core::plugin::DescribeCommandV1::from_clap`], and
//! [`crate::core::plugin::DescribeV1::from_clap_command`].
//!
//! At runtime, data flows roughly like this:
//!
//! ```text
//! argv / REPL line
//! │
//! ▼ [ cli ] parse grammar and flags
//! ▼ [ config ] resolve layered settings (builtin → file → env → cli)
//! ▼ [ app ] dispatch to plugin or native command ──► Vec<Row>
//! ▼ [ dsl ] apply pipeline stages to rows ──► OutputResult
//! ▼ [ ui ] render structured output to terminal or UiSink
//! ```
//!
//! Architecture contracts worth keeping stable:
//!
//! - lower-level modules should not depend on [`app`]
//! - [`completion`] stays pure and should not start doing network, plugin
//! discovery, or terminal I/O
//! - [`ui`] renders structured input but should not become a config-resolver or
//! service-execution layer
//! - [`cli`] describes the grammar of the program but does not execute it
//! - [`config`] owns precedence and legality rules so callers do not invent
//! their own merge semantics
//!
//! Public API shape:
//!
//! - semantic payload modules such as [`guide`] and most of [`completion`]
//! stay intentionally cheap to compose and inspect
//! - host machinery such as [`app::App`], [`app::AppBuilder`], and runtime
//! state is guided through constructors/builders/accessors rather than
//! compatibility shims or open-ended assembly
//! - each public concept should have one canonical home; duplicate aliases and
//! mirrored module paths are treated as API debt
//!
//! Guided construction naming:
//!
//! - `Type::new(...)` is the exact constructor when the caller already knows
//! the required inputs
//! - `Type::builder(...)` starts guided construction for heavier host/runtime
//! objects and returns a concrete `TypeBuilder`
//! - builder setters use `with_*` and the terminal step is always `build()`
//! - `Type::from_*` and `Type::detect()` are reserved for derived/probing
//! factories
//! - semantic DSLs may keep domain verbs such as `arg`, `flag`, or
//! `subcommand`; the `with_*` rule is for guided host configuration, not for
//! every fluent API
//! - avoid abstract "factory builder" layers in the public API; callers should
//! see concrete type-named builders and factories directly
//!
//! For embedders, choose the smallest surface that solves the problem you
//! actually have:
//!
//! - "I want a full `osp`-style binary or custom `main`" →
//! [`app::App::builder`], [`app::AppBuilder::build`], or
//! [`app::App::run_from`]
//! - "I want to capture rendered stdout/stderr in tests or another host" →
//! [`app::App::with_sink`] or [`app::AppBuilder::build_with_sink`]
//! - "I want parser + service execution + DSL, but not the full host" →
//! [`services::ServiceContext`] and [`services::execute_line`]
//! - "I already have rows and only want pipeline transforms" →
//! [`dsl::apply_pipeline`] or [`dsl::apply_output_pipeline`]
//! - "I need plugin discovery and catalog/policy integration" →
//! [`plugin::PluginManager`] on the host side, or [`core::plugin`] when
//! implementing the wire protocol itself
//! - "I need manual runtime/session state" → [`app::AppStateBuilder::new`],
//! [`app::UiState::new`], [`app::UiState::from_resolved_config`], and direct
//! [`app::LaunchContext`] setters
//! - "I want to embed the interactive editor loop directly" →
//! [`repl::ReplRunConfig::builder`] and [`repl::HistoryConfig::builder`]
//! - "I need semantic payload generation for help/completion surfaces" →
//! [`guide::GuideView`] and [`completion::CompletionTreeBuilder`]
//!
//! The root crate module tree is the only supported code path. Older mirrored
//! layouts have been removed so rustdoc and the source tree describe the same
//! architecture.
/// Main host-facing entrypoints, runtime state, and session types.
/// Command-line argument types and CLI parsing helpers.
/// Structured command and pipe completion types.
/// Layered configuration schema, loading, and resolution.
/// Shared command, output, row, and protocol primitives.
/// Canonical pipeline parsing and execution.
/// Structured help/guide view models and conversions.
/// External plugin discovery, protocol, and dispatch support.
/// Service-layer ports used by command execution.
/// Interactive REPL editor, prompt, history, and completion surface.
/// Library-level service entrypoints built on the core ports.
/// Rendering, theming, and structured output helpers.
pub use crate;
pub use cratecommand_policy;
pub use crate;