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 content_html: Arc<Mutex<Vec<String>>>,
126}
127
128impl SharedCaptureAdapter {
129 pub fn new() -> Self {
130 Self::default()
131 }
132
133 pub fn output(&self) -> Vec<String> {
135 self.captured
136 .lock()
137 .map(|v| v.clone())
138 .unwrap_or_else(|_| Vec::new())
139 }
140
141 pub fn clear(&self) {
143 if let Ok(mut v) = self.captured.lock() {
144 v.clear();
145 }
146 }
147
148 pub fn push_content_html(&self, html: String) {
150 if let Ok(mut v) = self.content_html.lock() {
151 v.push(html);
152 }
153 }
154
155 pub fn content_html(&self) -> Vec<String> {
157 self.content_html
158 .lock()
159 .map(|v| v.clone())
160 .unwrap_or_default()
161 }
162}
163
164impl OutputAdapter for SharedCaptureAdapter {
165 fn print(&mut self, result: PrintResult) -> ValueWord {
166 if let Ok(mut v) = self.captured.lock() {
167 v.push(result.rendered.clone());
168 }
169 ValueWord::none()
170 }
171
172 fn print_content_html(&mut self, html: String) {
173 self.push_content_html(html);
174 }
175
176 fn clone_box(&self) -> Box<dyn OutputAdapter> {
177 Box::new(self.clone())
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use shape_value::PrintSpan;
185 use shape_value::heap_value::HeapValue;
186
187 fn make_test_result() -> PrintResult {
188 PrintResult {
189 rendered: "Test output".to_string(),
190 spans: vec![PrintSpan::Literal {
191 text: "Test output".to_string(),
192 start: 0,
193 end: 11,
194 span_id: "span_1".to_string(),
195 }],
196 }
197 }
198
199 #[test]
200 fn test_stdout_adapter_returns_none() {
201 let mut adapter = StdoutAdapter;
202 let result = make_test_result();
203 let returned = adapter.print(result);
204
205 assert!(returned.is_none());
206 }
207
208 #[test]
209 fn test_repl_adapter_preserves_spans() {
210 let mut adapter = ReplAdapter;
211 let result = make_test_result();
212 let returned = adapter.print(result);
213
214 match returned.as_heap_ref().expect("Expected heap value") {
215 HeapValue::PrintResult(pr) => {
216 assert_eq!(pr.rendered, "Test output");
217 assert_eq!(pr.spans.len(), 1);
218 }
219 other => panic!("Expected PrintResult, got {:?}", other),
220 }
221 }
222
223 #[test]
224 fn test_mock_adapter_captures() {
225 let mut adapter = MockAdapter::new();
226
227 adapter.print(PrintResult {
228 rendered: "Output 1".to_string(),
229 spans: vec![],
230 });
231 adapter.print(PrintResult {
232 rendered: "Output 2".to_string(),
233 spans: vec![],
234 });
235
236 assert_eq!(adapter.output(), vec!["Output 1", "Output 2"]);
237
238 adapter.clear();
239 assert_eq!(adapter.output().len(), 0);
240 }
241
242 #[test]
243 fn test_shared_capture_adapter_captures() {
244 let mut adapter = SharedCaptureAdapter::new();
245
246 adapter.print(PrintResult {
247 rendered: "Output A".to_string(),
248 spans: vec![],
249 });
250 adapter.print(PrintResult {
251 rendered: "Output B".to_string(),
252 spans: vec![],
253 });
254
255 assert_eq!(adapter.output(), vec!["Output A", "Output B"]);
256
257 adapter.clear();
258 assert!(adapter.output().is_empty());
259 }
260}