Skip to main content

spreadsheet_mcp/diff/
address.rs

1use std::cmp::Ordering;
2
3#[derive(Debug, PartialEq, Eq, Clone)]
4pub struct CellAddress {
5    pub col: u32,
6    pub row: u32,
7    pub original: String,
8}
9
10impl CellAddress {
11    pub fn parse(s: &str) -> Option<Self> {
12        // Split into letters and numbers
13        let split_idx = s.find(|c: char| c.is_ascii_digit())?;
14        let (col_str, row_str) = s.split_at(split_idx);
15
16        let row = row_str.parse::<u32>().ok()?;
17        let col = col_from_letters(col_str)?;
18
19        Some(Self {
20            col,
21            row,
22            original: s.to_string(),
23        })
24    }
25}
26
27fn col_from_letters(s: &str) -> Option<u32> {
28    let mut col = 0;
29    for c in s.chars() {
30        if !c.is_ascii_alphabetic() {
31            return None;
32        }
33        col = col * 26 + (c.to_ascii_uppercase() as u32 - 'A' as u32 + 1);
34    }
35    Some(col)
36}
37
38impl Ord for CellAddress {
39    fn cmp(&self, other: &Self) -> Ordering {
40        // Row-major ordering
41        match self.row.cmp(&other.row) {
42            Ordering::Equal => self.col.cmp(&other.col),
43            ord => ord,
44        }
45    }
46}
47
48impl PartialOrd for CellAddress {
49    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
50        Some(self.cmp(other))
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn test_ordering() {
60        let a1 = CellAddress::parse("A1").unwrap();
61        let b1 = CellAddress::parse("B1").unwrap();
62        let a2 = CellAddress::parse("A2").unwrap();
63        let aa1 = CellAddress::parse("AA1").unwrap();
64
65        assert!(a1 < b1);
66        assert!(b1 < aa1); // B=2, AA=27
67        assert!(aa1 < a2); // Row 1 < Row 2
68    }
69}