Skip to main content

spec_driven_docs/commands/
read.rs

1//! The embedded-document readers: runtime-shape.
2//!
3//! One handler serves `method`, `spec`, and `template` — the shelf is the
4//! only difference. Shelf semantics live in `services::reader`.
5//!
6//! A listing renders each name with the catalog's summary, from the same
7//! source `sdd docs` reads, so a name described in two places cannot
8//! disagree. A shelf document the catalog omits is a canon-test failure
9//! rather than a runtime one, and the listing prints the bare name.
10
11use crate::cli::read::ReadArgs;
12use crate::context::AppContext;
13use crate::error::AppError;
14use crate::output;
15use crate::services::reader::{Shelf, get, list};
16
17/// Read one document from a shelf, or list it.
18///
19/// # Errors
20///
21/// [`AppError::Usage`] when the name resolves to nothing.
22pub fn run(_ctx: &AppContext, shelf: &Shelf, args: ReadArgs) -> Result<(), AppError> {
23    if args.list {
24        let names = list(shelf);
25        let width = names.iter().map(String::len).max().unwrap_or(0);
26        for name in names {
27            match crate::domain::docs_catalog::CATALOG.for_document(shelf.id, &name) {
28                Some(topic) => output::line(format!("{name:width$}  {}", topic.summary)),
29                None => output::line(name),
30            }
31        }
32        return Ok(());
33    }
34    let Some(name) = args.name else {
35        return Err(AppError::Usage(
36            "a document name or --list is required".to_string(),
37        ));
38    };
39    let Some(text) = get(shelf, &name) else {
40        return Err(AppError::Usage(format!("no such document: {name}")));
41    };
42    output::line(text.trim_end_matches('\n'));
43    Ok(())
44}