shape_runtime/
output_adapter.rs1use shape_value::{PrintResult, ValueWord};
7use std::sync::{Arc, Mutex};
8
9pub trait OutputAdapter: Send + Sync {
16 fn print(&mut self, result: PrintResult) -> ValueWord;
24
25 fn print_content_html(&mut self, _html: String) {}
28
29 fn clone_box(&self) -> Box<dyn OutputAdapter>;
31}
32
33impl Clone for Box<dyn OutputAdapter> {
35 fn clone(&self) -> Self {
36 self.clone_box()
37 }
38}
39
40#[derive(Debug, Clone)]
44pub struct StdoutAdapter;
45
46impl OutputAdapter for StdoutAdapter {
47 fn print(&mut self, result: PrintResult) -> ValueWord {
48 println!("{}", result.rendered);
50
51 ValueWord::none()
53 }
54
55 fn clone_box(&self) -> Box<dyn OutputAdapter> {
56 Box::new(self.clone())
57 }
58}
59
60#[derive(Debug, Clone)]
64pub struct ReplAdapter;
65
66impl OutputAdapter for ReplAdapter {
67 fn print(&mut self, result: PrintResult) -> ValueWord {
68 ValueWord::from_print_result(result)
71 }
72
73 fn clone_box(&self) -> Box<dyn OutputAdapter> {
74 Box::new(self.clone())
75 }
76}
77
78#[derive(Debug, Clone, Default)]
80pub struct MockAdapter {
81 pub captured: Vec<String>,
83}
84
85impl MockAdapter {
86 pub fn new() -> Self {
87 MockAdapter {
88 captured: Vec::new(),
89 }
90 }
91
92 pub fn output(&self) -> Vec<String> {
94 self.captured.clone()
95 }
96
97 pub fn clear(&mut self) {
99 self.captured.clear();
100 }
101}
102
103impl OutputAdapter for MockAdapter {
104 fn print(&mut self, result: PrintResult) -> ValueWord {
105 self.captured.push(result.rendered.clone());
107
108 ValueWord::none()
110 }
111
112 fn clone_box(&self) -> Box<dyn OutputAdapter> {
113 Box::new(self.clone())
114 }
115}
116
117#[derive(Debug, Clone, Default)]
123pub struct SharedCaptureAdapter {
124 captured: Arc<Mutex<Vec<String>>>,
125 captured_full: Arc<Mutex<Vec<PrintResult>>>,
126 content_html: Arc<Mutex<Vec<String>>>,
127}
128
129impl SharedCaptureAdapter {
130 pub fn new() -> Self {
131 Self::default()
132 }
133
134 pub fn output(&self) -> Vec<String> {
136 self.captured
137 .lock()
138 .map(|v| v.clone())
139 .unwrap_or_else(|_| Vec::new())
140 }
141
142 pub fn clear(&self) {
144 if let Ok(mut v) = self.captured.lock() {
145 v.clear();
146 }
147 }
148
149 pub fn push_content_html(&self, html: String) {
151 if let Ok(mut v) = self.content_html.lock() {
152 v.push(html);
153 }
154 }
155
156 pub fn content_html(&self) -> Vec<String> {
158 self.content_html
159 .lock()
160 .map(|v| v.clone())
161 .unwrap_or_default()
162 }
163
164 pub fn print_results(&self) -> Vec<PrintResult> {
166 self.captured_full
167 .lock()
168 .map(|v| v.clone())
169 .unwrap_or_default()
170 }
171}
172
173impl OutputAdapter for SharedCaptureAdapter {
174 fn print(&mut self, result: PrintResult) -> ValueWord {
175 if let Ok(mut v) = self.captured.lock() {
176 v.push(result.rendered.clone());
177 }
178 if let Ok(mut v) = self.captured_full.lock() {
179 v.push(result);
180 }
181 ValueWord::none()
182 }
183
184 fn print_content_html(&mut self, html: String) {
185 self.push_content_html(html);
186 }
187
188 fn clone_box(&self) -> Box<dyn OutputAdapter> {
189 Box::new(self.clone())
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use shape_value::PrintSpan;
197 use shape_value::heap_value::HeapValue;
198
199 fn make_test_result() -> PrintResult {
200 PrintResult {
201 rendered: "Test output".to_string(),
202 spans: vec![PrintSpan::Literal {
203 text: "Test output".to_string(),
204 start: 0,
205 end: 11,
206 span_id: "span_1".to_string(),
207 }],
208 }
209 }
210
211 #[test]
212 fn test_stdout_adapter_returns_none() {
213 let mut adapter = StdoutAdapter;
214 let result = make_test_result();
215 let returned = adapter.print(result);
216
217 assert!(returned.is_none());
218 }
219
220 #[test]
221 fn test_repl_adapter_preserves_spans() {
222 let mut adapter = ReplAdapter;
223 let result = make_test_result();
224 let returned = adapter.print(result);
225
226 match returned.as_heap_ref().expect("Expected heap value") {
227 HeapValue::PrintResult(pr) => {
228 assert_eq!(pr.rendered, "Test output");
229 assert_eq!(pr.spans.len(), 1);
230 }
231 other => panic!("Expected PrintResult, got {:?}", other),
232 }
233 }
234
235 #[test]
236 fn test_mock_adapter_captures() {
237 let mut adapter = MockAdapter::new();
238
239 adapter.print(PrintResult {
240 rendered: "Output 1".to_string(),
241 spans: vec![],
242 });
243 adapter.print(PrintResult {
244 rendered: "Output 2".to_string(),
245 spans: vec![],
246 });
247
248 assert_eq!(adapter.output(), vec!["Output 1", "Output 2"]);
249
250 adapter.clear();
251 assert_eq!(adapter.output().len(), 0);
252 }
253
254 #[test]
255 fn test_shared_capture_adapter_captures() {
256 let mut adapter = SharedCaptureAdapter::new();
257
258 adapter.print(PrintResult {
259 rendered: "Output A".to_string(),
260 spans: vec![],
261 });
262 adapter.print(PrintResult {
263 rendered: "Output B".to_string(),
264 spans: vec![],
265 });
266
267 assert_eq!(adapter.output(), vec!["Output A", "Output B"]);
268
269 adapter.clear();
270 assert!(adapter.output().is_empty());
271 }
272}