initial boilerplate for the shell
This commit is contained in:
parent
84fefcda09
commit
b11c20d512
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user