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 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}