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