use mlua::{Lua, Value};
pub fn register_os(lua: &Lua) -> mlua::Result<()> {
let os_table = lua.create_table()?;
let hostname_fn = lua.create_function(|_, ()| {
get_hostname().map_err(|e| mlua::Error::runtime(format!("os.hostname: {e}")))
})?;
os_table.set("hostname", hostname_fn)?;
let arch_fn = lua.create_function(|_, ()| Ok(std::env::consts::ARCH.to_string()))?;
os_table.set("arch", arch_fn)?;
let platform_fn = lua.create_function(|_, ()| Ok(std::env::consts::OS.to_string()))?;
os_table.set("platform", platform_fn)?;
let time_fn = lua.create_function(|_, ()| {
Ok(std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64)
})?;
os_table.set("time", time_fn)?;
let start = std::time::Instant::now();
let clock_fn = lua.create_function(move |_, ()| Ok(start.elapsed().as_secs_f64()))?;
os_table.set("clock", clock_fn)?;
let date_fn = lua.create_function(|lua, args: mlua::MultiValue| {
let mut args_iter = args.into_iter();
let format: String = match args_iter.next() {
Some(Value::String(s)) => s.to_str()?.to_string(),
Some(Value::Nil) | None => "%c".to_string(),
_ => return Err(mlua::Error::runtime("os.date: format must be a string")),
};
let epoch: i64 = match args_iter.next() {
Some(Value::Integer(n)) => n,
Some(Value::Number(n)) => n as i64,
Some(Value::Nil) | None => {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64
}
_ => return Err(mlua::Error::runtime("os.date: time must be a number")),
};
let tz_offset_hours: f64 = match args_iter.next() {
Some(Value::Number(n)) => n,
Some(Value::Integer(n)) => n as f64,
Some(Value::Nil) | None => 0.0,
_ => return Err(mlua::Error::runtime("os.date: tz_offset must be a number (hours from UTC)")),
};
let (fmt, offset_secs) = if let Some(stripped) = format.strip_prefix('!') {
(stripped, 0i64)
} else {
(format.as_str(), (tz_offset_hours * 3600.0) as i64)
};
let secs = epoch + offset_secs;
let mut days = (secs / 86400) as i32;
let tod = (secs % 86400) as i32;
let hour = tod / 3600;
let min = (tod % 3600) / 60;
let sec = tod % 60;
let mut year: i32 = 1970;
loop {
let yd = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) { 366 } else { 365 };
if days < yd { break; }
days -= yd;
year += 1;
}
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
let mdays: [i32; 12] = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let mut month: i32 = 0;
while month < 12 && days >= mdays[month as usize] {
days -= mdays[month as usize];
month += 1;
}
let day = days + 1;
month += 1;
if fmt == "*t" {
let t = lua.create_table()?;
t.set("year", year)?;
t.set("month", month)?;
t.set("day", day)?;
t.set("hour", hour)?;
t.set("min", min)?;
t.set("sec", sec)?;
return Ok(Value::Table(t));
}
let result = fmt
.replace("%Y", &format!("{year:04}"))
.replace("%m", &format!("{month:02}"))
.replace("%d", &format!("{day:02}"))
.replace("%H", &format!("{hour:02}"))
.replace("%M", &format!("{min:02}"))
.replace("%S", &format!("{sec:02}"))
.replace("%c", &format!("{year:04}-{month:02}-{day:02} {hour:02}:{min:02}:{sec:02}"));
Ok(Value::String(lua.create_string(&result)?))
})?;
os_table.set("date", date_fn)?;
lua.globals().set("os", os_table)?;
Ok(())
}
fn get_hostname() -> Result<String, String> {
let mut buf = [0u8; 256];
let ret = unsafe { libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), buf.len()) };
if ret != 0 {
let err = std::io::Error::last_os_error();
return Err(format!("{err}"));
}
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8(buf[..len].to_vec()).map_err(|e| format!("hostname is not valid UTF-8: {e}"))
}