switchbot_cli/
cli.rs

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}