frazaro_core/reflect/
histogram.rs1use std::collections::BTreeMap;
19
20use vla_lang::calc::calls;
21use vla_lang::rows::{quoted, Row, Sink};
22
23use super::open;
24use crate::messages::Refusal;
25
26#[derive(Debug, Default)]
28pub struct Histogram {
29 pub counts: BTreeMap<String, u64>,
30 pub formulas: u64,
31}
32
33impl Sink for Histogram {
34 fn row(&mut self, row: &Row<'_>) {
35 if let Row::Formula { text, .. } = row {
36 self.formulas += 1;
37 let mut names = calls(text);
38 names.sort_unstable();
39 names.dedup();
40 for name in names {
41 *self.counts.entry(name).or_insert(0) += 1;
42 }
43 }
44 }
45}
46
47impl Histogram {
48 pub fn text(&self) -> String {
50 let mut rows: Vec<(&String, &u64)> = self.counts.iter().collect();
51 rows.sort_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0)));
52 let mut out = String::new();
53 for (name, count) in rows {
54 out.push_str(&format!("(function {} {count})\n", quoted(name)));
55 }
56 out
57 }
58}
59
60pub fn histogram(bytes: &[u8], label: &str) -> Result<Histogram, Refusal> {
62 let package = open(bytes, label)?;
63 let mut h = Histogram::default();
64 for i in 0..package.sheets().len() {
65 package.walk_sheet(i, &mut h)?;
66 }
67 Ok(h)
68}
69
70pub fn histogram_text(bytes: &[u8], label: &str) -> Result<String, Refusal> {
72 Ok(histogram(bytes, label)?.text())
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 const FIXTURE: &[u8] = include_bytes!("../../../scripts/reflect/fixture.xlsx");
80
81 #[test]
82 fn the_fixture_s_functions_are_counted_once_per_cell() {
83 let h = histogram(FIXTURE, "fixture.xlsx").unwrap();
84 assert_eq!(h.counts.get("SUM"), Some(&3), "{:?}", h.counts);
85 assert_eq!(h.counts.get("INDIRECT"), Some(&1));
86 assert_eq!(h.counts.get("OFFSET"), Some(&1));
87 assert_eq!(h.counts.get("SEQUENCE"), Some(&1));
88 let text = h.text();
89 assert!(text.starts_with("(function \"SUM\" 3)\n"), "{text}");
90 assert!(h.formulas >= 25);
91 }
92}