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 clone_box(&self) -> Box<dyn OutputAdapter>;
27}
28
29impl Clone for Box<dyn OutputAdapter> {
31 fn clone(&self) -> Self {
32 self.clone_box()
33 }
34}
35
36#[derive(Debug, Clone)]
40pub struct StdoutAdapter;
41
42impl OutputAdapter for StdoutAdapter {
43 fn print(&mut self, result: PrintResult) -> ValueWord {
44 println!("{}", result.rendered);
46
47 ValueWord::none()
49 }
50
51 fn clone_box(&self) -> Box<dyn OutputAdapter> {
52 Box::new(self.clone())
53 }
54}
55
56#[derive(Debug, Clone)]
60pub struct ReplAdapter;
61
62impl OutputAdapter for ReplAdapter {
63 fn print(&mut self, result: PrintResult) -> ValueWord {
64 ValueWord::from_print_result(result)
67 }
68
69 fn clone_box(&self) -> Box<dyn OutputAdapter> {
70 Box::new(self.clone())
71 }
72}
73
74#[derive(Debug, Clone, Default)]
76pub struct MockAdapter {
77 pub captured: Vec<String>,
79}
80
81impl MockAdapter {
82 pub fn new() -> Self {
83 MockAdapter {
84 captured: Vec::new(),
85 }
86 }
87
88 pub fn output(&self) -> Vec<String> {
90 self.captured.clone()
91 }
92
93 pub fn clear(&mut self) {
95 self.captured.clear();
96 }
97}
98
99impl OutputAdapter for MockAdapter {
100 fn print(&mut self, result: PrintResult) -> ValueWord {
101 self.captured.push(result.rendered.clone());
103
104 ValueWord::none()
106 }
107
108 fn clone_box(&self) -> Box<dyn OutputAdapter> {
109 Box::new(self.clone())
110 }
111}
112
113#[derive(Debug, Clone, Default)]
118pub struct SharedCaptureAdapter {
119 captured: Arc<Mutex<Vec<String>>>,
120}
121
122impl SharedCaptureAdapter {
123 pub fn new() -> Self {
124 Self::default()
125 }
126
127 pub fn output(&self) -> Vec<String> {
129 self.captured
130 .lock()
131 .map(|v| v.clone())
132 .unwrap_or_else(|_| Vec::new())
133 }
134
135 pub fn clear(&self) {
137 if let Ok(mut v) = self.captured.lock() {
138 v.clear();
139 }
140 }
141}
142
143impl OutputAdapter for SharedCaptureAdapter {
144 fn print(&mut self, result: PrintResult) -> ValueWord {
145 if let Ok(mut v) = self.captured.lock() {
146 v.push(result.rendered.clone());
147 }
148 ValueWord::none()
149 }
150
151 fn clone_box(&self) -> Box<dyn OutputAdapter> {
152 Box::new(self.clone())
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use shape_value::PrintSpan;
160 use shape_value::heap_value::HeapValue;
161
162 fn make_test_result() -> PrintResult {
163 PrintResult {
164 rendered: "Test output".to_string(),
165 spans: vec![PrintSpan::Literal {
166 text: "Test output".to_string(),
167 start: 0,
168 end: 11,
169 span_id: "span_1".to_string(),
170 }],
171 }
172 }
173
174 #[test]
175 fn test_stdout_adapter_returns_none() {
176 let mut adapter = StdoutAdapter;
177 let result = make_test_result();
178 let returned = adapter.print(result);
179
180 assert!(returned.is_none());
181 }
182
183 #[test]
184 fn test_repl_adapter_preserves_spans() {
185 let mut adapter = ReplAdapter;
186 let result = make_test_result();
187 let returned = adapter.print(result);
188
189 match returned.as_heap_ref().expect("Expected heap value") {
190 HeapValue::PrintResult(pr) => {
191 assert_eq!(pr.rendered, "Test output");
192 assert_eq!(pr.spans.len(), 1);
193 }
194 other => panic!("Expected PrintResult, got {:?}", other),
195 }
196 }
197
198 #[test]
199 fn test_mock_adapter_captures() {
200 let mut adapter = MockAdapter::new();
201
202 adapter.print(PrintResult {
203 rendered: "Output 1".to_string(),
204 spans: vec![],
205 });
206 adapter.print(PrintResult {
207 rendered: "Output 2".to_string(),
208 spans: vec![],
209 });
210
211 assert_eq!(adapter.output(), vec!["Output 1", "Output 2"]);
212
213 adapter.clear();
214 assert_eq!(adapter.output().len(), 0);
215 }
216
217 #[test]
218 fn test_shared_capture_adapter_captures() {
219 let mut adapter = SharedCaptureAdapter::new();
220
221 adapter.print(PrintResult {
222 rendered: "Output A".to_string(),
223 spans: vec![],
224 });
225 adapter.print(PrintResult {
226 rendered: "Output B".to_string(),
227 spans: vec![],
228 });
229
230 assert_eq!(adapter.output(), vec!["Output A", "Output B"]);
231
232 adapter.clear();
233 assert!(adapter.output().is_empty());
234 }
235}