aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/metadata/setup.rs
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/metadata/setup.rs
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/metadata/setup.rs')
-rw-r--r--mingling_cli/src/metadata/setup.rs120
1 files changed, 120 insertions, 0 deletions
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;
+ }
+ }
+}