aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-21 06:40:46 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-21 06:40:46 +0800
commit04ce3a18efe971c5b6893977f0ebe1ce1d9a6947 (patch)
tree0f70ebbfae3e89404603721a1505dbed0cd6371d /mingling_cli/src
parent8a230bd93b4801cc9001d0611ade16261e4f0cc4 (diff)
feat(mingling_cli): add metadata and linter commands with cargo analysis
Introduce `metadata` and `mlint` subcommands, wire up `cargo_metadata` for manifest analysis, and enable procedural macro support in editors
Diffstat (limited to 'mingling_cli/src')
-rw-r--r--mingling_cli/src/linter.rs15
-rw-r--r--mingling_cli/src/linter/cmd_mlint.rs6
-rw-r--r--mingling_cli/src/main.rs27
-rw-r--r--mingling_cli/src/metadata.rs12
-rw-r--r--mingling_cli/src/metadata/cmd_metadata.rs16
-rw-r--r--mingling_cli/src/metadata/setup.rs120
6 files changed, 195 insertions, 1 deletions
diff --git a/mingling_cli/src/linter.rs b/mingling_cli/src/linter.rs
new file mode 100644
index 0000000..870efeb
--- /dev/null
+++ b/mingling_cli/src/linter.rs
@@ -0,0 +1,15 @@
+use mingling::{Program, macros::program_setup};
+
+use crate::linter::cmd_mlint::CMDMinglingLinter;
+
+pub mod cmd_mlint;
+
+#[program_setup]
+pub fn mingling_linter_setup(program: &mut Program<crate::ThisProgram>) {
+ program.with_setup(MinglingLinterCommandSetup);
+}
+
+#[program_setup]
+pub fn mingling_linter_command_setup(program: &mut Program<crate::ThisProgram>) {
+ program.with_dispatcher(CMDMinglingLinter);
+}
diff --git a/mingling_cli/src/linter/cmd_mlint.rs b/mingling_cli/src/linter/cmd_mlint.rs
new file mode 100644
index 0000000..1431c43
--- /dev/null
+++ b/mingling_cli/src/linter/cmd_mlint.rs
@@ -0,0 +1,6 @@
+use mingling::macros::{chain, dispatcher};
+
+dispatcher!("mlint", CMDMinglingLinter => EntryMinglingLinter);
+
+#[chain]
+pub fn handle_mlint(_args: EntryMinglingLinter) {}
diff --git a/mingling_cli/src/main.rs b/mingling_cli/src/main.rs
index e7a11a9..59fa92d 100644
--- a/mingling_cli/src/main.rs
+++ b/mingling_cli/src/main.rs
@@ -1,3 +1,28 @@
+use crate::{linter::MinglingLinterSetup, metadata::MinglingMetadataSetup};
+use mingling::{
+ macros::gen_program,
+ setup::{
+ ExitCodeSetup,
+ picker::{HelpFlagSetup, StructuralRendererSetup},
+ },
+};
+
+pub mod linter;
+pub mod metadata;
+
fn main() {
- println!("Hello, world!");
+ let mut program = ThisProgram::new();
+
+ // Setups
+ program.with_setup(HelpFlagSetup::default());
+ program.with_setup(ExitCodeSetup::default());
+ program.with_setup(StructuralRendererSetup);
+
+ program.with_setup(MinglingMetadataSetup);
+ program.with_setup(MinglingLinterSetup);
+
+ // Exec
+ program.exec_and_exit();
}
+
+gen_program!();
diff --git a/mingling_cli/src/metadata.rs b/mingling_cli/src/metadata.rs
new file mode 100644
index 0000000..0c0b0bf
--- /dev/null
+++ b/mingling_cli/src/metadata.rs
@@ -0,0 +1,12 @@
+use mingling::{Program, macros::program_setup};
+
+use crate::metadata::{cmd_metadata::CMDMetadata, setup::CargoMetadataSetup};
+
+pub mod cmd_metadata;
+pub mod setup;
+
+#[program_setup]
+pub fn mingling_metadata_setup(program: &mut Program<crate::ThisProgram>) {
+ program.with_setup(CargoMetadataSetup);
+ program.with_dispatcher(CMDMetadata);
+}
diff --git a/mingling_cli/src/metadata/cmd_metadata.rs b/mingling_cli/src/metadata/cmd_metadata.rs
new file mode 100644
index 0000000..867e606
--- /dev/null
+++ b/mingling_cli/src/metadata/cmd_metadata.rs
@@ -0,0 +1,16 @@
+use mingling::{
+ LazyRes,
+ macros::{chain, dispatcher, pack_structural},
+};
+
+use crate::metadata::setup::ResMetadata;
+
+dispatcher!("metadata");
+
+pack_structural!(ResultMetadata = ResMetadata);
+
+#[chain]
+pub fn handle_metadata(_: EntryMetadata, metadata: &mut LazyRes<ResMetadata>) -> ResultMetadata {
+ let metadata = metadata.get_ref().clone();
+ ResultMetadata::new(metadata)
+}
diff --git a/mingling_cli/src/metadata/setup.rs b/mingling_cli/src/metadata/setup.rs
new file mode 100644
index 0000000..4a891b6
--- /dev/null
+++ b/mingling_cli/src/metadata/setup.rs
@@ -0,0 +1,120 @@
+use std::{env::current_dir, path::PathBuf};
+
+use cargo_metadata::{CargoOpt, MetadataCommand};
+use mingling::{
+ LazyInit, Program,
+ consts::REMAINS,
+ macros::program_setup,
+ picker::{IntoPicker, value::Flag},
+ prelude::*,
+ res::ResCurrentDir,
+};
+
+/// Name of Cargo manifest file
+pub const CARGO_TOML: &str = "Cargo.toml";
+
+use cargo_metadata::Metadata;
+use serde::Serialize;
+
+#[derive(Default, Clone, Serialize)]
+pub struct ResMetadata {
+ data: Option<Metadata>,
+}
+
+impl ResMetadata {
+ /// Returns a reference to the parsed `cargo metadata`.
+ ///
+ /// # Panics
+ ///
+ /// This function does **not** panic in practice, because `ResMetadata` is
+ /// always initialized via [`LazyInit`] inside the program setup, and the
+ /// initialization either succeeds (setting `data` to `Some(...)`) or fails
+ /// by propagating the error from `cmd.exec().unwrap()`. Therefore, by the
+ /// time this getter is called, `self.data` is guaranteed to be `Some`.
+ pub fn data(&self) -> &Metadata {
+ self.data.as_ref().unwrap()
+ }
+}
+
+#[program_setup]
+pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) {
+ let args = program.take_args();
+ let (
+ feature_args,
+ manifest_path,
+ message_format,
+ enable_all_features,
+ no_default_features,
+ no_deps,
+ args,
+ ) = args
+ .pick(&arg![features: Vec<String>])
+ .pick(&arg![manifest_path: Option<String>])
+ .pick_or(&arg![message_format: String], || "disable".to_string())
+ .pick(&arg![all_features: Flag])
+ .pick(&arg![no_default_features: Flag])
+ .pick(&arg![no_deps: Flag])
+ .pick(&REMAINS)
+ .unwrap();
+ program.replace_args(args.into());
+
+ // Bind format setting to StructuralRenderer
+ program.structural_renderer_name = message_format.into();
+
+ // Current Dir
+ let current_dir = current_dir().unwrap();
+
+ // Leak `feature_args` into a static slice so it can be captured by the metadata closure.
+ // Since the process does not loop, this memory leak is harmless.
+ let feature_args_leaked: &[String] = Box::leak(feature_args.into_boxed_slice());
+ let manifest_path_clone = manifest_path.clone();
+ let current_dir_clone = current_dir.clone();
+
+ program.with_resource(ResMetadata::lazy_init(move || {
+ // Paths
+ let metadata_path = match manifest_path_clone {
+ Some(ref path_str) => PathBuf::from(path_str),
+ None => find_manifest().unwrap(),
+ };
+
+ // Cargo Metadata - bind to a longer-lived variable
+ let mut cmd = MetadataCommand::new();
+ cmd.manifest_path(metadata_path)
+ .current_dir(&current_dir_clone);
+
+ if *enable_all_features {
+ cmd.features(CargoOpt::AllFeatures);
+ }
+
+ if *no_default_features {
+ cmd.features(CargoOpt::NoDefaultFeatures);
+ }
+
+ if *no_deps {
+ cmd.no_deps();
+ }
+
+ cmd.features(CargoOpt::SomeFeatures(feature_args_leaked.to_vec()));
+
+ ResMetadata {
+ data: Some(cmd.exec().unwrap()),
+ }
+ }));
+
+ // Current Dir
+ program.with_resource(ResCurrentDir::from(current_dir));
+}
+
+/// Find `Cargo.toml` by searching upward from `current_dir`.
+fn find_manifest() -> Option<PathBuf> {
+ let mut dir = current_dir().ok()?;
+ loop {
+ let candidate = dir.join(CARGO_TOML);
+ if candidate.is_file() {
+ return Some(candidate);
+ }
+ if !dir.pop() {
+ return None;
+ }
+ }
+}