doiget_cli/commands/bib.rs
1//! `doiget bib <ref>` subcommand — emit a BibTeX entry for a stored entry.
2//!
3//! Reads the stored [`Metadata`](doiget_core::store::Metadata) for a
4//! [`Ref`] and writes a BibTeX entry on stdout. The actual rendering
5//! lives in [`doiget_core::store::render::to_bibtex`] so the CLI and the
6//! `doiget_bibtex_export` MCP tool share one implementation (Slice 15b).
7//! See that function's docs for the field/entry-type mapping and the
8//! brace-stripping policy.
9
10use std::io::Write;
11
12use anyhow::{bail, Context, Result};
13
14use doiget_core::store::{render, FsStore, Store};
15use doiget_core::Ref;
16
17use super::resolve_store_root;
18
19/// Run the `bib` subcommand against the configured store.
20///
21/// `input` is the user-supplied ref string (e.g. `"10.1234/example"`,
22/// `"arxiv:2401.12345"`, or any of the schemes accepted by [`Ref::parse`]).
23///
24/// On success, a BibTeX entry derived from the entry's `Metadata` is
25/// written to stdout. On a missing entry, the function returns an error
26/// so the CLI exits non-zero.
27pub fn run(input: String, _mode: super::output::OutputMode) -> Result<()> {
28 // `_mode` is threaded per ADR-0017 / #144. Bib's BibTeX output is
29 // product output (the requested artifact), not informational, so
30 // Quiet does NOT suppress it. No follow-up issue is needed here.
31 let ref_ = Ref::parse(&input).with_context(|| format!("invalid ref: {input}"))?;
32 let safekey = ref_.safekey();
33
34 let store_root = resolve_store_root()?;
35 let store = FsStore::new(store_root)?;
36
37 let metadata = store
38 .read(&safekey)
39 .with_context(|| format!("failed to read store entry for {input}"))?;
40
41 match metadata {
42 Some(m) => {
43 let bib = render::to_bibtex(safekey.as_str(), &m);
44 // Workspace lints deny `print_stdout` (the `print!`/`println!`
45 // macros) so JSON-RPC frames never collide with diagnostics.
46 // `write!` against an explicit `stdout().lock()` is the
47 // sanctioned escape hatch — `to_bibtex` already terminates
48 // the entry with `}\n`, so no extra newline is added.
49 // See `docs/SECURITY.md` §3 / ADR-0001.
50 let stdout = std::io::stdout();
51 let mut out = stdout.lock();
52 write!(out, "{bib}").context("failed to write BibTeX entry to stdout")?;
53 Ok(())
54 }
55 None => bail!("no entry for {input}"),
56 }
57}