1use switchbot_api::{CommandRequest, Device, SwitchBot};
2
3use crate::{Args, UserInput};
4
5#[derive(Debug, Default)]
6pub struct Cli {
7 args: Args,
8 switch_bot: SwitchBot,
9 current_device_index: Option<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 fn has_current_device(&self) -> bool {
21 self.current_device_index.is_some()
22 }
23
24 fn current_device(&self) -> Option<&Device> {
25 if let Some(index) = self.current_device_index {
26 return self.switch_bot.devices().get(index);
27 }
28 None
29 }
30
31 pub async fn run(&mut self) -> anyhow::Result<()> {
32 if !self.args.alias_updates.is_empty() {
33 self.args.print_aliases();
34 self.args.save()?;
35 return Ok(());
36 }
37
38 self.switch_bot = self.args.create_switch_bot()?;
39 self.switch_bot.load_devices().await?;
40
41 if !self.args.commands.is_empty() {
42 self.execute_args(&self.args.commands.clone()).await?;
43 self.args.save()?;
44 return Ok(());
45 }
46
47 self.run_interactive().await?;
48
49 self.args.save()?;
50 Ok(())
51 }
52
53 async fn run_interactive(&mut self) -> anyhow::Result<()> {
54 let mut input = UserInput::new();
55 loop {
56 if let Some(device) = self.current_device() {
57 println!("{device:#}");
58 input.set_prompt("Command> ");
59 } else {
60 self.print_devices();
61 input.set_prompt("Device> ");
62 }
63
64 let input_text = input.read_line()?;
65 match input_text {
66 "q" => break,
67 "" => {
68 if self.has_current_device() {
69 self.current_device_index = None;
70 continue;
71 }
72 break;
73 }
74 _ => {
75 if let Err(error) = self.execute(input_text).await {
76 log::error!("{error}");
77 }
78 }
79 }
80 }
81 Ok(())
82 }
83
84 fn print_devices(&self) {
85 for (i, device) in self.switch_bot.devices().iter().enumerate() {
86 println!("{}: {device}", i + 1);
87 }
88 }
89
90 async fn execute_args(&mut self, list: &[String]) -> anyhow::Result<()> {
91 for command in list {
92 self.execute(command).await?;
93 }
94 Ok(())
95 }
96
97 async fn execute(&mut self, mut text: &str) -> anyhow::Result<()> {
98 let alias_result: String;
99 if let Some(alias) = self.args.aliases.get(text) {
100 log::debug!(r#"alias: "{text}" -> "{alias}""#);
101 alias_result = alias.clone();
102 text = &alias_result;
103 }
104
105 if self.set_current_device(text).is_ok() {
106 return Ok(());
107 }
108 if let Some(device) = self.current_device() {
109 let command = self.parse_command(text);
110 device.command(&command).await?;
111 return Ok(());
112 }
113 self.set_current_device(text)?;
114 Ok(())
115 }
116
117 fn set_current_device(&mut self, value: &str) -> anyhow::Result<()> {
118 self.current_device_index = Some(self.parse_device_index(value)?);
119 log::debug!("current_device_index={:?}", self.current_device_index);
120 Ok(())
121 }
122
123 fn parse_device_index(&self, value: &str) -> anyhow::Result<usize> {
124 if let Ok(number) = value.parse::<usize>() {
125 if number > 0 && number <= self.switch_bot.devices().len() {
126 return Ok(number - 1);
127 }
128 }
129 self.switch_bot
130 .devices()
131 .index_by_device_id(value)
132 .ok_or_else(|| anyhow::anyhow!("Not a valid device: \"{value}\""))
133 }
134
135 fn parse_command(&self, mut text: &str) -> CommandRequest {
136 let mut command = CommandRequest::default();
137 if let Some((name, parameter)) = text.split_once(':') {
138 command.parameter = parameter.into();
139 text = name;
140 }
141 if let Some((command_type, name)) = text.split_once('/') {
142 command.command_type = command_type.into();
143 text = name;
144 }
145 command.command = text.into();
146 command
147 }
148}