//! `--json`: one JSON value on stdout, everything else on stderr. //! //! The flag started on the OAuth log, then on `pr list` and `pr view`, each //! spelling its own rules out in its own module. Those rules are the same //! everywhere and are now written down once, here, because the value of //! machine-readable output is that a caller can learn it from one command //! and rely on it from the next: //! //! 1. **Stdout carries exactly one JSON value**, pretty-printed, with a //! trailing newline. Not a stream of objects, not a value with a status //! line above it — a listing that finds nothing prints `[]` rather than //! prose, so a script never has to tell "nothing to report" apart from //! "the process died before printing". The `logs` readers are the //! documented exception — `--follow` is an unbounded stream and cannot be //! one value — and print JSON Lines instead; see `docs/output.md` and //! [`crate::cmd::logs::render`]. //! 2. **Everything else goes to stderr** — and does so on every flag, not //! only this one. The notes these commands print beside their output — //! index-lag warnings, `acting as …`, "give the firehose a moment" — are //! commentary rather than payload, so they travel through //! [`crate::term::say`], which puts them on stderr whether or not anybody //! asked for JSON. Nothing here has to move them: by the time a command //! reaches its `--json` branch, they are already on the other stream. //! 3. **Decoration is off, unconditionally** — colour, OSC 8 hyperlinks, //! ellipsizing, column padding. All of it is display logic for a person //! at a terminal, all of it would have to be undone by a caller parsing //! the output back, and none of it runs regardless of whether stdout //! happens to be a pipe. //! 4. **Errors are not JSON.** A failure prints the same human-readable //! message to stderr and exits non-zero that it always has; nothing here //! wraps it in an envelope. Stderr and the exit code are atgc's error //! channel on every other flag too, and keeping it that way is what lets //! a caller distinguish an empty result from a failed command without //! inspecting anything. //! //! ## What the shapes hold //! //! Read commands emit the *derived* view rather than the record underneath: //! a pull's state and number are in no record at all, and the resolving is //! the reason to run the command (see [`crate::docs::architecture`]). //! //! Unresolved is `null`, never `"?"` or an empty string: `?` is a display //! convention for a terminal column, and JSON has its own way to say a //! lookup did not answer. //! //! [`init`] records the mode for output sites that have no arguments of //! their own to consult — the shared printers a command calls from //! underneath its own `args`. A process-global is how `say` and `noinput` //! already answer that same question. use anyhow::Result; use std::sync::atomic::{AtomicBool, Ordering}; static ACTIVE: AtomicBool = AtomicBool::new(false); /// Declare this invocation's stdout to be a JSON document. /// /// Called at the top of every command that takes `--json`, before anything /// is printed. The decisions downstream of it are all about stdout — the /// shape of the payload, and whether a formatter runs at all; what a /// command says *beside* its answer is [`crate::term::say`]'s business and does /// not consult this flag. pub fn init(flag: bool) { ACTIVE.store(flag, Ordering::Relaxed); } /// Whether stdout is reserved for a JSON document. /// /// For the shared printers that have no arguments of their own to consult — /// [`crate::term::hyperlink::stdout_escapes_wanted`], the one gate every colour /// and OSC 8 decision in the tool goes through, is the first of them. A /// command with `args.json` in hand should branch on that instead: same /// answer, in front of the reader of that command. pub fn active() -> bool { ACTIVE.load(Ordering::Relaxed) } /// Print the one JSON value this invocation has to say, and a newline. /// /// Pretty-printed rather than compact: `jq` does not care, a person reading /// a captured file does, and the size difference is nothing next to the /// patches these records carry. pub fn emit(value: &impl serde::Serialize) -> Result<()> { serde_json::to_writer_pretty(std::io::stdout(), value)?; println!(); Ok(()) }