Renders an array of objects as a markdown table with three refinements: columns are ordered (preferred keys first, rest alphabetical), columns that are empty in every row are dropped, and columns whose value is constant across every row are hoisted into a header line (`edge_kind: calls`) rather than repeated in each cell.
(md: &mut Md, arr: &[Value])
| 563 | /// across every row are hoisted into a header line (`edge_kind: calls`) rather |
| 564 | /// than repeated in each cell. |
| 565 | fn render_object_array_table(md: &mut Md, arr: &[Value]) { |
| 566 | // Collect the union of keys. |
| 567 | let mut cols: Vec<String> = Vec::new(); |
| 568 | for e in arr { |
| 569 | if let Some(obj) = e.as_object() { |
| 570 | for k in obj.keys() { |
| 571 | if !cols.contains(k) { |
| 572 | cols.push(k.clone()); |
| 573 | } |
| 574 | } |
| 575 | } |
| 576 | } |
| 577 | cols.sort_by(|a, b| column_rank(a).cmp(&column_rank(b))); |
| 578 | |
| 579 | // Precompute each cell's rendered string once. |
| 580 | let rendered: Vec<Vec<String>> = arr |
| 581 | .iter() |
| 582 | .map(|e| { |
| 583 | cols.iter() |
| 584 | .map(|c| cell_str(c, e.get(c).unwrap_or(&Value::Null))) |
| 585 | .collect() |
| 586 | }) |
| 587 | .collect(); |
| 588 | |
| 589 | // Drop columns empty in every row; hoist columns constant across all rows. |
| 590 | let mut hoisted: Vec<(String, String)> = Vec::new(); |
| 591 | let mut keep: Vec<usize> = Vec::new(); |
| 592 | for (ci, col) in cols.iter().enumerate() { |
| 593 | let all_empty = rendered.iter().all(|row| row[ci].is_empty()); |
| 594 | if all_empty { |
| 595 | continue; |
| 596 | } |
| 597 | let first = &rendered[0][ci]; |
| 598 | let constant = rendered.len() > 1 && rendered.iter().all(|row| &row[ci] == first); |
| 599 | if constant { |
| 600 | hoisted.push((col.clone(), first.clone())); |
| 601 | } else { |
| 602 | keep.push(ci); |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | for (col, val) in &hoisted { |
| 607 | md.field(col, val); |
| 608 | } |
| 609 | if !hoisted.is_empty() && !keep.is_empty() { |
| 610 | md.blank(); |
| 611 | } |
| 612 | |
| 613 | if keep.is_empty() { |
| 614 | return; |
| 615 | } |
| 616 | let headers: Vec<&str> = keep.iter().map(|&ci| cols[ci].as_str()).collect(); |
| 617 | let rows: Vec<Vec<String>> = rendered |
| 618 | .iter() |
| 619 | .map(|row| keep.iter().map(|&ci| row[ci].clone()).collect()) |
| 620 | .collect(); |
| 621 | md.table(&headers, &rows); |
| 622 | } |