use std::path::PathBuf;
use crate::{CommandSpec, Context, PackageManager, Verb};
use super::marshal::{free_str, leak_bytes, os_to_bytes, read_os, read_str};
use super::types::{PmpxCommand, PmpxStr, PMPX_ERR_INTERNAL, PMPX_ERR_INVALID_ARGS, PMPX_OK};
pub unsafe fn write_command(out: *mut PmpxCommand, spec: CommandSpec) {
let program = leak_bytes(&os_to_bytes(&spec.program));
let args: Vec<PmpxStr> = spec
.args
.iter()
.map(|a| leak_bytes(&os_to_bytes(a)))
.collect();
let args_boxed: Box<[PmpxStr]> = args.into_boxed_slice();
let args_len = args_boxed.len();
let args_ptr = args_boxed.as_ptr();
std::mem::forget(args_boxed);
let cwd = match &spec.cwd {
Some(p) => leak_bytes(&os_to_bytes(p.as_os_str())),
None => PmpxStr::EMPTY,
};
unsafe {
*out = PmpxCommand {
program,
args: args_ptr,
args_len,
cwd,
};
}
}
pub unsafe fn free_command(c: *mut PmpxCommand) {
if c.is_null() {
return;
}
let cmd = unsafe { &*c };
unsafe { free_str(cmd.program) };
unsafe { free_str(cmd.cwd) };
if !cmd.args.is_null() && cmd.args_len > 0 {
let raw = std::ptr::slice_from_raw_parts_mut(cmd.args as *mut PmpxStr, cmd.args_len);
let args = unsafe { Box::from_raw(raw) };
for s in args.iter() {
unsafe { free_str(*s) };
}
}
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn dispatch_command(
plugin: &dyn PackageManager,
project_root: PmpxStr,
matched: *const PmpxStr,
matched_len: usize,
verb: u32,
args: *const PmpxStr,
args_len: usize,
out: *mut PmpxCommand,
) -> u32 {
if out.is_null() {
return PMPX_ERR_INVALID_ARGS;
}
let Some(verb) = Verb::from_abi(verb) else {
return PMPX_ERR_INVALID_ARGS;
};
let project_root = PathBuf::from(unsafe { read_os(project_root) });
let mut matched_names = Vec::with_capacity(matched_len);
for i in 0..matched_len {
let raw = unsafe { *matched.add(i) };
match unsafe { read_str(raw) } {
Ok(s) => matched_names.push(s.to_string()),
Err(code) => return code,
}
}
let mut arg_list = Vec::with_capacity(args_len);
for i in 0..args_len {
let raw = unsafe { *args.add(i) };
arg_list.push(unsafe { read_os(raw) });
}
let ctx = Context {
project_root,
matched: matched_names,
};
match plugin.command(&ctx, verb, &arg_list) {
Ok(spec) => {
unsafe { write_command(out, spec) };
PMPX_OK
}
Err(e) => e.code(),
}
}
pub fn guard(f: impl FnOnce() -> u32) -> u32 {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(PMPX_ERR_INTERNAL)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_and_frees_a_command() {
let spec = CommandSpec::new("cargo")
.arg("add")
.arg("serde")
.cwd("/tmp/project");
let mut out = std::mem::MaybeUninit::<PmpxCommand>::uninit();
unsafe { write_command(out.as_mut_ptr(), spec) };
let mut cmd = unsafe { out.assume_init() };
assert_eq!(cmd.args_len, 2);
let program = unsafe { std::slice::from_raw_parts(cmd.program.ptr, cmd.program.len) };
assert_eq!(program, b"cargo");
let arg0 = unsafe { *cmd.args.add(0) };
let a0 = unsafe { std::slice::from_raw_parts(arg0.ptr, arg0.len) };
assert_eq!(a0, b"add");
let cwd = unsafe { std::slice::from_raw_parts(cmd.cwd.ptr, cmd.cwd.len) };
assert_eq!(cwd, b"/tmp/project");
unsafe { free_command(&mut cmd as *mut _) };
}
#[test]
fn writes_a_command_with_no_args_and_no_cwd() {
let spec = CommandSpec::new("cargo");
let mut out = std::mem::MaybeUninit::<PmpxCommand>::uninit();
unsafe { write_command(out.as_mut_ptr(), spec) };
let mut cmd = unsafe { out.assume_init() };
assert_eq!(cmd.args_len, 0);
assert!(
cmd.cwd.is_empty(),
"cwd without an override should be EMPTY"
);
unsafe { free_command(&mut cmd as *mut _) };
}
#[test]
fn free_command_tolerates_null() {
unsafe { free_command(std::ptr::null_mut()) };
}
#[test]
fn guard_turns_a_panic_into_internal_error() {
assert_eq!(guard(|| PMPX_OK), PMPX_OK);
assert_eq!(guard(|| panic!("the plugin blew up")), PMPX_ERR_INTERNAL);
}
}