Skip to main content

nmbrs_runtime/readouts/
buf.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! [`ReadoutBuf`] — the surface-supplied output buffer.
5//!
6//! Push 1 only needs the [`String`]-backed buffer that the
7//! terminal-mode log path uses (ANSI escape sequences land
8//! inline as text). Later pushes will add a `Vec<Span>`-backed
9//! buffer for the TUI surface; the [`ReadoutBuf`] trait is
10//! the single write surface a [`Readout`](crate::Readout)
11//! impl sees regardless of which backing store is in use.
12
13use std::fmt::{self, Write as _};
14
15/// Append-only write surface a readout writes its rendered
16/// piece into. Implementations decide how to handle styling
17/// (ANSI inline vs. typed style runs) and ANSI stripping
18/// (TTY vs. non-TTY sinks).
19///
20/// The trait is intentionally minimal — `write_str` is
21/// enough for the Push 1 built-in. Later pushes will extend
22/// it with style-run pushes (`push_styled`) once the colour
23/// / style sub-language lands; until then, readouts emit
24/// ANSI escapes as part of `write_str` payloads when they
25/// want styling, and the buffer's own ANSI policy decides
26/// whether to keep or strip.
27pub trait ReadoutBuf {
28    /// Append the given UTF-8 text. Must not allocate
29    /// independently of the underlying storage's growth.
30    fn write_str(&mut self, s: &str) -> fmt::Result;
31
32    /// Hint about how many more bytes will be appended.
33    /// Implementations may use this to reserve capacity.
34    /// Optional — default no-op.
35    fn reserve(&mut self, _additional: usize) {}
36}
37
38/// `String`-backed buffer. Used by the terminal-mode log
39/// surface, which routes through `nmbrs-runtime::observer::log`
40/// and ultimately `eprint!`. ANSI escape sequences pass
41/// through unchanged; the surface above strips them when the
42/// destination is a non-TTY pipe.
43pub struct StringBuf<'a> {
44    inner: &'a mut String,
45}
46
47impl<'a> StringBuf<'a> {
48    pub fn new(inner: &'a mut String) -> Self {
49        Self { inner }
50    }
51}
52
53impl ReadoutBuf for StringBuf<'_> {
54    fn write_str(&mut self, s: &str) -> fmt::Result {
55        self.inner.write_str(s)
56    }
57
58    fn reserve(&mut self, additional: usize) {
59        self.inner.reserve(additional);
60    }
61}
62
63/// Convenience: render a readout into a freshly allocated
64/// `String`. Used by the in-process tests and the
65/// `crate::diag!` bridge in Push 1; later pushes will route
66/// directly through a borrowed buffer instead.
67pub fn render_to_string<F>(estimated_size: usize, render: F) -> String
68where
69    F: FnOnce(&mut StringBuf<'_>) -> usize,
70{
71    let mut s = String::with_capacity(estimated_size);
72    {
73        let mut buf = StringBuf::new(&mut s);
74        let _ = render(&mut buf);
75    }
76    s
77}