nmbrs_runtime/readouts/builtins/
each_close.rs1use std::fmt::Write as _;
15
16use crate::lifecycle::SubjectKind;
17use crate::readouts::buf::ReadoutBuf;
18use crate::readouts::context::ReadoutContext;
19use crate::readouts::readout::{ContentMode, Lod, Readout, ReadoutOptions};
20
21pub struct EachClose;
22
23impl Readout for EachClose {
24 fn name(&self) -> &'static str {
25 "each_close"
26 }
27 fn accepts(&self) -> &'static [SubjectKind] {
28 &[SubjectKind::Iteration]
29 }
30
31 fn render(
32 &self,
33 ctx: &dyn ReadoutContext,
34 lod: Lod,
35 mode: ContentMode,
36 _opts: &ReadoutOptions,
37 out: &mut dyn ReadoutBuf,
38 ) -> usize {
39 match (lod, mode) {
40 (Lod::Compact, ContentMode::Value) => render_compact(ctx, out),
41 (Lod::Labeled, ContentMode::Value) => render_labeled(ctx, out),
42 (Lod::Expanded, ContentMode::Value) => render_expanded(ctx, out),
43 (_, ContentMode::Explanation) => render_explanation(out),
44 }
45 }
46}
47
48fn render_compact(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
49 let color = ctx.use_color();
50 let cyan = if color { "\x1b[36m" } else { "" };
51 let reset = if color { "\x1b[0m" } else { "" };
52 let mut tmp = String::with_capacity(16);
53 let _ = write!(&mut tmp, "{cyan}└{reset}");
54 let len = tmp.len();
55 let _ = out.write_str(&tmp);
56 len
57}
58
59fn render_labeled(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
60 let color = ctx.use_color();
61 let cyan = if color { "\x1b[36m" } else { "" };
62 let italic = if color { "\x1b[3m" } else { "" };
63 let reset = if color { "\x1b[0m" } else { "" };
64 let mut tmp = String::with_capacity(32);
65 let _ = write!(&mut tmp, "{cyan}└─{reset} {italic}end{reset}");
66 let len = tmp.len();
67 let _ = out.write_str(&tmp);
68 len
69}
70
71fn render_expanded(ctx: &dyn ReadoutContext, out: &mut dyn ReadoutBuf) -> usize {
72 let color = ctx.use_color();
73 let cyan = if color { "\x1b[36m" } else { "" };
74 let italic = if color { "\x1b[3m" } else { "" };
75 let reset = if color { "\x1b[0m" } else { "" };
76 let labels = ctx.subject_labels();
77 let mut tmp = String::with_capacity(64);
78 if labels.is_empty() {
79 let _ = write!(&mut tmp, "{cyan}└─{reset} {italic}end{reset}");
80 } else {
81 let _ = write!(&mut tmp, "{cyan}└─{reset} {italic}end{reset} {labels}");
82 }
83 let len = tmp.len();
84 let _ = out.write_str(&tmp);
85 len
86}
87
88fn render_explanation(out: &mut dyn ReadoutBuf) -> usize {
89 let s = "└─ end — closing marker for the surrounding for_each / for_combinations iteration";
90 let _ = out.write_str(s);
91 s.len()
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::readouts::buf::StringBuf;
98
99 #[derive(Default)]
100 struct TestCtx {
101 labels: String,
102 use_color: bool,
103 }
104 impl ReadoutContext for TestCtx {
105 fn subject_name(&self) -> &str {
106 ""
107 }
108 fn subject_seq(&self) -> Option<(usize, usize)> {
109 None
110 }
111 fn subject_labels(&self) -> &str {
112 &self.labels
113 }
114 fn cycles_completed(&self) -> u64 {
115 0
116 }
117 fn cycles_total(&self) -> u64 {
118 0
119 }
120 fn ops_ok(&self) -> u64 {
121 0
122 }
123 fn errors(&self) -> u64 {
124 0
125 }
126 fn retries(&self) -> u64 {
127 0
128 }
129 fn concurrency(&self) -> usize {
130 0
131 }
132 fn elapsed_secs(&self) -> f64 {
133 0.0
134 }
135 fn consumed(&self) -> u64 {
136 0
137 }
138 fn status_metric_chips(&self) -> String {
139 String::new()
140 }
141 fn depth_indent(&self) -> &str {
142 ""
143 }
144 fn use_color(&self) -> bool {
145 self.use_color
146 }
147 fn event(&self) -> crate::lifecycle::EventType {
148 crate::lifecycle::EventType::EachEnd
149 }
150 }
151
152 fn render(ctx: &TestCtx, lod: Lod) -> String {
153 let mut s = String::new();
154 let mut buf = StringBuf::new(&mut s);
155 EachClose.render(
156 ctx,
157 lod,
158 ContentMode::Value,
159 &ReadoutOptions::new(),
160 &mut buf,
161 );
162 s
163 }
164
165 #[test]
166 fn labeled_no_color() {
167 let ctx = TestCtx::default();
168 assert_eq!(render(&ctx, Lod::Labeled), "└─ end");
169 }
170
171 #[test]
172 fn compact_thinner_than_labeled() {
173 let ctx = TestCtx::default();
174 assert_eq!(render(&ctx, Lod::Compact), "└");
175 }
176
177 #[test]
178 fn expanded_includes_iter_labels() {
179 let ctx = TestCtx {
180 labels: "(profile=alpha, k=10)".into(),
181 ..Default::default()
182 };
183 let out = render(&ctx, Lod::Expanded);
184 assert!(out.contains("end"));
185 assert!(out.contains("(profile=alpha, k=10)"));
186 }
187
188 #[test]
189 fn ansi_emitted_when_color_enabled() {
190 let ctx = TestCtx {
191 use_color: true,
192 ..Default::default()
193 };
194 let out = render(&ctx, Lod::Labeled);
195 assert!(out.contains("\x1b[36m"));
196 assert!(out.contains("\x1b[3m"));
197 }
198}