diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-28 16:18:12 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-28 16:18:12 +0800 |
| commit | 881e7399b2417c32fa996d94c6b389c1e06d8eb1 (patch) | |
| tree | fd88cb181e9c5a0bae8677c43dff859f4cd82d80 /mling/src/cli | |
| parent | dbc811d84fd809ea606a8bbed84b3e78e8cda334 (diff) | |
Add scaffolding CLI tool `mling`
Diffstat (limited to 'mling/src/cli')
| -rw-r--r-- | mling/src/cli/list.rs | 117 | ||||
| -rw-r--r-- | mling/src/cli/namespace_mgr.rs | 128 | ||||
| -rw-r--r-- | mling/src/cli/read.rs | 77 | ||||
| -rw-r--r-- | mling/src/cli/refresh.rs | 32 |
4 files changed, 354 insertions, 0 deletions
diff --git a/mling/src/cli/list.rs b/mling/src/cli/list.rs new file mode 100644 index 0000000..9aff22b --- /dev/null +++ b/mling/src/cli/list.rs @@ -0,0 +1,117 @@ +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::{ThisProgram, 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) -> NextProcess { + 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) -> NextProcess { + 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, r); + print_list( + "Unrusted".bright_red().bold().to_string(), + prev.untrusted, + r, + ); + print_list( + "Untagged".bright_black().bold().to_string(), + prev.untagged, + r, + ); + } + StateListInstalledOptions::OnlyTrusted => { + print_list("Trusted".bright_green().bold().to_string(), prev.trusted, r); + } + StateListInstalledOptions::OnlyUntrusted => { + print_list( + "Unrusted".bright_red().bold().to_string(), + prev.untrusted, + r, + ); + } + } +} + +fn print_list(title: String, list: Vec<String>, r: &mut RenderResult) { + if list.is_empty() { + return; + } + + r_println!("{}", title); + + let mut i = 1; + for namespace in list.iter() { + r_println!(" {}. {}\n", i.to_string(), namespace.bold()); + i += 1; + } +} diff --git a/mling/src/cli/namespace_mgr.rs b/mling/src/cli/namespace_mgr.rs new file mode 100644 index 0000000..9781040 --- /dev/null +++ b/mling/src/cli/namespace_mgr.rs @@ -0,0 +1,128 @@ +use mingling::{ + ShellContext, Suggest, SuggestItem, + macros::{chain, completion, dispatcher, pack, r_println, renderer, route, suggest}, + parser::{Picker, Yes}, +}; + +use crate::{ + ThisProgram, + 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(|i| SuggestItem::new(i)) + .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(|i| SuggestItem::new(i)) + .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(|i| SuggestItem::new(i)) + .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(|i| SuggestItem::new(i)) + .collect::<std::collections::BTreeSet<_>>(), + ); + } + return suggest!(); +} + +#[chain] +pub(crate) fn handle_set_trust(p: SetTrustNamespaceEntry) -> NextProcess { + 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) -> NextProcess { + 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) -> NextProcess { + 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) -> NextProcess { + 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 new file mode 100644 index 0000000..8717932 --- /dev/null +++ b/mling/src/cli/read.rs @@ -0,0 +1,77 @@ +use colored::Colorize; +use std::path::PathBuf; + +use mingling::{ + Groupped, + macros::{chain, dispatcher, pack, r_println, renderer}, +}; +use serde::Serialize; + +use crate::{ + ThisProgram, + 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] +pub(crate) fn handle_target_dir_entry(_prev: ReadTargetDirEntry) -> NextProcess { + match solve_current_dir() { + Ok(solved) => { + let dir = solved.target_dir; + ResultDir::new(dir).to_render() + } + Err(_) => ResultTargetDirNotFound::new(()).to_render(), + } +} + +#[chain] +pub(crate) fn handle_workspace_root_entry(_prev: ReadWorkspaceRootEntry) -> NextProcess { + match solve_current_dir() { + Ok(solved) => { + let dir = solved.workspace_root; + ResultDir::new(dir).to_render() + } + Err(_) => ResultTargetDirNotFound::new(()).to_render(), + } +} + +#[chain] +pub(crate) fn handle_binaries_entry(_prev: ReadBinariesEntry) -> NextProcess { + 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) { + let mut i = 1; + for item in prev.bin.iter() { + r_println!( + "{}. {} ({})", + i.to_string(), + item.name.bold(), + item.path.to_string_lossy().underline().bright_cyan() + ); + i += 1; + } +} diff --git a/mling/src/cli/refresh.rs b/mling/src/cli/refresh.rs new file mode 100644 index 0000000..368670e --- /dev/null +++ b/mling/src/cli/refresh.rs @@ -0,0 +1,32 @@ +use mingling::{ + ShellContext, Suggest, + macros::{chain, completion, dispatcher, pack, suggest}, + parser::Picker, +}; + +use crate::{ThisProgram, project_installer::install_all}; + +dispatcher!("refresh", RefreshCommand => RefreshEntry); + +pack!(ResultRefreshCompleted = ()); + +#[completion(RefreshEntry)] +pub(crate) fn comp_refresh(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_refresh_entry(prev: RefreshEntry) -> NextProcess { + let is_clean_before_build = Picker::new(prev.inner) + .pick::<bool>(["--clean", "-c"]) + .unpack(); + let _ = install_all(is_clean_before_build); + + ResultRefreshCompleted::new(()) +} |
