excel_cli/utils/
helpers.rs1#[must_use]
2pub fn index_to_col_name(index: usize) -> String {
3 let mut col_name = String::new();
4 let mut n = index;
5
6 while n > 0 {
7 let remainder = (n - 1) % 26;
8 col_name.insert(0, (b'A' + remainder as u8) as char);
9 n = (n - 1) / 26;
10 }
11
12 if col_name.is_empty() {
13 col_name.push('A');
14 }
15
16 col_name
17}
18
19#[must_use]
20pub fn col_name_to_index(name: &str) -> Option<usize> {
21 let mut result = 0;
22
23 for c in name.chars() {
24 if !c.is_ascii_alphabetic() {
25 return None;
26 }
27
28 let val = (c.to_ascii_uppercase() as u8 - b'A' + 1) as usize;
29 result = result * 26 + val;
30 }
31
32 Some(result)
33}
34
35#[must_use]
37pub fn cell_reference(cell: (usize, usize)) -> String {
38 format!("{}{}", index_to_col_name(cell.1), cell.0)
39}
40
41#[must_use]
43pub fn parse_cell_reference(reference: &str) -> Option<(usize, usize)> {
44 let reference = reference.trim();
45 if reference.is_empty() {
46 return None;
47 }
48
49 let first_digit_pos = reference
50 .char_indices()
51 .find(|(_, c)| c.is_ascii_digit())
52 .map(|(index, _)| index)
53 .unwrap_or(reference.len());
54
55 if first_digit_pos == 0 || first_digit_pos == reference.len() {
56 return None;
57 }
58
59 let col_part = &reference[..first_digit_pos];
60 let row_part = &reference[first_digit_pos..];
61
62 let col = col_name_to_index(col_part)?;
63 let row = row_part.parse::<usize>().ok()?;
64
65 if row == 0 {
66 return None;
67 }
68
69 Some((row, col))
70}
71
72#[must_use]
74pub fn parse_range(range: &str) -> Option<((usize, usize), (usize, usize))> {
75 let range = range.trim();
76 if range.is_empty() {
77 return None;
78 }
79
80 let parts: Vec<&str> = range.split(':').collect();
81 if parts.len() != 2 {
82 return None;
83 }
84
85 let start = parse_cell_reference(parts[0])?;
86 let end = parse_cell_reference(parts[1])?;
87
88 Some((start, end))
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn test_parse_cell_reference_basic() {
97 assert_eq!(parse_cell_reference("A1"), Some((1, 1)));
98 assert_eq!(parse_cell_reference("B2"), Some((2, 2)));
99 assert_eq!(parse_cell_reference("Z26"), Some((26, 26)));
100 assert_eq!(parse_cell_reference("AA27"), Some((27, 27)));
101 assert_eq!(parse_cell_reference("AB28"), Some((28, 28)));
102 }
103
104 #[test]
105 fn test_parse_cell_reference_case_insensitive() {
106 assert_eq!(parse_cell_reference("a1"), Some((1, 1)));
107 assert_eq!(parse_cell_reference("aA100"), Some((100, 27)));
108 }
109
110 #[test]
111 fn test_parse_cell_reference_invalid() {
112 assert_eq!(parse_cell_reference(""), None);
113 assert_eq!(parse_cell_reference("1A"), None);
114 assert_eq!(parse_cell_reference("A"), None);
115 assert_eq!(parse_cell_reference("0"), None);
116 assert_eq!(parse_cell_reference("A0"), None);
117 assert_eq!(parse_cell_reference("AAA"), None);
118 }
119
120 #[test]
121 fn test_parse_range_basic() {
122 assert_eq!(parse_range("A1:B2"), Some(((1, 1), (2, 2))));
123 assert_eq!(parse_range("A1:F10"), Some(((1, 1), (10, 6))));
124 }
125
126 #[test]
127 fn test_parse_range_invalid() {
128 assert_eq!(parse_range(""), None);
129 assert_eq!(parse_range("A1"), None);
130 assert_eq!(parse_range("A1:B"), None);
131 assert_eq!(parse_range("A:B2"), None);
132 assert_eq!(parse_range("A1:B2:C3"), None);
133 }
134}