Skip to main content

doiget_cli/commands/
csl.rs

1//! `doiget csl <ref>` — emit a CSL JSON 1.0 array for a stored Metadata.
2//!
3//! Reads the stored [`Metadata`](doiget_core::store::Metadata) for a
4//! [`Ref`] and writes a single-element CSL JSON 1.0 **array** (a drop-in
5//! for citeproc-js / pandoc `--csl-json` consumers that expect a list)
6//! on stdout. Rendering lives in
7//! [`doiget_core::store::render::to_csl_array`] so the CLI and the
8//! `doiget_csl_export` MCP tool share one implementation (Slice 15b).
9//!
10//! Reads go through [`Store::read`]; network access is never required.
11
12use std::io::Write;
13
14use anyhow::{bail, Context, Result};
15
16use doiget_core::store::{render, FsStore, Store};
17use doiget_core::Ref;
18
19use super::resolve_store_root;
20
21/// Run the `csl` subcommand against the configured store.
22///
23/// `input` is the user-supplied ref string (anything accepted by
24/// [`Ref::parse`]). On success a single-element CSL JSON 1.0 array is
25/// written to stdout; a missing entry returns an error so the CLI exits
26/// non-zero (pipelines can distinguish "not in store" from "empty").
27pub fn run(input: String, _mode: super::output::OutputMode) -> Result<()> {
28    // `_mode` is threaded per ADR-0017 / #144. Csl's JSON output is
29    // product output, not informational; Quiet does NOT suppress it.
30    let ref_ = Ref::parse(&input).with_context(|| format!("invalid ref: {input}"))?;
31    let safekey = ref_.safekey();
32
33    let store_root = resolve_store_root()?;
34    let store = FsStore::new(store_root)?;
35
36    let metadata = store
37        .read(&safekey)
38        .with_context(|| format!("failed to read store entry for {input}"))?;
39
40    let metadata = match metadata {
41        Some(m) => m,
42        None => bail!("no entry for {input}"),
43    };
44
45    let array = render::to_csl_array(safekey.as_str(), &metadata);
46    let json =
47        serde_json::to_string_pretty(&array).context("failed to serialize CSL JSON for stdout")?;
48
49    // Workspace lints deny `print_stdout` (`println!`/`print!`); the
50    // sanctioned escape hatch is `writeln!(stdout().lock(), ...)`.
51    // See `docs/SECURITY.md` §3 / ADR-0001 — guarantees JSON-RPC frames
52    // never collide with diagnostics.
53    let stdout = std::io::stdout();
54    let mut out = stdout.lock();
55    writeln!(out, "{json}").context("failed to write CSL JSON to stdout")?;
56    Ok(())
57}