refactor: eliminate Box<dyn Iterator> and Option sentinels in export_csv

The CSV export used Box<dyn Iterator<Item = Option<usize>>> to unify
empty and non-empty row iteration, violating the CLAUDE.md rule that
Box/Rc container management should be split from logic. Replaced with a
direct for loop over row indices, removing both the Box and the Option
sentinels used to represent "placeholder empty row/col".

Also removes unused pub use cell::CellKey re-export and an unused
import in cell.rs tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ed L
2026-03-24 09:00:37 -07:00
parent 6038cb2d81
commit c8b88c63b3
3 changed files with 10 additions and 25 deletions

View File

@ -143,7 +143,7 @@ impl DataStore {
#[cfg(test)] #[cfg(test)]
mod cell_key { mod cell_key {
use super::{CellKey, CellValue, DataStore}; use super::CellKey;
fn key(pairs: &[(&str, &str)]) -> CellKey { fn key(pairs: &[(&str, &str)]) -> CellKey {
CellKey::new(pairs.iter().map(|(c, i)| (c.to_string(), i.to_string())).collect()) CellKey::new(pairs.iter().map(|(c, i)| (c.to_string(), i.to_string())).collect())

View File

@ -2,5 +2,4 @@ pub mod category;
pub mod cell; pub mod cell;
pub mod model; pub mod model;
pub use cell::CellKey;
pub use model::Model; pub use model::Model;

View File

@ -79,33 +79,19 @@ pub fn export_csv(model: &Model, view_name: &str, path: &Path) -> Result<()> {
out.push_str(&col_labels.join(",")); out.push_str(&col_labels.join(","));
out.push('\n'); out.push('\n');
// Data rows — treat zero-item axes as a single empty placeholder row/col // Data rows
let row_range: Box<dyn Iterator<Item = Option<usize>>> = if layout.row_count() == 0 { for ri in 0..layout.row_count() {
Box::new(std::iter::once(None)) let row_label = layout.row_label(ri);
} else {
Box::new((0..layout.row_count()).map(Some))
};
let col_indices: Vec<Option<usize>> = if layout.col_count() == 0 {
vec![None]
} else {
(0..layout.col_count()).map(Some).collect()
};
for row_opt in row_range {
let row_label = row_opt.map(|ri| layout.row_label(ri)).unwrap_or_default();
if !row_label.is_empty() { if !row_label.is_empty() {
out.push_str(&row_label); out.push_str(&row_label);
out.push(','); out.push(',');
} }
let row_values: Vec<String> = col_indices.iter().map(|&col_opt| { let row_values: Vec<String> = (0..layout.col_count())
match (row_opt, col_opt) { .map(|ci| layout.cell_key(ri, ci)
(Some(ri), Some(ci)) => layout.cell_key(ri, ci)
.and_then(|key| model.evaluate(&key)) .and_then(|key| model.evaluate(&key))
.map(|v| v.to_string()) .map(|v| v.to_string())
.unwrap_or_default(), .unwrap_or_default())
_ => String::new(), .collect();
}
}).collect();
out.push_str(&row_values.join(",")); out.push_str(&row_values.join(","));
out.push('\n'); out.push('\n');
} }