1use std::str::FromStr;
2
3use crate::cells::column_name_to_number;
4
5pub enum Command {
6 Get {
7 cell_identifier: CellIdentifier,
8 },
9 Set {
10 cell_identifier: CellIdentifier,
11 cell_expr: String,
12 },
13}
14
15#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
19pub struct CellIdentifier {
20 pub col: u32,
21 pub row: u32,
22}
23
24impl FromStr for Command {
25 type Err = String;
26
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
28 const MAX_PARTS: usize = 3;
29
30 let opt = (|| {
31 let mut parts = s.splitn(MAX_PARTS, |c: char| c.is_ascii_whitespace());
32 let command = parts.next()?;
33 let cell_identifier = parts.next()?.parse::<CellIdentifier>().ok()?;
34
35 let request = match command {
36 "get" => {
37 let non_empty_rest = parts.next().is_some();
38 if non_empty_rest {
39 return None;
41 }
42 Self::Get { cell_identifier }
43 }
44 "set" => {
45 let cell_expr = parts.next()?.to_string();
46 Self::Set {
47 cell_identifier,
48 cell_expr,
49 }
50 }
51 _ => return None,
52 };
53
54 Some(request)
55 })();
56
57 opt.ok_or_else(|| format!("Error parsing request: {s}"))
58 }
59}
60
61impl FromStr for CellIdentifier {
62 type Err = String;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 let opt = (|| {
66 let col_name = s
67 .chars()
68 .take_while(char::is_ascii_uppercase)
69 .collect::<String>();
70
71 if col_name.is_empty() {
72 return None;
73 }
74
75 let col = column_name_to_number(&col_name);
76
77 let row = s
78 .chars()
79 .skip_while(char::is_ascii_uppercase)
80 .collect::<String>()
81 .parse::<u32>()
82 .ok()?
83 .checked_sub(1)?;
84
85 Some(Self { col, row })
86 })();
87
88 opt.ok_or_else(|| format!("Error parsing cell position: {s}"))
89 }
90}