aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/metadata/setup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_cli/src/metadata/setup.rs')
-rw-r--r--mingling_cli/src/metadata/setup.rs136
1 files changed, 136 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..198baf1
--- /dev/null
+++ b/mingling_cli/src/metadata/setup.rs
@@ -0,0 +1,136 @@
+use std::{env::current_dir, path::PathBuf};
+
+use cargo_metadata::{CargoOpt, MetadataCommand};
+use mingling::{
+ LazyInit, Program,
+ 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;
+
+/// Resource holding parsed `cargo metadata` output.
+///
+/// This is lazily initialized during program setup by calling `cargo metadata`
+/// with the appropriate CLI flags (e.g., `--features`, `--all-features`,
+/// `--no-default-features`, `--no-deps`, `--message-format`).
+///
+/// Access the inner [`Metadata`] via [`ResMetadata::data()`], which panics if
+/// called before initialization (guaranteed not to happen in normal usage).
+#[derive(Default, Clone, Serialize)]
+pub struct ResMetadata {
+ data: Option<Metadata>,
+}
+
+/// Resource indicating whether the output format is JSON.
+///
+/// Set to `true` when `--message-format json` (or similar) is passed.
+/// Used by renderers to decide whether to serialize structs as JSON.
+#[derive(Default, Clone, Serialize)]
+pub struct ResUsingJson {
+ pub using: bool,
+}
+
+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.get_args().to_vec();
+
+ let (
+ feature_args,
+ manifest_path,
+ message_format,
+ enable_all_features,
+ no_default_features,
+ no_deps,
+ ) = 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])
+ .unwrap();
+
+ // Is Using Json
+ program.with_resource(ResUsingJson {
+ using: message_format.contains("json"),
+ });
+
+ // 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;
+ }
+ }
+}