initial boilerplate for the shell
This commit is contained in:
		| @@ -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" | ||||
							
								
								
									
										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() { | ||||
|     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!("<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