...
This commit is contained in:
144
core/src/lib.rs
144
core/src/lib.rs
@@ -21,6 +21,57 @@ pub fn write_to_mize_stdout(args: std::fmt::Arguments) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mize Shell (msh) macro - wraps command execution with nixpkgs executable resolution.
|
||||
///
|
||||
/// If the executable is given as a bare name (identifier), it is resolved via
|
||||
/// [`msh_get_nix_bin`] to a nixpkgs store path. If it is given as a string literal
|
||||
/// containing `/`, it is treated as a path and used as-is.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// msh!(git status)?; // resolves `git` from nixpkgs
|
||||
/// msh!(curl https://example.com)?;
|
||||
/// msh!("/usr/bin/git" status)?; // uses path directly
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! msh {
|
||||
// First token is a string literal — check if it's a path at runtime
|
||||
($cmd:literal $($rest:tt)*) => {{
|
||||
let cmd_str: &str = $cmd;
|
||||
let resolved: String = if cmd_str.contains('/') {
|
||||
cmd_str.to_string()
|
||||
} else {
|
||||
$crate::msh_get_nix_bin(cmd_str)
|
||||
};
|
||||
let mut cmd = ::std::process::Command::new(resolved);
|
||||
$(cmd.arg(::std::stringify!($rest));)*
|
||||
let status = cmd.status()?;
|
||||
if status.success() {
|
||||
::std::result::Result::Ok(())
|
||||
} else {
|
||||
::std::result::Result::Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
::std::format!("msh: command exited with {}", status),
|
||||
))
|
||||
}
|
||||
}};
|
||||
// First token is an identifier — always resolve via nixpkgs
|
||||
($cmd:ident $($rest:tt)*) => {{
|
||||
let resolved: String = $crate::msh_get_nix_bin(::std::stringify!($cmd));
|
||||
let mut cmd = ::std::process::Command::new(resolved);
|
||||
$(cmd.arg(::std::stringify!($rest));)*
|
||||
let status = cmd.status()?;
|
||||
if status.success() {
|
||||
::std::result::Result::Ok(())
|
||||
} else {
|
||||
::std::result::Result::Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
::std::format!("msh: command exited with {}", status),
|
||||
))
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::*;
|
||||
pub use instance::Mize;
|
||||
@@ -97,6 +148,99 @@ pub extern "C" fn mize_rust_hack_set_global(mize: Arc<Mutex<Mize>>) {
|
||||
let _ = GLOBAL_MIZE.set(mize);
|
||||
}
|
||||
|
||||
/// Resolve an executable name to its full path in the nixpkgs store.
|
||||
///
|
||||
/// Reads `nix.pkgs.ref` (nixpkgs commit hash) and `nix.bin` (path to the `nix`
|
||||
/// binary) from the Mize config, then runs:
|
||||
///
|
||||
/// ```text
|
||||
/// <nix.bin> eval --raw nixpkgs/<hash>#<pkg>
|
||||
/// ```
|
||||
///
|
||||
/// and returns `<outPath>/bin/<executable>`.
|
||||
///
|
||||
/// Certain executable names are mapped to different nixpkgs attribute names
|
||||
/// via a hardcoded match (e.g. `npm` → `nodejs`).
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the global Mize instance is not set, the config values are missing,
|
||||
/// or `nix eval` fails.
|
||||
pub fn msh_get_nix_bin(name: &str) -> String {
|
||||
// Hardcoded mapping of executable names to nixpkgs attribute names
|
||||
let pkg = match name {
|
||||
"npm" => "nodejs",
|
||||
"node" => "nodejs",
|
||||
"yarn" => "yarn",
|
||||
"pnpm" => "nodePackages.pnpm",
|
||||
_ => name,
|
||||
};
|
||||
|
||||
let mut mize = get_global_mize().expect("msh_get_nix_bin: no global Mize instance set");
|
||||
|
||||
let nixpkgs_ref = mize
|
||||
.get_config("nix.pkgs.ref")
|
||||
.expect("msh_get_nix_bin: failed to get config nix.pkgs.ref")
|
||||
.value_string()
|
||||
.expect("msh_get_nix_bin: nix.pkgs.ref is not a string");
|
||||
|
||||
let nix_bin = mize
|
||||
.get_config("nix.bin")
|
||||
.expect("msh_get_nix_bin: failed to get config nix.bin")
|
||||
.value_string()
|
||||
.expect("msh_get_nix_bin: nix.bin is not a string");
|
||||
|
||||
// Run: nix eval --raw nixpkgs/<hash>#<pkg>
|
||||
// --raw outputs just the store path without quotes
|
||||
let attr = format!("nixpkgs/{}#{}", nixpkgs_ref, pkg);
|
||||
let output = std::process::Command::new(&nix_bin)
|
||||
.args(["eval", "--raw", &attr])
|
||||
.output()
|
||||
.expect("msh_get_nix_bin: failed to spawn nix eval");
|
||||
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"msh_get_nix_bin: nix eval failed for {}: {}",
|
||||
attr,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
let out_path = String::from_utf8(output.stdout)
|
||||
.expect("msh_get_nix_bin: nix eval output is not valid UTF-8")
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
format!("{}/bin/{}", out_path, name)
|
||||
}
|
||||
|
||||
/// Run a command with Mize Shell executable resolution.
|
||||
///
|
||||
/// This is the function-based equivalent of the [`msh!`] macro, with proper
|
||||
/// argument handling (no `stringify!` limitations).
|
||||
///
|
||||
/// If `cmd` contains `/` it is treated as a path and used as-is.
|
||||
/// Otherwise it is resolved via [`msh_get_nix_bin`].
|
||||
///
|
||||
/// Returns `Ok(())` if the command exits successfully, or an `io::Error` otherwise.
|
||||
pub fn msh_run(cmd: &str, args: &[&str]) -> std::io::Result<()> {
|
||||
let resolved = if cmd.contains('/') {
|
||||
cmd.to_string()
|
||||
} else {
|
||||
msh_get_nix_bin(cmd)
|
||||
};
|
||||
let mut command = std::process::Command::new(resolved);
|
||||
command.args(args);
|
||||
let status = command.status()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("msh: command exited with {}", status),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// platform specific stuff
|
||||
pub mod platform {
|
||||
use crate::{Mize, MizeResult};
|
||||
|
||||
Reference in New Issue
Block a user