use ::std::path::{MAIN_SEPARATOR, Path, PathBuf};
use ::std::ptr::NonNull;
use bun_core::ZBox;
use mozjs::jsapi::*;
use mozjs::jsval::{JSVal, ObjectValue, UndefinedValue};
use mozjs::rooted;
use mozjs::rust::wrappers2 as w2;
use crate::require::cache_builtin;
pub fn install(cx: &mut mozjs::context::JSContext) {
rooted!(&in(cx) let path_obj = unsafe { w2::JS_NewPlainObject(cx) });
if path_obj.get().is_null() {
return;
}
unsafe {
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"join".as_ptr(),
Some(path_join),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"resolve".as_ptr(),
Some(path_resolve),
0,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"dirname".as_ptr(),
Some(path_dirname),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"basename".as_ptr(),
Some(path_basename),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"extname".as_ptr(),
Some(path_extname),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"normalize".as_ptr(),
Some(path_normalize),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"isAbsolute".as_ptr(),
Some(path_is_absolute),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"relative".as_ptr(),
Some(path_relative),
2,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"parse".as_ptr(),
Some(path_parse),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"format".as_ptr(),
Some(path_format),
1,
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineFunction(
cx,
path_obj.handle(),
c"toNamespacedPath".as_ptr(),
Some(path_to_namespaced),
1,
JSPROP_ENUMERATE as u32,
);
let sep_cstr = ZBox::from_bytes(if MAIN_SEPARATOR == '/' { "/" } else { "\\" }.as_bytes());
let sep_str = JS_NewStringCopyZ(cx.raw_cx(), sep_cstr.as_ptr());
if !sep_str.is_null() {
let sep_val = mozjs::jsval::StringValue(&*sep_str);
rooted!(&in(cx) let sep_root = sep_val);
JS_DefineProperty(
cx.raw_cx(),
path_obj.handle().into(),
c"sep".as_ptr(),
sep_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let platform_bytes: &[u8] = if cfg!(target_os = "linux") {
b"linux"
} else if cfg!(target_os = "macos") {
b"darwin"
} else if cfg!(target_os = "windows") {
b"win32"
} else {
b"unknown"
};
let platform_cstr = ZBox::from_bytes(platform_bytes);
let platform_str = JS_NewStringCopyZ(cx.raw_cx(), platform_cstr.as_ptr());
if !platform_str.is_null() {
let platform_val = mozjs::jsval::StringValue(&*platform_str);
rooted!(&in(cx) let platform_root = platform_val);
JS_DefineProperty(
cx.raw_cx(),
path_obj.handle().into(),
c"platform".as_ptr(),
platform_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let delim_cstr = ZBox::from_bytes(if cfg!(windows) { b";" } else { b":" });
let delim_str = JS_NewStringCopyZ(cx.raw_cx(), delim_cstr.as_ptr());
if !delim_str.is_null() {
let delim_val = mozjs::jsval::StringValue(&*delim_str);
rooted!(&in(cx) let delim_root = delim_val);
JS_DefineProperty(
cx.raw_cx(),
path_obj.handle().into(),
c"delimiter".as_ptr(),
delim_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
unsafe {
w2::JS_DefineProperty3(
cx,
path_obj.handle(),
c"posix".as_ptr(),
path_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
unsafe {
rooted!(&in(cx) let win32_obj = w2::JS_NewPlainObject(cx));
if !win32_obj.get().is_null() {
for fn_name in &[
"join",
"resolve",
"dirname",
"basename",
"extname",
"normalize",
"isAbsolute",
"relative",
"parse",
"format",
"toNamespaced",
] {
let c_name = ZBox::from_bytes(fn_name.as_bytes());
let mut fn_val = UndefinedValue();
JS_GetProperty(
cx.raw_cx(),
path_obj.handle().into(),
c_name.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut fn_val,
},
);
if fn_val.is_object() {
rooted!(&in(cx) let fv = fn_val);
JS_DefineProperty(
cx.raw_cx(),
win32_obj.handle().into(),
c_name.as_ptr(),
fv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let winsep_cstr = ZBox::from_bytes(b"\\");
let winsep_str = JS_NewStringCopyZ(cx.raw_cx(), winsep_cstr.as_ptr());
if !winsep_str.is_null() {
let v = mozjs::jsval::StringValue(&*winsep_str);
rooted!(&in(cx) let vr = v);
JS_DefineProperty(
cx.raw_cx(),
win32_obj.handle().into(),
c"sep".as_ptr(),
vr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let wdlm_cstr = ZBox::from_bytes(b";");
let wdlm_str = JS_NewStringCopyZ(cx.raw_cx(), wdlm_cstr.as_ptr());
if !wdlm_str.is_null() {
let v = mozjs::jsval::StringValue(&*wdlm_str);
rooted!(&in(cx) let vr = v);
JS_DefineProperty(
cx.raw_cx(),
win32_obj.handle().into(),
c"delimiter".as_ptr(),
vr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
w2::JS_DefineProperty3(
cx,
win32_obj.handle(),
c"win32".as_ptr(),
win32_obj.handle(),
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineProperty3(
cx,
win32_obj.handle(),
c"posix".as_ptr(),
path_obj.handle(),
JSPROP_ENUMERATE as u32,
);
w2::JS_DefineProperty3(
cx,
path_obj.handle(),
c"win32".as_ptr(),
win32_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
unsafe {
let matches_src = r#"(function(p){
function globToRegExp(pattern) {
// Anchor the pattern; translate `**` → `.*`, `*` → `.*`, `?` → `.`,
// and escape regex metacharacters in literals. Mirrors Bun.Glob's
// full-path glob semantics (a single `*` matches across path segments),
// which is what Node.js' path.matchesGlob surfaces.
var s = '^';
for (var i = 0; i < pattern.length; i++) {
var c = pattern.charAt(i);
if (c === '*') {
if (pattern.charAt(i + 1) === '*') {
s += '.*';
i++;
} else {
s += '.*';
}
} else if (c === '?') {
s += '.';
} else if ('.+^${}()|[]\\'.indexOf(c) >= 0) {
s += '\\' + c;
} else {
s += c;
}
}
return new RegExp(s + '$');
}
p.matchesGlob = function matchesGlob(path, pattern) {
if (typeof path !== 'string') {
throw new TypeError('The "path" argument must be of type string.');
}
if (typeof pattern !== 'string') {
throw new TypeError('The "pattern" argument must be of type string.');
}
return globToRegExp(pattern).test(path);
};
})"#;
let mut msrc = mozjs::rust::transform_str_to_source_text(matches_src);
let mut mval = UndefinedValue();
let mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut mval,
};
let mopts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<path-matchesGlob>".as_ptr(), 1);
if !mopts.is_null() {
let global = CurrentGlobalOrNull(cx.raw_cx());
if !global.is_null()
&& JS::Evaluate2(cx.raw_cx(), mopts, &mut msrc, mh)
&& mval.is_object()
{
let wrapped_cx =
mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx.raw_cx()));
rooted!(&in(wrapped_cx) let global_root = global);
rooted!(&in(wrapped_cx) let path_val_root = ObjectValue(path_obj.get()));
let args_arr = HandleValueArray {
length_: 1,
elements_: &path_val_root.get() as *const Value,
};
let mut call_rval = UndefinedValue();
let call_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut call_rval,
};
rooted!(&in(wrapped_cx) let factory_obj = mval.to_object());
rooted!(&in(wrapped_cx) let factory_obj_h = ObjectValue(factory_obj.get()));
JS_CallFunctionValue(
cx.raw_cx(),
global_root.handle().into(),
factory_obj_h.handle().into(),
&args_arr,
call_rval_h,
);
}
libc::free(mopts as *mut _);
}
}
cache_builtin(cx, "path", path_obj.get());
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn arg_to_string(
cx: *mut JSContext,
val: JSVal,
) -> ::std::option::Option<::std::string::String> {
if val.is_undefined() || val.is_null() {
return ::std::option::Option::None;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let val_root = val);
let s = mozjs::rust::ToString(&mut wrapped_cx, val_root.handle().into());
if s.is_null() {
return ::std::option::Option::None;
}
let rust_str = crate::jsstr_to_rust_string(cx, s);
::std::option::Option::Some(rust_str)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn return_string(cx: *mut JSContext, args: &CallArgs, s: &str) -> bool {
let c_str = ZBox::from_bytes(s.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_str.as_ptr());
if js_str.is_null() {
args.rval().set(UndefinedValue());
} else {
args.rval().set(mozjs::jsval::StringValue(&*js_str));
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_join(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut parts: Vec<::std::string::String> = Vec::new();
for val in ::std::slice::from_raw_parts(args.argv_, argc as usize) {
match arg_to_string(cx, *val) {
Some(s) => parts.push(s),
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
}
}
let joined = posix_join(&parts);
return_string(cx, &args, &joined)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_resolve(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let cwd = {
let mut buf = bun_core::PathBuffer::default();
bun_core::getcwd(&mut buf)
.map(|z| PathBuf::from(String::from_utf8_lossy(z.as_bytes()).into_owned()))
.unwrap_or_else(|_| PathBuf::from("."))
};
let mut resolved = cwd;
for val in ::std::slice::from_raw_parts(args.argv_, argc as usize) {
match arg_to_string(cx, *val) {
Some(s) => {
let p = Path::new(&s);
if p.is_absolute() {
resolved = p.to_path_buf();
} else {
resolved = resolved.join(p);
}
}
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
}
}
let result = normalize_path(&resolved);
return_string(cx, &args, &result.to_string_lossy())
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_dirname(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
};
let result = Path::new(&s)
.parent()
.map(|p| {
let pstr = p.to_string_lossy().into_owned();
if pstr.is_empty() {
".".to_string()
} else {
pstr
}
})
.unwrap_or_else(|| ".".to_string());
return_string(cx, &args, &result)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_basename(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
};
let mut base = Path::new(&s)
.file_name()
.map(|f| f.to_string_lossy().into_owned())
.unwrap_or_else(|| s.clone());
if argc >= 2 {
let ext_val = *args.get(1).ptr;
if let Some(ext) = arg_to_string(cx, ext_val)
&& base.ends_with(&ext)
&& !ext.is_empty()
{
base.truncate(base.len() - ext.len());
}
}
return_string(cx, &args, &base)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_extname(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
};
let ext = Path::new(&s)
.extension()
.map(|e| format!(".{}", e.to_string_lossy()))
.unwrap_or_default();
return_string(cx, &args, &ext)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_normalize(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
};
let p = Path::new(&s);
let normalized = normalize_path(p);
return_string(cx, &args, &normalized.to_string_lossy())
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_is_absolute(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
args.rval().set(mozjs::jsval::BooleanValue(false));
return true;
}
};
args.rval()
.set(mozjs::jsval::BooleanValue(Path::new(&s).is_absolute()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_relative(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc < 2 {
JS_ReportErrorUTF8(
cx,
c"The \"from\" and \"to\" arguments must be of type string".as_ptr(),
);
return false;
}
let from_val = *args.get(0).ptr;
let to_val = *args.get(1).ptr;
let from_str = match arg_to_string(cx, from_val) {
Some(s) => s,
None => return return_string(cx, &args, ""),
};
let to_str = match arg_to_string(cx, to_val) {
Some(s) => s,
None => return return_string(cx, &args, ""),
};
let from_abs = make_absolute(&from_str);
let to_abs = make_absolute(&to_str);
let result = pathdiff(&to_abs, &from_abs);
return_string(
cx,
&args,
result.unwrap_or_default().to_string_lossy().as_ref(),
)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_parse(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
JS_ReportErrorUTF8(cx, c"The \"path\" argument must be of type string".as_ptr());
return false;
}
};
let p = Path::new(&s);
let root = if p.is_absolute() {
"/".to_string()
} else {
String::new()
};
let dir = p
.parent()
.map(|d| d.to_string_lossy().into_owned())
.unwrap_or_default();
let file_name = p
.file_name()
.map(|f| f.to_string_lossy().into_owned())
.unwrap_or_default();
let ext = p
.extension()
.map(|e| format!(".{}", e.to_string_lossy()))
.unwrap_or_default();
let name = if !file_name.is_empty() && !ext.is_empty() {
file_name[..file_name.len() - ext.len()].to_string()
} else {
file_name.clone()
};
let parsed = JS_NewPlainObject(cx);
if parsed.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let parsed_root = parsed);
define_string_prop(cx, parsed_root.handle().into(), "root", &root);
define_string_prop(cx, parsed_root.handle().into(), "dir", &dir);
define_string_prop(cx, parsed_root.handle().into(), "base", &file_name);
define_string_prop(cx, parsed_root.handle().into(), "ext", &ext);
define_string_prop(cx, parsed_root.handle().into(), "name", &name);
args.rval().set(mozjs::jsval::ObjectValue(parsed));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_format(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(
cx,
c"The \"pathObject\" argument must be of type object".as_ptr(),
);
return false;
}
let val = *args.get(0).ptr;
if !val.is_object() {
JS_ReportErrorUTF8(
cx,
c"The \"pathObject\" argument must be of type object".as_ptr(),
);
return false;
}
let obj = val.to_object();
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let obj_root = obj);
let dir = get_string_prop(cx, obj_root.handle().into(), "dir");
let base = get_string_prop(cx, obj_root.handle().into(), "base");
let name = get_string_prop(cx, obj_root.handle().into(), "name");
let ext = get_string_prop(cx, obj_root.handle().into(), "ext");
let result = if let Some(b) = base {
if dir.as_ref().is_some_and(|d| !d.is_empty()) {
format!("{}/{}", dir.unwrap_or_default(), b)
} else {
b
}
} else {
let mut s = dir.unwrap_or_default();
if !s.is_empty() && !s.ends_with('/') {
s.push('/');
}
s.push_str(&name.unwrap_or_default());
s.push_str(&ext.unwrap_or_default());
s
};
return_string(cx, &args, &result)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn path_to_namespaced(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
args.rval().set(UndefinedValue());
return true;
}
let val = *args.get(0).ptr;
let s = match arg_to_string(cx, val) {
Some(s) => s,
None => {
args.rval().set(UndefinedValue());
return true;
}
};
let resolved = make_absolute(&s);
return_string(cx, &args, &resolved.to_string_lossy())
}
use bun_paths::resolve_path::{self, platform::Posix};
fn cwd_bytes() -> Vec<u8> {
let mut buf = bun_core::PathBuffer::default();
match bun_core::getcwd(&mut buf) {
Ok(z) => z.as_bytes().to_vec(),
Err(_) => b".".to_vec(),
}
}
pub(crate) fn posix_join(parts: &[::std::string::String]) -> ::std::string::String {
if parts.is_empty() {
return ".".to_string();
}
let mut segments: Vec<&str> = Vec::new();
let mut has_root = false;
let mut first_seen = false;
let mut trailing_slash = false;
for part in parts {
if part.is_empty() {
continue;
}
if !first_seen {
first_seen = true;
has_root = part.starts_with('/');
}
trailing_slash = part.ends_with('/');
for seg in part.split('/') {
if !seg.is_empty() && seg != "." {
segments.push(seg);
}
}
}
if !first_seen {
return ".".to_string();
}
let mut normalized: Vec<&str> = Vec::new();
for seg in &segments {
if *seg == ".." {
if !normalized.is_empty() && *normalized.last().expect("segments") != ".." {
normalized.pop();
} else if !has_root {
normalized.push("..");
}
} else {
normalized.push(seg);
}
}
let mut result = if has_root {
"/".to_string()
} else {
String::new()
};
result.push_str(&normalized.join("/"));
if result.is_empty() && trailing_slash {
"./".to_string()
} else if !result.is_empty() && trailing_slash && !result.ends_with('/') {
result.push('/');
result
} else if result.is_empty() {
".".to_string()
} else {
result
}
}
pub(crate) fn normalize_path(path: &::std::path::Path) -> PathBuf {
let mut components = Vec::new();
let has_root = path.is_absolute();
for comp in path.components() {
match comp {
::std::path::Component::CurDir => {}
::std::path::Component::ParentDir => {
if let Some(last) = components.last()
&& last != &".."
{
components.pop();
continue;
}
components.push("..");
}
::std::path::Component::Normal(s) => {
components.push(s.to_string_lossy().into_owned().leak() as &'static str);
}
_ => {}
}
}
let mut result = PathBuf::new();
if has_root {
result.push("/");
}
for seg in &components {
result.push(*seg);
}
if result.as_os_str().is_empty() {
PathBuf::from(".")
} else {
result
}
}
pub(crate) fn make_absolute(s: &str) -> PathBuf {
let p = PathBuf::from(s);
if p.is_absolute() {
normalize_path(&p)
} else {
let cwd = cwd_bytes();
let part_bytes = s.as_bytes();
let resolved = resolve_path::join_abs_string::<Posix>(&cwd, &[part_bytes]);
PathBuf::from(String::from_utf8_lossy(resolved).into_owned())
}
}
pub(crate) fn pathdiff(to: &Path, from: &Path) -> ::std::option::Option<PathBuf> {
let cwd = cwd_bytes();
let to_abs = if to.is_absolute() {
to.to_string_lossy().into_owned()
} else {
let resolved =
resolve_path::join_abs_string::<Posix>(&cwd, &[to.to_string_lossy().as_bytes()]);
String::from_utf8_lossy(resolved).into_owned()
};
let from_abs = if from.is_absolute() {
from.to_string_lossy().into_owned()
} else {
let resolved =
resolve_path::join_abs_string::<Posix>(&cwd, &[from.to_string_lossy().as_bytes()]);
String::from_utf8_lossy(resolved).into_owned()
};
let rel =
resolve_path::relative_platform::<Posix, true>(from_abs.as_bytes(), to_abs.as_bytes());
::std::option::Option::Some(PathBuf::from(String::from_utf8_lossy(rel).into_owned()))
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn define_string_prop(
cx: *mut JSContext,
obj: Handle<*mut JSObject>,
name: &str,
value: &str,
) {
let c_name = ZBox::from_bytes(name.as_bytes());
let c_val = ZBox::from_bytes(value.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_val.as_ptr());
if !js_str.is_null() {
let val = mozjs::jsval::StringValue(&*js_str);
let wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
rooted!(&in(wrapped_cx) let val_root = val);
JS_DefineProperty(
cx,
obj,
c_name.as_ptr(),
val_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn get_string_prop(
cx: *mut JSContext,
obj: Handle<*mut JSObject>,
name: &str,
) -> ::std::option::Option<::std::string::String> {
let c_name = ZBox::from_bytes(name.as_bytes());
let mut val = UndefinedValue();
let handle = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
};
JS_GetProperty(cx, obj, c_name.as_ptr(), handle);
arg_to_string(cx, val)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_posix_join_empty() {
assert_eq!(posix_join(&[]), ".");
}
#[test]
fn test_posix_join_single() {
assert_eq!(posix_join(&["foo".to_string()]), "foo");
}
#[test]
fn test_posix_join_multiple() {
assert_eq!(
posix_join(&["a".to_string(), "b".to_string(), "c".to_string()]),
"a/b/c"
);
}
#[test]
fn test_posix_join_absolute_part() {
assert_eq!(
posix_join(&["a".to_string(), "/b".to_string(), "c".to_string()]),
"a/b/c"
);
}
#[test]
fn test_posix_join_trailing_slash() {
assert_eq!(posix_join(&["a/".to_string(), "b".to_string()]), "a/b");
}
#[test]
fn test_posix_join_dot() {
assert_eq!(posix_join(&[".".to_string(), "b".to_string()]), "b");
}
#[test]
fn test_posix_join_empty_parts_skipped() {
assert_eq!(
posix_join(&["a".to_string(), "".to_string(), "b".to_string()]),
"a/b"
);
}
#[test]
fn test_posix_join_root() {
assert_eq!(posix_join(&["/".to_string()]), "/");
}
#[test]
fn test_posix_join_dot_dot_normalizes() {
assert_eq!(
posix_join(&["a".to_string(), "b".to_string(), "..".to_string()]),
"a"
);
}
#[test]
fn test_posix_join_dot_dot_beyond_root_stays() {
assert_eq!(
posix_join(&["a".to_string(), "..".to_string(), "..".to_string()]),
".."
);
}
#[test]
fn test_normalize_path_dot_dot() {
assert_eq!(
normalize_path(::std::path::Path::new("/a/b/../c")),
PathBuf::from("/a/c")
);
}
#[test]
fn test_normalize_path_dot() {
assert_eq!(
normalize_path(::std::path::Path::new("/a/./b")),
PathBuf::from("/a/b")
);
}
#[test]
fn test_normalize_path_root() {
assert_eq!(
normalize_path(::std::path::Path::new("/")),
PathBuf::from("/")
);
}
#[test]
fn test_normalize_path_relative() {
assert_eq!(
normalize_path(::std::path::Path::new("a/b/../c")),
PathBuf::from("a/c")
);
}
#[test]
fn test_normalize_path_double_dot_beyond_root() {
assert_eq!(
normalize_path(::std::path::Path::new("/a/../../b")),
PathBuf::from("/../b")
);
}
#[test]
fn test_normalize_path_empty_relative() {
assert_eq!(
normalize_path(::std::path::Path::new(".")),
PathBuf::from(".")
);
}
#[test]
fn test_normalize_path_multiple_dots() {
assert_eq!(
normalize_path(::std::path::Path::new("/a/b/c/../../d")),
PathBuf::from("/a/d")
);
}
#[test]
fn test_make_absolute_already_absolute() {
assert_eq!(make_absolute("/foo/bar"), PathBuf::from("/foo/bar"));
}
#[test]
fn test_make_absolute_relative() {
let result = make_absolute("foo/bar");
assert!(
result.is_absolute(),
"result should be absolute: {:?}",
result
);
assert!(result.to_str().unwrap().contains("foo/bar"));
}
#[test]
fn test_make_absolute_dot() {
let result = make_absolute(".");
assert!(result.is_absolute());
}
#[test]
fn test_make_absolute_dot_dot() {
let result = make_absolute("/a/b/..");
let s = result.to_str().unwrap();
assert!(s == "/a" || s == "/a/", "expected /a or /a/, got {}", s);
}
#[test]
fn test_pathdiff_same() {
let p = Path::new("/a/b/c");
assert_eq!(pathdiff(p, p), Some(PathBuf::from("")));
}
#[test]
fn test_pathdiff_sibling() {
let to = Path::new("/a/b/c");
let from = Path::new("/a/b/d");
assert_eq!(pathdiff(to, from), Some(PathBuf::from("../c")));
}
#[test]
fn test_pathdiff_child() {
let to = Path::new("/a/b/c/d");
let from = Path::new("/a/b");
assert_eq!(pathdiff(to, from), Some(PathBuf::from("c/d")));
}
#[test]
fn test_pathdiff_parent() {
let to = Path::new("/a/b");
let from = Path::new("/a/b/c/d");
assert_eq!(pathdiff(to, from), Some(PathBuf::from("../..")));
}
#[test]
fn test_pathdiff_different_roots() {
let to = Path::new("/a/b");
let from = Path::new("/c/d");
let result = pathdiff(to, from);
assert!(result.is_some());
}
#[test]
fn test_pathdiff_relative() {
let to = Path::new("a/b");
let from = Path::new("a/c");
let result = pathdiff(to, from);
assert!(result.is_some());
}
}