diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-07 02:25:27 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-07 02:25:27 +0800 |
| commit | 81528b273c18693ebd3f05c6f8057ff8e632f4a0 (patch) | |
| tree | 85026c27535337c0123d4650c844ae364bc9780a /mling | |
| parent | e41e8bda221b44d09d7e93ffc43675147ab60a6d (diff) | |
Refactor mling to use new program architecture and install scripts
Diffstat (limited to 'mling')
| -rw-r--r-- | mling/.gitignore | 2 | ||||
| -rw-r--r-- | mling/Cargo.toml | 10 | ||||
| -rw-r--r-- | mling/build.rs | 62 | ||||
| -rw-r--r-- | mling/res/help-mling.txt | 15 | ||||
| -rw-r--r-- | mling/res/version.txt | 20 | ||||
| -rw-r--r-- | mling/src/bin/mling.rs | 86 | ||||
| -rw-r--r-- | mling/src/cargo_style.rs | 161 | ||||
| -rw-r--r-- | mling/src/cli.rs | 77 | ||||
| -rw-r--r-- | mling/src/cli/install.rs | 32 | ||||
| -rw-r--r-- | mling/src/cli/list.rs | 123 | ||||
| -rw-r--r-- | mling/src/cli/namespace_mgr.rs | 128 | ||||
| -rw-r--r-- | mling/src/cli/read.rs | 78 | ||||
| -rw-r--r-- | mling/src/display.rs | 32 | ||||
| -rw-r--r-- | mling/src/helps/mling_help.txt | 14 | ||||
| -rw-r--r-- | mling/src/lib.rs | 12 | ||||
| -rw-r--r-- | mling/src/main.rs | 15 | ||||
| -rw-r--r-- | mling/src/namespace_manager.rs | 125 | ||||
| -rw-r--r-- | mling/src/proj_mgr/mod.rs | 3 | ||||
| -rw-r--r-- | mling/src/project_installer.rs | 162 | ||||
| -rw-r--r-- | mling/src/project_solver.rs | 113 | ||||
| -rw-r--r-- | mling/src/res/current_dir.rs | 6 | ||||
| -rw-r--r-- | mling/src/res/mod.rs | 2 | ||||
| -rw-r--r-- | mling/tmpl/load.fish | 75 | ||||
| -rw-r--r-- | mling/tmpl/load.ps1 | 90 | ||||
| -rw-r--r-- | mling/tmpl/load.sh | 100 |
25 files changed, 373 insertions, 1170 deletions
diff --git a/mling/.gitignore b/mling/.gitignore new file mode 100644 index 0000000..b124b7c --- /dev/null +++ b/mling/.gitignore @@ -0,0 +1,2 @@ +debug.rs +version.txt diff --git a/mling/Cargo.toml b/mling/Cargo.toml index df9c675..a08c8d5 100644 --- a/mling/Cargo.toml +++ b/mling/Cargo.toml @@ -11,8 +11,12 @@ keywords = ["cli", "scaffolding", "command-line", "framework"] categories = ["command-line-interface"] [[bin]] +name = "cargo-mling" +path = "src/bin/mling.rs" + +[[bin]] name = "mling" -path = "src/main.rs" +path = "src/bin/mling.rs" [dependencies] mingling = { path = "../mingling", features = [ @@ -21,6 +25,7 @@ mingling = { path = "../mingling", features = [ "comp", "general_renderer", "extra_macros", + "dispatch_tree", ] } serde = { version = "1", features = ["derive"] } @@ -32,4 +37,5 @@ just_fmt = "0.1.2" toml.workspace = true [build-dependencies] -mingling = { path = "../mingling", features = ["comp"] } +chrono = "0.4" +mingling = { path = "../mingling", features = ["comp", "builds"] } diff --git a/mling/build.rs b/mling/build.rs new file mode 100644 index 0000000..abbcc50 --- /dev/null +++ b/mling/build.rs @@ -0,0 +1,62 @@ +use std::path::Path; +use std::process::Command; + +use mingling::build::build_comp_scripts; + +fn main() { + build_version_info(); + build_completion(); +} + +fn build_version_info() { + // Read version from CARGO_PKG_VERSION (inherited from workspace Cargo.toml) + let version = env!("CARGO_PKG_VERSION"); + + // Get git commit hash (first 9 characters) + let commit_hash = Command::new("git") + .args(["rev-parse", "--short=9", "HEAD"]) + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + + // Get date from git commit, fallback to current date + let date = Command::new("git") + .args(["log", "-1", "--format=%ad", "--date=format:%Y-%-m-%-d"]) + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| chrono::Local::now().format("%Y-%-m-%-d").to_string()); + + let version_string = format!("mling {version} ({commit_hash} {date})"); + + let out_dir = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src") + .join("helps") + .join("version.txt"); + + std::fs::write(&out_dir, version_string).expect("failed to write version.txt"); + + println!("cargo::rerun-if-changed=../Cargo.toml"); + println!("cargo::rerun-if-changed=../Cargo.lock"); + println!("cargo::rerun-if-changed=.git/HEAD"); +} + +fn build_completion() { + build_comp_scripts("cargo-mling").unwrap(); + build_comp_scripts("mling").unwrap(); +} diff --git a/mling/res/help-mling.txt b/mling/res/help-mling.txt deleted file mode 100644 index 4d6fa02..0000000 --- a/mling/res/help-mling.txt +++ /dev/null @@ -1,15 +0,0 @@ -**Usage**: mling [COMMAND] - -**COMMANDS** -__ *install* Install programs in a Cargo workspace; **[--clean | -c]** -__ *rm-namespace* Delete a namespace -__ *ls-namespace* List namespaces -__ *set-trust* Set trust for a namespace **[--trusted | -t <yes/no>]** -__ *trust* Set a namespace as trusted -__ *untrust* Set a namespace as untrusted -__ *show-binaries* List binary programs in the current Cargo workspace -__ *show-target-dir* Show the output directory of the current Cargo workspace -__ *show-workspace-root* Show the root location of the current Cargo workspace - -**OPTIONS** -__ [[b_cyan]]-h, --help[[/]] Print this help message diff --git a/mling/res/version.txt b/mling/res/version.txt deleted file mode 100644 index 4b6c716..0000000 --- a/mling/res/version.txt +++ /dev/null @@ -1,20 +0,0 @@ - █ - ███ - ██▒██ ┌────────────────────────────────────┐ - ██▒▒▒██ │ Mingling │ - ██▒ ▒██ │ USE: │ - ███▒ ▒███ │ Run command in your project │ - ███▒▒█████▒▒███ │ > cargo add mingling │ - █████▒ ████ ▒█████ │ ^^^^^^^^ │ - ██████▒▒ █▒ ██ ▒█ ▒███████ │ Or add this to your Cargo.toml │ - █████▒▒▒▒█▒ █▒ ██ ▒█ ▒██▒▒▒▒█████ │ > mingling = "0.1.9" │ - ▒▒▒▒▒ ██ ██ ▒██ ▒▒▒▒▒ └────────────────────────────────────┘ - ▒▒███████████████ ┌────────────────────────────────────┐ - ▒▒▒███▒▒▒▒▒▒▒▒ │ INSTALL TOOLS: │ - ████ │ Run command to install │ - ███▒▒ │ > cargo install mingling-cli │ - ████▒ │ ^^^^^^^^^^^^ │ - ▒▒▒█████ │ Then run command to use │ - ▒▒▒▒▒████ │ > mling --help │ - ▒▒▒█████ └────────────────────────────────────┘ - ▒▒▒▒▒ diff --git a/mling/src/bin/mling.rs b/mling/src/bin/mling.rs new file mode 100644 index 0000000..587aeb9 --- /dev/null +++ b/mling/src/bin/mling.rs @@ -0,0 +1,86 @@ +use std::{env::current_dir, process::exit}; + +use colored::Colorize; +use mingling::{ + Program, + hook::ProgramHook, + macros::program_setup, + setup::{ExitCodeSetup, GeneralRendererSetup, HelpFlagSetup, QuietFlagSetup}, +}; +use mingling_cli::{ThisProgram, display::markdown, res::ResCurrentDir}; + +fn main() { + #[cfg(windows)] + colored::control::set_virtual_terminal(true).unwrap(); + + // Preprocess args to handle cargo-mling invocations + let mut args: Vec<String> = std::env::args().collect(); + if args.first().map_or(false, |a| a.contains("cargo-mling")) { + args[0] = "cargo-mling".to_string(); + } + if args.get(1).map_or(false, |a| a == "mling") { + args.remove(1); + } + + // Build program with preprocessed args + let mut program = Program::<ThisProgram>::new_with_args(args); + + // Intercept Version + program.global_flag(["-V", "--version"], |_| { + eprintln!(include_str!("../helps/version.txt")); + exit(0) + }); + + // Intercept Help + program.with_hook(ProgramHook::empty().on_post_dispatch(|c| match c { + // When dispatcher is not found + ThisProgram::ErrorDispatcherNotFound => { + // And user requests Help + if ThisProgram::this().user_context.help { + // Print help + eprintln!("{}", markdown(include_str!("../helps/mling_help.txt"))); + exit(0) + } + } + _ => {} + })); + + // Setups + program.with_setup(HelpFlagSetup::new(["-h", "--help"])); + program.with_setup(GeneralRendererSetup); + program.with_setup(StandardOutputSetup); + program.with_setup(ExitCodeSetup::default()); + + // Resources + program.with_resource(ResCurrentDir { + path: current_dir().unwrap(), + }); + + // Execute + let quiet = program.stdout_setting.quiet; + let error_output = program.stdout_setting.error_output && !quiet; + let render_output = program.stdout_setting.render_output && !quiet; + let result = program.exec_without_render().unwrap(); + if !result.is_empty() { + if result.exit_code == 0 && render_output { + println!("{}", result.trim()); + } else if error_output { + eprintln!("{}", result.trim().bright_red()); + } + } + exit(result.exit_code); +} + +#[program_setup] +fn standard_output_setup(program: &mut Program<ThisProgram>) { + program.with_setup(QuietFlagSetup::new("--silence")); + program.global_flag(["--no-error"], |program| { + program.stdout_setting.error_output = false; + }); + program.global_flag(["--no-result"], |program| { + program.stdout_setting.render_output = false; + }); + program.global_flag(["--silence", "--quiet"], |program| { + program.stdout_setting.quiet = true; + }); +} diff --git a/mling/src/cargo_style.rs b/mling/src/cargo_style.rs new file mode 100644 index 0000000..7663985 --- /dev/null +++ b/mling/src/cargo_style.rs @@ -0,0 +1,161 @@ +use colored::Colorize; + +/// Formats a message in cargo-style format with a bold green prefix. +/// +/// The message should be in the format `prefix: content`. The prefix will be +/// bold green and right-padded to 12 characters. If there is no colon in the +/// string, the entire string is printed as content with an empty prefix. +/// +/// # Macros +/// +/// - `format_cargo!("prefix: {}", arg)` — format-style invocation +/// - `format_cargo!(expr)` — direct expression invocation +/// +/// # Panics +/// +/// Panics if the prefix (text before the first `:`) exceeds 12 characters. +/// +/// # Examples +/// +/// ``` +/// format_cargo!("Compiling: hello.rs"); +/// // Output: " Compiling hello.rs" (green bold "Compiling" padded to 12) +/// ``` +#[macro_export] +macro_rules! format_cargo { + ($fmt:literal, $($arg:tt)*) => { + $crate::format_cargo(format!($fmt, $($arg)*)) + }; + ($cmd:expr) => { + $crate::format_cargo($cmd) + }; +} + +/// Formats an error message in cargo-style format with a bold red "error" prefix. +/// +/// # Macros +/// +/// - `eformat_cargo!("prefix: {}", arg)` — format-style invocation +/// - `eformat_cargo!(expr)` — direct expression invocation +/// +/// # Examples +/// +/// ``` +/// eformat_cargo!("failed to parse input"); +/// // Output: "error: failed to parse input" (red bold "error") +/// ``` +#[macro_export] +macro_rules! eformat_cargo { + ($fmt:literal, $($arg:tt)*) => { + $crate::eformat_cargo(format!($fmt, $($arg)*)) + }; + ($cmd:expr) => { + $crate::eformat_cargo($cmd) + }; +} + +/// Print a message in cargo-style format with a bold green prefix. +/// +/// # Macros +/// +/// - `println_cargo!("prefix: {}", arg)` — format-style invocation +/// - `println_cargo!(expr)` — direct expression invocation +/// +/// # Examples +/// +/// ``` +/// println_cargo!("Compiling: hello.rs"); +/// ``` +#[macro_export] +macro_rules! println_cargo { + ($fmt:literal, $($arg:tt)*) => { + println!("{}", $crate::format_cargo(format!($fmt, $($arg)*))) + }; + ($cmd:expr) => { + println!("{}", $crate::format_cargo($cmd)) + }; +} + +/// Print an error message in cargo-style format with a bold red "error" prefix. +/// +/// # Macros +/// +/// - `eprintln_cargo!("prefix: {}", arg)` — format-style invocation +/// - `eprintln_cargo!(expr)` — direct expression invocation +/// +/// # Examples +/// +/// ``` +/// eprintln_cargo!("failed to parse input"); +/// ``` +#[macro_export] +macro_rules! eprintln_cargo { + ($fmt:literal, $($arg:tt)*) => { + eprintln!("{}", $crate::eformat_cargo(format!($fmt, $($arg)*))) + }; + ($cmd:expr) => { + eprintln!("{}", $crate::eformat_cargo($cmd)) + }; +} + +/// Format a message in cargo style format, with bold green prefix. +/// +/// The input string is split at the first `:`. The part before the colon becomes +/// the prefix (bold green, right-padded to 12 characters), and the part after +/// becomes the content. +/// +/// If no colon is found, the entire string is treated as content and no prefix +/// is shown. +/// +/// # Panics +/// +/// Panics if the prefix (text before the first `:`) exceeds 12 characters. +/// +/// # Examples +/// +/// ```ignore +/// format_cargo("Compiling: my_program.rs"); +/// // returns " Compiling my_program.rs" +/// ``` +pub fn format_cargo(str: impl Into<String>) -> String { + let s = str.into(); + let (prefix, content) = if let Some(pos) = s.find(':') { + ( + s[..pos].trim().to_string(), + s[pos + 1..].trim_start().to_string(), + ) + } else { + (String::new(), s.trim().to_string()) + }; + + assert!( + prefix.len() <= 12, + "prefix length exceeds 12: '{}' has length {}", + prefix, + prefix.len() + ); + + let padding = " ".repeat(12 - prefix.len()); + + format!( + "{}{} {}", + padding, + prefix.bold().bright_green(), + content.trim() + ) +} + +/// Format an error message in cargo style format, with bold red "error" prefix. +/// +/// The input string is printed as the error content, prefixed by a bold red +/// `error:` label. +/// +/// # Examples +/// +/// ```ignore +/// eformat_cargo("something went wrong"); +/// // returns "error: something went wrong" +/// ``` +pub fn eformat_cargo(str: impl Into<String>) -> String { + format!("{}: {}", "error".bold().bright_red(), str.into()) +} diff --git a/mling/src/cli.rs b/mling/src/cli.rs deleted file mode 100644 index b628021..0000000 --- a/mling/src/cli.rs +++ /dev/null @@ -1,77 +0,0 @@ -use mingling::{ - macros::{r_println, renderer}, - setup::{BasicProgramSetup, GeneralRendererSetup}, -}; - -use crate::{CMDCompletion, ErrorDispatcherNotFound, ThisProgram, display::markdown}; - -pub mod list; -pub use list::*; - -pub mod namespace_mgr; -pub use namespace_mgr::*; - -pub mod read; -pub use read::*; - -pub mod install; -pub use install::*; - -/// Entry point for the CLI application. -/// -/// # Panics -/// -/// Panics on Windows if the virtual terminal processing cannot be enabled. -pub fn cli_entry() { - let mut program = ThisProgram::new(); - - // Plugins - program.with_setup(BasicProgramSetup); - program.with_setup(GeneralRendererSetup); - program.with_dispatcher(CMDCompletion); - - if program.pick_global_flag(["-v", "--version"]) { - println!("{}", include_str!("../res/version.txt").trim_end()); - return; - } - - // Help - if program.user_context.help { - println!( - "{}", - markdown(include_str!("../res/help-mling.txt").trim_end()) - ); - return; - } - - // Context query commands - program.with_dispatcher(ListInstalledCommand); - program.with_dispatchers(( - ReadTargetDirCommand, - ReadWorkspaceRootCommand, - ReadBinariesCommand, - )); - - // Namespace manage commands - program.with_dispatchers(( - TrustNamespaceCommand, - UntrustNamespaceCommand, - SetTrustNamespaceCommand, - RemoveNamespaceCommand, - )); - - // Install binaries command - program.with_dispatcher(InstallCommand); - - // Colored Setup - #[cfg(windows)] - colored::control::set_virtual_terminal(true).unwrap(); - - let _ = program.exec(); -} - -#[renderer] -pub(crate) fn fallback_disp(prev: ErrorDispatcherNotFound) { - r_println!("Error: command \"{}\" not found!", prev.join(" ")); - r_println!("Use \"mling --help\" for more information."); -} diff --git a/mling/src/cli/install.rs b/mling/src/cli/install.rs deleted file mode 100644 index cca7483..0000000 --- a/mling/src/cli/install.rs +++ /dev/null @@ -1,32 +0,0 @@ -use mingling::{ - ShellContext, Suggest, - macros::{chain, completion, dispatcher, pack, suggest}, - parser::Picker, -}; - -use crate::{Next, project_installer::install_all}; - -dispatcher!("install", InstallCommand => InstallEntry); - -pack!(ResultInstallCompleted = ()); - -#[completion(InstallEntry)] -pub(crate) fn comp_install(ctx: &ShellContext) -> Suggest { - if ctx.typing_argument() { - return suggest! { - "--clean": "Clean build artifacts before installation", - "-c": "Clean build artifacts before installation", - }; - } - return suggest!(); -} - -#[chain] -pub(crate) fn handle_install_entry(prev: InstallEntry) -> Next { - let is_clean_before_build = Picker::new(prev.inner) - .pick::<bool>(["--clean", "-c"]) - .unpack(); - let _ = install_all(is_clean_before_build); - - ResultInstallCompleted::new(()) -} diff --git a/mling/src/cli/list.rs b/mling/src/cli/list.rs deleted file mode 100644 index a2a9434..0000000 --- a/mling/src/cli/list.rs +++ /dev/null @@ -1,123 +0,0 @@ -use colored::Colorize; -use mingling::{ - Groupped, RenderResult, ShellContext, Suggest, - macros::{chain, completion, dispatcher, pack, r_println, renderer, suggest}, - parser::Picker, -}; -use serde::Serialize; - -use crate::{Next, namespace_manager::list_namespaces}; - -dispatcher!("ls-namespace", ListInstalledCommand => ListInstalledEntry); - -#[completion(ListInstalledEntry)] -pub(crate) fn comp_list_installed(ctx: &ShellContext) -> Suggest { - if ctx.typing_argument() { - return suggest! { - "--trusted": "Show only trusted namespaces", - "--untrusted": "Show only untrusted namespaces", - }; - } - return suggest!(); -} - -#[derive(Debug, Serialize, Default, Groupped)] -pub(crate) enum StateListInstalledOptions { - #[default] - All, - OnlyTrusted, - OnlyUntrusted, -} - -pack!(MutexErrorListInstalled = ()); - -#[chain] -pub(crate) fn handle_list_installed_entry(prev: ListInstalledEntry) -> Next { - let picker = Picker::new(prev.inner); - let r = picker - .pick::<bool>("--trusted") - .pick::<bool>("--untrusted") - .unpack(); - - let option: StateListInstalledOptions = match r { - // (show_trusted, show_untrusted) - (true, false) => StateListInstalledOptions::OnlyTrusted, - (false, true) => StateListInstalledOptions::OnlyUntrusted, - (false, false) => StateListInstalledOptions::All, - (true, true) => return MutexErrorListInstalled::default().to_render(), - }; - - option.to_chain() -} - -#[renderer] -pub(crate) fn render_list_installed_mutex_error(_prev: MutexErrorListInstalled) { - r_println!("Error: cannot use both --trusted and --untrusted options at the same time") -} - -#[derive(Debug, Groupped, Serialize)] -pub(crate) struct ResultInstalledNamespaces { - trusted: Vec<String>, - untrusted: Vec<String>, - untagged: Vec<String>, - option: StateListInstalledOptions, -} - -#[chain] -pub(crate) fn handle_state_list_installed_option(prev: StateListInstalledOptions) -> Next { - ResultInstalledNamespaces { - trusted: list_namespaces(true, false, false), - untrusted: list_namespaces(false, true, false), - untagged: list_namespaces(false, false, true), - option: prev, - } -} - -#[renderer] -pub(crate) fn render_installed(prev: ResultInstalledNamespaces) { - match prev.option { - StateListInstalledOptions::All => { - print_list( - &"Trusted".bright_green().bold().to_string(), - &prev.trusted, - __renderer_inner_result, - ); - print_list( - &"Untrusted".bright_red().bold().to_string(), - &prev.untrusted, - __renderer_inner_result, - ); - print_list( - &"Untagged".bright_black().bold().to_string(), - &prev.untagged, - __renderer_inner_result, - ); - } - StateListInstalledOptions::OnlyTrusted => { - print_list( - &"Trusted".bright_green().bold().to_string(), - &prev.trusted, - __renderer_inner_result, - ); - } - StateListInstalledOptions::OnlyUntrusted => { - print_list( - &"Untrusted".bright_red().bold().to_string(), - &prev.untrusted, - __renderer_inner_result, - ); - } - } -} - -fn print_list(title: &str, list: &[String], __renderer_inner_result: &mut RenderResult) { - if list.is_empty() { - return; - } - - r_println!("{title}"); - - for (i, namespace) in (1..).zip(list.iter()) { - r_println!(" {}. {}", i.to_string(), namespace.bold()); - } -} diff --git a/mling/src/cli/namespace_mgr.rs b/mling/src/cli/namespace_mgr.rs deleted file mode 100644 index 4ea2229..0000000 --- a/mling/src/cli/namespace_mgr.rs +++ /dev/null @@ -1,128 +0,0 @@ -use mingling::{ - ShellContext, Suggest, SuggestItem, - macros::{chain, completion, dispatcher, pack, r_println, renderer, route, suggest}, - parser::{Picker, Yes}, -}; - -use crate::{ - Next, - namespace_manager::{list_namespaces, remove_namespace, set_namespace_trusted}, -}; - -dispatcher!("trust", TrustNamespaceCommand => TrustNamespaceEntry); -dispatcher!("untrust", UntrustNamespaceCommand => UntrustNamespaceEntry); - -dispatcher!("set-trust", SetTrustNamespaceCommand => SetTrustNamespaceEntry); - -dispatcher!("rm-namespace", RemoveNamespaceCommand => RemoveNamespaceEntry); - -pack!(ErrorNamespaceNotProvided = ()); -pack!(ResultNamespaceTrustChanged = ()); -pack!(ResultNamespaceRemoved = ()); - -#[completion(TrustNamespaceEntry)] -pub(crate) fn comp_trust(ctx: &ShellContext) -> Suggest { - if ctx.previous_word == "trust" { - return Suggest::Suggest( - list_namespaces(false, true, true) - .into_iter() - .map(SuggestItem::new) - .collect::<std::collections::BTreeSet<_>>(), - ); - } - return suggest!(); -} - -#[completion(UntrustNamespaceEntry)] -pub(crate) fn comp_untrust(ctx: &ShellContext) -> Suggest { - if ctx.previous_word == "untrust" { - return Suggest::Suggest( - list_namespaces(true, false, true) - .into_iter() - .map(SuggestItem::new) - .collect::<std::collections::BTreeSet<_>>(), - ); - } - return suggest!(); -} - -#[completion(SetTrustNamespaceEntry)] -pub(crate) fn comp_set_trust(ctx: &ShellContext) -> Suggest { - if ctx.typing_argument() { - return suggest!( - "-t": "Whether to trust this namespace", - "--trusted": "Whether to trust this namespace", - ); - } - if ctx.filling_argument_first(["-t", "--trusted"]) { - return suggest!("yes", "no"); - } - if ctx.previous_word == "set-trust" { - return Suggest::Suggest( - list_namespaces(true, true, true) - .into_iter() - .map(SuggestItem::new) - .collect::<std::collections::BTreeSet<_>>(), - ); - } - return suggest!(); -} - -#[completion(RemoveNamespaceEntry)] -pub(crate) fn comp_remove_namespace(ctx: &ShellContext) -> Suggest { - if ctx.previous_word == "rm-namespace" { - return Suggest::Suggest( - list_namespaces(true, true, true) - .into_iter() - .map(SuggestItem::new) - .collect::<std::collections::BTreeSet<_>>(), - ); - } - return suggest!(); -} - -#[chain] -pub(crate) fn handle_set_trust(p: SetTrustNamespaceEntry) -> Next { - let (trusted, namespace) = route!( - Picker::new(p.inner) - .pick::<Yes>(["-t", "--trusted"]) - .pick_or_route((), ErrorNamespaceNotProvided::default().to_render()) - .unpack() - ); - set_namespace_trusted(namespace, trusted.is_yes()); - ResultNamespaceTrustChanged::default().to_render() -} - -#[chain] -pub(crate) fn handle_trust(p: TrustNamespaceEntry) -> Next { - SetTrustNamespaceEntry::new({ - let mut args = p.inner.clone(); - args.extend(vec!["-t".to_string(), "yes".to_string()]); - args - }) -} - -#[chain] -pub(crate) fn handle_untrust(p: UntrustNamespaceEntry) -> Next { - SetTrustNamespaceEntry::new({ - let mut args = p.inner.clone(); - args.extend(vec!["-t".to_string(), "no".to_string()]); - args - }) -} - -#[chain] -pub(crate) fn handle_remove_namespace(p: RemoveNamespaceEntry) -> Next { - let namespace = route!( - Picker::new(p.inner) - .pick_or_route((), ErrorNamespaceNotProvided::default().to_render()) - .unpack() - ); - remove_namespace(namespace); - ResultNamespaceRemoved::default().to_render() -} - -#[renderer] -pub(crate) fn render_error_namespace_not_provided(_prev: ErrorNamespaceNotProvided) { - r_println!("Error: no namespace was provided!") -} diff --git a/mling/src/cli/read.rs b/mling/src/cli/read.rs deleted file mode 100644 index e51e78f..0000000 --- a/mling/src/cli/read.rs +++ /dev/null @@ -1,78 +0,0 @@ -use colored::Colorize; -use std::path::PathBuf; - -use mingling::{ - Groupped, - macros::{chain, dispatcher, pack, r_println, renderer}, -}; -use serde::Serialize; - -use crate::{ - Next, - project_solver::{BinaryItem, solve_current_dir}, -}; - -dispatcher!("show-target-dir", ReadTargetDirCommand => ReadTargetDirEntry); -dispatcher!("show-workspace-root", ReadWorkspaceRootCommand => ReadWorkspaceRootEntry); -dispatcher!("show-binaries", ReadBinariesCommand => ReadBinariesEntry); - -pack!(ResultDir = PathBuf); -pack!(ResultTargetDirNotFound = ()); - -#[derive(Debug, Serialize, Default, Groupped)] -pub(crate) struct ResultBinaries { - bin: Vec<BinaryItem>, -} - -#[chain] -#[allow(unused_variables)] -pub(crate) fn handle_target_dir_entry(entry: ReadTargetDirEntry) -> Next { - match solve_current_dir() { - Ok(solved) => { - let dir = solved.target_dir; - ResultDir::new(dir).to_render() - } - Err(_) => ResultTargetDirNotFound::new(()).to_render(), - } -} - -#[chain] -#[allow(unused_variables)] -pub(crate) fn handle_workspace_root_entry(entry: ReadWorkspaceRootEntry) -> Next { - match solve_current_dir() { - Ok(solved) => { - let dir = solved.workspace_root; - ResultDir::new(dir).to_render() - } - Err(_) => ResultTargetDirNotFound::new(()).to_render(), - } -} - -#[chain] -#[allow(unused_variables)] -pub(crate) fn handle_binaries_entry(entry: ReadBinariesEntry) -> Next { - match solve_current_dir() { - Ok(solved) => { - let binaries = solved.binaries; - ResultBinaries { bin: binaries }.to_render() - } - Err(_) => ResultTargetDirNotFound::new(()).to_render(), - } -} - -#[renderer] -pub(crate) fn render_dir(prev: ResultDir) { - r_println!("{}", prev.inner.display()) -} - -#[renderer] -pub(crate) fn render_binaries(prev: ResultBinaries) { - for (i, item) in (1..).zip(prev.bin.iter()) { - r_println!( - "{}. {} ({})", - i.to_string(), - item.name.bold(), - item.path.to_string_lossy().underline().bright_cyan() - ); - } -} diff --git a/mling/src/display.rs b/mling/src/display.rs index 3816d89..5635cae 100644 --- a/mling/src/display.rs +++ b/mling/src/display.rs @@ -356,24 +356,26 @@ fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { let text = text.as_ref(); let color_name = color_name.as_ref(); match color_name { - // Normal colors and their bright short aliases - "black" | "b_black" => text.black().to_string(), - "red" | "b_red" => text.red().to_string(), - "green" | "b_green" => text.green().to_string(), - "yellow" | "b_yellow" => text.yellow().to_string(), - "blue" | "b_blue" => text.blue().to_string(), - "magenta" | "b_magenta" => text.magenta().to_string(), - "cyan" | "b_cyan" => text.cyan().to_string(), + // Normal colors + "black" => text.black().to_string(), + "red" => text.red().to_string(), + "green" => text.green().to_string(), + "yellow" => text.yellow().to_string(), + "blue" => text.blue().to_string(), + "magenta" => text.magenta().to_string(), + "cyan" => text.cyan().to_string(), "white" | "b_white" | "bright_gray" | "bright_grey" | "b_gray" | "b_grey" => { text.white().to_string() } - "bright_black" | "gray" | "grey" => text.bright_black().to_string(), - "bright_red" => text.bright_red().to_string(), - "bright_green" => text.bright_green().to_string(), - "bright_yellow" => text.bright_yellow().to_string(), - "bright_blue" => text.bright_blue().to_string(), - "bright_magenta" => text.bright_magenta().to_string(), - "bright_cyan" => text.bright_cyan().to_string(), + + // Bright colors and their b_ short aliases + "bright_black" | "b_black" | "gray" | "grey" => text.bright_black().to_string(), + "bright_red" | "b_red" => text.bright_red().to_string(), + "bright_green" | "b_green" => text.bright_green().to_string(), + "bright_yellow" | "b_yellow" => text.bright_yellow().to_string(), + "bright_blue" | "b_blue" => text.bright_blue().to_string(), + "bright_magenta" | "b_magenta" => text.bright_magenta().to_string(), + "bright_cyan" | "b_cyan" => text.bright_cyan().to_string(), "bright_white" => text.bright_white().to_string(), // Default to white if color not recognized diff --git a/mling/src/helps/mling_help.txt b/mling/src/helps/mling_help.txt new file mode 100644 index 0000000..64a2f67 --- /dev/null +++ b/mling/src/helps/mling_help.txt @@ -0,0 +1,14 @@ +Mingling's scaffolding tool + +[[b_green]]**Usage:**[[/]] [[b_cyan]]**cargo mling**[[/]] [[cyan]][COMMAND] [OPTIONS]...[[/]] + +[[b_green]]**Options:**[[/]] +__ [[b_cyan]]**-V**[[/]], [[b_cyan]]**--version**[[/]] Print version info and exit +__ [[b_cyan]]**-h**[[/]], [[b_cyan]]**--help**[[/]] Print this help message + +__ [[b_cyan]]**--silence**[[/]], [[b_cyan]]**--quiet**[[/]] Suppress all output +__ [[b_cyan]]**--no-error**[[/]] Suppress error output +__ [[b_cyan]]**--no-result**[[/]] Suppress result output + +[[b_green]]**Commands:**[[/]] +__ [[b_cyan]]**install**[[/]] Install diff --git a/mling/src/lib.rs b/mling/src/lib.rs new file mode 100644 index 0000000..2ea6305 --- /dev/null +++ b/mling/src/lib.rs @@ -0,0 +1,12 @@ +#![allow(unused_imports)] + +use mingling::macros::gen_program; + +pub mod cargo_style; +pub mod display; +pub mod res; + +mod proj_mgr; +use proj_mgr::*; + +gen_program!(); diff --git a/mling/src/main.rs b/mling/src/main.rs deleted file mode 100644 index 799ad4b..0000000 --- a/mling/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use mingling::macros::gen_program; - -pub mod cli; -pub mod display; -pub mod namespace_manager; -pub mod project_installer; -pub mod project_solver; - -use crate::cli::*; - -fn main() { - cli_entry(); -} - -gen_program!(); diff --git a/mling/src/namespace_manager.rs b/mling/src/namespace_manager.rs deleted file mode 100644 index d5176dd..0000000 --- a/mling/src/namespace_manager.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::path::PathBuf; - -use just_fmt::kebab_case; - -#[must_use] -pub fn list_namespaces( - show_trusted: bool, - show_untrusted: bool, - show_untagged: bool, -) -> Vec<String> { - let wdir = working_dir(); - if !wdir.exists() { - return Vec::new(); - } - - let mut namespaces = Vec::new(); - let Ok(entries) = std::fs::read_dir(&wdir) else { - return Vec::new(); - }; - for entry in entries { - let Ok(entry) = entry else { - continue; - }; - let path = entry.path(); - if path.is_dir() - && let Some(name) = path.file_name() - && let Some(name_str) = name.to_str() - { - // Skip directories starting with a dot - if name_str.starts_with('.') { - continue; - } - let namespace = name_str.to_string(); - let is_trusted = is_trusted_namespace(namespace.clone()); - let is_untrusted = is_untrusted_namespace(namespace.clone()); - let is_untagged = is_untagged_namespace(namespace.clone()); - - if (show_trusted && is_trusted) - || (show_untrusted && is_untrusted) - || (show_untagged && is_untagged) - { - namespaces.push(namespace); - } - } - } - - namespaces -} - -pub fn set_namespace_trusted(namespace: String, trusted: bool) { - let ndir = namespace_dir(namespace); - let trusted_file = ndir.join("TRUSTED"); - let untrusted_file = ndir.join("UNTRUSTED"); - - if trusted { - // Create TRUSTED file and remove UNTRUSTED if it exists - let _ = std::fs::write(&trusted_file, ""); - let _ = std::fs::remove_file(&untrusted_file); - } else { - // Create UNTRUSTED file and remove TRUSTED if it exists - let _ = std::fs::write(&untrusted_file, ""); - let _ = std::fs::remove_file(&trusted_file); - } -} - -pub fn remove_namespace(namespace: String) { - let ndir = namespace_dir(namespace); - if ndir.exists() { - let _ = std::fs::remove_dir_all(&ndir); - } -} - -/// Returns the mingling data directory. -/// -/// # Panics -/// -/// Panics if the platform's data directory cannot be determined. -#[must_use] -pub fn working_dir() -> PathBuf { - dirs::data_dir().unwrap().join("mingling") -} - -#[must_use] -pub fn namespace_dir(namespace: String) -> PathBuf { - working_dir().join(kebab_case!(namespace)) -} - -#[must_use] -pub fn is_untrusted_namespace(namespace: String) -> bool { - let untrusted_file = namespace_dir(namespace).join("UNTRUSTED"); - untrusted_file.exists() -} - -#[must_use] -pub fn is_trusted_namespace(namespace: String) -> bool { - let trusted = namespace_dir(namespace).join("TRUSTED"); - trusted.exists() -} - -#[must_use] -pub fn is_untagged_namespace(namespace: String) -> bool { - let ndir = namespace_dir(namespace); - let trusted = ndir.join("TRUSTED"); - let untrusted = ndir.join("UNTRUSTED"); - !trusted.exists() && !untrusted.exists() -} - -#[must_use] -pub fn bin_dir(namespace: String) -> PathBuf { - namespace_dir(namespace).join("bin") -} - -#[must_use] -pub fn comp_dir(namespace: String) -> PathBuf { - namespace_dir(namespace).join("comp") -} - -#[must_use] -pub fn exe_path(namespace: String, bin_name_without_ext: String) -> PathBuf { - if cfg!(target_os = "windows") { - bin_dir(namespace).join(bin_name_without_ext + ".exe") - } else { - bin_dir(namespace).join(bin_name_without_ext) - } -} diff --git a/mling/src/proj_mgr/mod.rs b/mling/src/proj_mgr/mod.rs new file mode 100644 index 0000000..86c05e3 --- /dev/null +++ b/mling/src/proj_mgr/mod.rs @@ -0,0 +1,3 @@ +use mingling::macros::dispatcher; + +dispatcher!("install"); diff --git a/mling/src/project_installer.rs b/mling/src/project_installer.rs deleted file mode 100644 index 983307f..0000000 --- a/mling/src/project_installer.rs +++ /dev/null @@ -1,162 +0,0 @@ -use mingling::{ShellFlag, build::build_comp_script_to}; - -use crate::{ - namespace_manager::{bin_dir, comp_dir, exe_path, working_dir}, - project_solver::solve, -}; - -const SCRIPT_LOAD_BASH: &str = include_str!("../tmpl/load.sh"); -const SCRIPT_LOAD_FISH: &str = include_str!("../tmpl/load.fish"); -const SCRIPT_LOAD_PWSH: &str = include_str!("../tmpl/load.ps1"); - -#[derive(serde::Deserialize)] -struct CargoToml { - package: Package, -} - -#[derive(serde::Deserialize)] -struct Package { - name: String, -} - -/// Installs all projects and shell scripts. -/// -/// # Errors -/// -/// Returns an `io::Error` if the current directory cannot be determined, if the project -/// installation fails, or if the shell scripts cannot be installed. -pub fn install_all(clean_before_build: bool) -> Result<(), std::io::Error> { - let current = std::env::current_dir()?; - install_this_project(¤t, clean_before_build)?; - install_shell_scripts()?; - Ok(()) -} - -/// Installs a project from the given path. -/// -/// # Errors -/// -/// Returns an `io::Error` if the project installation fails, e.g., if `cargo build` -/// fails, the Cargo.toml cannot be parsed, or file operations (copy, create dir) fail. -pub fn install_this_project( - current: &std::path::PathBuf, - clean_before_build: bool, -) -> Result<(), std::io::Error> { - // Obtain context data - let solved = solve(current)?; - - let workspace_root = &solved.workspace_root; - - // If clean_before_build, execute cargo clean in workspace_root first - if clean_before_build { - let status = std::process::Command::new("cargo") - .arg("clean") - .current_dir(workspace_root) - .status()?; - if !status.success() { - return Err(std::io::Error::other("exec `cargo clean` failed")); - } - } - - // Execute cargo build --release in workspace_root - let status = std::process::Command::new("cargo") - .args(["build", "--release"]) - .current_dir(workspace_root) - .status()?; - if !status.success() { - return Err(std::io::Error::other("cargo build --release failed")); - } - - // Parse package.name from workspace_root's Cargo.toml as namespace - let cargo_toml_content = std::fs::read_to_string(workspace_root.join("Cargo.toml"))?; - let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content).map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("failed to parse Cargo.toml: {e}"), - ) - })?; - let namespace = cargo_toml.package.name; - - // Ensure destination directories exist - std::fs::create_dir_all(bin_dir(namespace.clone()))?; - std::fs::create_dir_all(comp_dir(namespace.clone()))?; - - // Copy binaries to corresponding exe_path - for bin in &solved.binaries { - let dst = exe_path(namespace.clone(), bin.name.clone()); - std::fs::copy(&bin.path, &dst)?; - } - - // Copy all completion scripts containing _comp from target/release to comp_dir - let target_dir = &solved.target_dir; - let release_dir = target_dir.join("release"); - if release_dir.exists() { - for entry in std::fs::read_dir(&release_dir)? { - let entry = entry?; - let file_name = entry.file_name(); - let file_name_str = file_name.to_string_lossy(); - if file_name_str.contains("_comp") { - let dest = comp_dir(namespace.clone()).join(file_name.as_os_str()); - std::fs::copy(entry.path(), &dest)?; - } - } - } - - Ok(()) -} - -/// Installs shell completion scripts for the `mling` command. -/// -/// # Errors -/// -/// Returns an `io::Error` if the shell scripts cannot be built or installed. -pub fn install_shell_scripts() -> Result<(), std::io::Error> { - // Get the working directory (mingling data dir) - let wdir = working_dir(); - std::fs::create_dir_all(&wdir)?; - - // Build shell completion scripts for the "mling" command based on the current OS - let mling_comp = if cfg!(target_os = "windows") { - vec![ShellFlag::Powershell] - } else if cfg!(target_os = "macos") || cfg!(target_os = "linux") { - vec![ShellFlag::Bash, ShellFlag::Zsh, ShellFlag::Fish] - } else { - vec![ShellFlag::Bash] - }; - - for flag in mling_comp { - build_comp_script_to( - &flag, - "mling", - wdir.join(".comp").display().to_string().as_str(), - )?; - } - - // Determine which scripts to write based on platform - let scripts: Vec<(&str, &str)> = if cfg!(target_os = "windows") { - vec![("load.ps1", SCRIPT_LOAD_PWSH)] - } else if cfg!(target_os = "macos") || cfg!(target_os = "linux") { - vec![ - ("load.sh", SCRIPT_LOAD_BASH), - ("load.fish", SCRIPT_LOAD_FISH), - ] - } else { - // Fallback: write bash script - vec![("load.sh", SCRIPT_LOAD_BASH)] - }; - - for (filename, content) in scripts { - let dest = wdir.join(filename); - std::fs::write(&dest, content)?; - if cfg!(target_os = "linux") { - let status = std::process::Command::new("chmod") - .args(["+x", &dest.to_string_lossy()]) - .status()?; - if !status.success() { - eprintln!("Failed to chmod {filename}"); - } - } - } - - Ok(()) -} diff --git a/mling/src/project_solver.rs b/mling/src/project_solver.rs deleted file mode 100644 index 3aec2b4..0000000 --- a/mling/src/project_solver.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::path::PathBuf; - -use serde::Serialize; - -pub type BinaryName = String; -pub type BinaryTargetPath = PathBuf; - -pub struct ProjectSolveResult { - pub target_dir: PathBuf, - pub workspace_root: PathBuf, - pub binaries: Vec<BinaryItem>, -} - -#[derive(Debug, Serialize)] -pub struct BinaryItem { - pub name: String, - pub path: PathBuf, -} - -/// Solves the current directory for project metadata. -/// -/// # Errors -/// -/// Returns an `io::Error` if the current directory cannot be determined -/// or if `cargo metadata` fails. -pub fn solve_current_dir() -> Result<ProjectSolveResult, std::io::Error> { - let current = std::env::current_dir()?; - solve(¤t) -} - -/// Solves the given directory path for project metadata. -/// -/// # Errors -/// -/// Returns an `io::Error` if `cargo metadata` fails for the given path. -pub fn solve(current: &PathBuf) -> Result<ProjectSolveResult, std::io::Error> { - let (target_dir, workspace_root, binaries) = solve_inner(current)?; - Ok(ProjectSolveResult { - target_dir, - workspace_root, - binaries, - }) -} - -fn solve_inner(current: &PathBuf) -> Result<(PathBuf, PathBuf, Vec<BinaryItem>), std::io::Error> { - let output = std::process::Command::new("cargo") - .arg("metadata") - .arg("--format-version") - .arg("1") - .current_dir(current) - .output()?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(std::io::Error::other(format!( - "cargo metadata failed: {stderr}" - ))); - } - let metadata: serde_json::Value = serde_json::from_slice(&output.stdout) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - - let workspace_root_str = metadata["workspace_root"].as_str().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "missing workspace_root") - })?; - let workspace_root = PathBuf::from(workspace_root_str); - - let target_dir_str = metadata["target_directory"].as_str().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "missing target_directory") - })?; - let target_dir = PathBuf::from(target_dir_str); - - let packages = metadata["packages"].as_array().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "missing packages array") - })?; - - let mut binaries = Vec::new(); - let cargo_toml_path = workspace_root.join("Cargo.toml"); - - // Find the package whose manifest_path matches workspace_root/Cargo.toml - for pkg in packages { - let manifest_path = pkg["manifest_path"].as_str().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "missing manifest_path") - })?; - let manifest_path_buf = PathBuf::from(manifest_path); - if manifest_path_buf == cargo_toml_path { - // Found the workspace root package - if let Some(targets) = pkg["targets"].as_array() { - for target in targets { - let kind = target["kind"].as_array(); - let is_bin = kind.is_some_and(|k| k.iter().any(|v| v.as_str() == Some("bin"))); - if is_bin { - let name = target["name"].as_str().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "missing target name", - ) - })?; - let mut binary_path = target_dir.join("release").join(name); - if cfg!(target_os = "windows") { - binary_path.set_extension("exe"); - } - binaries.push(BinaryItem { - name: name.to_string(), - path: binary_path, - }); - } - } - } - break; - } - } - - Ok((target_dir, workspace_root, binaries)) -} diff --git a/mling/src/res/current_dir.rs b/mling/src/res/current_dir.rs new file mode 100644 index 0000000..b928596 --- /dev/null +++ b/mling/src/res/current_dir.rs @@ -0,0 +1,6 @@ +use std::path::PathBuf; + +#[derive(Default, Clone)] +pub struct ResCurrentDir { + pub path: PathBuf, +} diff --git a/mling/src/res/mod.rs b/mling/src/res/mod.rs new file mode 100644 index 0000000..b6ea60d --- /dev/null +++ b/mling/src/res/mod.rs @@ -0,0 +1,2 @@ +mod current_dir; +pub use current_dir::*; diff --git a/mling/tmpl/load.fish b/mling/tmpl/load.fish deleted file mode 100644 index 19e1ef7..0000000 --- a/mling/tmpl/load.fish +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env fish - -# Save original directory -set -l _load_original_dir $PWD - -# Switch to script directory -set -l _load_dir (dirname (status filename)) -cd $_load_dir - -# Load mling.fish from path -source .comp/mling_comp.fish - -# Add all namespace bin directories to PATH -for _dir in */bin/ - if test -d $_dir - set -gx PATH $PWD/$_dir $PATH - end -end - -function _load_comp_script - if string match -q '*.fish' -- $argv[1] - source $argv[1] 2>/dev/null - end -end - -# Iterate through all namespaces -for _namespace in */ - set _namespace (string trim -r -c / $_namespace) - - # Skip if UNTRUSTED marked or no comp directory - test -f $_namespace/UNTRUSTED && continue - test -d $_namespace/comp || continue - - # Find all loadable scripts in comp - set _scripts (find $_namespace/comp -maxdepth 1 -type f \( -name '*.sh' -o -name '*.zsh' -o -name '*.fish' \) 2>/dev/null) - test -z "$_scripts" && continue - - # Count scripts - set _count (count $_scripts) - - # If TRUSTED marked, load directly - if test -f $_namespace/TRUSTED - for _script in $_scripts - _load_comp_script $_script - end - continue - end - - # Ask user - read -l -p 'printf "%s has %d completion script(s) to load, do you trust it? [Y/n] " $_namespace $_count' _answer - switch $_answer - case '' Y y - for _script in $_scripts - chmod +x $_script - end - touch $_namespace/TRUSTED - - # Ask whether to load immediately - read -l -p 'printf "Load it immediately? [Y/n] "' _load_answer - switch $_load_answer - case '' Y y - for _script in $_scripts - _load_comp_script $_script - end - end - case '*' - touch $_namespace/UNTRUSTED - end -end - -# Restore original directory -cd $_load_original_dir - -# Clean up -functions -e _load_comp_script diff --git a/mling/tmpl/load.ps1 b/mling/tmpl/load.ps1 deleted file mode 100644 index fb616f4..0000000 --- a/mling/tmpl/load.ps1 +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env pwsh - -# Save original directory, restore after execution -$_load_original_dir = Get-Location - -# Resolve script directory (works with dot-source: . ./load.ps1) -$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path -if (-not $scriptPath) { - Write-Error "load.ps1: failed to resolve script directory" - return -} - -# Load completion script mling_comp.ps1 from the .comp subdirectory -$compScript = Join-Path -Path $scriptPath -ChildPath ".comp" | Join-Path -ChildPath "mling_comp.ps1" -if (Test-Path $compScript) { - . $compScript -} - -# Change to script directory -try { - Set-Location $scriptPath -ErrorAction Stop -} catch { - Write-Error "load.ps1: failed to cd to script directory" - return -} - -# Add bin directories from all namespaces to PATH -$allDirs = Get-ChildItem -Directory -Get-ChildItem -Directory | Where-Object { Test-Path (Join-Path -Path $_.FullName -ChildPath "bin") } | ForEach-Object { - $binPath = Join-Path -Path $_.FullName -ChildPath "bin" - $env:PATH = "$binPath;$env:PATH" -} - -# Helper function: execute script with appropriate shell -function _load_script { - param([string]$script) - if ($script -like "*.ps1") { - & $script 2>$null - } -} - -# Iterate over all namespaces (top-level directories except .comp) -$nsDirs = Get-ChildItem -Directory -Exclude ".comp" -foreach ($_dir in $nsDirs) { - $ns = $_dir.Name - - # Skip if UNTRUSTED marker exists - $untrustedMarker = Join-Path -Path $ns -ChildPath "UNTRUSTED" - if (Test-Path $untrustedMarker) { continue } - - $compDir = Join-Path -Path $ns -ChildPath "comp" - if (-not (Test-Path $compDir -PathType Container)) { continue } - - # Find all loadable scripts under comp - $scripts = Get-ChildItem -Path $compDir -Filter "*.ps1" -File -ErrorAction SilentlyContinue - if (-not $scripts) { continue } - - $count = ($scripts | Measure-Object).Count - - # If TRUSTED marker exists, load directly - $trustedMarker = Join-Path -Path $ns -ChildPath "TRUSTED" - if (Test-Path $trustedMarker) { - foreach ($_script in $scripts) { _load_script $_script.FullName } - continue - } - - # No marker, ask user - $answer = Read-Host "'$ns' has $count completion script(s) to load, do you trust it? [Y/n] " - if ($answer -eq "" -or $answer -match "^(y|yes)$") { - # Mark as TRUSTED - New-Item -ItemType File -Path $trustedMarker -Force | Out-Null - - # Ask whether to load immediately - $load_answer = Read-Host "Load it immediately? [Y/n] " - if ($load_answer -eq "" -or $load_answer -match "^(y|yes)$") { - foreach ($_script in $scripts) { _load_script $_script.FullName } - } - } else { - New-Item -ItemType File -Path $untrustedMarker -Force | Out-Null - } -} - -# Restore original working directory -try { - Set-Location $_load_original_dir -ErrorAction Stop -} catch {} - -# Cleanup -Remove-Variable -Name _load_original_dir -ErrorAction SilentlyContinue -Remove-Item Function:_load_script -ErrorAction SilentlyContinue diff --git a/mling/tmpl/load.sh b/mling/tmpl/load.sh deleted file mode 100644 index 0d48e95..0000000 --- a/mling/tmpl/load.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash - -# Save original directory, restore after execution -_load_original_dir="$PWD" - -cd "$(dirname "$0")" 2>/dev/null || { - echo "load.sh: failed to cd to script directory" >&2 - return 1 -} - -# If in zsh, source mling.zsh, otherwise source mling.sh -if [ -n "$ZSH_VERSION" ]; then - [ -f "./.comp/mling_comp.zsh" ] && source "./.comp/mling_comp.zsh" -else - [ -f "./.comp/mling_comp.sh" ] && source "./.comp/mling_comp.sh" -fi - -# Add bin directories from all namespaces to PATH -for _dir in */bin/; do - [ -d "$_dir" ] && export PATH="$PWD/${_dir%/}:$PATH" -done - -# Helper function: execute script with appropriate shell -_load_script() { - local script="$1" - if [ -n "$ZSH_VERSION" ]; then - case "$script" in - *.zsh|*.sh) - source "$script" 2>/dev/null - ;; - esac - else - case "$script" in - *.sh) - bash "$script" 2>/dev/null - ;; - esac - fi -} - -# Iterate over all namespaces -for _namespace in */; do - _namespace="${_namespace%/}" - [ "$_namespace" = "*" ] && continue - - # Skip if UNTRUSTED marker exists - [ -f "$_namespace/UNTRUSTED" ] && continue - - _comp_dir="$_namespace/comp" - [ ! -d "$_comp_dir" ] && continue - - # Find all loadable scripts under comp - _scripts=$(find "$_comp_dir" -maxdepth 1 -type f \( -name '*.sh' -o -name '*.zsh' -o -name '*.fish' \) 2>/dev/null) - [ -z "$_scripts" ] && continue - - # Count scripts - _count=$(echo "$_scripts" | wc -l) - - # If TRUSTED marker exists, load directly - if [ -f "$_namespace/TRUSTED" ]; then - echo "$_scripts" | while IFS= read -r _script; do - _load_script "$_script" - done - continue - fi - - # No marker, ask user - printf "'%s' has %d completion script(s) to load, do you trust it? [Y/n] " "$_namespace" "$_count" - read _answer - case "$_answer" in - [Yy]*|"") - # Mark as TRUSTED and set executable permissions - echo "$_scripts" | while IFS= read -r _script; do - chmod +x "$_script" - done - touch "$_namespace/TRUSTED" - - # Ask whether to load immediately - printf "Load it immediately? [Y/n] " - read _load_answer - case "$_load_answer" in - [Yy]*|"") - echo "$_scripts" | while IFS= read -r _script; do - _load_script "$_script" - done - ;; - esac - ;; - *) - touch "$_namespace/UNTRUSTED" - ;; - esac -done - -# Restore original working directory -cd "$_load_original_dir" 2>/dev/null || true - -# Cleanup -unset -f _load_script -unset _load_original_dir _dir _namespace _comp_dir _scripts _count _answer _load_answer _script |
