From e42567b25093907cfd939edc92ace94a5d59b398 Mon Sep 17 00:00:00 2001 From: Weicao-CatilGrass <1992414357@qq.com> Date: Mon, 11 May 2026 19:56:10 +0800 Subject: Add `builds` feature and install completion scripts --- examples/example-completion/src/main.rs | 16 +++++-- mingling/Cargo.lock | 59 +++++++++++++++++++++++++ mingling/Cargo.toml | 3 +- mingling/src/example_docs.rs | 16 +++++-- mingling_core/Cargo.lock | 77 +++++++++++++++++++++++++++++++++ mingling_core/Cargo.toml | 3 ++ mingling_core/src/builds/comp.rs | 23 ++++++++++ mingling_core/src/comp.rs | 6 +++ mingling_core/src/comp/installation.rs | 72 ++++++++++++++++++++++++++++++ mingling_core/src/lib.rs | 2 + mling/Cargo.lock | 1 + mling/Cargo.toml | 1 + mling/src/project_installer.rs | 8 +--- 13 files changed, 274 insertions(+), 13 deletions(-) create mode 100644 mingling_core/src/comp/installation.rs diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index 413afd3..3f1a377 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -3,13 +3,23 @@ //! # How to Deploy //! 1. Enable the `comp` feature //! ```toml +//! [dependencies] //! mingling = { version = "...", features = [ //! "comp", // Enable this feature //! "parser" //! ] } //! ``` //! -//! 2. Write `build.rs` to generate completion scripts at compile time +//! 2. Add `mingling` as a build dependency, enabling the `builds` and `comp` features +//! ```toml +//! [build-dependencies] +//! mingling = { version = "...", features = [ +//! "builds", // Enable this feature for build scripts +//! "comp" +//! ] } +//! ``` +//! +//! 3. Write `build.rs` to generate completion scripts at compile time //! ```ignore //! use mingling::build::{build_comp_scripts, build_comp_scripts_with_bin_name}; //! fn main() { @@ -21,8 +31,8 @@ //! } //! ``` //! -//! 3. Write `main.rs`, adding completion logic for your command entry point -//! 4. Execute `cargo install --path ./`, then run the corresponding completion script in your shell +//! 4. Write `main.rs`, adding completion logic for your command entry point +//! 5. Execute `cargo install --path ./`, then run the corresponding completion script in your shell use mingling::{ EnumTag, Groupped, ShellContext, Suggest, diff --git a/mingling/Cargo.lock b/mingling/Cargo.lock index 879973d..56c591c 100644 --- a/mingling/Cargo.lock +++ b/mingling/Cargo.lock @@ -88,6 +88,27 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "env_filter" version = "1.0.1" @@ -127,6 +148,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.17.0" @@ -200,6 +232,15 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -237,6 +278,7 @@ dependencies = [ name = "mingling_core" version = "0.1.8" dependencies = [ + "dirs", "env_logger", "just_fmt", "just_template", @@ -284,6 +326,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "parking_lot" version = "0.12.5" @@ -355,6 +403,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.12.3" diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml index dd93a5e..ebe3620 100644 --- a/mingling/Cargo.toml +++ b/mingling/Cargo.toml @@ -16,12 +16,13 @@ tokio = { version = "1", features = ["full"] } mingling = { path = ".", features = ["comp", "general_renderer", "parser"] } [package.metadata.docs.rs] -features = ["general_renderer", "repl", "comp", "parser"] +features = ["builds", "general_renderer", "repl", "comp", "parser"] [features] nightly = ["mingling_core/nightly", "mingling_macros/nightly"] debug = ["mingling_core/debug"] async = ["mingling_core/async", "mingling_macros/async"] +builds = ["mingling_core/builds"] default = ["mingling_core/default", "mingling_macros/default"] diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 9f1b46b..0a6c1b9 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -133,13 +133,23 @@ pub mod example_basic {} /// # How to Deploy /// 1. Enable the `comp` feature /// ```toml +/// [dependencies] /// mingling = { version = "...", features = [ /// "comp", // Enable this feature /// "parser" /// ] } /// ``` /// -/// 2. Write `build.rs` to generate completion scripts at compile time +/// 2. Add `mingling` as a build dependency, enabling the `builds` and `comp` features +/// ```toml +/// [build-dependencies] +/// mingling = { version = "...", features = [ +/// "builds", // Enable this feature for build scripts +/// "comp" +/// ] } +/// ``` +/// +/// 3. Write `build.rs` to generate completion scripts at compile time /// ```ignore /// use mingling::build::{build_comp_scripts, build_comp_scripts_with_bin_name}; /// fn main() { @@ -151,8 +161,8 @@ pub mod example_basic {} /// } /// ``` /// -/// 3. Write `main.rs`, adding completion logic for your command entry point -/// 4. Execute `cargo install --path ./`, then run the corresponding completion script in your shell +/// 4. Write `main.rs`, adding completion logic for your command entry point +/// 5. Execute `cargo install --path ./`, then run the corresponding completion script in your shell /// /// Cargo.toml /// ```ignore diff --git a/mingling_core/Cargo.lock b/mingling_core/Cargo.lock index 9c2f9aa..358fa67 100644 --- a/mingling_core/Cargo.lock +++ b/mingling_core/Cargo.lock @@ -70,12 +70,39 @@ dependencies = [ "serde_core", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "env_filter" version = "1.0.1" @@ -105,6 +132,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.17.0" @@ -172,6 +210,21 @@ dependencies = [ "just_fmt", ] +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + [[package]] name = "log" version = "0.4.29" @@ -188,6 +241,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" name = "mingling_core" version = "0.1.8" dependencies = [ + "dirs", "env_logger", "just_fmt", "just_template", @@ -213,6 +267,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -246,6 +306,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.12.3" @@ -454,6 +525,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "windows-link" version = "0.2.1" diff --git a/mingling_core/Cargo.toml b/mingling_core/Cargo.toml index 4a8d593..f3ba9a2 100644 --- a/mingling_core/Cargo.toml +++ b/mingling_core/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/catilgrass/mingling" nightly = [] default = [] async = [] +builds = ["dep:dirs"] dispatch_tree = [] general_renderer = [ @@ -31,6 +32,8 @@ thiserror = "2" once_cell = "1.21.4" +dirs = { version = "6", optional = true } + # comp just_template = { version = "0.1.3", optional = true } diff --git a/mingling_core/src/builds/comp.rs b/mingling_core/src/builds/comp.rs index 228ef81..ad70cd2 100644 --- a/mingling_core/src/builds/comp.rs +++ b/mingling_core/src/builds/comp.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use just_template::tmpl_param; use crate::ShellFlag; @@ -84,6 +86,27 @@ pub fn build_comp_script_to( std::fs::write(&output_path, tmpl.to_string()) } +/// Generate a shell completion script and write it to a specified file path. +/// +/// This function takes a shell flag, a binary name, and an output file path, +/// selects the appropriate template, substitutes the binary name into the template, +/// and writes the resulting completion script directly to the specified file path. +/// +/// # Example +/// ``` +/// build_comp_script_to_file(&ShellFlag::Bash, "myapp", "target/completions/myapp_comp.sh").unwrap(); +/// ``` +pub fn build_comp_script_to_file( + shell_flag: &ShellFlag, + bin_name: &str, + output_path: impl Into, +) -> 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); + std::fs::write(output_path.into(), tmpl.to_string()) +} + fn get_tmpl(shell_flag: &ShellFlag) -> (&'static str, &'static str) { match shell_flag { ShellFlag::Bash => (TMPL_COMP_BASH, ".sh"), diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs index 4fb17c7..fd26e1b 100644 --- a/mingling_core/src/comp.rs +++ b/mingling_core/src/comp.rs @@ -2,11 +2,17 @@ mod flags; mod shell_ctx; mod suggest; +#[cfg(feature = "builds")] +mod installation; + use std::collections::BTreeSet; use std::fmt::Display; #[doc(hidden)] pub use flags::*; +#[cfg(feature = "builds")] +#[doc(hidden)] +pub use installation::*; #[doc(hidden)] pub use shell_ctx::*; #[doc(hidden)] diff --git a/mingling_core/src/comp/installation.rs b/mingling_core/src/comp/installation.rs new file mode 100644 index 0000000..d3d31d6 --- /dev/null +++ b/mingling_core/src/comp/installation.rs @@ -0,0 +1,72 @@ +use crate::{build::build_comp_script_to_file, ShellFlag}; + +pub fn install_comp_script( + flag: ShellFlag, + bin_name: impl AsRef, +) -> Result<(), std::io::Error> { + match flag { + // ~/.local/share/bash-completion/completions/ + ShellFlag::Bash => { + let Some(data_dir) = dirs::data_dir() else { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Data directory not found!", + )); + }; + + let bin_name = bin_name.as_ref(); + + let comp_script_path = data_dir + .join("bash-completion") + .join("completions") + .join(format!("{}.sh", bin_name)); + + build_comp_script_to_file(&ShellFlag::Bash, bin_name, comp_script_path)?; + Ok(()) + } + + // ~/.zsh/completions/ + ShellFlag::Zsh => { + let Some(home_dir) = dirs::home_dir() else { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Home directory not found!", + )); + }; + + let bin_name = bin_name.as_ref(); + + let comp_script_path = home_dir + .join(".zsh") + .join("completions") + .join(format!("{}.zsh", bin_name)); + + build_comp_script_to_file(&ShellFlag::Zsh, bin_name, comp_script_path)?; + Ok(()) + } + + // ~/.config/fish/completions/ + ShellFlag::Fish => { + let Some(config_dir) = dirs::config_dir() else { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Config directory not found!", + )); + }; + + let bin_name = bin_name.as_ref(); + + let comp_script_path = config_dir + .join("fish") + .join("completions") + .join(format!("{}.fish", bin_name)); + + build_comp_script_to_file(&ShellFlag::Fish, bin_name, comp_script_path)?; + Ok(()) + } + _ => Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "unsupported shell flag", + )), + } +} diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index 83edda8..e1c188f 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -51,10 +51,12 @@ pub mod setup { pub use crate::program::setup::*; } +#[cfg(feature = "builds")] #[doc(hidden)] pub mod builds; /// Provides build scripts for users +#[cfg(feature = "builds")] pub mod build { #[cfg(feature = "comp")] pub use crate::builds::comp::*; diff --git a/mling/Cargo.lock b/mling/Cargo.lock index fbcd4a7..073b69b 100644 --- a/mling/Cargo.lock +++ b/mling/Cargo.lock @@ -149,6 +149,7 @@ dependencies = [ name = "mingling_core" version = "0.1.8" dependencies = [ + "dirs", "just_fmt", "just_template", "once_cell", diff --git a/mling/Cargo.toml b/mling/Cargo.toml index bbee212..c2985f7 100644 --- a/mling/Cargo.toml +++ b/mling/Cargo.toml @@ -24,6 +24,7 @@ strip = true [dependencies] mingling = { path = "../mingling", features = [ + "builds", "parser", "comp", "general_renderer", diff --git a/mling/src/project_installer.rs b/mling/src/project_installer.rs index 5c21462..2e9ca8d 100644 --- a/mling/src/project_installer.rs +++ b/mling/src/project_installer.rs @@ -44,9 +44,7 @@ pub fn install_this_project( .current_dir(workspace_root) .status()?; if !status.success() { - return Err(std::io::Error::other( - "exec `cargo clean` failed", - )); + return Err(std::io::Error::other("exec `cargo clean` failed")); } } @@ -56,9 +54,7 @@ pub fn install_this_project( .current_dir(workspace_root) .status()?; if !status.success() { - return Err(std::io::Error::other( - "cargo build --release failed", - )); + return Err(std::io::Error::other("cargo build --release failed")); } // Parse package.name from workspace_root's Cargo.toml as namespace -- cgit