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