switchbot_cli/
cli.rs

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}