1use std::io::stdout;
2
3use switchbot_api::{CommandRequest, Device, DeviceList, SwitchBot};
4
5use crate::{Args, UserInput};
6
7#[derive(Debug, Default)]
8pub struct Cli {
9 args: Args,
10 switch_bot: SwitchBot,
11 current_device_indexes: Vec<usize>,
12}
13
14impl Cli {
15 pub fn new_from_args() -> Self {
16 Self {
17 args: Args::new_from_args(),
18 ..Default::default()
19 }
20 }
21
22 #[cfg(test)]
23 fn new_for_test(n_devices: usize) -> Self {
24 Self {
25 switch_bot: SwitchBot::new_for_test(n_devices),
26 ..Default::default()
27 }
28 }
29
30 fn devices(&self) -> &DeviceList {
31 self.switch_bot.devices()
32 }
33
34 fn has_current_device(&self) -> bool {
35 !self.current_device_indexes.is_empty()
36 }
37
38 fn current_devices(&self) -> Vec<&Device> {
39 self.current_device_indexes
40 .iter()
41 .map(|&index| &self.devices()[index])
42 .collect() }
44
45 pub async fn run(&mut self) -> anyhow::Result<()> {
46 if !self.args.alias_updates.is_empty() {
47 self.args.print_aliases();
48 self.args.save()?;
49 return Ok(());
50 }
51
52 self.switch_bot = self.args.create_switch_bot()?;
53 self.switch_bot.load_devices().await?;
54
55 if !self.args.commands.is_empty() {
56 self.execute_args(&self.args.commands.clone()).await?;
57 self.args.save()?;
58 return Ok(());
59 }
60
61 self.run_interactive().await?;
62
63 self.args.save()?;
64 Ok(())
65 }
66
67 async fn run_interactive(&mut self) -> anyhow::Result<()> {
68 let mut input = UserInput::new();
69 self.print_devices();
70 loop {
71 input.set_prompt(if self.has_current_device() {
72 "Command> "
73 } else {
74 "Device> "
75 });
76
77 let input_text = input.read_line()?;
78 match input_text {
79 "q" => break,
80 "" => {
81 if self.has_current_device() {
82 self.current_device_indexes.clear();
83 self.print_devices();
84 continue;
85 }
86 break;
87 }
88 _ => match self.execute(input_text).await {
89 Ok(true) => self.print_devices(),
90 Ok(false) => {}
91 Err(error) => log::error!("{error}"),
92 },
93 }
94 }
95 Ok(())
96 }
97
98 fn print_devices(&self) {
99 if self.current_device_indexes.is_empty() {
100 for (i, device) in self.devices().iter().enumerate() {
101 println!("{}: {device}", i + 1);
102 }
103 return;
104 }
105
106 if self.current_device_indexes.len() >= 2 {
107 for (i, device) in self
108 .current_device_indexes
109 .iter()
110 .map(|i| (i, &self.devices()[*i]))
111 {
112 println!("{}: {device}", i + 1);
113 }
114 return;
115 }
116
117 let device = self.current_devices()[0];
118 print!("{device:#}");
119 }
120
121 async fn execute_args(&mut self, list: &[String]) -> anyhow::Result<()> {
122 for command in list {
123 self.execute(command).await?;
124 }
125 Ok(())
126 }
127
128 async fn execute(&mut self, mut text: &str) -> anyhow::Result<bool> {
129 let alias_result: String;
130 if let Some(alias) = self.args.aliases.get(text) {
131 log::debug!(r#"alias: "{text}" -> "{alias}""#);
132 alias_result = alias.clone();
133 text = &alias_result;
134 }
135
136 let set_device_result = self.set_current_devices(text);
137 if set_device_result.is_ok() {
138 return Ok(true);
139 }
140 if self.has_current_device() {
141 if text == "status" {
142 self.update_status("").await?;
143 return Ok(false);
144 }
145 if let Some(key) = text.strip_prefix("status.") {
146 self.update_status(key).await?;
147 return Ok(false);
148 }
149 self.execute_command(&CommandRequest::from(text)).await?;
150 return Ok(false);
151 }
152 Err(set_device_result.unwrap_err())
153 }
154
155 fn set_current_devices(&mut self, text: &str) -> anyhow::Result<()> {
156 self.current_device_indexes = self.parse_device_indexes(text)?;
157 log::debug!("current_device_indexes={:?}", self.current_device_indexes);
158 Ok(())
159 }
160
161 fn parse_device_indexes(&self, value: &str) -> anyhow::Result<Vec<usize>> {
162 let values = value.split(',');
163 let mut indexes: Vec<usize> = Vec::new();
164 for s in values {
165 if let Some(alias) = self.args.aliases.get(s) {
166 indexes.extend(self.parse_device_indexes(alias)?);
167 continue;
168 }
169 indexes.push(self.parse_device_index(s)?);
170 }
171 indexes.sort();
172 indexes.dedup();
173 Ok(indexes)
174 }
175
176 fn parse_device_index(&self, value: &str) -> anyhow::Result<usize> {
177 if let Ok(number) = value.parse::<usize>() {
178 if number > 0 && number <= self.devices().len() {
179 return Ok(number - 1);
180 }
181 }
182 self.devices()
183 .index_by_device_id(value)
184 .ok_or_else(|| anyhow::anyhow!("Not a valid device: \"{value}\""))
185 }
186
187 async fn execute_command(&self, command: &CommandRequest) -> anyhow::Result<()> {
188 let devices = self.current_devices();
189 for device in devices {
190 device.command(command).await?;
191 }
192 Ok(())
193 }
194
195 async fn update_status(&mut self, key: &str) -> anyhow::Result<()> {
196 for device in self.current_devices() {
197 device.update_status().await?;
198
199 if key.is_empty() {
200 device.write_status_to(stdout())?;
201 } else if let Some(value) = device.status_by_key(key) {
202 println!("{}", value);
203 } else {
204 log::error!(r#"No status key "{key}" for {device}"#);
205 }
206 }
207 Ok(())
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn parse_device_indexes() {
217 let cli = Cli::new_for_test(10);
218 assert!(cli.parse_device_indexes("").is_err());
219 assert_eq!(cli.parse_device_indexes("4").unwrap(), vec![3]);
220 assert_eq!(cli.parse_device_indexes("device4").unwrap(), vec![3]);
221 assert_eq!(cli.parse_device_indexes("2,4").unwrap(), vec![1, 3]);
222 assert_eq!(cli.parse_device_indexes("2,device4").unwrap(), vec![1, 3]);
223 assert_eq!(cli.parse_device_indexes("4,2").unwrap(), vec![1, 3]);
225 assert_eq!(cli.parse_device_indexes("device4,2").unwrap(), vec![1, 3]);
226 assert_eq!(cli.parse_device_indexes("2,4,2").unwrap(), vec![1, 3]);
228 }
229
230 #[test]
231 fn parse_device_indexes_alias() {
232 let mut cli = Cli::new_for_test(10);
233 cli.args.aliases.insert("k".into(), "3,5".into());
234 assert_eq!(cli.parse_device_indexes("k").unwrap(), vec![2, 4]);
235 assert_eq!(cli.parse_device_indexes("1,k,4").unwrap(), vec![0, 2, 3, 4]);
236 cli.args.aliases.insert("j".into(), "2,k".into());
237 assert_eq!(
238 cli.parse_device_indexes("1,j,4").unwrap(),
239 vec![0, 1, 2, 3, 4]
240 );
241 assert_eq!(cli.parse_device_indexes("1,j,5").unwrap(), vec![0, 1, 2, 4]);
242 }
243}