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