aboutsummaryrefslogtreecommitdiff
path: root/mling/src/cli
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-28 16:18:12 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-28 16:18:12 +0800
commit881e7399b2417c32fa996d94c6b389c1e06d8eb1 (patch)
treefd88cb181e9c5a0bae8677c43dff859f4cd82d80 /mling/src/cli
parentdbc811d84fd809ea606a8bbed84b3e78e8cda334 (diff)
Add scaffolding CLI tool `mling`
Diffstat (limited to 'mling/src/cli')
-rw-r--r--mling/src/cli/list.rs117
-rw-r--r--mling/src/cli/namespace_mgr.rs128
-rw-r--r--mling/src/cli/read.rs77
-rw-r--r--mling/src/cli/refresh.rs32
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(())
+}