1use core::{fmt, ops::ControlFlow, str::FromStr};
2
3pub(crate) const SEMI_COLON: char = ';';
5const BACKSLASH: char = '\\';
6
7#[derive(Debug, Clone)]
9pub struct Command {
10 inner: Box<str>,
11}
12
13impl Command {
14 pub fn new(command: impl AsRef<str>) -> Result<Self, CommandParseError> {
15 let command = command.as_ref();
16
17 let control_flow = command.chars().try_fold(None, |prev, x| {
18 if x == SEMI_COLON {
19 if prev == Some(BACKSLASH) {
20 ControlFlow::Continue(Some(x))
21 } else {
22 ControlFlow::Break(())
23 }
24 } else {
25 ControlFlow::Continue(Some(x))
26 }
27 });
28
29 match control_flow {
30 ControlFlow::Continue(_) => {}
31 ControlFlow::Break(_) => return Err(CommandParseError::RequireEscapeSemiColon),
32 }
33
34 Ok(Self {
35 inner: command.into(),
36 })
37 }
38
39 pub fn as_str(&self) -> &str {
40 &self.inner
41 }
42
43 pub fn to_write_bytes(&self) -> Vec<u8> {
44 format!("{}\r\n", self.as_str()).as_bytes().to_vec()
45 }
46}
47
48impl Command {
49 pub fn show_info() -> Self {
50 Self::new("show info").expect("")
51 }
52
53 pub fn show_stat() -> Self {
54 Self::new("show stat").expect("")
55 }
56
57 pub fn show_env() -> Self {
58 Self::new("show env").expect("")
59 }
60}
61
62#[derive(Debug)]
64pub enum CommandParseError {
65 RequireEscapeSemiColon,
66}
67
68impl fmt::Display for CommandParseError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{:?}", self)
71 }
72}
73
74impl std::error::Error for CommandParseError {}
75
76impl fmt::Display for Command {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.as_str())
80 }
81}
82
83impl FromStr for Command {
85 type Err = CommandParseError;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 Self::new(s)
89 }
90}
91
92#[derive(Debug, Clone)]
96pub struct Commands<'a>(pub &'a [Command]);
97
98impl<'a> Commands<'a> {
99 pub fn new(inner: &'a [Command]) -> Self {
100 Self(inner)
101 }
102
103 fn internal_to_string(&self) -> String {
104 self.0
105 .iter()
106 .map(|x| x.as_str())
107 .collect::<Vec<_>>()
108 .join(SEMI_COLON.to_string().as_str())
109 }
110
111 pub fn to_write_bytes(&self) -> Vec<u8> {
112 format!("{}\r\n", self.internal_to_string())
113 .as_bytes()
114 .to_vec()
115 }
116}
117
118impl<'a> fmt::Display for Commands<'a> {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(f, "{}", self.internal_to_string())
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_command_new() {
131 assert_eq!(Command::show_stat().as_str(), "show stat");
132
133 match Command::new("show stat;") {
135 Err(CommandParseError::RequireEscapeSemiColon) => {}
136 x => panic!("{:?}", x),
137 }
138 }
139}