diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-11 16:50:57 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-11 16:50:57 +0800 |
| commit | 58ef8a8f42a68c7a81118ef9120705730ce3f458 (patch) | |
| tree | 80f302b07f011d2e636f5f8d3ec815fe6a4dafab /mingling_core/src | |
| parent | 839326946560166da84c04d4770385795d96cff0 (diff) | |
Add shell completion script generation feature
Diffstat (limited to 'mingling_core/src')
| -rw-r--r-- | mingling_core/src/asset/comp.rs | 27 | ||||
| -rw-r--r-- | mingling_core/src/builds.rs | 3 | ||||
| -rw-r--r-- | mingling_core/src/builds/comp.rs | 80 | ||||
| -rw-r--r-- | mingling_core/src/lib.rs | 7 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 24 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 23 |
6 files changed, 139 insertions, 25 deletions
diff --git a/mingling_core/src/asset/comp.rs b/mingling_core/src/asset/comp.rs index eeef0c0..3c22e12 100644 --- a/mingling_core/src/asset/comp.rs +++ b/mingling_core/src/asset/comp.rs @@ -11,7 +11,7 @@ pub use shell_ctx::*; #[doc(hidden)] pub use suggest::*; -use crate::{ProgramCollect, this}; +use crate::{ProgramCollect, exec::match_user_input, this}; /// Trait for implementing completion logic. /// @@ -36,15 +36,34 @@ pub struct CompletionHelper; impl CompletionHelper { pub fn exec_completion<P>(ctx: &ShellContext) -> Suggest where - P: ProgramCollect + Display + 'static, + P: ProgramCollect<Enum = P> + Display + 'static, { let program = this::<P>(); - Suggest::FileCompletion + let suggest = if let Some((dispatcher, args)) = match_user_input(program).ok() { + let begin = dispatcher.begin(args); + if let crate::ChainProcess::Ok((any, _)) = begin { + Some(P::do_comp(&any, ctx)) + } else { + None + } + } else { + None + }; + + match suggest { + Some(suggest) => suggest, + None => default_completion(ctx), + } } pub fn render_suggest<P>(ctx: ShellContext, suggest: Suggest) where - P: ProgramCollect + Display + 'static, + P: ProgramCollect<Enum = P> + Display + 'static, { + todo!() } } + +fn default_completion(ctx: &ShellContext) -> Suggest { + todo!() +} diff --git a/mingling_core/src/builds.rs b/mingling_core/src/builds.rs new file mode 100644 index 0000000..0123c82 --- /dev/null +++ b/mingling_core/src/builds.rs @@ -0,0 +1,3 @@ +#[doc(hidden)] +#[cfg(feature = "comp")] +pub mod comp; diff --git a/mingling_core/src/builds/comp.rs b/mingling_core/src/builds/comp.rs new file mode 100644 index 0000000..694af0c --- /dev/null +++ b/mingling_core/src/builds/comp.rs @@ -0,0 +1,80 @@ +use just_template::tmpl_param; + +use crate::ShellFlag; + +const TMPL_COMP_BASH: &str = include_str!("../../tmpls/comps/bash.sh"); +const TMPL_COMP_ZSH: &str = include_str!("../../tmpls/comps/zsh.zsh"); +const TMPL_COMP_FISH: &str = include_str!("../../tmpls/comps/fish.fish"); +const TMPL_COMP_PWSL: &str = include_str!("../../tmpls/comps/pwsl.ps1"); + +/// Generate shell completion scripts for the current binary. +/// On Windows, generates PowerShell completion. +/// On Linux, generates Zsh, Bash, and Fish completions. +/// Scripts are written to the `OUT_DIR` (or `target/` if `OUT_DIR` is not set). +/// +/// # Example +/// ``` +/// // Typically called from a build script (`build.rs`): +/// build_comp_scripts().unwrap(); +/// // Or, to specify a custom binary name: +/// build_comp_scripts_with_bin_name("myapp").unwrap(); +/// ``` +pub fn build_comp_scripts() -> Result<(), std::io::Error> { + let bin_name = env!("CARGO_PKG_NAME"); + build_comp_scripts_with_bin_name(bin_name) +} + +/// Generate shell completion scripts for a given binary name. +/// On Windows, generates PowerShell completion. +/// On Linux, generates Zsh, Bash, and Fish completions. +/// Scripts are written to the `OUT_DIR` (or `target/` if `OUT_DIR` is not set). +/// +/// # Example +/// ``` +/// // Generate completion scripts for "myapp" +/// build_comp_scripts_with_bin_name("myapp").unwrap(); +/// ``` +pub fn build_comp_scripts_with_bin_name(name: &str) -> Result<(), std::io::Error> { + #[cfg(target_os = "windows")] + { + build_comp_script(&ShellFlag::Powershell, name)?; + Ok(()) + } + + #[cfg(target_os = "linux")] + { + build_comp_script(&ShellFlag::Zsh, name)?; + build_comp_script(&ShellFlag::Bash, name)?; + build_comp_script(&ShellFlag::Fish, name)?; + Ok(()) + } + + #[cfg(target_os = "macos")] + { + build_comp_script(&ShellFlag::Zsh, name)?; + build_comp_script(&ShellFlag::Bash, name)?; + build_comp_script(&ShellFlag::Fish, name)?; + Ok(()) + } +} + +fn build_comp_script(shell_flag: &ShellFlag, bin_name: &str) -> Result<(), std::io::Error> { + let (tmpl_str, ext) = get_tmpl(shell_flag); + let mut tmpl = just_template::Template::from(tmpl_str); + tmpl_param!(tmpl, bin_name = bin_name); + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let target_dir = out_dir.join("../../../").to_path_buf(); + let output_path = target_dir.join(format!("{}_comp{}", bin_name, ext)); + std::fs::create_dir_all(&target_dir)?; + std::fs::write(&output_path, tmpl.to_string()) +} + +fn get_tmpl(shell_flag: &ShellFlag) -> (&'static str, &'static str) { + match shell_flag { + ShellFlag::Bash => (TMPL_COMP_BASH, ".sh"), + ShellFlag::Zsh => (TMPL_COMP_ZSH, ".zsh"), + ShellFlag::Fish => (TMPL_COMP_FISH, ".fish"), + ShellFlag::Powershell => (TMPL_COMP_PWSL, ".ps1"), + ShellFlag::Other(_) => (TMPL_COMP_BASH, ".sh"), + } +} diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index 7801d34..dacc1b4 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -48,3 +48,10 @@ pub mod marker { pub mod setup { pub use crate::program::setup::*; } + +#[doc(hidden)] +pub mod builds; +pub mod build { + #[cfg(feature = "comp")] + pub use crate::builds::comp::*; +} diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 7b9f8d4..42ca531 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -164,6 +164,11 @@ where } } } + + // Get all registered dispatcher names from the program + pub fn get_nodes(&self) -> Vec<(String, &Box<dyn Dispatcher<G> + Send + Sync>)> { + get_nodes(self) + } } /// Collected program context @@ -251,3 +256,22 @@ macro_rules! __dispatch_program_chains { } }; } + +// Get all registered dispatcher names from the program +pub fn get_nodes<C: ProgramCollect<Enum = G>, G: Display>( + program: &Program<C, G>, +) -> Vec<(String, &Box<dyn Dispatcher<G> + Send + Sync>)> { + program + .dispatcher + .iter() + .map(|disp| { + let node_str = disp + .node() + .to_string() + .split('.') + .collect::<Vec<_>>() + .join(" "); + (node_str, disp) + }) + .collect() +} diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index f578064..072f4cb 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -73,14 +73,14 @@ where /// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. #[allow(clippy::type_complexity)] -fn match_user_input<C, G>( +pub fn match_user_input<C, G>( program: &Program<C, G>, ) -> Result<(&Box<dyn Dispatcher<G> + Send + Sync>, Vec<String>), ProgramInternalExecuteError> where C: ProgramCollect<Enum = G>, G: Display, { - let nodes = get_nodes(program); + let nodes = program.get_nodes(); let command = format!("{} ", program.args.join(" ")); // Find all nodes that match the command prefix @@ -140,22 +140,3 @@ fn render<C: ProgramCollect<Enum = G>, G: Display>( } } } - -// Get all registered dispatcher names from the program -fn get_nodes<C: ProgramCollect<Enum = G>, G: Display>( - program: &Program<C, G>, -) -> Vec<(String, &Box<dyn Dispatcher<G> + Send + Sync>)> { - program - .dispatcher - .iter() - .map(|disp| { - let node_str = disp - .node() - .to_string() - .split('.') - .collect::<Vec<_>>() - .join(" "); - (node_str, disp) - }) - .collect() -} |
