1use asdf_yaml::YamlEvent;
15
16use crate::error::Result;
17use crate::layout::{BlockLocation, Layout, scan};
18
19#[derive(Clone, Debug)]
21pub enum Event {
22 AsdfVersion(String),
24 StandardVersion(String),
26 Comment(String),
28 BlockIndex(Vec<u64>),
33 TreeStart { start: usize },
35 Yaml(YamlEvent),
37 TreeEnd { start: usize, end: usize, text: Option<String> },
42 Block(BlockLocation),
44 End,
46}
47
48#[derive(Clone, Copy, Default, Debug)]
50pub struct EventOptions {
51 pub yaml: bool,
53 pub buffer_tree: bool,
55}
56
57pub fn events(buf: &[u8], options: EventOptions) -> Result<Vec<Event>> {
63 let layout = scan(buf)?;
64 Ok(events_from(buf, &layout, options))
65}
66
67pub fn events_from(buf: &[u8], layout: &Layout, options: EventOptions) -> Vec<Event> {
69 let mut out = Vec::new();
70
71 out.push(Event::AsdfVersion(layout.format_version.to_string()));
72 if let Some(standard) = &layout.standard_version {
73 out.push(Event::StandardVersion(standard.to_string()));
74 }
75 for comment in &layout.comments {
76 out.push(Event::Comment(comment.clone()));
77 }
78 if layout.block_index_pos.is_some() && !layout.block_index_offsets.is_empty() {
79 out.push(Event::BlockIndex(layout.block_index_offsets.clone()));
80 }
81 if let Some(tree) = layout.tree.clone() {
82 out.push(Event::TreeStart { start: tree.start });
83 if options.yaml
84 && let Some(text) = layout.tree_str(buf)
85 && let Ok(parsed) = asdf_yaml::scan_events(text)
86 {
87 out.extend(parsed.into_iter().map(Event::Yaml));
88 }
89 let text = options.buffer_tree.then(|| layout.tree_str(buf).map(str::to_string)).flatten();
90 out.push(Event::TreeEnd { start: tree.start, end: tree.end, text });
91 }
92 for block in &layout.blocks {
93 out.push(Event::Block(block.clone()));
94 }
95 out.push(Event::End);
96 out
97}
98
99impl Event {
100 pub fn type_name(&self) -> &'static str {
102 match self {
103 Event::AsdfVersion(_) => "ASDF_ASDF_VERSION_EVENT",
104 Event::StandardVersion(_) => "ASDF_STANDARD_VERSION_EVENT",
105 Event::Comment(_) => "ASDF_COMMENT_EVENT",
106 Event::BlockIndex(_) => "ASDF_BLOCK_INDEX_EVENT",
107 Event::TreeStart { .. } => "ASDF_TREE_START_EVENT",
108 Event::Yaml(_) => "ASDF_YAML_EVENT",
109 Event::TreeEnd { .. } => "ASDF_TREE_END_EVENT",
110 Event::Block(_) => "ASDF_BLOCK_EVENT",
111 Event::End => "ASDF_END_EVENT",
112 }
113 }
114}
115
116pub fn yaml_event_name(event: &YamlEvent) -> &'static str {
123 use asdf_yaml::YamlEventKind as K;
124 match event.kind {
125 K::StreamStart => "+STR",
126 K::StreamEnd => "-STR",
127 K::DocumentStart => "+DOC",
128 K::DocumentEnd => "-DOC",
129 K::MappingStart => "+MAP",
130 K::MappingEnd => "-MAP",
131 K::SequenceStart => "+SEQ",
132 K::SequenceEnd => "-SEQ",
133 K::Scalar => "=VAL",
134 K::Alias => "=ALI",
135 }
136}
137
138pub fn render_event(event: &Event, verbose: bool) -> String {
143 let mut out = format!("Event: {}\n", event.type_name());
144 if !verbose {
145 return out;
146 }
147
148 match event {
149 Event::AsdfVersion(v) => out.push_str(&format!(" ASDF Version: {v}\n")),
150 Event::StandardVersion(v) => out.push_str(&format!(" Standard Version: {v}\n")),
151 Event::Comment(text) => out.push_str(&format!(" Comment: {text}\n")),
152 Event::BlockIndex(offsets) => {
153 let listed: Vec<String> = offsets.iter().map(u64::to_string).collect();
154 out.push_str(&format!(" Offsets: {}\n", listed.join(", ")));
155 }
156 Event::TreeStart { start } => {
157 out.push_str(&format!(" Tree start position: {start} (0x{start:x})\n"));
158 }
159 Event::TreeEnd { end, text, .. } => {
160 out.push_str(&format!(" Tree end position: {end} (0x{end:x})\n"));
161 if let Some(text) = text {
162 out.push_str(text);
163 out.push('\n');
164 }
165 }
166 Event::Yaml(yaml) => {
167 out.push_str(&format!(" Type: {}\n", yaml_event_name(yaml)));
168 if let Some(tag) = &yaml.tag {
169 out.push_str(&format!(" Tag: {tag}\n"));
170 }
171 if let Some(value) = &yaml.value
172 && !value.is_empty()
173 {
174 out.push_str(&format!(" Value: {value}\n"));
175 }
176 }
177 Event::Block(block) => {
178 let header = &block.header;
179 out.push_str(&format!(
180 " Header position: {} (0x{:x})\n",
181 block.header_pos, block.header_pos
182 ));
183 out.push_str(&format!(
184 " Data position: {} (0x{:x})\n",
185 block.data_pos, block.data_pos
186 ));
187 out.push_str(&format!(
188 " Allocated size: {} (0x{:x})\n",
189 header.allocated_size, header.allocated_size
190 ));
191 out.push_str(&format!(
192 " Used size: {} (0x{:x})\n",
193 header.used_size, header.used_size
194 ));
195 out.push_str(&format!(
196 " Data size: {} (0x{:x})\n",
197 header.data_size, header.data_size
198 ));
199 if header.compression[0] != 0 {
202 let name = header.compression.split(|b| *b == 0).next().unwrap_or(&[]);
203 out.push_str(&format!(" Compression: {}\n", String::from_utf8_lossy(name)));
204 }
205 out.push_str(" Checksum: ");
206 for byte in header.checksum {
207 out.push_str(&format!("{byte:02x}"));
208 }
209 out.push('\n');
210 }
211 Event::End => {}
212 }
213 out
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn sample() -> Vec<u8> {
221 let mut buf = Vec::new();
222 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n#a note\n");
223 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
224 buf.extend_from_slice(b"n: 1\nlist: [a]\n");
225 buf.extend_from_slice(b"...\n");
226 buf
227 }
228
229 fn names(options: EventOptions) -> Vec<&'static str> {
230 events(&sample(), options).unwrap().iter().map(Event::type_name).collect()
231 }
232
233 #[test]
234 fn the_stream_follows_upstreams_order() {
235 assert_eq!(
236 names(EventOptions::default()),
237 vec![
238 "ASDF_ASDF_VERSION_EVENT",
239 "ASDF_STANDARD_VERSION_EVENT",
240 "ASDF_COMMENT_EVENT",
241 "ASDF_TREE_START_EVENT",
242 "ASDF_TREE_END_EVENT",
243 "ASDF_END_EVENT",
244 ]
245 );
246 }
247
248 #[test]
249 fn yaml_events_are_opt_in() {
250 let quiet = names(EventOptions::default());
251 assert!(!quiet.contains(&"ASDF_YAML_EVENT"));
252
253 let loud = names(EventOptions { yaml: true, ..Default::default() });
254 assert_eq!(loud.iter().filter(|n| **n == "ASDF_YAML_EVENT").count(), 12);
257 }
258
259 #[test]
262 fn the_tree_text_is_kept_only_when_asked_for() {
263 let buf = sample();
264
265 let quiet = events(&buf, EventOptions::default()).unwrap();
266 assert!(quiet.iter().any(|e| matches!(e, Event::TreeEnd { text: None, .. })));
267
268 let loud = events(&buf, EventOptions { yaml: false, buffer_tree: true }).unwrap();
269 let text = loud
270 .iter()
271 .find_map(|e| match e {
272 Event::TreeEnd { text, .. } => text.clone(),
273 _ => None,
274 })
275 .expect("the tree text");
276 assert!(text.starts_with("%YAML 1.1"), "{text}");
277 assert!(text.contains("list: [a]"), "{text}");
278
279 let rendered: String =
282 loud.iter().map(|e| render_event(e, true)).collect::<Vec<_>>().join("");
283 let end = rendered.find("Tree end position").expect("the end event");
284 assert!(rendered[end..].contains("list: [a]"));
285 }
286
287 #[test]
288 fn comments_lose_their_leading_hash() {
289 let stream = events(&sample(), EventOptions::default()).unwrap();
290 let comment = stream
291 .iter()
292 .find_map(|e| match e {
293 Event::Comment(text) => Some(text.clone()),
294 _ => None,
295 })
296 .expect("a comment event");
297 assert_eq!(comment, "a note");
298 }
299
300 #[test]
301 fn rendering_is_the_header_line_alone_unless_verbose() {
302 let stream = events(&sample(), EventOptions { yaml: true, ..Default::default() }).unwrap();
303 for event in &stream {
304 let terse = render_event(event, false);
305 assert_eq!(terse, format!("Event: {}\n", event.type_name()));
306 assert!(render_event(event, true).starts_with(&terse));
307 }
308 }
309
310 #[test]
311 fn a_damaged_tree_still_reports_the_rest_of_the_file() {
312 let mut buf = Vec::new();
313 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
314 buf.extend_from_slice(b"%YAML 1.1\n--- !core/asdf-1.1.0\n");
315 buf.extend_from_slice(b"a: [1, 2\n");
317 buf.extend_from_slice(b"...\n");
318
319 let stream = events(&buf, EventOptions { yaml: true, ..Default::default() }).unwrap();
320 let names: Vec<&str> = stream.iter().map(Event::type_name).collect();
321 assert!(names.contains(&"ASDF_TREE_START_EVENT"));
322 assert!(names.contains(&"ASDF_TREE_END_EVENT"));
323 assert_eq!(names.last(), Some(&"ASDF_END_EVENT"));
324 }
325}