#![allow(warnings)] #![feature(str_as_str)] use std::cell::RefCell; use std::fmt::format; #[macro_export] macro_rules! out { ($($arg:tt)*) => { // format_args! handles string interpolation safely at compile time if let Err(e) = $crate::write_to_mize_stdout(format_args!($($arg)*)) { // Choose your error handling behavior here (panic, print to stderr, or ignore) eprintln!("out! macro failed to write!!"); } }; } pub fn write_to_mize_stdout(args: std::fmt::Arguments) -> MizeResult<()> { println!("{}", args); 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; pub use id::*; pub use error::MizeError; pub use crate::mize_err; pub use error::MizeResult; pub use item::{Item, ItemData, IntoItemData}; pub use config::*; pub use api::*; pub use mize_macros::*; } pub use flume; pub use tracing; // the core part has the code that can run on any platform pub mod config; pub mod error; pub mod id; pub mod instance; pub mod item; pub mod memstore; pub mod proto; pub mod types; pub mod c_api; pub mod api; pub use async_trait::async_trait; pub use ciborium; pub use error::MizeError; pub use error::MizeResult; pub use instance::Mize; pub use instance::{ MizePart, }; pub use mize_macros::*; use std::path::PathBuf; use std::sync::{Arc, Mutex, OnceLock}; /// Global Mize instance storage. /// This is set once when the Mize instance is created and can be accessed from anywhere. pub static GLOBAL_MIZE: OnceLock>> = OnceLock::new(); /// Get the global Mize instance. /// Returns a clone of the Mize instance (which is cheap as it only clones Arcs internally). pub fn get_global_mize() -> MizeResult { let arc = GLOBAL_MIZE .get() .ok_or_else(|| mize_err!("No global Mize instance set"))?; let mize = arc .lock() .map_err(|_| mize_err!("Failed to lock global Mize"))?; Ok(mize.clone()) } /// Set the global Mize instance. pub fn set_global_mize(mize: Mize) -> MizeResult<()> { let arc = Arc::new(Mutex::new(mize.clone())); if let Err(_) = GLOBAL_MIZE.set(arc) { let a = GLOBAL_MIZE.get().unwrap(); let mut b = a.lock().map_err(|_| mize_err!("Failed to lock global Mize"))?; *b = mize; } Ok(()) } /// C API function to set the global Mize instance from a dynamically loaded part. /// This is a hack to allow Rust parts loaded as .so files to access the global Mize instance. #[unsafe(no_mangle)] pub extern "C" fn mize_rust_hack_set_global(mize: Arc>) { 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 /// eval --raw nixpkgs/# /// ``` /// /// and returns `/bin/`. /// /// 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/# // --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}; #[cfg(feature = "target-js")] pub mod js; #[cfg(feature = "target-os")] pub mod os; pub fn init(mize: &mut Mize) -> MizeResult<()> { #[cfg(feature = "target-os")] { os::os_instance_init(mize)?; } #[cfg(feature = "target-js")] { js::wasm_instance_init(mize)?; } Ok(()) } pub fn load_part(mize: &mut Mize, part_name: &str, path: Option) -> MizeResult<()> { #[cfg(feature = "target-os")] { os::load_part(mize, part_name, path)?; } Ok(()) } pub fn fetch_part(mize: &mut Mize, part_name: &str) -> MizeResult { #[cfg(feature = "target-os")] { os::fetch_part(mize, part_name)?; } Ok("alsdkjf".to_owned()) } }