use crate::runtime::validation::validate_arg_count;
use crate::runtime::{InterpreterError, Value};
pub(crate) fn eval_path_join(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_join", args, 2)?;
match (&args[0], &args[1]) {
(Value::String(base), Value::String(component)) => {
let path = std::path::Path::new(base.as_ref()).join(component.as_ref());
Ok(Value::from_string(path.to_string_lossy().to_string()))
}
_ => Err(InterpreterError::RuntimeError(
"path_join() expects two string arguments".to_string(),
)),
}
}
pub(crate) fn build_path_from_value_components(
components: &[Value],
) -> Result<std::path::PathBuf, InterpreterError> {
let mut path = std::path::PathBuf::new();
for component in components {
match component {
Value::String(s) => path.push(s.as_ref()),
_ => {
return Err(InterpreterError::RuntimeError(
"path_join_many() expects array of strings".to_string(),
))
}
}
}
Ok(path)
}
pub(crate) fn eval_path_join_many(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_join_many", args, 1)?;
match &args[0] {
Value::Array(components) => {
let path = build_path_from_value_components(components)?;
Ok(Value::from_string(path.to_string_lossy().to_string()))
}
_ => Err(InterpreterError::RuntimeError(
"path_join_many() expects an array argument".to_string(),
)),
}
}
pub(crate) fn eval_path_parent(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_parent", args, 1)?;
match &args[0] {
Value::String(path) => {
let p = std::path::Path::new(path.as_ref());
match p.parent() {
Some(parent) => Ok(Value::from_string(parent.to_string_lossy().to_string())),
None => Ok(Value::Nil),
}
}
_ => Err(InterpreterError::RuntimeError(
"path_parent() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_file_name(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_file_name", args, 1)?;
match &args[0] {
Value::String(path) => {
let p = std::path::Path::new(path.as_ref());
match p.file_name() {
Some(name) => Ok(Value::from_string(name.to_string_lossy().to_string())),
None => Ok(Value::Nil),
}
}
_ => Err(InterpreterError::RuntimeError(
"path_file_name() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_file_stem(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_file_stem", args, 1)?;
match &args[0] {
Value::String(path) => {
let p = std::path::Path::new(path.as_ref());
match p.file_stem() {
Some(stem) => Ok(Value::from_string(stem.to_string_lossy().to_string())),
None => Ok(Value::Nil),
}
}
_ => Err(InterpreterError::RuntimeError(
"path_file_stem() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_extension(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_extension", args, 1)?;
match &args[0] {
Value::String(path) => {
let p = std::path::Path::new(path.as_ref());
match p.extension() {
Some(ext) => Ok(Value::from_string(ext.to_string_lossy().to_string())),
None => Ok(Value::Nil),
}
}
_ => Err(InterpreterError::RuntimeError(
"path_extension() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_is_absolute(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_is_absolute", args, 1)?;
match &args[0] {
Value::String(path) => Ok(Value::Bool(
std::path::Path::new(path.as_ref()).is_absolute(),
)),
_ => Err(InterpreterError::RuntimeError(
"path_is_absolute() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_is_relative(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_is_relative", args, 1)?;
match &args[0] {
Value::String(path) => Ok(Value::Bool(
std::path::Path::new(path.as_ref()).is_relative(),
)),
_ => Err(InterpreterError::RuntimeError(
"path_is_relative() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_canonicalize(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_canonicalize", args, 1)?;
match &args[0] {
Value::String(path) => match std::fs::canonicalize(path.as_ref()) {
Ok(canonical) => Ok(Value::from_string(canonical.to_string_lossy().to_string())),
Err(e) => Err(InterpreterError::RuntimeError(format!(
"Failed to canonicalize path: {e}"
))),
},
_ => Err(InterpreterError::RuntimeError(
"path_canonicalize() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_with_extension(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_with_extension", args, 2)?;
match (&args[0], &args[1]) {
(Value::String(path), Value::String(ext)) => {
let p = std::path::Path::new(path.as_ref()).with_extension(ext.as_ref());
Ok(Value::from_string(p.to_string_lossy().to_string()))
}
_ => Err(InterpreterError::RuntimeError(
"path_with_extension() expects two string arguments".to_string(),
)),
}
}
pub(crate) fn eval_path_with_file_name(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_with_file_name", args, 2)?;
match (&args[0], &args[1]) {
(Value::String(path), Value::String(name)) => {
let p = std::path::Path::new(path.as_ref()).with_file_name(name.as_ref());
Ok(Value::from_string(p.to_string_lossy().to_string()))
}
_ => Err(InterpreterError::RuntimeError(
"path_with_file_name() expects two string arguments".to_string(),
)),
}
}
pub(crate) fn eval_path_components(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_components", args, 1)?;
match &args[0] {
Value::String(path) => {
let p = std::path::Path::new(path.as_ref());
let components: Vec<Value> = p
.components()
.map(|c| Value::from_string(c.as_os_str().to_string_lossy().to_string()))
.collect();
Ok(Value::Array(components.into()))
}
_ => Err(InterpreterError::RuntimeError(
"path_components() expects a string argument".to_string(),
)),
}
}
pub(crate) fn eval_path_normalize(args: &[Value]) -> Result<Value, InterpreterError> {
validate_arg_count("path_normalize", args, 1)?;
match &args[0] {
Value::String(path) => {
let p = std::path::Path::new(path.as_ref());
let mut normalized = std::path::PathBuf::new();
for component in p.components() {
match component {
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
normalized.pop();
}
_ => normalized.push(component),
}
}
Ok(Value::from_string(normalized.to_string_lossy().to_string()))
}
_ => Err(InterpreterError::RuntimeError(
"path_normalize() expects a string argument".to_string(),
)),
}
}
pub(crate) fn try_eval_path_part1(name: &str, args: &[Value]) -> Result<Option<Value>, InterpreterError> {
match name {
"__builtin_path_join__" => Ok(Some(eval_path_join(args)?)),
"__builtin_path_join_many__" => Ok(Some(eval_path_join_many(args)?)),
"__builtin_path_parent__" => Ok(Some(eval_path_parent(args)?)),
"__builtin_path_file_name__" => Ok(Some(eval_path_file_name(args)?)),
_ => Ok(None),
}
}
pub(crate) fn try_eval_path_part2(name: &str, args: &[Value]) -> Result<Option<Value>, InterpreterError> {
match name {
"__builtin_path_file_stem__" => Ok(Some(eval_path_file_stem(args)?)),
"__builtin_path_extension__" => Ok(Some(eval_path_extension(args)?)),
"__builtin_path_is_absolute__" => Ok(Some(eval_path_is_absolute(args)?)),
"__builtin_path_is_relative__" => Ok(Some(eval_path_is_relative(args)?)),
_ => Ok(None),
}
}
pub(crate) fn try_eval_path_part3a(name: &str, args: &[Value]) -> Result<Option<Value>, InterpreterError> {
match name {
"__builtin_path_canonicalize__" => Ok(Some(eval_path_canonicalize(args)?)),
"__builtin_path_with_extension__" => Ok(Some(eval_path_with_extension(args)?)),
"__builtin_path_with_file_name__" => Ok(Some(eval_path_with_file_name(args)?)),
_ => Ok(None),
}
}
pub(crate) fn try_eval_path_part3b(name: &str, args: &[Value]) -> Result<Option<Value>, InterpreterError> {
match name {
"__builtin_path_components__" => Ok(Some(eval_path_components(args)?)),
"__builtin_path_normalize__" => Ok(Some(eval_path_normalize(args)?)),
_ => Ok(None),
}
}
pub(crate) fn try_eval_path_function(name: &str, args: &[Value]) -> Result<Option<Value>, InterpreterError> {
let dispatchers: &[fn(&str, &[Value]) -> Result<Option<Value>, InterpreterError>] = &[
try_eval_path_part1,
try_eval_path_part2,
try_eval_path_part3a,
try_eval_path_part3b,
];
for dispatcher in dispatchers {
if let Some(result) = dispatcher(name, args)? {
return Ok(Some(result));
}
}
Ok(None)
}