spreadsheet_formulas/
range.rs1use crate::cellref::CellRef;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum Range {
9 CellRange { start: CellRef, end: CellRef },
11 ColRange { start_col: usize, end_col: usize },
13}
14
15impl Range {
16 pub fn parse(input: &str) -> Option<Self> {
18 let input = input.trim();
19 let parts: Vec<&str> = input.split(':').collect();
20 if parts.len() != 2 {
21 return None;
22 }
23
24 let left = parts[0].trim();
25 let right = parts[1].trim();
26
27 let _left_letters: String = left.chars().filter(|c| c.is_ascii_alphabetic()).collect();
29 let _left_digits: String = left.chars().filter(|c| c.is_ascii_digit()).collect();
30 let _right_letters: String = right.chars().filter(|c| c.is_ascii_alphabetic()).collect();
31 let _right_digits: String = right.chars().filter(|c| c.is_ascii_digit()).collect();
32
33 let left_clean: String = left.chars().filter(|c| *c != '$').collect();
35 let right_clean: String = right.chars().filter(|c| *c != '$').collect();
36
37 let left_has_no_digits = left_clean.chars().all(|c| c.is_ascii_alphabetic());
39 let right_has_no_digits = right_clean.chars().all(|c| c.is_ascii_alphabetic());
40
41 if left_has_no_digits && right_has_no_digits {
42 let start_col = crate::cellref::col_letters_to_index(&left_clean)?;
43 let end_col = crate::cellref::col_letters_to_index(&right_clean)?;
44 return Some(Range::ColRange { start_col, end_col });
45 }
46
47 let start = CellRef::parse(left)?;
49 let end = CellRef::parse(right)?;
50 Some(Range::CellRange { start, end })
51 }
52
53 pub fn iter_cells(&self) -> Box<dyn Iterator<Item = (usize, usize)> + '_> {
55 match self {
56 Range::CellRange { start, end } => {
57 let min_row = start.row.min(end.row);
58 let max_row = start.row.max(end.row);
59 let min_col = start.col.min(end.col);
60 let max_col = start.col.max(end.col);
61 Box::new(
62 (min_row..=max_row)
63 .flat_map(move |r| (min_col..=max_col).map(move |c| (r, c))),
64 )
65 }
66 Range::ColRange { start_col, end_col } => {
67 let min = *start_col.min(end_col);
69 let max = *start_col.max(end_col);
70 Box::new((min..=max).map(|c| (0usize, c)))
71 }
72 }
73 }
74}
75
76impl std::fmt::Display for Range {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 match self {
79 Range::CellRange { start, end } => write!(f, "{}:{}", start, end),
80 Range::ColRange { start_col, end_col } => write!(
81 f,
82 "{}:{}",
83 crate::cellref::index_to_col_letters(*start_col),
84 crate::cellref::index_to_col_letters(*end_col)
85 ),
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn parse_cell_range() {
96 let r = Range::parse("A1:B5").unwrap();
97 assert!(matches!(r, Range::CellRange { .. }));
98 }
99
100 #[test]
101 fn parse_col_range() {
102 let r = Range::parse("B:B").unwrap();
103 assert!(matches!(r, Range::ColRange { start_col: 1, end_col: 1 }));
104 }
105
106 #[test]
107 fn parse_multi_col_range() {
108 let r = Range::parse("B:D").unwrap();
109 assert!(matches!(r, Range::ColRange { start_col: 1, end_col: 3 }));
110 }
111
112 #[test]
113 fn parse_absolute_range() {
114 let r = Range::parse("$A$1:$C$5").unwrap();
115 if let Range::CellRange { start, end } = r {
116 assert_eq!(start.col, 0);
117 assert_eq!(start.row, 0);
118 assert!(start.abs_col && start.abs_row);
119 assert_eq!(end.col, 2);
120 assert_eq!(end.row, 4);
121 } else {
122 panic!("Expected CellRange");
123 }
124 }
125
126 #[test]
127 fn iter_cells_range() {
128 let r = Range::parse("A1:B2").unwrap();
129 let cells: Vec<_> = r.iter_cells().collect();
130 assert_eq!(cells, vec![(0, 0), (0, 1), (1, 0), (1, 1)]);
131 }
132
133 #[test]
134 fn parse_invalid_no_colon() {
135 assert!(Range::parse("A1").is_none());
136 }
137}