use runmat_filesystem as vfs;
#[cfg(test)]
use std::env;
use std::path::Path;
use runmat_builtins::{CharArray, Value};
use runmat_macros::runtime_builtin;
use crate::builtins::common::spec::{
BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
ReductionNaN, ResidencyPolicy, ShapeRequirements,
};
use crate::{build_runtime_error, RuntimeError};
#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::io::repl_fs::pwd")]
pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
name: "pwd",
op_kind: GpuOpKind::Custom("io"),
supported_precisions: &[],
broadcast: BroadcastSemantics::None,
provider_hooks: &[],
constant_strategy: ConstantStrategy::InlineLiteral,
residency: ResidencyPolicy::GatherImmediately,
nan_mode: ReductionNaN::Include,
two_pass_threshold: None,
workgroup_size: None,
accepts_nan_mode: false,
notes: "Host-only operation that queries the process working folder; no GPU provider hooks are required.",
};
#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::io::repl_fs::pwd")]
pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
name: "pwd",
shape: ShapeRequirements::Any,
constant_strategy: ConstantStrategy::InlineLiteral,
elementwise: None,
reduction: None,
emits_nan: false,
notes: "I/O builtins are not eligible for fusion; metadata registered for introspection completeness.",
};
const BUILTIN_NAME: &str = "pwd";
fn pwd_error(message: impl Into<String>) -> RuntimeError {
build_runtime_error(message)
.with_builtin(BUILTIN_NAME)
.build()
}
#[runtime_builtin(
name = "pwd",
category = "io/repl_fs",
summary = "Return the absolute path to the folder where RunMat is currently executing.",
keywords = "pwd,current directory,working folder,present working directory",
accel = "cpu",
suppress_auto_output = true,
type_resolver(crate::builtins::io::type_resolvers::pwd_type),
builtin_path = "crate::builtins::io::repl_fs::pwd"
)]
async fn pwd_builtin(args: Vec<Value>) -> crate::BuiltinResult<Value> {
if !args.is_empty() {
return Err(pwd_error("pwd: too many input arguments"));
}
let current = vfs::current_dir().map_err(|err| {
pwd_error(format!(
"pwd: unable to determine current directory ({err})"
))
})?;
Ok(path_to_value(¤t))
}
fn path_to_value(path: &Path) -> Value {
let text = path.to_string_lossy().into_owned();
Value::CharArray(CharArray::new_row(&text))
}
#[cfg(test)]
pub(crate) mod tests {
use super::super::REPL_FS_TEST_LOCK;
use super::*;
use crate::BuiltinResult;
use runmat_builtins::CharArray;
use std::convert::TryFrom;
use std::path::PathBuf;
use tempfile::tempdir;
fn pwd_builtin(args: Vec<Value>) -> BuiltinResult<Value> {
futures::executor::block_on(super::pwd_builtin(args))
}
struct DirGuard {
original: PathBuf,
}
impl DirGuard {
fn new() -> Self {
let original = env::current_dir().expect("current dir");
Self { original }
}
}
impl Drop for DirGuard {
fn drop(&mut self) {
let _ = env::set_current_dir(&self.original);
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn pwd_returns_current_directory() {
let _lock = REPL_FS_TEST_LOCK
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let _guard = DirGuard::new();
let expected = env::current_dir().expect("current dir");
let value = pwd_builtin(Vec::new()).expect("pwd");
let actual = String::try_from(&value).expect("string conversion");
assert_eq!(actual, expected.to_string_lossy());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn pwd_reflects_directory_changes() {
let _lock = REPL_FS_TEST_LOCK
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let guard = DirGuard::new();
let temp = tempdir().expect("tempdir");
env::set_current_dir(temp.path()).expect("set temp dir");
let value = pwd_builtin(Vec::new()).expect("pwd");
let actual = String::try_from(&value).expect("string conversion");
let actual_path = PathBuf::from(actual);
let expected_path =
std::fs::canonicalize(temp.path()).unwrap_or_else(|_| temp.path().to_path_buf());
let canonical_actual =
std::fs::canonicalize(&actual_path).unwrap_or_else(|_| actual_path.clone());
assert_eq!(canonical_actual, expected_path);
drop(guard);
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn pwd_returns_char_array_row_vector() {
let _lock = REPL_FS_TEST_LOCK
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let _guard = DirGuard::new();
let value = pwd_builtin(Vec::new()).expect("pwd");
match value {
Value::CharArray(CharArray { rows, cols, .. }) => {
assert_eq!(rows, 1);
assert!(cols >= 1, "expected at least one column in pwd output");
}
other => panic!("expected CharArray result, got {other:?}"),
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn pwd_errors_when_arguments_provided() {
let _lock = REPL_FS_TEST_LOCK
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let _guard = DirGuard::new();
let err = pwd_builtin(vec![Value::Num(1.0)]).expect_err("expected error");
assert_eq!(err.message(), "pwd: too many input arguments");
}
}