From 6d88a7e5c9f973765db3b3e6acbb09c56cde0340 Mon Sep 17 00:00:00 2001 From: Compey Date: Tue, 22 Aug 2023 21:58:28 +0530 Subject: [PATCH] feat: initial concept and 20% of implementation --- Cargo.lock | 86 +++++++++++++++ Cargo.toml | 1 + src/lune/builtins/date_time.rs | 123 ++++++++++++++++++++++ src/lune/lua/mod.rs | 1 + src/lune/lua/time/clock.rs | 187 +++++++++++++++++++++++++++++++++ src/lune/lua/time/mod.rs | 1 + 6 files changed, 399 insertions(+) create mode 100644 src/lune/builtins/date_time.rs create mode 100644 src/lune/lua/time/clock.rs create mode 100644 src/lune/lua/time/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c7e4220..4c61b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.3.2" @@ -294,6 +309,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clap" version = "4.3.21" @@ -393,6 +423,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.9" @@ -883,6 +919,29 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.4.0" @@ -1033,6 +1092,7 @@ dependencies = [ "anyhow", "async-compression", "async-trait", + "chrono", "clap", "console", "dialoguer", @@ -2123,6 +2183,17 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.2.27" @@ -2471,6 +2542,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2612,6 +2689,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.1", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 0bc1d5c..12b1c16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,3 +133,4 @@ rbx_dom_weak = { optional = true, version = "2.5.0" } rbx_reflection = { optional = true, version = "4.3.0" } rbx_reflection_database = { optional = true, version = "0.2.7" } rbx_xml = { optional = true, version = "0.13.1" } +chrono = "0.4.26" diff --git a/src/lune/builtins/date_time.rs b/src/lune/builtins/date_time.rs new file mode 100644 index 0000000..fd2db01 --- /dev/null +++ b/src/lune/builtins/date_time.rs @@ -0,0 +1,123 @@ +use std::io::ErrorKind as IoErrorKind; +use std::path::{PathBuf, MAIN_SEPARATOR}; + +use mlua::prelude::*; +use tokio::fs; + +use crate::lune::lua::{ + fs::{copy, FsMetadata, FsWriteOptions}, + table::TableBuilder, +}; + +pub fn create(lua: &'static Lua) -> LuaResult { + TableBuilder::new(lua)? + .with_async_function("readFile", fs_read_file)? + .with_async_function("readDir", fs_read_dir)? + .with_async_function("writeFile", fs_write_file)? + .with_async_function("writeDir", fs_write_dir)? + .with_async_function("removeFile", fs_remove_file)? + .with_async_function("removeDir", fs_remove_dir)? + .with_async_function("metadata", fs_metadata)? + .with_async_function("isFile", fs_is_file)? + .with_async_function("isDir", fs_is_dir)? + .with_async_function("move", fs_move)? + .with_async_function("copy", fs_copy)? + .build_readonly() +} + +async fn fs_read_file(lua: &Lua, path: String) -> LuaResult { + let bytes = fs::read(&path).await.into_lua_err()?; + lua.create_string(bytes) +} + +async fn fs_read_dir(_: &Lua, path: String) -> LuaResult> { + let mut dir_strings = Vec::new(); + let mut dir = fs::read_dir(&path).await.into_lua_err()?; + while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? { + if let Some(dir_path_str) = dir_entry.path().to_str() { + dir_strings.push(dir_path_str.to_owned()); + } else { + return Err(LuaError::RuntimeError(format!( + "File path could not be converted into a string: '{}'", + dir_entry.path().display() + ))); + } + } + let mut dir_string_prefix = path; + if !dir_string_prefix.ends_with(MAIN_SEPARATOR) { + dir_string_prefix.push(MAIN_SEPARATOR); + } + let dir_strings_no_prefix = dir_strings + .iter() + .map(|inner_path| { + inner_path + .trim() + .trim_start_matches(&dir_string_prefix) + .to_owned() + }) + .collect::>(); + Ok(dir_strings_no_prefix) +} + +async fn fs_write_file(_: &Lua, (path, contents): (String, LuaString<'_>)) -> LuaResult<()> { + fs::write(&path, &contents.as_bytes()).await.into_lua_err() +} + +async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> { + fs::create_dir_all(&path).await.into_lua_err() +} + +async fn fs_remove_file(_: &Lua, path: String) -> LuaResult<()> { + fs::remove_file(&path).await.into_lua_err() +} + +async fn fs_remove_dir(_: &Lua, path: String) -> LuaResult<()> { + fs::remove_dir_all(&path).await.into_lua_err() +} + +async fn fs_metadata(_: &Lua, path: String) -> LuaResult { + match fs::metadata(path).await { + Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()), + Ok(meta) => Ok(FsMetadata::from(meta)), + Err(e) => Err(e.into()), + } +} + +async fn fs_is_file(_: &Lua, path: String) -> LuaResult { + match fs::metadata(path).await { + Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false), + Ok(meta) => Ok(meta.is_file()), + Err(e) => Err(e.into()), + } +} + +async fn fs_is_dir(_: &Lua, path: String) -> LuaResult { + match fs::metadata(path).await { + Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false), + Ok(meta) => Ok(meta.is_dir()), + Err(e) => Err(e.into()), + } +} + +async fn fs_move(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> { + let path_from = PathBuf::from(from); + if !path_from.exists() { + return Err(LuaError::RuntimeError(format!( + "No file or directory exists at the path '{}'", + path_from.display() + ))); + } + let path_to = PathBuf::from(to); + if !options.overwrite && path_to.exists() { + return Err(LuaError::RuntimeError(format!( + "A file or directory already exists at the path '{}'", + path_to.display() + ))); + } + fs::rename(path_from, path_to).await.into_lua_err()?; + Ok(()) +} + +async fn fs_copy(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> { + copy(from, to, options).await +} diff --git a/src/lune/lua/mod.rs b/src/lune/lua/mod.rs index 944ea44..7bd037c 100644 --- a/src/lune/lua/mod.rs +++ b/src/lune/lua/mod.rs @@ -9,5 +9,6 @@ pub mod serde; pub mod stdio; pub mod table; pub mod task; +pub mod time; pub use create::create as create_lune_lua; diff --git a/src/lune/lua/time/clock.rs b/src/lune/lua/time/clock.rs new file mode 100644 index 0000000..d41c583 --- /dev/null +++ b/src/lune/lua/time/clock.rs @@ -0,0 +1,187 @@ +use anyhow::Result; +use chrono::prelude::*; + +// TODO: Proper error handing and stuff + +pub enum TimestampType { + Seconds, + Millis, +} + +pub struct Clock { + pub unix_timestamp: i64, + pub unix_timestamp_millis: i64, +} + +impl Clock { + /// Returns a DateTime representing the current moment in time + pub fn now() -> Self { + let time = Utc::now(); + + Self { + unix_timestamp: time.timestamp(), + unix_timestamp_millis: time.timestamp_millis(), + } + } + + /// Returns a new DateTime object from the given unix timestamp, in either seconds on + /// milliseconds. In case of failure, defaults to the (seconds or + /// milliseconds) since January 1st, 1970 at 00:00 (UTC) + pub fn from_unix_timestamp(timestamp_kind: TimestampType, unix_timestamp: i64) -> Self { + let time_chrono = match timestamp_kind { + TimestampType::Seconds => NaiveDateTime::from_timestamp_opt(unix_timestamp, 0), + TimestampType::Millis => NaiveDateTime::from_timestamp_millis(unix_timestamp), + }; + + if let Some(time) = time_chrono { + Self { + unix_timestamp: time.timestamp(), + unix_timestamp_millis: time.timestamp_millis(), + } + } else { + Self::now() + } + } + + pub fn from_local_time(date_time: Option) -> Self { + if let Some(date_time) = date_time { + let local_time: DateTime = Local + .from_local_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day) + .expect("invalid date"), + NaiveTime::from_hms_milli_opt( + date_time.hour, + date_time.minute, + date_time.second, + date_time.millisecond, + ) + .expect("invalid time"), + )) + .unwrap(); + + Self { + unix_timestamp: local_time.timestamp(), + unix_timestamp_millis: local_time.timestamp_millis(), + } + } else { + let local_time = Local::now(); + + Self { + unix_timestamp: local_time.timestamp(), + unix_timestamp_millis: local_time.timestamp_millis(), + } + } + } + + pub fn from_iso_date(iso_date: T) -> Self + where + T: ToString, + { + let time = DateTime::parse_from_str(iso_date.to_string().as_str(), "%Y-%m-%dT%H:%M:%SZ") + .expect("invalid ISO 8601 string"); + + Self { + unix_timestamp: time.timestamp(), + unix_timestamp_millis: time.timestamp_millis(), + } + } +} + +pub struct DateTimeConstructor { + year: i32, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, + millisecond: u32, +} + +impl Default for DateTimeConstructor { + /// Constructs the default state for DateTimeConstructor, which is the Unix Epoch. + fn default() -> Self { + Self { + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + millisecond: 0, + } + } +} + +pub enum Month { + January, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, +} + +impl DateTimeConstructor { + pub fn with_year(&mut self, year: i32) -> &Self { + self.year = year; + + self + } + + pub fn with_month(&mut self, month: Month) -> &Self { + let month = match month { + Month::January => 1, + Month::February => 2, + Month::March => 3, + Month::April => 4, + Month::May => 5, + Month::June => 6, + Month::July => 7, + Month::August => 8, + Month::September => 9, + Month::October => 10, + Month::November => 11, + Month::December => 12, + }; + + self.month = month; + + self + } + + pub fn with_day(&mut self, day: u32) -> &Self { + self.day = day; + + self + } + + pub fn with_hour(&mut self, hour: u32) -> &Self { + self.hour = hour; + + self + } + + pub fn with_minute(&mut self, minute: u32) -> &Self { + self.minute = minute; + + self + } + + pub fn with_second(&mut self, second: u32) -> &Self { + self.second = second; + + self + } + + pub fn with_millisecond(&mut self, millisecond: u32) -> &Self { + self.millisecond = millisecond; + + self + } +} diff --git a/src/lune/lua/time/mod.rs b/src/lune/lua/time/mod.rs new file mode 100644 index 0000000..159730d --- /dev/null +++ b/src/lune/lua/time/mod.rs @@ -0,0 +1 @@ +pub mod clock;