From b11c20d512b857bba32973ace1b92ca304e18904 Mon Sep 17 00:00:00 2001 From: lemonsh Date: Sat, 13 May 2023 00:35:20 +0200 Subject: [PATCH] initial boilerplate for the shell --- connectbox-shell/Cargo.toml | 8 ++++ connectbox-shell/src/cli.rs | 27 +++++++++++++ connectbox-shell/src/main.rs | 71 ++++++++++++++++++++++++++++++++++- connectbox-shell/src/utils.rs | 40 ++++++++++++++++++++ 4 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 connectbox-shell/src/cli.rs create mode 100644 connectbox-shell/src/utils.rs diff --git a/connectbox-shell/Cargo.toml b/connectbox-shell/Cargo.toml index 25f231d..3d8f81e 100644 --- a/connectbox-shell/Cargo.toml +++ b/connectbox-shell/Cargo.toml @@ -1,5 +1,7 @@ [package] name = "connectbox-shell" +description = "A shell for managing your Connect Box router, based on the connectbox-rs library" +authors = ["lemonsh"] version = "0.1.0" edition = "2021" @@ -7,4 +9,10 @@ edition = "2021" rustyline = "11" color-eyre = "0.6" tokio = { version = "1.0", default-features = false, features = ["macros"] } +tracing = "0.1" tracing-subscriber = "0.3" +clap = { version = "4.2", default-features = false, features = ["suggestions", "color", "std", "help", "usage", "derive"] } +dirs = "5.0" +connectbox = { path = "../connectbox" } +color-print = "0.3" +anstream = "0.3" \ No newline at end of file diff --git a/connectbox-shell/src/cli.rs b/connectbox-shell/src/cli.rs new file mode 100644 index 0000000..c9572c3 --- /dev/null +++ b/connectbox-shell/src/cli.rs @@ -0,0 +1,27 @@ +use clap::{Command, Parser, Subcommand}; +use tracing::Level; + +#[derive(Parser, Debug)] +#[command(author, version, about)] +pub(crate) struct Args { + /// Address of the modem + pub address: String, + + /// Password used to log in to the modem. If not supplied, it will be asked interactively + pub password: Option, + + /// Log level, one of 'trace', 'debug', 'info', 'warn', 'error' + #[arg(short, default_value = "warn")] + pub log_level: Level, +} + +#[derive(Parser, Debug)] +pub(crate) enum ShellCommand { + Exit, + #[command(name = "pfw")] + PortForwards, +} + +pub(crate) fn shell_cmd() -> Command { + ShellCommand::augment_subcommands(Command::new("").multicall(true)) +} diff --git a/connectbox-shell/src/main.rs b/connectbox-shell/src/main.rs index e7a11a9..b263cff 100644 --- a/connectbox-shell/src/main.rs +++ b/connectbox-shell/src/main.rs @@ -1,3 +1,70 @@ -fn main() { - println!("Hello, world!"); +use anstream::println; +use color_print::cstr; + +use clap::{FromArgMatches, Parser}; +use cli::Args; +use color_eyre::Result; +use connectbox::ConnectBox; +use rustyline::{error::ReadlineError, DefaultEditor}; + +use crate::{cli::ShellCommand, utils::QuotableArgs}; + +mod cli; +mod utils; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + let args = Args::parse(); + let mut shell_cmd = cli::shell_cmd(); + + color_eyre::install()?; + tracing_subscriber::fmt::fmt() + .with_max_level(args.log_level) + .init(); + + let mut rl = DefaultEditor::new()?; + let password = if let Some(password) = args.password { + password + } else { + rl.readline("Password: ")? + }; + let history_path = dirs::data_dir() + .unwrap_or_default() + .join(".connectbox-shell-history"); + let _err = rl.load_history(&history_path); + + println!(cstr!("Logging in...")); + let connectbox = ConnectBox::new(args.address, password, true)?; + connectbox.login().await?; + + loop { + match rl.readline(cstr!(" > ")) { + Ok(line) => { + if line.chars().all(char::is_whitespace) { + continue; + } + let cmd = match shell_cmd.try_get_matches_from_mut(QuotableArgs::new(&line)) { + Ok(mut matches) => ShellCommand::from_arg_matches_mut(&mut matches)?, + Err(e) => { + e.print()?; + continue; + } + }; + match cmd { + ShellCommand::Exit => break, + ShellCommand::PortForwards => todo!(), + } + } + Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break, + Err(err) => { + println!("{err:?}"); + break; + } + } + } + println!("Logging out..."); + connectbox.logout().await?; + + rl.save_history(&history_path)?; + Ok(()) } diff --git a/connectbox-shell/src/utils.rs b/connectbox-shell/src/utils.rs new file mode 100644 index 0000000..355d5b3 --- /dev/null +++ b/connectbox-shell/src/utils.rs @@ -0,0 +1,40 @@ +pub(crate) struct QuotableArgs<'a> { + s: &'a str, +} + +impl<'a> QuotableArgs<'a> { + pub fn new(s: &'a str) -> Self { + Self { s } + } +} + +impl<'a> Iterator for QuotableArgs<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + self.s = self.s.trim_start(); + if self.s.is_empty() { + return None; + } + if self.s.as_bytes()[0] == b'"' { + self.s = &self.s[1..]; + if let Some(pos) = self.s.find('"') { + let result = &self.s[..pos]; + self.s = &self.s[pos + 1..]; + return Some(result); + } + let result = self.s; + self.s = &self.s[..0]; + return Some(result); + } + if let Some(pos) = self.s.find(char::is_whitespace) { + let result = &self.s[..pos]; + self.s = &self.s[pos..]; + Some(result) + } else { + let result = self.s; + self.s = &self.s[..0]; + Some(result) + } + } +}