shape_runtime/
output_adapter.rs1use crate::print_result::PrintResult;
18use shape_value::KindedSlot;
19use std::sync::{Arc, Mutex};
20
21pub trait OutputAdapter: Send + Sync {
29 fn print(&mut self, result: PrintResult) -> KindedSlot;
36
37 fn print_content_html(&mut self, _html: String) {}
40
41 fn clone_box(&self) -> Box<dyn OutputAdapter>;
43}
44
45impl Clone for Box<dyn OutputAdapter> {
47 fn clone(&self) -> Self {
48 self.clone_box()
49 }
50}
51
52#[derive(Debug, Clone)]
56pub struct StdoutAdapter;
57
58impl OutputAdapter for StdoutAdapter {
59 fn print(&mut self, result: PrintResult) -> KindedSlot {
60 println!("{}", result.rendered);
61 KindedSlot::none()
62 }
63
64 fn clone_box(&self) -> Box<dyn OutputAdapter> {
65 Box::new(self.clone())
66 }
67}
68
69#[derive(Debug, Clone)]
79pub struct ReplAdapter;
80
81impl OutputAdapter for ReplAdapter {
82 fn print(&mut self, _result: PrintResult) -> KindedSlot {
83 KindedSlot::none()
84 }
85
86 fn clone_box(&self) -> Box<dyn OutputAdapter> {
87 Box::new(self.clone())
88 }
89}
90
91#[derive(Debug, Clone, Default)]
93pub struct MockAdapter {
94 pub captured: Vec<String>,
96}
97
98impl MockAdapter {
99 pub fn new() -> Self {
100 MockAdapter {
101 captured: Vec::new(),
102 }
103 }
104
105 pub fn output(&self) -> Vec<String> {
107 self.captured.clone()
108 }
109
110 pub fn clear(&mut self) {
112 self.captured.clear();
113 }
114}
115
116impl OutputAdapter for MockAdapter {
117 fn print(&mut self, result: PrintResult) -> KindedSlot {
118 self.captured.push(result.rendered.clone());
119 KindedSlot::none()
120 }
121
122 fn clone_box(&self) -> Box<dyn OutputAdapter> {
123 Box::new(self.clone())
124 }
125}
126
127#[derive(Debug, Clone, Default)]
133pub struct SharedCaptureAdapter {
134 captured: Arc<Mutex<Vec<String>>>,
135 captured_full: Arc<Mutex<Vec<PrintResult>>>,
136 content_html: Arc<Mutex<Vec<String>>>,
137}
138
139impl SharedCaptureAdapter {
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn output(&self) -> Vec<String> {
146 self.captured
147 .lock()
148 .map(|v| v.clone())
149 .unwrap_or_else(|_| Vec::new())
150 }
151
152 pub fn clear(&self) {
154 if let Ok(mut v) = self.captured.lock() {
155 v.clear();
156 }
157 }
158
159 pub fn push_content_html(&self, html: String) {
161 if let Ok(mut v) = self.content_html.lock() {
162 v.push(html);
163 }
164 }
165
166 pub fn content_html(&self) -> Vec<String> {
168 self.content_html
169 .lock()
170 .map(|v| v.clone())
171 .unwrap_or_default()
172 }
173
174 pub fn print_results(&self) -> Vec<PrintResult> {
176 self.captured_full
177 .lock()
178 .map(|v| v.clone())
179 .unwrap_or_default()
180 }
181}
182
183impl OutputAdapter for SharedCaptureAdapter {
184 fn print(&mut self, result: PrintResult) -> KindedSlot {
185 if let Ok(mut v) = self.captured.lock() {
186 v.push(result.rendered.clone());
187 }
188 if let Ok(mut v) = self.captured_full.lock() {
189 v.push(result);
190 }
191 KindedSlot::none()
192 }
193
194 fn print_content_html(&mut self, html: String) {
195 self.push_content_html(html);
196 }
197
198 fn clone_box(&self) -> Box<dyn OutputAdapter> {
199 Box::new(self.clone())
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::print_result::PrintSpan;
207
208 fn make_test_result() -> PrintResult {
209 PrintResult {
210 rendered: "Test output".to_string(),
211 spans: vec![PrintSpan::Literal {
212 text: "Test output".to_string(),
213 start: 0,
214 end: 11,
215 span_id: "span_1".to_string(),
216 }],
217 }
218 }
219
220 #[test]
221 fn test_stdout_adapter_returns_none() {
222 let mut adapter = StdoutAdapter;
223 let result = make_test_result();
224 let returned = adapter.print(result);
225
226 assert_eq!(returned.slot().raw(), 0, "script-mode print returns none");
227 }
228
229 #[test]
230 fn test_repl_adapter_returns_none_phase1b() {
231 let mut adapter = ReplAdapter;
235 let result = make_test_result();
236 let returned = adapter.print(result);
237 assert_eq!(returned.slot().raw(), 0);
238 }
239
240 #[test]
241 fn test_mock_adapter_captures() {
242 let mut adapter = MockAdapter::new();
243
244 adapter.print(PrintResult {
245 rendered: "Output 1".to_string(),
246 spans: vec![],
247 });
248 adapter.print(PrintResult {
249 rendered: "Output 2".to_string(),
250 spans: vec![],
251 });
252
253 assert_eq!(adapter.output(), vec!["Output 1", "Output 2"]);
254
255 adapter.clear();
256 assert_eq!(adapter.output().len(), 0);
257 }
258
259 #[test]
260 fn test_shared_capture_adapter_captures() {
261 let mut adapter = SharedCaptureAdapter::new();
262
263 adapter.print(PrintResult {
264 rendered: "Output A".to_string(),
265 spans: vec![],
266 });
267 adapter.print(PrintResult {
268 rendered: "Output B".to_string(),
269 spans: vec![],
270 });
271
272 assert_eq!(adapter.output(), vec!["Output A", "Output B"]);
273
274 adapter.clear();
275 assert!(adapter.output().is_empty());
276 }
277}