1use clap::Parser;
2use switchbot_api::{CommandRequest, Device, SwitchBot};
3
4use crate::{Args, UserInput};
5
6#[derive(Debug, Default)]
7pub struct Cli {
8 args: Args,
9 switch_bot: SwitchBot,
10 current_device_index: Option<usize>,
11}
12
13impl Cli {
14 pub fn new_from_args() -> Self {
15 let mut args = Args::parse();
16 if let Err(error) = args.merge_config() {
17 log::debug!("Load config error: {error}");
18 }
19 args.ensure_default();
20 if !args.alias_updates.is_empty() {
21 args.update_aliases();
22 args.print_aliases();
23 }
24 Self {
25 args,
26 ..Default::default()
27 }
28 }
29
30 fn has_current_device(&self) -> bool {
31 self.current_device_index.is_some()
32 }
33
34 fn current_device(&self) -> Option<&Device> {
35 if let Some(index) = self.current_device_index {
36 return self.switch_bot.devices().get(index);
37 }
38 None
39 }
40
41 pub async fn run(&mut self) -> anyhow::Result<()> {
42 self.switch_bot = self.args.create_switch_bot()?;
43 self.switch_bot.load_devices().await?;
44
45 if !self.args.commands.is_empty() {
46 self.execute_commands(&self.args.commands.clone()).await?;
47 return Ok(());
48 }
49
50 self.run_interactive().await
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_command(input_text).await {
76 log::error!("{error}");
77 }
78 }
79 }
80 }
81 self.args.save()?;
82 Ok(())
83 }
84
85 fn print_devices(&self) {
86 for (i, device) in self.switch_bot.devices().iter().enumerate() {
87 println!("{}: {device}", i + 1);
88 }
89 }
90
91 async fn execute_commands(&mut self, list: &[String]) -> anyhow::Result<()> {
92 for command in list {
93 self.execute_command(command).await?;
94 }
95 Ok(())
96 }
97
98 async fn execute_command(&mut self, text: &str) -> anyhow::Result<()> {
99 if self.set_current_device(text).is_ok() {
100 return Ok(());
101 }
102 if let Some(device) = self.current_device() {
103 let command = self.parse_command(text);
104 device.command(&command).await?;
105 return Ok(());
106 }
107 self.set_current_device(text)?;
108 Ok(())
109 }
110
111 fn set_current_device(&mut self, value: &str) -> anyhow::Result<()> {
112 self.current_device_index = Some(self.parse_device_index(value)?);
113 log::debug!("current_device_index={:?}", self.current_device_index);
114 Ok(())
115 }
116
117 fn parse_device_index(&self, value: &str) -> anyhow::Result<usize> {
118 if let Ok(number) = value.parse::<usize>() {
119 if number > 0 && number <= self.switch_bot.devices().len() {
120 return Ok(number - 1);
121 }
122 }
123 self.switch_bot
124 .devices()
125 .index_by_device_id(value)
126 .ok_or_else(|| anyhow::anyhow!("Not a valid device: \"{value}\""))
127 }
128
129 fn parse_command(&self, text: &str) -> CommandRequest {
130 if let Some(alias) = self.args.aliases.get(text) {
131 log::debug!(r#"Command alias: "{text}" -> "{alias}""#);
132 return self.parse_command_no_alias(alias);
133 }
134 self.parse_command_no_alias(text)
135 }
136
137 fn parse_command_no_alias(&self, mut text: &str) -> CommandRequest {
138 let mut command = CommandRequest::default();
139 if let Some((name, parameter)) = text.split_once(':') {
140 command.parameter = parameter.into();
141 text = name;
142 }
143 if let Some((command_type, name)) = text.split_once('/') {
144 command.command_type = command_type.into();
145 text = name;
146 }
147 command.command = text.into();
148 command
149 }
150}