diff --git a/Cargo.lock b/Cargo.lock index 64a85c7..bdc3d93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1197,6 +1197,15 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "clap_complete" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.28" @@ -1961,6 +1970,7 @@ dependencies = [ "chrono", "cidr", "clap", + "clap_complete", "crossbeam", "dashmap", "dbus", diff --git a/README.md b/README.md index 34a7c92..ee3636b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,11 @@ brew install --cask easytier-gui # 6. OpenWrt Luci Web UI # Visit https://github.com/EasyTier/luci-app-easytier + +# 7. (Optional) Install shell completions: +easytier-core --gen-autocomplete fish > ~/.config/fish/completions/easytier-core.fish +easytier-cli gen-autocomplete fish > ~/.config/fish/completions/easytier-cli.fish + ``` ### 🚀 Basic Usage diff --git a/README_CN.md b/README_CN.md index 980c4e4..0755991 100644 --- a/README_CN.md +++ b/README_CN.md @@ -67,6 +67,12 @@ brew install --cask easytier-gui # 6. OpenWrt Luci Web 界面 # 访问 https://github.com/EasyTier/luci-app-easytier + +# 7.(可选)安装 Shell 补全功能: +# Fish 补全 +easytier-core --gen-autocomplete fish > ~/.config/fish/completions/easytier-core.fish +easytier-cli gen-autocomplete fish > ~/.config/fish/completions/easytier-cli.fish + ``` ### 🚀 基本用法 diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index b355d66..45d779a 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -132,6 +132,7 @@ clap = { version = "4.5.30", features = [ "wrap_help", "env", ] } +clap_complete = { version = "4.5.55" } async-recursion = "1.0.5" diff --git a/easytier/locales/app.yml b/easytier/locales/app.yml index 69c1eca..5f2c17c 100644 --- a/easytier/locales/app.yml +++ b/easytier/locales/app.yml @@ -18,6 +18,9 @@ core_clap: config_file: en: "path to the config file, NOTE: the options set by cmdline args will override options in config file" zh-CN: "配置文件路径,注意:命令行中的配置的选项会覆盖配置文件中的选项" + generate_completions: + en: "generate shell completions" + zh-CN: "生成 shell 补全脚本" network_name: en: "network name to identify this vpn network" zh-CN: "用于标识此VPN网络的网络名称" diff --git a/easytier/src/easytier-cli.rs b/easytier/src/easytier-cli.rs index 713e0ac..8551ca2 100644 --- a/easytier/src/easytier-cli.rs +++ b/easytier/src/easytier-cli.rs @@ -11,8 +11,10 @@ use std::{ use anyhow::Context; use cidr::Ipv4Inet; -use clap::{command, Args, Parser, Subcommand}; +use clap::{command, Args, CommandFactory, Parser, Subcommand}; +use clap_complete::Shell; use humansize::format_size; +use rust_i18n::t; use service_manager::*; use tabled::settings::Style; use tokio::time::timeout; @@ -86,6 +88,10 @@ enum SubCommand { Service(ServiceArgs), #[command(about = "show tcp/kcp proxy status")] Proxy, + #[command(about = t!("core_clap.generate_completions").to_string())] + GenAutocomplete{ + shell:Shell + }, } #[derive(clap::ValueEnum, Debug, Clone, PartialEq)] @@ -985,7 +991,10 @@ where #[tokio::main] #[tracing::instrument] async fn main() -> Result<(), Error> { + let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")); + rust_i18n::set_locale(&locale); let cli = Cli::parse(); + let client = RpcClient::new(TcpTunnelConnector::new( format!("tcp://{}:{}", cli.rpc_portal.ip(), cli.rpc_portal.port()) .parse() @@ -1315,6 +1324,10 @@ async fn main() -> Result<(), Error> { print_output(&table_rows, &cli.output_format)?; } + SubCommand::GenAutocomplete { shell } => { + let mut cmd = Cli::command(); + easytier::print_completions(shell, &mut cmd, "easytier-cli"); + } } Ok(()) diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index a7ef78b..4126cf4 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -4,16 +4,14 @@ extern crate rust_i18n; use std::{ - net::{Ipv4Addr, SocketAddr}, - path::PathBuf, - process::ExitCode, - sync::Arc, + net::{Ipv4Addr, SocketAddr}, path::PathBuf, process::ExitCode, sync::Arc }; use anyhow::Context; use cidr::IpCidr; -use clap::Parser; +use clap::{CommandFactory, Parser}; +use clap_complete::Shell; use easytier::{ common::{ config::{ @@ -122,6 +120,9 @@ struct Cli { #[command(flatten)] logging_options: LoggingOptions, + + #[clap(long, help = t!("core_clap.generate_completions").to_string())] + gen_autocomplete: Option, } #[derive(Parser, Debug)] @@ -1158,6 +1159,11 @@ async fn main() -> ExitCode { let _monitor = std::thread::spawn(memory_monitor); let cli = Cli::parse(); + if let Some(shell) = cli.gen_autocomplete { + let mut cmd = Cli::command(); + easytier::print_completions(shell, &mut cmd, "easytier-core"); + return ExitCode::SUCCESS; + } let mut ret_code = 0; if let Err(e) = run_main(cli).await { diff --git a/easytier/src/lib.rs b/easytier/src/lib.rs index 793cf8f..4fe1e2c 100644 --- a/easytier/src/lib.rs +++ b/easytier/src/lib.rs @@ -1,5 +1,10 @@ #![allow(dead_code)] +use std::io; + +use clap::Command; +use clap_complete::Generator; + mod arch; mod gateway; mod instance; @@ -21,3 +26,7 @@ mod tests; pub const VERSION: &str = common::constants::EASYTIER_VERSION; rust_i18n::i18n!("locales", fallback = "en"); + +pub fn print_completions(generator: G, cmd: &mut Command, bin_name:&str) { + clap_complete::generate(generator, cmd, bin_name, &mut io::stdout()); +} \ No newline at end of file