Skip to content

Commit

Permalink
feat: add auto-completion for shell prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
XOR-op committed Oct 18, 2023
1 parent f9df256 commit 937387e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 42 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions boltconn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ readme = "README.md"

[features]
tokio-console = ["dep:console-subscriber", "tokio/tracing"]
internal-test = []

[dependencies]
# Core
Expand Down Expand Up @@ -76,6 +77,7 @@ shadowsocks = { version = "1.16.0", default-features = false }
smoltcp = "0.9.0"
# Command line
clap = { version = "4.4.6", features = ["derive"] }
clap_complete = "4.4.3"
colored = "2.0.0"
tabular = "0.2.0"

Expand Down
131 changes: 97 additions & 34 deletions boltconn/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ mod request_web;

use crate::ProgramArgs;
use anyhow::anyhow;
use clap::{Args, Subcommand};
use clap::{Args, CommandFactory, Subcommand, ValueHint};
use is_root::is_root;
use std::path::PathBuf;
use std::process::exit;

#[derive(Debug, Subcommand)]
pub(crate) enum ProxyOptions {
/// Set group's proxy
Set { group: String, proxy: String },
Set {
#[clap(value_hint = ValueHint::Other)]
group: String,
#[clap(value_hint = ValueHint::Other)]
proxy: String,
},
/// List all groups
List,
}
Expand All @@ -24,14 +29,22 @@ pub(crate) enum ConnOptions {
/// List all active connections
List,
Stop {
#[clap(value_hint = ValueHint::Other)]
nth: Option<usize>,
},
}

#[derive(Debug, Subcommand)]
#[derive(Debug, Clone, Copy, Subcommand)]
pub(crate) enum TunSetOptions {
On,
Off,
}

#[derive(Debug, Clone, Copy, Subcommand)]
pub(crate) enum TunOptions {
/// Set TUN
Set { s: String },
#[command(subcommand)]
Set(TunSetOptions),
/// Get TUN status
Get,
}
Expand All @@ -45,9 +58,15 @@ pub(crate) struct CertOptions {
#[derive(Debug, Subcommand)]
pub(crate) enum TempRuleOptions {
/// Add a temporary rule to the head of rule list
Add { literal: String },
Add {
#[clap(value_hint = ValueHint::Other)]
literal: String,
},
/// Delete temporary rules matching this prefix
Delete { literal: String },
Delete {
#[clap(value_hint = ValueHint::Other)]
literal: String,
},
/// Delete all temporary rules
Clear,
}
Expand All @@ -57,9 +76,17 @@ pub(crate) enum InterceptOptions {
/// List all captured data
List,
/// List data ranged from *start* to *end*
Range { start: u32, end: Option<u32> },
Range {
#[clap(value_hint = ValueHint::Other)]
start: u32,
#[clap(value_hint = ValueHint::Other)]
end: Option<u32>,
},
/// Get details of the packet
Get { id: u32 },
Get {
#[clap(value_hint = ValueHint::Other)]
id: u32,
},
}

#[derive(Debug, Args)]
Expand All @@ -85,41 +112,59 @@ pub(crate) struct InitOptions {
pub app_data: Option<PathBuf>,
}

#[derive(Debug, Clone, Copy, Subcommand)]
pub(crate) enum PromptOptions {
Bash,
Zsh,
Fish,
}

#[derive(Debug, Subcommand)]
pub(crate) enum SubCommand {
/// Start Main Program
Start(StartOptions),
/// Create Configurations
pub(crate) enum GenerateOptions {
/// Create configurations
Init(InitOptions),
/// Proxy Settings
/// Generate certificates
Cert(CertOptions),
/// Generate auto-completion profiles for shells
#[command(subcommand)]
Proxy(ProxyOptions),
/// Connection Settings
Prompt(PromptOptions),
}

#[derive(Debug, Subcommand)]
pub(crate) enum SubCommand {
/// Start the main program
Start(StartOptions),
/// Reload configurations
Reload,
/// Connection settings
#[command(subcommand)]
Conn(ConnOptions),
/// Generate Certificates
Cert(CertOptions),
/// Captured HTTP Data
/// Captured HTTP data
#[command(subcommand)]
Intercept(InterceptOptions),
/// Adjust TUN Status
/// Proxy settings
#[command(subcommand)]
Tun(TunOptions),
/// Modify Temporary Rules
Proxy(ProxyOptions),
/// Modify temporary rules
#[command(subcommand)]
Rule(TempRuleOptions),
/// Clean Unexpected Shutdown
TempRule(TempRuleOptions),
/// Adjust TUN status
#[command(subcommand)]
Tun(TunOptions),
/// Clean unexpected shutdown
Clean,
/// Reload Configuration
Reload,
/// Generate necessary files before the first run
#[command(subcommand)]
Generate(GenerateOptions),
#[cfg(feature = "internal-test")]
#[clap(hide = true)]
Internal,
}

pub(crate) async fn controller_main(args: ProgramArgs) -> ! {
let default_uds_path = "/var/run/boltconn.sock";
match args.cmd {
SubCommand::Init(init) => {
SubCommand::Generate(GenerateOptions::Init(init)) => {
fn create(init: InitOptions) -> anyhow::Result<()> {
let (config, data, _) =
crate::config::parse_paths(&init.config, &init.app_data, &None)?;
Expand All @@ -142,7 +187,7 @@ pub(crate) async fn controller_main(args: ProgramArgs) -> ! {
}
}
}
SubCommand::Cert(opt) => {
SubCommand::Generate(GenerateOptions::Cert(opt)) => {
if !is_root() {
eprintln!("Must be run with root/admin privilege");
exit(-1)
Expand Down Expand Up @@ -173,6 +218,17 @@ pub(crate) async fn controller_main(args: ProgramArgs) -> ! {
}
}
}
SubCommand::Generate(GenerateOptions::Prompt(shell)) => {
let generator = match shell {
PromptOptions::Bash => clap_complete::Shell::Bash,
PromptOptions::Zsh => clap_complete::Shell::Zsh,
PromptOptions::Fish => clap_complete::Shell::Fish,
};
let mut command = ProgramArgs::command();
let bin_name = command.get_name().to_string();
clap_complete::generate(generator, &mut command, bin_name, &mut std::io::stdout());
exit(0)
}
SubCommand::Clean => {
if !is_root() {
eprintln!("Must be run with root/admin privilege");
Expand Down Expand Up @@ -206,24 +262,31 @@ pub(crate) async fn controller_main(args: ProgramArgs) -> ! {
},
SubCommand::Tun(opt) => match opt {
TunOptions::Get => requester.get_tun().await,
TunOptions::Set { s } => requester.set_tun(s.as_str()).await,
TunOptions::Set(s) => {
requester
.set_tun(match s {
TunSetOptions::On => true,
TunSetOptions::Off => false,
})
.await
}
},
SubCommand::Intercept(opt) => match opt {
InterceptOptions::List => requester.intercept(None).await,
InterceptOptions::Range { start, end } => requester.intercept(Some((start, end))).await,
InterceptOptions::Get { id } => requester.get_intercept_payload(id).await,
},
SubCommand::Reload => requester.reload_config().await,
SubCommand::Rule(opt) => match opt {
SubCommand::TempRule(opt) => match opt {
TempRuleOptions::Add { literal } => requester.add_temporary_rule(literal).await,
TempRuleOptions::Delete { literal } => requester.delete_temporary_rule(literal).await,
TempRuleOptions::Clear => requester.clear_temporary_rule().await,
},
SubCommand::Start(_)
| SubCommand::Init(_)
| SubCommand::Cert(_)
| SubCommand::Clean
| SubCommand::Internal => {
SubCommand::Start(_) | SubCommand::Generate(_) | SubCommand::Clean => {
unreachable!()
}
#[cfg(feature = "internal-test")]
SubCommand::Internal => {
unreachable!()
}
};
Expand Down
10 changes: 2 additions & 8 deletions boltconn/src/cli/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,8 @@ impl Requester {
Ok(())
}

pub async fn set_tun(&self, content: &str) -> Result<()> {
let enabled = boltapi::TunStatusSchema {
enabled: match content.to_lowercase().as_str() {
"on" => true,
"off" => false,
_ => return Err(anyhow::anyhow!("Unknown TUN setting: {}", content)),
},
};
pub async fn set_tun(&self, enabled: bool) -> Result<()> {
let enabled = boltapi::TunStatusSchema { enabled };
match &self.inner {
Inner::Web(c) => c.set_tun(enabled).await,
Inner::Uds(c) => c.set_tun(enabled).await,
Expand Down
2 changes: 2 additions & 0 deletions boltconn/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fn main() -> ExitCode {
let args: ProgramArgs = ProgramArgs::parse();
let cmds = match args.cmd {
SubCommand::Start(sub) => sub,
#[cfg(feature = "internal-test")]
SubCommand::Internal => return internal_code(),
_ => rt.block_on(cli::controller_main(args)),
};
Expand Down Expand Up @@ -113,6 +114,7 @@ fn main() -> ExitCode {
}

/// This function is a shortcut for testing things conveniently. Only for development use.
#[cfg(feature = "internal-test")]
fn internal_code() -> ExitCode {
println!("This option is not for end-user.");
ExitCode::SUCCESS
Expand Down

0 comments on commit 937387e

Please sign in to comment.