use crate::userscript_api::{
fs_api::error::Error,
include::{
IntoLua, Lua, LuaEither, LuaNil, LuaUserData, LuaUserDataFields, LuaUserDataMethods,
LuaUserDataRef, LuaValue,
},
};
use serde::Serialize;
use std::{path::PathBuf, time::UNIX_EPOCH};
#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct PathObj(pub PathBuf);
impl LuaUserData for PathObj {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("path", |_, this: &PathObj| Ok(this.0.clone()));
fields.add_field_method_get("name", |lua: &Lua, this: &PathObj| {
let Some(filename) = this.0.file_name() else {
return Ok(LuaNil);
};
let filename: LuaValue = filename.into_lua(lua)?;
if filename.is_string() {
Ok(filename)
} else {
Ok(LuaNil)
}
});
fields.add_field_method_get("ext", |lua: &Lua, this: &PathObj| {
let Some(extension) = this.0.extension() else {
return Ok(LuaNil);
};
let extension: LuaValue = extension.into_lua(lua)?;
if extension.is_string() {
Ok(extension)
} else {
Ok(LuaNil)
}
});
fields.add_field_method_get("stem", |lua: &Lua, this: &PathObj| {
let Some(stem) = this.0.file_stem() else {
return Ok(LuaNil);
};
let stem: LuaValue = stem.into_lua(lua)?;
if stem.is_string() {
Ok(stem)
} else {
Ok(LuaNil)
}
});
fields.add_field_method_get("parent", |lua: &Lua, this: &PathObj| {
let Some(parent) = this.0.parent() else {
return Ok(LuaNil);
};
let parent: PathObj = PathObj(parent.to_owned());
parent.into_lua(lua)
});
fields.add_field_method_get("type", |_, this: &PathObj| {
if this.0.is_dir() {
Ok("directory")
} else if this.0.is_file() {
Ok("file")
} else if this.0.is_symlink() {
Ok("symlink")
} else {
Ok("unknown")
}
});
fields.add_field_method_get("size", |_, this: &PathObj| {
let Ok(metadata) = this.0.metadata() else {
return Ok(LuaNil);
};
#[allow(clippy::cast_possible_wrap)]
Ok(LuaValue::Integer(metadata.len() as i64))
});
fields.add_field_method_get("atime", |_, this: &PathObj| {
let Ok(metadata) = this.0.metadata() else {
return Ok(LuaNil);
};
let Ok(atime) = metadata.accessed() else {
return Ok(LuaNil);
};
let Ok(atime) = atime.duration_since(UNIX_EPOCH) else {
return Ok(LuaNil);
};
#[allow(clippy::cast_possible_wrap)]
Ok(LuaValue::Integer(atime.as_secs() as i64))
});
fields.add_field_method_get("mtime", |_, this: &PathObj| {
let Ok(metadata) = this.0.metadata() else {
return Ok(LuaNil);
};
let Ok(mtime) = metadata.modified() else {
return Ok(LuaNil);
};
let Ok(mtime) = mtime.duration_since(UNIX_EPOCH) else {
return Ok(LuaNil);
};
#[allow(clippy::cast_possible_wrap)]
Ok(LuaValue::Integer(mtime.as_secs() as i64))
});
fields.add_field_method_get("ctime", |_, this: &PathObj| {
let Ok(metadata) = this.0.metadata() else {
return Ok(LuaNil);
};
let Ok(ctime) = metadata.created() else {
return Ok(LuaNil);
};
let Ok(ctime) = ctime.duration_since(UNIX_EPOCH) else {
return Ok(LuaNil);
};
#[allow(clippy::cast_possible_wrap)]
Ok(LuaValue::Integer(ctime.as_secs() as i64))
});
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method(
"join",
|_,
this: LuaUserDataRef<PathObj>,
other: LuaEither<PathBuf, LuaUserDataRef<PathObj>>| async move {
let other: PathBuf = match other {
LuaEither::Left(pb) => pb,
LuaEither::Right(po) => po.0.clone(),
};
Ok(PathObj(this.0.join(other)))
},
);
methods.add_async_method(
"absolute",
|_, this: LuaUserDataRef<PathObj>, ()| async move {
Ok(PathObj(this.0.canonicalize().map_err(|source| {
Error::InvalidPath {
path: this.0.clone(),
source,
}
})?))
},
);
methods.add_async_meta_method(
"__concat",
|_,
this: LuaUserDataRef<PathObj>,
other: LuaEither<PathBuf, LuaUserDataRef<PathObj>>| async move {
let other: PathBuf = match other {
LuaEither::Left(pb) => pb,
LuaEither::Right(po) => po.0.clone(),
};
Ok(PathObj(this.0.join(other)))
},
);
methods.add_async_meta_method(
"__unm",
|lua: Lua, this: LuaUserDataRef<PathObj>, ()| async move {
let Some(parent) = this.0.parent() else {
return Ok(LuaNil);
};
let parent: PathObj = PathObj(parent.to_owned());
parent.into_lua(&lua)
},
);
methods.add_async_meta_method(
"__eq",
|_, this: LuaUserDataRef<PathObj>, other: LuaUserDataRef<PathObj>| async move {
Ok(*this == *other)
},
);
methods.add_async_meta_method(
"__lt",
|_, this: LuaUserDataRef<PathObj>, other: LuaUserDataRef<PathObj>| async move {
Ok(*this < *other)
},
);
methods.add_async_meta_method(
"__le",
|_, this: LuaUserDataRef<PathObj>, other: LuaUserDataRef<PathObj>| async move {
Ok(*this <= *other)
},
);
methods.add_async_meta_method(
"__tostring",
|_, this: LuaUserDataRef<PathObj>, ()| async move { Ok(this.0.clone()) },
);
}
}