initial boilerplate for the shell
This commit is contained in:
		| @@ -1,5 +1,7 @@ | |||||||
| [package] | [package] | ||||||
| name = "connectbox-shell" | name = "connectbox-shell" | ||||||
|  | description = "A shell for managing your Connect Box router, based on the connectbox-rs library" | ||||||
|  | authors = ["lemonsh"] | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
|  |  | ||||||
| @@ -7,4 +9,10 @@ edition = "2021" | |||||||
| rustyline = "11" | rustyline = "11" | ||||||
| color-eyre = "0.6" | color-eyre = "0.6" | ||||||
| tokio = { version = "1.0", default-features = false, features = ["macros"] } | tokio = { version = "1.0", default-features = false, features = ["macros"] } | ||||||
|  | tracing = "0.1" | ||||||
| tracing-subscriber = "0.3" | 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" | ||||||
							
								
								
									
										27
									
								
								connectbox-shell/src/cli.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								connectbox-shell/src/cli.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<String>, | ||||||
|  |  | ||||||
|  |     /// 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)) | ||||||
|  | } | ||||||
| @@ -1,3 +1,70 @@ | |||||||
| fn main() { | use anstream::println; | ||||||
|     println!("Hello, world!"); | 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!("<blue!>Logging in...")); | ||||||
|  |     let connectbox = ConnectBox::new(args.address, password, true)?; | ||||||
|  |     connectbox.login().await?; | ||||||
|  |  | ||||||
|  |     loop { | ||||||
|  |         match rl.readline(cstr!("<green!> > ")) { | ||||||
|  |             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(()) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								connectbox-shell/src/utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								connectbox-shell/src/utils.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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::Item> { | ||||||
|  |         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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user