diff --git a/Cargo.lock b/Cargo.lock index d073452..7549995 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,17 +572,21 @@ dependencies = [ name = "dal_core" version = "0.1.0" dependencies = [ + "anstyle", "anyhow", + "async-trait", "auth-git2", "blake3", "clap", "darklua-demo", "dirs", + "durationfmt", + "env_logger", "fs-err", - "full_moon 1.1.0", + "full_moon", "git2", "hex", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "path-slash", "pathdiff", @@ -608,7 +612,7 @@ dependencies = [ "durationfmt", "elsa", "env_logger", - "full_moon 1.1.2", + "full_moon", "hex", "json5", "log", @@ -873,20 +877,6 @@ dependencies = [ "libc", ] -[[package]] -name = "full_moon" -version = "1.1.0" -source = "git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute#ec432d5cac3999cfe46ae782cadad5dd5c442631" -dependencies = [ - "bytecount", - "cfg-if 1.0.0", - "derive_more", - "full_moon_derive 0.11.0 (git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute)", - "paste", - "serde", - "smol_str", -] - [[package]] name = "full_moon" version = "1.1.2" @@ -896,7 +886,7 @@ dependencies = [ "bytecount", "cfg-if 1.0.0", "derive_more", - "full_moon_derive 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "full_moon_derive", "paste", "serde", "smol_str", @@ -914,17 +904,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "full_moon_derive" -version = "0.11.0" -source = "git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute#ec432d5cac3999cfe46ae782cadad5dd5c442631" -dependencies = [ - "indexmap 2.6.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "futures" version = "0.3.31" @@ -1092,7 +1071,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -1414,9 +1393,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.1", @@ -1533,7 +1512,7 @@ dependencies = [ [[package]] name = "kaledis" -version = "1.2.1" +version = "1.3.0" dependencies = [ "anyhow", "async-compression", @@ -1545,10 +1524,13 @@ dependencies = [ "dal_core", "futures", "glob", + "indexmap 2.7.0", "inquire", "reqwest", "semver", "serde", + "strum", + "strum_macros", "tokio", "tokio-tar", "toml", @@ -2483,7 +2465,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "ryu", "serde", @@ -2618,6 +2600,9 @@ name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] [[package]] name = "strum_macros" @@ -2914,7 +2899,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -3614,7 +3599,7 @@ dependencies = [ "displaydoc", "flate2", "hmac", - "indexmap 2.6.0", + "indexmap 2.7.0", "lzma-rs", "memchr", "pbkdf2", diff --git a/Cargo.toml b/Cargo.toml index 9b3bc6d..835951b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kaledis" -version = "1.2.1" +version = "1.3.0" edition = "2021" authors = ["lettuce-magician", "orpos"] documentation = "https://github.com/orpos/kaledis" @@ -15,7 +15,7 @@ inquire = "0.7.5" serde = { version = "1.0.215", features = ["derive"] } tokio = { version = "1.41.1", features = ["full"] } toml = "0.8.19" -dal_core = { path="dal_custom", version="0.1.0" } +dal_core = { path="./dal_custom", version="0.1.0" } url = { version = "2.5.3", features = ["serde"] } glob = "0.3.1" zip = "2.2.0" @@ -27,3 +27,6 @@ console = { version = "0.15.8", features = ["windows-console-colors"] } tokio-tar = "0.3.1" async-compression = { version = "0.4.18", features = ["gzip", "tokio"] } futures = "0.3.31" +strum_macros = "0.26.4" +strum = { version = "0.26.3", features = ["strum_macros"] } +indexmap = "2.7.0" diff --git a/dal_custom/.gitignore b/dal_custom/.gitignore index e69de29..a282fd7 100644 --- a/dal_custom/.gitignore +++ b/dal_custom/.gitignore @@ -0,0 +1,6 @@ +playground + + +# Added by cargo + +/target diff --git a/dal_custom/.gitmodules b/dal_custom/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/dal_custom/Cargo.lock b/dal_custom/Cargo.lock index 586291d..8d08a18 100644 --- a/dal_custom/Cargo.lock +++ b/dal_custom/Cargo.lock @@ -93,6 +93,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "auth-git2" version = "0.5.5" @@ -309,9 +320,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -352,17 +363,21 @@ dependencies = [ ] [[package]] -name = "dal" +name = "dal_core" version = "0.1.0" dependencies = [ + "anstyle", "anyhow", + "async-trait", "auth-git2", "blake3", "clap", "darklua-demo", "dirs", + "durationfmt", + "env_logger", "fs-err", - "full_moon 1.1.0 (git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute)", + "full_moon", "git2", "hex", "indexmap 2.6.0", @@ -391,7 +406,7 @@ dependencies = [ "durationfmt", "elsa", "env_logger", - "full_moon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "full_moon", "hex", "json5", "log", @@ -552,28 +567,14 @@ dependencies = [ [[package]] name = "full_moon" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcd269eb6078dde1b26db6368cb0b062045976d4d858d85b28f4f59ba49aab4e" -dependencies = [ - "bytecount", - "cfg-if 1.0.0", - "derive_more", - "full_moon_derive 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "paste", - "serde", - "smol_str", -] - -[[package]] -name = "full_moon" -version = "1.1.0" -source = "git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute#ec432d5cac3999cfe46ae782cadad5dd5c442631" +checksum = "4c7dafc2cbadd914f7019a08395a3a29c5d735cb6762751c5669e48c5ff2c775" dependencies = [ "bytecount", "cfg-if 1.0.0", "derive_more", - "full_moon_derive 0.11.0 (git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute)", + "full_moon_derive", "paste", "serde", "smol_str", @@ -591,17 +592,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "full_moon_derive" -version = "0.11.0" -source = "git+https://github.com/jiwonz/full-moon.git?branch=fix-feature-attribute#ec432d5cac3999cfe46ae782cadad5dd5c442631" -dependencies = [ - "indexmap 2.6.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -754,9 +744,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -775,10 +765,11 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1213,7 +1204,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax", ] @@ -1225,9 +1216,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1295,9 +1286,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -1638,9 +1629,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if 1.0.0", "once_cell", @@ -1649,9 +1640,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", @@ -1664,9 +1655,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1674,9 +1665,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", @@ -1687,9 +1678,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wax" @@ -1711,9 +1702,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/dal_custom/Cargo.toml b/dal_custom/Cargo.toml index e46f0e4..f2a9753 100644 --- a/dal_custom/Cargo.toml +++ b/dal_custom/Cargo.toml @@ -3,6 +3,9 @@ name = "dal_core" version = "0.1.0" edition = "2021" +[badges] +github = { repository = "CavefulGames/dal" } + [lib] name = "dal_core" path = "src/lib/mod.rs" @@ -16,10 +19,8 @@ anyhow = "1.0.89" auth-git2 = "0.5.5" blake3 = "1.5.4" clap = { version = "4.5.20", features = ["derive"] } -darklua-demo = "3.2.1" dirs = "5.0.1" fs-err = "2.11.0" -full_moon = { git = "https://github.com/jiwonz/full-moon.git", branch = "fix-feature-attribute", features = ["luau", "lua53"] } git2 = "0.19.0" hex = "0.4.3" serde = { version = "1.0.214", features = ["derive"] } @@ -33,3 +34,9 @@ log = "0.4.21" indexmap = { version = "2.6.0", features = ["serde"] } pathdiff = "0.2.2" path-slash = "0.2.1" +durationfmt = "0.1.1" +async-trait = "0.1.83" +full_moon = { version = "1.1.2", features = ["luau", "lua53"] } +darklua-demo = "3.2.1" +env_logger = "0.11.5" +anstyle = "1.0.10" diff --git a/dal_custom/README.md b/dal_custom/README.md index 8673415..6ab7d87 100644 --- a/dal_custom/README.md +++ b/dal_custom/README.md @@ -1,8 +1,6 @@ # Dal Dal(달) is a Luau-to-Lua transpiler based on `darklua`, designed specifically for `Lua 5.3`. -## This copy was made because some fields were not public - ## Note This project is still in W.I.P @@ -10,11 +8,14 @@ This project is still in W.I.P - [x] Implement CLI. - [x] Implement basic transpilation process using `darklua` and `full-moon`. - [x] Implement modifiers (such as converting number literals and generalized iterations) -- [ ] Implement basic lua polyfills. -- [ ] Add tests for polyfills. -- [ ] Add tests for transpilation. +- [x] Implement basic lua polyfills. +- [x] Add tests for polyfills. +- [ ] Add tests for transpilation. (to ensure the same results in lua and luau) - [ ] Add tests for dal internally. -- [ ] Add logging for dal internally for debug. +- [x] Add logging for dal internally for debug. +- [x] `convert_bit32` modifier now converts `bit32.btest`. +- [x] Add comments for docs and code readability. (WIP) +- [x] Optimize polyfill. ## Installation Coming soon! (will be available at `rokit` and `crates.io`(for `cargo install`)) @@ -43,17 +44,14 @@ dal transpile [input] [output] ## Example ### `dal.toml` ```toml -input = "inputs" -output = "outputs" file_extension = "lua" target_version = "lua53" minify = true [modifiers] convert_bit32 = true -optimize_table_initializers = true -[libs] +[globals] ``` @@ -82,15 +80,15 @@ print(t) ### `outputs/output.luau` ```lua -local math=require'./__dal_libs__'.math local io=nil local module=nil local package=nil local dofile=nil local loadfile=nil local log=math.log +local math=require'./__polyfill__'.math local table=require'./__polyfill__'.table local io=nil local module=nil local package=nil local dofile=nil local loadfile=nil local load=nil local log=math.log local floor=math.floor -do -end do -end do -end do end do + do +end do +end do +end do end do -end do -end local t={} +end do +end local t=table.create(1) local function byteswap(n) return ((((((((n<<24)&0xFFFFFFFF)|((((n<<8)&0xFFFFFFFF)&0xff0000)&0xFFFFFFFF))&0xFFFFFFFF)|((((n>>8)&0xFFFFFFFF)&0xff00)&0xFFFFFFFF))&0xFFFFFFFF)|((n>>24)&0xFFFFFFFF))&0xFFFFFFFF) diff --git a/dal_custom/src/cli/clean.rs b/dal_custom/src/cli/clean.rs new file mode 100644 index 0000000..b79f827 --- /dev/null +++ b/dal_custom/src/cli/clean.rs @@ -0,0 +1,24 @@ +use std::process::ExitCode; + +use anyhow::Result; +use clap::Parser; +use dal_core::polyfill; +use url::Url; + +/// Clean dal polyfills cache +#[derive(Debug, Clone, Parser)] +pub struct CleanCommand { + repo: Option +} + +impl CleanCommand { + pub async fn run(self) -> Result { + if let Some(repo) = self.repo { + polyfill::clean_cache(&repo).await?; + } else { + polyfill::clean_cache_all().await?; + } + + return Ok(ExitCode::SUCCESS); + } +} diff --git a/dal_custom/src/cli/init.rs b/dal_custom/src/cli/init.rs index 202c1b5..1f2e781 100644 --- a/dal_custom/src/cli/init.rs +++ b/dal_custom/src/cli/init.rs @@ -2,7 +2,7 @@ use std::process::ExitCode; use anyhow::Result; use clap::Parser; -use dal_core::manifest::Manifest; +use dal_core::manifest::{Manifest, WritableManifest}; use crate::cli::DEFAULT_MANIFEST_PATH; diff --git a/dal_custom/src/cli/mod.rs b/dal_custom/src/cli/mod.rs index ab54bf1..a379fae 100644 --- a/dal_custom/src/cli/mod.rs +++ b/dal_custom/src/cli/mod.rs @@ -1,15 +1,18 @@ use std::process::ExitCode; use anyhow::Result; -use clap::{Parser, Subcommand}; +use clap::{Args, Parser, Subcommand}; mod fetch; mod init; mod transpile; +mod clean; use fetch::FetchCommand; use init::InitCommand; +use log::LevelFilter; use transpile::TranspileCommand; +use clean::CleanCommand; pub const DEFAULT_POLYFILL_URL: &str = "https://github.com/CavefulGames/dal-polyfill"; pub const DEFAULT_MANIFEST_PATH: &str = "dal.toml"; @@ -19,26 +22,48 @@ pub enum CliSubcommand { Transpile(TranspileCommand), Init(InitCommand), Fetch(FetchCommand), + Clean(CleanCommand), +} + +#[derive(Debug, Args, Clone)] +pub struct GlobalOptions { + /// Sets verbosity level (can be specified multiple times) + #[arg(long, short, global(true), action = clap::ArgAction::Count)] + verbose: u8, +} + +impl GlobalOptions { + pub fn get_log_level_filter(&self) -> LevelFilter { + match self.verbose { + 0 => LevelFilter::Warn, + 1 => LevelFilter::Info, + 2 => LevelFilter::Debug, + _ => LevelFilter::Trace, + } + } } /// Transpile Luau scripts #[derive(Parser, Debug, Clone)] #[command(version, about, long_about = None)] pub struct Dal { + #[command(flatten)] + global_options: GlobalOptions, #[clap(subcommand)] subcommand: CliSubcommand, } impl Dal { - pub fn new() -> Self { - Self::parse() - } - pub async fn run(self) -> Result { match self.subcommand { CliSubcommand::Transpile(cmd) => cmd.run().await, CliSubcommand::Init(cmd) => cmd.run().await, CliSubcommand::Fetch(cmd) => cmd.run().await, + CliSubcommand::Clean(cmd) => cmd.run().await, } } + + pub fn get_log_level_filter(&self) -> LevelFilter { + self.global_options.get_log_level_filter() + } } diff --git a/dal_custom/src/cli/transpile.rs b/dal_custom/src/cli/transpile.rs index 61072fb..49d47a3 100644 --- a/dal_custom/src/cli/transpile.rs +++ b/dal_custom/src/cli/transpile.rs @@ -2,7 +2,12 @@ use std::{path::PathBuf, process::ExitCode, str::FromStr}; use anyhow::Result; use clap::Parser; -use dal_core::{manifest::Manifest, polyfill::Polyfill, transpiler::Transpiler}; +use dal_core::{ + manifest::{Manifest, WritableManifest}, + polyfill::Polyfill, + transpiler::Transpiler, +}; +use std::time::Instant; use url::Url; use super::{DEFAULT_MANIFEST_PATH, DEFAULT_POLYFILL_URL}; @@ -16,11 +21,13 @@ pub struct TranspileCommand { impl TranspileCommand { pub async fn run(self) -> Result { + let process_start_time = Instant::now(); + let manifest = Manifest::from_file(DEFAULT_MANIFEST_PATH).await?; let polyfill = Polyfill::new(&Url::from_str(DEFAULT_POLYFILL_URL)?).await?; let mut transpiler = Transpiler::default(); transpiler = transpiler.with_manifest(&manifest); - transpiler = transpiler.with_polyfill(polyfill); + transpiler = transpiler.with_polyfill(polyfill, None); transpiler .process( @@ -29,6 +36,10 @@ impl TranspileCommand { ) .await?; + let process_duration = durationfmt::to_string(process_start_time.elapsed()); + + println!("Successfully transpiled in {}", process_duration); + return Ok(ExitCode::SUCCESS); } } diff --git a/dal_custom/src/lib/injector.rs b/dal_custom/src/lib/injector.rs new file mode 100644 index 0000000..18263d0 --- /dev/null +++ b/dal_custom/src/lib/injector.rs @@ -0,0 +1,135 @@ +use std::{ + borrow::Cow, + collections::HashSet, + path::{Path, PathBuf}, +}; + +use anyhow::{anyhow, Result}; +use full_moon::{ + tokenizer::{Token, TokenType}, + visitors::Visitor, + LuaVersion, +}; +use path_slash::PathBufExt; +use pathdiff::diff_paths; +use tokio::fs; + +#[inline] +fn make_relative(path: &PathBuf) -> Cow { + if path.starts_with(".") | path.starts_with("..") { + Cow::Borrowed(path.as_path()) + } else { + Cow::Owned(Path::new(".").join(path)) + } +} + +#[derive(Debug)] +struct CollectUsedLibraries { + libraries: HashSet, + used_libraries: HashSet, +} + +impl CollectUsedLibraries { + fn new(libraries: HashSet) -> Self { + Self { + libraries, + used_libraries: HashSet::new(), + } + } +} + +impl Visitor for CollectUsedLibraries { + fn visit_identifier(&mut self, identifier: &Token) { + if let TokenType::Identifier { identifier } = identifier.token_type() { + let identifier = identifier.to_string(); + if self.libraries.contains(&identifier) { + self.used_libraries.insert(identifier); + } + } + } +} + +/// Injector that injects module's export which is a table constructor. +pub struct Injector { + module_path: PathBuf, + exports: HashSet, + removes: Option>, + lua_version: LuaVersion, +} + +impl Injector { + pub fn new( + module_path: PathBuf, + exports: HashSet, + lua_version: LuaVersion, + removes: Option>, + ) -> Self { + Self { + module_path, + exports, + removes, + lua_version, + } + } + + pub fn module_path(&self) -> &PathBuf { + &self.module_path + } + + pub fn removes(&self) -> &Option> { + &self.removes + } + + pub async fn inject(&self, source_path: &PathBuf) -> Result<()> { + let parent = source_path + .parent() + .ok_or(anyhow!("File path must have parent path"))?; + let require_path = diff_paths(self.module_path(), parent) + .ok_or(anyhow!("Couldn't resolve the require path"))? + .with_extension(""); + let require_path = make_relative(&require_path).to_path_buf(); + + let code = fs::read_to_string(source_path).await?; + + let mut lines: Vec = code.lines().map(String::from).collect(); + let mut libraries_texts: Vec = Vec::new(); + + let ast = full_moon::parse_fallible(code.as_str(), self.lua_version) + .into_result() + .map_err(|errors| anyhow!("{:?}", errors))?; + + let mut collect_used_libs = CollectUsedLibraries::new(self.exports.clone()); + collect_used_libs.visit_ast(&ast); + + for lib in collect_used_libs.used_libraries { + log::debug!("used library: {}", lib); + libraries_texts.push(format!( + "local {}=require'{}'.{} ", + lib, + require_path.to_slash_lossy(), + lib + )); + } + + if let Some(removes) = self.removes() { + for lib in removes { + libraries_texts.push(format!("local {}=nil ", lib)); + } + } + + let libraries_text = libraries_texts.join(""); + if let Some(first_line) = lines.get_mut(0) { + first_line.insert_str(0, &libraries_text); + } else { + lines.push(libraries_text); + } + + let new_content = lines.join("\n"); + + log::debug!("injected source path: {:?}", source_path); + + fs::write(source_path, new_content).await?; + + Ok(()) + } +} diff --git a/dal_custom/src/lib/manifest.rs b/dal_custom/src/lib/manifest.rs index b27adb5..f00d322 100644 --- a/dal_custom/src/lib/manifest.rs +++ b/dal_custom/src/lib/manifest.rs @@ -1,11 +1,29 @@ use anyhow::{anyhow, Result}; use indexmap::IndexMap; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::path::PathBuf; use tokio::fs; use crate::TargetVersion; +#[async_trait::async_trait] +pub trait WritableManifest: Send + Sized + Serialize + DeserializeOwned { + #[inline] + async fn from_file(path: impl Into + Send) -> Result { + let content = fs::read_to_string(path.into()).await?; + + Ok(toml::from_str(content.as_str())?) + } + + #[inline] + async fn write(&self, path: impl Into + Send) -> Result<()> { + fs::write(path.into(), toml::to_string(self)?).await?; + + Ok(()) + } +} + +/// Manifest for dal transpiler. #[derive(Debug, Deserialize, Serialize)] pub struct Manifest { pub output: Option, @@ -13,10 +31,8 @@ pub struct Manifest { pub file_extension: Option, pub target_version: TargetVersion, pub minify: bool, - // #[serde(default, deserialize_with = "crate::serde_utils::string_or_struct")] - // generator: GeneratorParameters, pub modifiers: IndexMap, - pub libs: IndexMap, + pub globals: IndexMap, } impl Default for Manifest { @@ -29,82 +45,37 @@ impl Default for Manifest { minify: true, // generator: GeneratorParameters::RetainLines, modifiers: IndexMap::new(), - libs: IndexMap::new(), + globals: IndexMap::new(), } } } -impl Manifest { - // pub fn add_default_modifiers(&mut self) { - // for modifier_name in DEFAULT_LUAU_TO_LUA_MODIFIERS { - // self.insert_modifier(modifier_name.to_owned(), true); - // } - // if self.auto_optimize { - // for modifier_name in DEFAULT_OPTIMIZING_MODIFIERS { - // self.insert_modifier(modifier_name.to_owned(), true); - // } - // } - // } - - pub async fn from_file(path: impl Into) -> Result { - let content = fs::read_to_string(path.into()).await?; - - Ok(toml::from_str(content.as_str())?) - } - - pub async fn write(&self, path: impl Into) -> Result<()> { - fs::write(path.into(), toml::to_string(self)?).await?; - - Ok(()) - } - - // pub fn insert_modifier(&mut self, modifier_name: String, enabled: bool) { - // let enabled = if let Some(&old_enabled) = self.modifiers.get(&modifier_name) { - // old_enabled && enabled - // } else { - // enabled - // }; - // self.modifiers.insert(modifier_name, enabled); - // } - - // pub fn contains_rule(&self, modifier_name: String) -> bool { - // self.modifiers.contains_key(&modifier_name) - // } - - // pub fn modifiers(&self) -> Result> { - // self.modifiers.iter() - // .filter_map(|(key, &value)| { - // if value { - // Some(get_modifier_by_name(key.as_str())) - // } else { - // None - // } - // }) - // .collect() - // } +impl WritableManifest for Manifest {} +impl Manifest { + #[inline] pub fn modifiers(&self) -> &IndexMap { &self.modifiers } + #[inline] pub fn target_version(&self) -> &TargetVersion { &self.target_version } - // pub fn generator(&self) -> &GeneratorParameters { - // &self.generator - // } - + #[inline] pub fn extension(&self) -> &Option { &self.file_extension } + #[inline] pub fn require_input(&self, replacement: Option) -> Result { replacement .or(self.input.clone()) .ok_or_else(|| anyhow!("Error: 'inputs' is required but not provided.")) } + #[inline] pub fn require_output(&self, replacement: Option) -> Result { replacement .or(self.output.clone()) diff --git a/dal_custom/src/lib/mod.rs b/dal_custom/src/lib/mod.rs index 9865c21..05186ec 100644 --- a/dal_custom/src/lib/mod.rs +++ b/dal_custom/src/lib/mod.rs @@ -1,11 +1,14 @@ use full_moon::LuaVersion; use serde::{Deserialize, Serialize}; +pub mod injector; pub mod manifest; pub mod modifiers; pub mod polyfill; pub mod transpiler; +pub mod utils; +/// Represents lua versions that implement serde #[non_exhaustive] #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "snake_case")] @@ -13,6 +16,7 @@ pub enum TargetVersion { Lua51, Lua52, Lua53, + Luau, Default, } @@ -22,6 +26,7 @@ impl TargetVersion { TargetVersion::Lua51 => LuaVersion::lua51(), TargetVersion::Lua52 => LuaVersion::lua52(), TargetVersion::Lua53 => LuaVersion::lua53(), + TargetVersion::Luau => LuaVersion::luau(), TargetVersion::Default => LuaVersion::default(), } } diff --git a/dal_custom/src/lib/modifiers/convert_bit32.rs b/dal_custom/src/lib/modifiers/convert_bit32.rs index 77861b4..b086f13 100644 --- a/dal_custom/src/lib/modifiers/convert_bit32.rs +++ b/dal_custom/src/lib/modifiers/convert_bit32.rs @@ -61,10 +61,12 @@ enum Bit32Method { And, #[strum(serialize = "bor")] Or, - #[strum(serialize = "xor")] + #[strum(serialize = "bxor")] Xor, #[strum(serialize = "bnot")] Not, + #[strum(serialize = "btest")] + Test, } impl Bit32Method { @@ -101,6 +103,25 @@ impl Bit32Method { Some(parentheses.clone()), )); } + Bit32Method::Test => { + let second_arg = iter.next()?; + let band_exp = ast_util::create_binary_operator( + first_arg.clone(), + BinOp::Ampersand(TokenReference::symbol("&").unwrap()), + second_arg.clone(), + ); + let parenthese = ast_util::create_parentheses(band_exp, None); + let masking_bin_exp = mask_32bit(parenthese); + let not_equal_exp = ast_util::create_binary_operator( + masking_bin_exp, + BinOp::TildeEqual(TokenReference::symbol("~=").unwrap()), + ast_util::create_number("0"), + ); + return Some(ast_util::create_parentheses( + not_equal_exp, + Some(parentheses.clone()), + )); + } }; let second_arg = iter.next()?; @@ -181,7 +202,9 @@ impl VisitorMut for ConvertBit32 { return Stmt::Do( Do::new() .with_do_token(TokenReference::new( - Vec::new(), + vec![Token::new(TokenType::Whitespace { + characters: ShortString::new(" "), + })], Token::new(TokenType::Symbol { symbol: Symbol::Do }), do_trailing_trivia, )) @@ -192,7 +215,9 @@ impl VisitorMut for ConvertBit32 { Token::new(TokenType::Symbol { symbol: Symbol::End, }), - Vec::new(), + vec![Token::new(TokenType::Whitespace { + characters: ShortString::new(" "), + })], )), ); } @@ -215,7 +240,9 @@ impl VisitorMut for ConvertBit32 { return Stmt::Do( Do::new() .with_do_token(TokenReference::new( - Vec::new(), + vec![Token::new(TokenType::Whitespace { + characters: ShortString::new(" "), + })], Token::new(TokenType::Symbol { symbol: Symbol::Do }), do_trailing_trivia, )) @@ -265,7 +292,6 @@ impl ConvertBit32 { if let Expression::Var(exp) = exp { match exp { Var::Expression(var_exp) => { - // println!("{}", var_exp.prefix().to_string()); if !self.is_bit32_identifier(var_exp.prefix().to_string()) { return false; } @@ -276,15 +302,9 @@ impl ConvertBit32 { if let Suffix::Index(index) = first { let index = index_to_string(index); if let Some(index) = index { - // println!( - // "debug index: {}, var: {}", - // index.trim(), - // var.to_string().trim() - // ); if let Ok(method) = Bit32Method::from_str(index.trim()) { self.bit32_methods .insert(var.to_string().trim().to_owned(), method); - // println!("bit32 methods: {:?}", self.bit32_methods); return true; } } @@ -329,8 +349,6 @@ impl ConvertBit32 { (Some(first), None) => { // there's only a call(ex. `(1, 2)`) if let Suffix::Call(call) = first { - // println!("bit32 methods: {:?}", self.bit32_methods); - // println!("bit32 method: {:?}", self.bit32_methods.get(&prefix)); if let Some(method) = self.bit32_methods.get(&prefix) { return method.convert(call); } diff --git a/dal_custom/src/lib/modifiers/get_love_modules.rs b/dal_custom/src/lib/modifiers/get_love_modules.rs new file mode 100644 index 0000000..61c947c --- /dev/null +++ b/dal_custom/src/lib/modifiers/get_love_modules.rs @@ -0,0 +1,53 @@ +use std::sync::{ Arc, Mutex }; + +use darklua_core::{ + nodes::{ Block, FieldExpression, Prefix }, + process::{ DefaultVisitor, NodeProcessor, NodeVisitor }, + rules::{ Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties }, +}; +use indexmap::IndexSet; + +pub const GET_LOVE_MODIFIER_NAME: &str = "get_love"; + +struct Processor { + modules: Arc>>, +} + +impl NodeProcessor for Processor { + fn process_field_expression(&mut self, data: &mut FieldExpression) { + if let Prefix::Identifier(layer1) = data.get_prefix() { + if layer1.get_name() == "love" { + let module = data.get_field().get_name(); + self.modules.lock().unwrap().insert(module.clone()); + } + } + } +} + +#[derive(Default, Debug)] +pub struct GetLoveModules { + pub modules: Arc>>, +} + +impl FlawlessRule for GetLoveModules { + fn flawless_process(&self, block: &mut Block, _: &Context) { + let mut processor = Processor { + modules: Arc::clone(&self.modules), + }; + DefaultVisitor::visit_block(block, &mut processor); + } +} + +impl RuleConfiguration for GetLoveModules { + fn configure(&mut self, _: RuleProperties) -> Result<(), RuleConfigurationError> { + Ok(()) + } + + fn get_name(&self) -> &'static str { + GET_LOVE_MODIFIER_NAME + } + + fn serialize_to_properties(&self) -> RuleProperties { + RuleProperties::new() + } +} diff --git a/dal_custom/src/lib/modifiers/mod.rs b/dal_custom/src/lib/modifiers/mod.rs index eda5ee6..79fe16f 100644 --- a/dal_custom/src/lib/modifiers/mod.rs +++ b/dal_custom/src/lib/modifiers/mod.rs @@ -1,21 +1,27 @@ use std::str::FromStr; -use anyhow::{ anyhow, Result }; +use anyhow::{anyhow, Result}; use darklua_core::rules::Rule; -use full_moon::{ ast::Ast, visitors::VisitorMut }; +use full_moon::{ast::Ast, visitors::VisitorMut}; pub(crate) mod ast_util; +mod relative_path; mod convert_bit32; mod optimize_table_initializers; mod remove_generalized_iteration; mod remove_number_literals; -mod relative_path; +mod remove_redeclared_keys; +mod runtime_identifier; +mod get_love_modules; +pub use relative_path::*; +pub use get_love_modules::*; pub use convert_bit32::*; pub use optimize_table_initializers::*; -pub use relative_path::*; pub use remove_generalized_iteration::*; pub use remove_number_literals::*; +pub use remove_redeclared_keys::*; +pub use runtime_identifier::*; pub trait VisitorMutWrapper { fn visit_ast_boxed(&mut self, ast: Ast) -> Ast; @@ -48,8 +54,11 @@ impl FromStr for Modifier { } CONVERT_BIT32_MODIFIER_NAME => Modifier::FullMoonVisitor(Box::new( ConvertBit32::default(), - ) + ) as Box), + REMOVE_REDECLARED_KEYS_RULE_NAME => { + Modifier::DarkluaRule(Box::::default()) + } _ => Modifier::DarkluaRule(s.parse::>().map_err(|err| anyhow!(err))?), }; diff --git a/dal_custom/src/lib/modifiers/relative_path.rs b/dal_custom/src/lib/modifiers/relative_path.rs index b95da0a..f779aee 100644 --- a/dal_custom/src/lib/modifiers/relative_path.rs +++ b/dal_custom/src/lib/modifiers/relative_path.rs @@ -1,5 +1,6 @@ -// This is a custom rule needed for making relative imports like ../ +// This is a custom rule needed for making relative imports like ../ work // in love2d (lettuce asked for this btw) +// love2d gets with base the root of the project so i just translate it use std::path::{ self, Path, PathBuf } ; @@ -26,12 +27,11 @@ impl<'a> NodeProcessor for Processor<'a> { if expr.get_value().starts_with("../") { let pth = path ::absolute(self.path.parent().unwrap().join(expr.get_value())) - .expect("A"); - let ceb = pth.strip_prefix(self.project_root).expect("Path strip."); - + .expect("Failed To Find Module"); + let new_path = pth.strip_prefix(self.project_root).expect("Path strip failed."); *expr = StringExpression::from_value( - &ceb + &new_path .to_path_buf() .into_iter() .map(|x| x.to_str().unwrap()) diff --git a/dal_custom/src/lib/modifiers/remove_generalized_iteration.rs b/dal_custom/src/lib/modifiers/remove_generalized_iteration.rs index 83891e6..f6bd639 100644 --- a/dal_custom/src/lib/modifiers/remove_generalized_iteration.rs +++ b/dal_custom/src/lib/modifiers/remove_generalized_iteration.rs @@ -1,41 +1,16 @@ use darklua_core::nodes::{ - AssignStatement, - BinaryExpression, - BinaryOperator, - Block, - DoStatement, - Expression, - FieldExpression, - FunctionCall, - Identifier, - IfBranch, - IfStatement, - LocalAssignStatement, - Prefix, - Statement, - StringExpression, - TupleArguments, - TypedIdentifier, - Variable, + AssignStatement, BinaryExpression, BinaryOperator, Block, DoStatement, Expression, + FieldExpression, FunctionCall, Identifier, IfBranch, IfStatement, LocalAssignStatement, Prefix, + Statement, StringExpression, TupleArguments, TypedIdentifier, Variable, }; -use darklua_core::process::{ DefaultVisitor, NodeProcessor, NodeVisitor }; -use darklua_core::rules::{ Context, RuleConfiguration, RuleConfigurationError, RuleProperties }; +use darklua_core::process::{DefaultVisitor, NodeProcessor, NodeVisitor}; +use darklua_core::rules::{Context, RuleConfiguration, RuleConfigurationError, RuleProperties}; -use darklua_core::rules::runtime_identifier::RuntimeIdentifierBuilder; -use darklua_core::rules::{ Rule, RuleProcessResult }; +use super::runtime_identifier::RuntimeIdentifierBuilder; +use darklua_core::rules::{Rule, RuleProcessResult}; const METATABLE_VARIABLE_NAME: &str = "m"; -// const SETMETATABLE_IDENTIFIER: &str = "__DAL_getmetatable_iter"; - -// this is a very ugly workaround -// also i don't know if it's setmetatable_iter -const SETMETATABLE_IDENTIFIER: &str = - r#"(function(...) -if __DAL_getmetatable_iter == nil then -require("__dal_libs__") -end -return __DAL_getmetatable_iter(...) -end)"#; +const SETMETATABLE_IDENTIFIER: &str = "__DAL_getmetatable_iter"; struct Processor { iterator_identifier: String, @@ -45,16 +20,16 @@ struct Processor { } fn get_type_condition(arg: Expression, type_name: &str) -> Box { - let type_call = Box::new( - FunctionCall::new(Prefix::from_name("type"), TupleArguments::new(vec![arg]).into(), None) - ); - Box::new( - BinaryExpression::new( - BinaryOperator::Equal, - Expression::Call(type_call), - Expression::String(StringExpression::from_value(type_name)) - ) - ) + let type_call = Box::new(FunctionCall::new( + Prefix::from_name("type"), + TupleArguments::new(vec![arg]).into(), + None, + )); + Box::new(BinaryExpression::new( + BinaryOperator::Equal, + Expression::Call(type_call), + Expression::String(StringExpression::from_value(type_name)), + )) } impl Processor { @@ -78,28 +53,25 @@ impl Processor { _ => {} } let mut stmts: Vec = Vec::new(); - let iterator_typed_identifier = TypedIdentifier::new( - self.iterator_identifier.as_str() - ); + let iterator_typed_identifier = + TypedIdentifier::new(self.iterator_identifier.as_str()); let iterator_identifier = iterator_typed_identifier.get_identifier().clone(); - let invariant_typed_identifier = TypedIdentifier::new( - self.invariant_identifier.as_str() - ); + let invariant_typed_identifier = + TypedIdentifier::new(self.invariant_identifier.as_str()); let invariant_identifier = invariant_typed_identifier.get_identifier().clone(); - let control_typed_identifier = TypedIdentifier::new( - self.control_identifier.as_str() - ); + let control_typed_identifier = + TypedIdentifier::new(self.control_identifier.as_str()); let control_identifier = control_typed_identifier.get_identifier().clone(); let iterator_local_assign = LocalAssignStatement::new( vec![iterator_typed_identifier], - vec![exps[0].to_owned()] + vec![exps[0].to_owned()], ); let invar_control_local_assign = LocalAssignStatement::new( vec![invariant_typed_identifier, control_typed_identifier], - Vec::new() + Vec::new(), ); let iterator_exp = Expression::Identifier(iterator_identifier.clone()); @@ -117,78 +89,63 @@ impl Processor { let get_mt_call = FunctionCall::new( Prefix::from_name(SETMETATABLE_IDENTIFIER), TupleArguments::new(vec![iterator_exp.clone()]).into(), - None + None, ); let mt_local_assign = LocalAssignStatement::new( vec![mt_typed_identifier], - vec![get_mt_call.into()] + vec![get_mt_call.into()], ); - let if_mt_table_condition = get_type_condition( - mt_identifier.clone().into(), - "table" - ); + let if_mt_table_condition = + get_type_condition(mt_identifier.clone().into(), "table"); let mt_iter = FieldExpression::new( Prefix::Identifier(mt_identifier), - Identifier::new("__iter") - ); - let if_mt_iter_function_condition = get_type_condition( - mt_iter.clone().into(), - "function" + Identifier::new("__iter"), ); + let if_mt_iter_function_condition = + get_type_condition(mt_iter.clone().into(), "function"); let mt_iter_call = FunctionCall::from_prefix(Prefix::Field(Box::new(mt_iter))); let assign_from_iter = AssignStatement::new( vec![ Variable::Identifier(iterator_identifier.clone()), Variable::Identifier(invariant_identifier.clone()), - Variable::Identifier(control_identifier.clone()) + Variable::Identifier(control_identifier.clone()), ], - vec![mt_iter_call.into()] + vec![mt_iter_call.into()], ); let pairs_call = FunctionCall::new( Prefix::from_name("pairs"), TupleArguments::new(vec![iterator_identifier.clone().into()]).into(), - None + None, ); let assign_from_pairs = AssignStatement::new( vec![ Variable::Identifier(iterator_identifier), Variable::Identifier(invariant_identifier), - Variable::Identifier(control_identifier) + Variable::Identifier(control_identifier), ], - vec![pairs_call.into()] + vec![pairs_call.into()], ); let if_mt_table_block = Block::new(vec![assign_from_iter.into()], None); let if_not_mt_table_block = Block::new(vec![assign_from_pairs.into()], None); let if_mt_table_branch = IfBranch::new( - Expression::Binary( - Box::new( - BinaryExpression::new( - BinaryOperator::And, - Expression::Binary(if_mt_table_condition), - Expression::Binary(if_mt_iter_function_condition) - ) - ) - ), - if_mt_table_block - ); - let if_mt_table_stmt = IfStatement::new( - vec![if_mt_table_branch], - Some(if_not_mt_table_block) - ); - - let if_table_block = Block::new( - vec![mt_local_assign.into(), if_mt_table_stmt.into()], - None - ); - let if_table_branch = IfBranch::new( - Expression::Binary(if_table_condition), - if_table_block - ); - // TODO make else block + Expression::Binary(Box::new(BinaryExpression::new( + BinaryOperator::And, + Expression::Binary(if_mt_table_condition), + Expression::Binary(if_mt_iter_function_condition), + ))), + if_mt_table_block, + ); + let if_mt_table_stmt = + IfStatement::new(vec![if_mt_table_branch], Some(if_not_mt_table_block)); + + let if_table_block = + Block::new(vec![mt_local_assign.into(), if_mt_table_stmt.into()], None); + let if_table_branch = + IfBranch::new(Expression::Binary(if_table_condition), if_table_block); let if_table_stmt = IfStatement::new(vec![if_table_branch], None); stmts.push(iterator_local_assign.into()); @@ -231,7 +188,8 @@ pub struct RemoveGeneralizedIteration { impl Default for RemoveGeneralizedIteration { fn default() -> Self { Self { - runtime_identifier_format: "_DARKLUA_REMOVE_GENERALIZED_ITERATION_{name}{hash}".to_string(), + runtime_identifier_format: "_DARKLUA_REMOVE_GENERALIZED_ITERATION_{name}{hash}" + .to_string(), } } } @@ -241,7 +199,7 @@ impl Rule for RemoveGeneralizedIteration { let var_builder = RuntimeIdentifierBuilder::new( self.runtime_identifier_format.as_str(), format!("{block:?}").as_bytes(), - Some(vec![METATABLE_VARIABLE_NAME.to_string()]) + Some(vec![METATABLE_VARIABLE_NAME.to_string()]), )?; let mut processor = Processor { iterator_identifier: var_builder.build("iter")?, diff --git a/dal_custom/src/lib/modifiers/remove_redeclared_keys.rs b/dal_custom/src/lib/modifiers/remove_redeclared_keys.rs new file mode 100644 index 0000000..0f457a7 --- /dev/null +++ b/dal_custom/src/lib/modifiers/remove_redeclared_keys.rs @@ -0,0 +1,198 @@ +use darklua_core::nodes::{ + AssignStatement, Block, Expression, FunctionCall, FunctionExpression, Identifier, + IndexExpression, LocalAssignStatement, ParentheseExpression, ReturnStatement, Statement, + StringExpression, TableEntry, TableExpression, +}; +use darklua_core::process::{DefaultVisitor, Evaluator, LuaValue, NodeProcessor, NodeVisitor}; +use darklua_core::rules::{Context, RuleConfiguration, RuleConfigurationError, RuleProperties}; + +use super::runtime_identifier::RuntimeIdentifierBuilder; +use darklua_core::rules::{Rule, RuleProcessResult}; + +#[derive(Default)] +struct Processor { + evaluator: Evaluator, + table_identifier: String, + skip_next_table_exp: bool, +} + +impl Processor { + fn skip(&mut self, active: bool) { + self.skip_next_table_exp = active; + } +} + +use std::collections::HashMap; +use std::fmt::Debug; + +impl NodeProcessor for Processor { + fn process_expression(&mut self, exp: &mut Expression) { + if let Expression::Table(table_exp) = exp { + if self.skip_next_table_exp { + self.skip(false); + return; + } + let entries = table_exp.mutate_entries(); + let mut numeral_table = HashMap::new(); + let mut str_table = HashMap::new(); + let mut num_index: usize = 0; + let mut side_effect_stmts: Vec = Vec::new(); + + for (i, entry) in entries.iter().enumerate() { + match entry { + TableEntry::Index(index_entry) => { + let value = self.evaluator.evaluate(index_entry.get_key()); + match value { + LuaValue::Number(lua_index) => { + if lua_index.fract() == 0.0 && lua_index > 0.0 { + let key = (lua_index as usize) - 1; + if side_effect_stmts.is_empty() { + numeral_table.insert(key, i); + } else { + let assignment = AssignStatement::from_variable( + IndexExpression::new( + Identifier::new(self.table_identifier.as_str()), + key + 1, + ), + index_entry.get_value().clone(), + ); + side_effect_stmts.push(assignment.into()); + } + } + } + LuaValue::String(key) => { + if side_effect_stmts.is_empty() { + str_table.insert(key, i); + } else { + let assignment = AssignStatement::from_variable( + IndexExpression::new( + Identifier::new(self.table_identifier.as_str()), + StringExpression::from_value(key), + ), + index_entry.get_value().clone(), + ); + side_effect_stmts.push(assignment.into()); + } + } + LuaValue::Unknown => { + let assignment = AssignStatement::from_variable( + IndexExpression::new( + Identifier::new(self.table_identifier.as_str()), + index_entry.get_key().clone(), + ), + index_entry.get_value().clone(), + ); + side_effect_stmts.push(assignment.into()); + } + _ => (), + } + } + TableEntry::Value(_) => { + numeral_table.insert(num_index, i); + num_index += 1; + } + TableEntry::Field(field_entry) => { + let key = field_entry.get_field().get_name(); + str_table.insert(key.to_owned(), i); + } + } + } + + let mut keys: Vec<_> = numeral_table.keys().collect(); + keys.sort(); + let mut new_entries: Vec = Vec::new(); + + for i in keys { + let v = numeral_table[i]; + let entry = &entries[v]; + let new_entry = match entry { + TableEntry::Index(index_entry) => { + if *i <= num_index { + Some(TableEntry::Value(index_entry.get_value().clone())) + } else { + Some(TableEntry::Index(index_entry.clone())) + } + } + TableEntry::Value(exp) => Some(TableEntry::Value(exp.clone())), + _ => None, + }; + if let Some(new_entry) = new_entry { + new_entries.push(new_entry); + } + } + + for (_, v) in str_table { + let entry = &entries[v]; + new_entries.push(entry.clone()); + } + + entries.clear(); + for ent in new_entries { + entries.push(ent); + } + + if !side_effect_stmts.is_empty() { + let var = Identifier::new(self.table_identifier.as_str()); + let table_stmt = TableExpression::new(entries.clone()); + self.skip(true); + let local_assign_stmt = + LocalAssignStatement::new(vec![var.clone().into()], vec![table_stmt.into()]); + side_effect_stmts.insert(0, local_assign_stmt.into()); + let return_stmt = ReturnStatement::one(var); + let func_block = Block::new(side_effect_stmts, Some(return_stmt.into())); + let func = Expression::Function(FunctionExpression::from_block(func_block)); + let parenthese_func = ParentheseExpression::new(func); + let func_call = FunctionCall::from_prefix(parenthese_func); + let call_exp = Expression::Call(Box::new(func_call)); + *exp = call_exp; + } + } + } +} + +pub const REMOVE_REDECLARED_KEYS_RULE_NAME: &str = "remove_redeclared_keys"; + +/// A rule that removes redeclared keys in table and organize the components of a mixed table +#[derive(Debug, PartialEq, Eq)] +pub struct RemoveRedeclaredKeys { + runtime_identifier_format: String, +} + +impl Default for RemoveRedeclaredKeys { + fn default() -> Self { + Self { + runtime_identifier_format: "_DARKLUA_REMOVE_REDECLARED_KEYS_{name}{hash}".to_string(), + } + } +} + +impl Rule for RemoveRedeclaredKeys { + fn process(&self, block: &mut Block, _: &Context) -> RuleProcessResult { + let var_builder = RuntimeIdentifierBuilder::new( + self.runtime_identifier_format.as_str(), + format!("{block:?}").as_bytes(), + None, + )?; + let mut processor = Processor { + evaluator: Evaluator::default(), + table_identifier: var_builder.build("tbl")?, + skip_next_table_exp: false, + }; + DefaultVisitor::visit_block(block, &mut processor); + Ok(()) + } +} + +impl RuleConfiguration for RemoveRedeclaredKeys { + fn configure(&mut self, _: RuleProperties) -> Result<(), RuleConfigurationError> { + Ok(()) + } + + fn get_name(&self) -> &'static str { + REMOVE_REDECLARED_KEYS_RULE_NAME + } + + fn serialize_to_properties(&self) -> RuleProperties { + RuleProperties::new() + } +} diff --git a/dal_custom/src/lib/modifiers/runtime_identifier.rs b/dal_custom/src/lib/modifiers/runtime_identifier.rs new file mode 100644 index 0000000..8bfe550 --- /dev/null +++ b/dal_custom/src/lib/modifiers/runtime_identifier.rs @@ -0,0 +1,45 @@ +use blake3; +use hex; +use std::collections::HashMap; +use strfmt::strfmt; + +pub struct RuntimeIdentifierBuilder { + format: String, + hash: String, + keywords: Option>, +} + +impl RuntimeIdentifierBuilder { + pub fn new( + format: impl Into, + identifier: &[u8], + keywords: Option>, + ) -> Result { + let format: String = format.into(); + if !format.as_str().contains("{name}") { + return Err("`name` field is required for runtime identifier".to_string()); + } + let hash = blake3::hash(identifier); + Ok(Self { + format, + hash: hex::encode(&hash.as_bytes()[..8]), + keywords, + }) + } + + pub fn build(&self, name: &str) -> Result { + let mut vars = HashMap::new(); + vars.insert("name".to_owned(), name); + vars.insert("hash".to_owned(), self.hash.as_str()); + + let name = strfmt(&self.format, &vars).map_err(|err| err.to_string())?; + + if let Some(keywords) = &self.keywords { + if keywords.contains(&name) { + Err(format!("Runtime variable `{name}` cannot be set because it contains a reserved keyword."))?; + } + } + + Ok(name) + } +} diff --git a/dal_custom/src/lib/polyfill.rs b/dal_custom/src/lib/polyfill.rs index ecf6af7..2a8845e 100644 --- a/dal_custom/src/lib/polyfill.rs +++ b/dal_custom/src/lib/polyfill.rs @@ -8,49 +8,74 @@ use git2::Repository; use hex; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::io; use std::path::PathBuf; use tokio::fs; use url::Url; -#[derive(Debug, Deserialize, Serialize)] -pub struct Config { - input: PathBuf, - removes: Option>, - settings: IndexMap, - libraries: IndexMap, +use crate::manifest::WritableManifest; +use crate::{utils, TargetVersion}; + +/// Cleans cache from polyfill repository url. +pub async fn clean_cache(url: &Url) -> Result<()> { + let index_path = index_path(url)?; + fs::remove_dir_all(index_path).await?; + Ok(()) } -impl Config { - pub fn input(&self) -> &PathBuf { - &self.input - } +/// Cleans every caches of polyfill. +pub async fn clean_cache_all() -> Result<()> { + let path = cache_dir()?; + fs::remove_dir_all(path).await?; + Ok(()) +} - pub fn settings(&self) -> &IndexMap { - &self.settings - } +/// Gets cache directory path of polyfills. +pub fn cache_dir() -> Result { + Ok(dirs::cache_dir() + .ok_or_else(|| anyhow!("could not find cache directory"))? + .join("dal") + .join("polyfills")) +} - pub fn libraries(&self) -> &IndexMap { - &self.libraries - } +/// Polyfill's manifest (`/polyfill.toml` in a polyfill repository) +#[derive(Debug, Deserialize, Serialize)] +pub struct Manifest { + globals: PathBuf, + removes: Option>, + config: IndexMap, + lua_version: TargetVersion, +} - pub fn removes(&self) -> &Option> { - &self.removes - } +impl WritableManifest for Manifest {} + +/// Polyfill's globals. +#[derive(Debug)] +pub struct Globals { + path: PathBuf, + exports: HashSet, } -impl Config { - pub async fn from_file(path: impl Into) -> Result { - let content = fs::read_to_string(path.into()).await?; +impl Globals { + #[inline] + pub fn path(&self) -> &PathBuf { + &self.path + } - Ok(toml::from_str(content.as_str())?) + #[inline] + pub fn exports(&self) -> &HashSet { + &self.exports } } +/// Represents a polyfill structure. pub struct Polyfill { - path: PathBuf, repository: Repository, - config: Config, + path: PathBuf, + globals: Globals, + removes: Option>, + config: IndexMap, } fn index_path(url: &Url) -> anyhow::Result { @@ -64,16 +89,16 @@ fn index_path(url: &Url) -> anyhow::Result { let hash_hex = hex::encode(&hash.as_bytes()[..8]); let ident = format!("{}-{}", name, hash_hex); - let path = dirs::cache_dir() - .ok_or_else(|| anyhow!("could not find cache directory"))? - .join("dal") - .join("polyfills") + let path = cache_dir()? .join(ident); + log::debug!("index path {:?}", path); + Ok(path) } impl Polyfill { + /// Creates a new polyfill from git repository. pub async fn new(url: &Url) -> Result { let path = index_path(url)?; let repository = match Repository::open(path.as_path()) { @@ -91,15 +116,36 @@ impl Polyfill { } }; - let config = Config::from_file(path.join("config.toml")).await?; + log::info!("repository is ready"); + + //let manifest = Manifest::from_file(path.join("polyfill.toml")).await?; + let manifest_content = fs::read_to_string(path.join("polyfill.toml")).await?; + let manifest: Manifest = toml::from_str(&manifest_content)?; + + let globals_path = path.join(&manifest.globals); + log::debug!("globals path {:?}", globals_path); + let globals_ast = utils::parse_file(&globals_path, &manifest.lua_version).await?; + let exports = utils::get_exports_from_last_stmt(&utils::ParseTarget::FullMoonAst(globals_ast)) + .await? + .ok_or_else(|| anyhow!("Invalid polyfill structure. Polyfills' globals must return at least one global in a table."))?; + + let globals = Globals { + path: globals_path, + exports, + }; + + log::info!("polyfill ready"); Ok(Self { path, repository, - config, + globals: globals, + removes: manifest.removes, + config: manifest.config, }) } + /// Fetches and updates polyfill repository using git. pub fn fetch(&self) -> Result<()> { let mut remote = self.repository.find_remote("origin")?; let auth = GitAuthenticator::new(); @@ -124,11 +170,23 @@ impl Polyfill { Ok(()) } + #[inline] pub fn path(&self) -> &PathBuf { &self.path } - pub fn config(&self) -> &Config { + #[inline] + pub fn globals(&self) -> &Globals { + &self.globals + } + + #[inline] + pub fn removes(&self) -> &Option> { + &self.removes + } + + #[inline] + pub fn config(&self) -> &IndexMap { &self.config } } diff --git a/dal_custom/src/lib/transpiler.rs b/dal_custom/src/lib/transpiler.rs index 1aa9289..aaa2dd1 100644 --- a/dal_custom/src/lib/transpiler.rs +++ b/dal_custom/src/lib/transpiler.rs @@ -1,29 +1,21 @@ -use std::{ - borrow::Cow, - collections::HashSet, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{path::PathBuf, str::FromStr}; use anyhow::{anyhow, Result}; use darklua_core::{ rules::{self, bundle::BundleRequireMode}, BundleConfiguration, Configuration, GeneratorParameters, Options, Resources, }; -use full_moon::{ - ast::Ast, - tokenizer::{Token, TokenType}, - visitors::Visitor, -}; +use full_moon::ast::Ast; use indexmap::IndexMap; -use path_slash::PathBufExt; -use pathdiff::diff_paths; use tokio::fs; -use crate::{manifest::Manifest, modifiers::Modifier, polyfill::Polyfill, TargetVersion}; +use crate::{ + injector::Injector, manifest::Manifest, modifiers::Modifier, polyfill::Polyfill, utils, + TargetVersion, +}; pub const DAL_GLOBAL_IDENTIFIER_PREFIX: &str = "DAL_"; -pub const DEFAULT_INJECTED_LIB_FILE_NAME: &str = "__dal_libs__"; +pub const DEFAULT_INJECTED_POLYFILL_NAME: &str = "__polyfill__"; pub const DEFAULT_LUAU_TO_LUA_MODIFIERS: [&str; 8] = [ "remove_interpolated_string", @@ -37,17 +29,17 @@ pub const DEFAULT_LUAU_TO_LUA_MODIFIERS: [&str; 8] = [ ]; pub const DEFAULT_MINIFYING_MODIFIERS: [&str; 11] = [ - "remove_unused_variable", - "remove_unused_while", - "remove_unused_if_branch", "remove_spaces", "remove_nil_declaration", "remove_function_call_parens", - "remove_empty_do", "remove_comments", "convert_index_to_field", "compute_expression", "filter_after_early_return", + "remove_unused_variable", + "remove_unused_while", + "remove_unused_if_branch", + "remove_empty_do", ]; #[inline] @@ -73,52 +65,13 @@ fn default_modifiers_index() -> IndexMap { modifiers } -#[inline] -fn make_relative(path: &PathBuf) -> Cow { - if path.starts_with(".") | path.starts_with("..") { - Cow::Borrowed(path.as_path()) - } else { - Cow::Owned(Path::new(".").join(path)) - } -} - -#[derive(Debug)] -struct CollectUsedLibraries { - libraries_option: IndexMap, - used_libraries: HashSet, -} - -impl CollectUsedLibraries { - fn new(libraries_option: IndexMap) -> Self { - Self { - libraries_option, - used_libraries: HashSet::new(), - } - } - - fn think(&mut self, identifier: String) { - if let Some(&enabled) = self.libraries_option.get(&identifier) { - if enabled { - self.used_libraries.insert(identifier); - } - } - } -} - -impl Visitor for CollectUsedLibraries { - fn visit_identifier(&mut self, identifier: &Token) { - if let TokenType::Identifier { identifier } = identifier.token_type() { - self.think(identifier.to_string()); - } - } -} - /// A transpiler that transforms luau to lua pub struct Transpiler { modifiers: IndexMap, polyfill: Option, extension: Option, target_version: TargetVersion, + injected_polyfill_name: Option, } impl Default for Transpiler { @@ -128,6 +81,7 @@ impl Default for Transpiler { polyfill: None, extension: None, target_version: TargetVersion::Default, + injected_polyfill_name: None, } } } @@ -169,80 +123,19 @@ impl Transpiler { self } - pub fn with_polyfill(mut self, polyfill: Polyfill) -> Self { + pub fn with_polyfill( + mut self, + polyfill: Polyfill, + new_injected_polyfill_name: Option, + ) -> Self { self.polyfill = Some(polyfill); + self.injected_polyfill_name = new_injected_polyfill_name; self } + #[inline] async fn parse_file(&self, path: &PathBuf) -> Result { - let code = fs::read_to_string(&path).await?; - let ast = full_moon::parse_fallible( - code.as_str(), - (&self.target_version).to_lua_version().clone(), - ) - .into_result() - .map_err(|errors| anyhow!("full_moon parsing error: {:?}", errors))?; - - Ok(ast) - } - - pub async fn inject_library( - &self, - file_path: &PathBuf, - module_path: &PathBuf, - libraries: &IndexMap, - removes: &Option>, - ) -> Result<()> { - let parent = file_path - .parent() - .ok_or(anyhow!("File path must have parent path"))?; - let require_path = diff_paths(module_path, parent) - .ok_or(anyhow!("Couldn't resolve the require path"))? - .with_extension(""); - let require_path = make_relative(&require_path).to_path_buf(); - - let code = fs::read_to_string(file_path).await?; - - let mut lines: Vec = code.lines().map(String::from).collect(); - let mut libraries_texts: Vec = Vec::new(); - - let ast = full_moon::parse_fallible( - code.as_str(), - (&self.target_version).to_lua_version().clone(), - ) - .into_result() - .map_err(|errors| anyhow!("{:?}", errors))?; - - let mut collect_used_libs = CollectUsedLibraries::new(libraries.clone()); - collect_used_libs.visit_ast(&ast); - - for lib in collect_used_libs.used_libraries { - libraries_texts.push(format!( - "local {}=require'{}'.{} ", - lib, - require_path.to_slash_lossy(), - lib - )); - } - - if let Some(removes) = removes { - for lib in removes { - libraries_texts.push(format!("local {}=nil ", lib)); - } - } - - let libraries_text = libraries_texts.join(""); - if let Some(first_line) = lines.get_mut(0) { - first_line.insert_str(0, &libraries_text); - } else { - lines.push(libraries_text); - } - - let new_content = lines.join("\n"); - - fs::write(file_path, new_content).await?; - - Ok(()) + utils::parse_file(path, &self.target_version).await } async fn private_process( @@ -280,7 +173,6 @@ impl Transpiler { // Configuration::empty() // }; let mut config = Configuration::empty(); - if bundle { config = config.with_bundle_configuration(BundleConfiguration::new( BundleRequireMode::default(), @@ -344,64 +236,70 @@ impl Transpiler { Ok(created_files) } - pub async fn process(&self, input: PathBuf, output: PathBuf, additional_modifiers: Option<&mut Vec>) -> Result<()> { - let output_files = self.private_process(input, output, additional_modifiers, false).await?; + pub async fn process(&self, input: PathBuf, output: PathBuf, additional_modifiers: Option<&mut Vec>, bundle: bool) -> Result<()> { + let output_files = self.private_process(input, output, additional_modifiers, bundle).await?; if let Some(polyfill) = &self.polyfill { let polyfill_config = polyfill.config(); - let polyfill_path = polyfill.path(); // needed additional modifiers: inject_global_value let mut additional_modifiers: Vec = Vec::new(); - for (key, value) in polyfill_config - .settings() - .iter() - .chain(polyfill_config.libraries().iter()) - { + for (key, value) in polyfill_config { let mut identifier = DAL_GLOBAL_IDENTIFIER_PREFIX.to_string(); identifier.push_str(key); let inject_global_value = rules::InjectGlobalValue::boolean(identifier, *value); additional_modifiers.push(Modifier::DarkluaRule(Box::new(inject_global_value))); } - for (index, output_path) in output_files.iter().enumerate() { - if let Some(parent) = output_path.parent() { - let mut module_path: Option = None; - if index == 0 { - let extension = if let Some(extension) = &self.extension { - extension.to_owned() - } else { - output_path - .extension() - .unwrap() - .to_string_lossy() - .into_owned() - }; - module_path = Some( - parent - .join(DEFAULT_INJECTED_LIB_FILE_NAME) - .with_extension(extension), - ); - let _ = self - .private_process( - polyfill_path.join(polyfill_config.input()), - module_path.to_owned().unwrap(), - Some(&mut additional_modifiers), - true, - ) - .await?; - } - if let Some(module_path) = module_path { - self.inject_library( - &output_path, - &module_path, - polyfill_config.libraries(), - polyfill_config.removes(), + if let Some(first_output) = output_files.first() { + log::debug!("first output found!"); + let extension = if let Some(extension) = &self.extension { + extension.to_owned() + } else { + first_output + .extension() + .ok_or_else(|| anyhow!("Failed to get extension from output file."))? + .to_string_lossy() + .into_owned() + }; + + if let Some(module_path) = first_output.parent().map(|parent| { + parent + .join( + if let Some(injected_polyfill_name) = &self.injected_polyfill_name { + injected_polyfill_name + } else { + DEFAULT_INJECTED_POLYFILL_NAME + }, + ) + .with_extension(extension) + }) { + let _ = self + .private_process( + polyfill.globals().path().to_path_buf(), + module_path.to_owned(), + Some(&mut additional_modifiers), + true, ) .await?; + + log::info!("[injector] exports to inject: {:?}", polyfill.globals().exports().to_owned()); + + let injector = Injector::new( + module_path, + polyfill.globals().exports().to_owned(), + self.target_version.to_lua_version(), + polyfill.removes().to_owned(), + ); + + for source_path in &output_files { + injector.inject(source_path).await? } } } + + Ok(()) + } else { + Err(anyhow!("Polyfill not found.")) } - Ok(()) } } diff --git a/dal_custom/src/lib/utils.rs b/dal_custom/src/lib/utils.rs new file mode 100644 index 0000000..ab6e6db --- /dev/null +++ b/dal_custom/src/lib/utils.rs @@ -0,0 +1,88 @@ +use std::{collections::HashSet, path::PathBuf}; + +use anyhow::{anyhow, Result}; +use full_moon::{ast::{Ast, Expression, Field, LastStmt}, tokenizer::TokenKind}; +use tokio::fs; + +use crate::TargetVersion; + +pub enum ParseTarget { + FullMoonAst(Ast), + File(PathBuf, TargetVersion), +} + +pub(crate) async fn parse_file(path: &PathBuf, target_version: &TargetVersion) -> Result { + let code = fs::read_to_string(&path).await?; + let ast = full_moon::parse_fallible(code.as_str(), target_version.to_lua_version().clone()) + .into_result() + .map_err(|errors| anyhow!("full_moon parsing error: {:?}", errors))?; + + Ok(ast) +} + +/// Gets exports of lua modules by parsing last statement's table constructor. +pub async fn get_exports_from_last_stmt(target: &ParseTarget) -> Result>> { + let ast = match target { + ParseTarget::FullMoonAst(ast) => ast, + ParseTarget::File(path, target_version) => &parse_file(path, target_version).await?, + }; + let block = ast.nodes(); + + if let Some(exports) = block + .last_stmt() + .and_then(|last_stmt| match last_stmt { + LastStmt::Return(return_stmt) => return_stmt.returns().first(), + _ => None, + }) + .and_then(|first_return| match first_return.value() { + Expression::TableConstructor(table_constructor) => Some(table_constructor), + _ => None, + }) + .map(|table_constructor| { + let mut exports: HashSet = HashSet::new(); + for field in table_constructor.fields() { + if let Some(new_export) = match field { + Field::ExpressionKey { + brackets: _, + key, + equal: _, + value: _, + } => { + if let Expression::String(string_token) = key { + let string_token = string_token.token(); + if let TokenKind::StringLiteral = string_token.token_kind() { + log::debug!("[get_exports_from_last_stmt] ExpressionKey token kind: {:?} string: {}", string_token.token_kind(), string_token.to_string()); + Some(string_token.to_string().trim().to_owned()) + } else { + None + } + } else { + None + } + } + Field::NameKey { + key, + equal: _, + value: _, + } => { + let key = key.token(); + if let TokenKind::Identifier = key.token_kind() { + log::debug!("[get_exports_from_last_stmt] NameKey token kind: {:?} string: {}", key.token_kind(), key.to_string()); + Some(key.to_string().trim().to_owned()) + } else { + None + } + } + _ => None, + } { + exports.insert(new_export); + } + } + exports + }) + { + return Ok(Some(exports)); + } + + Ok(None) +} diff --git a/dal_custom/src/main.rs b/dal_custom/src/main.rs index 5e9f907..4d7c1cc 100644 --- a/dal_custom/src/main.rs +++ b/dal_custom/src/main.rs @@ -2,16 +2,26 @@ use std::{process::ExitCode, thread}; mod cli; +use clap::Parser; use cli::Dal; pub use dal_core; use tokio::runtime; +use anstyle::{AnsiColor, Color, Style}; +use env_logger::Builder; +use log::Level; const STACK_SIZE: usize = 4 * 1024 * 1024; -// #[tokio::main(flavor = "multi_thread")] fn run() -> ExitCode { let rt = runtime::Builder::new_multi_thread().build().unwrap(); - match rt.block_on(Dal::new().run()) { + + let dal = Dal::parse(); + + let filter = dal.get_log_level_filter(); + + formatted_logger().filter_module("dal", filter).init(); + + match rt.block_on(dal.run()) { Ok(code) => code, Err(err) => { eprintln!("{:?}", err); @@ -28,3 +38,27 @@ fn main() -> ExitCode { child.join().unwrap() } + +fn formatted_logger() -> Builder { + let mut builder = Builder::from_default_env(); + builder.format(|f, record| { + use std::io::Write; + + let level = record.level(); + let (style, text) = colored_level(level); + + writeln!(f, " {style}{text}{style:#} > {}", record.args()) + }); + builder +} + +fn colored_level(level: Level) -> (Style, &'static str) { + let (color, text) = match level { + Level::Trace => (AnsiColor::Magenta, "TRACE"), + Level::Debug => (AnsiColor::Blue, "DEBUG"), + Level::Info => (AnsiColor::Green, "INFO"), + Level::Warn => (AnsiColor::Yellow, "WARN"), + Level::Error => (AnsiColor::Red, "ERROR"), + }; + (Style::new().fg_color(Some(Color::Ansi(color))), text) +} diff --git a/src/commands/build.rs b/src/commands/build.rs index 01939ce..287bd3a 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,8 +1,12 @@ use std::str::FromStr; use std::path::PathBuf; +use std::sync::Arc; +use dal_core::polyfill::clean_cache; +use indexmap::IndexSet; +use strum::IntoEnumIterator; use tokio::io::{ AsyncReadExt, AsyncWriteExt }; -use tokio::fs::{ create_dir, remove_dir_all, File }; +use tokio::fs::{ self, create_dir, remove_dir_all, File }; use dal_core::{ manifest::Manifest, polyfill::Polyfill, transpiler::Transpiler }; use url::Url; @@ -12,7 +16,243 @@ use crate::{ toml_conf::{ Config, Modules }, utils::relative }; use colored::Colorize; use crate::{ allow, zip_utils::* }; -pub const DEFAULT_POLYFILL_URL: &str = "https://github.com/orpos/dal-polyfill"; +pub const DEFAULT_POLYFILL_URL: &str = "https://github.com/CavefulGames/dal-polyfill"; + +pub enum ModelConf { + Exclude(Vec), + Include(Vec), +} + +impl From<&Config> for ModelConf { + fn from(config: &Config) -> Self { + if config.modules.len() < 1 && config.exclude_modules.len() > 0 { + Self::Exclude(config.exclude_modules.clone()) + } else if config.modules.len() > 0 { + if config.exclude_modules.len() > 0 { + eprintln!( + "{}", + "Both modules and exclude modules used, the exclude modules will be ignored".red() + ); + } + Self::Include(config.modules.clone()) + } else { + Self::Exclude(vec![]) + } + } +} + +struct Builder { + transpiler: (Transpiler, Manifest), + strategy: Strategy, + zip: Option, + local: PathBuf, + build_path: PathBuf, + bar: LoadingStatusBar, + one_file: bool, +} + +impl Builder { + pub async fn new(local: &PathBuf, run: Strategy, one_file: bool) -> anyhow::Result { + let config = get_transpiler().await; + let bar = LoadingStatusBar::new("Building project...".into()); + bar.start_animation().await; + Ok(Self { + transpiler: config, + zip: if let Strategy::BuildDev = run { + None + } else { + Some(Zipper::new()) + }, + strategy: run, + bar, + build_path: local.join(".build"), + local: local.clone(), + one_file, + }) + } + + pub fn generate_conf_modules( + &self, + imported_modules: Vec, + model_conf: ModelConf + ) -> String { + let mut modules_string = "".to_string(); + for module in imported_modules { + let enabled = match model_conf { + ModelConf::Include(ref models) | ModelConf::Exclude(ref models) => { + let found_model = models + .iter() + .find(|x| **x == module) + .is_some(); + if let ModelConf::Exclude(_) = model_conf { + !found_model + } else { + found_model + } + } + }; + modules_string += &format!( + "t.modules.{}={}\n", + &module.to_string().to_lowercase(), + enabled + ); + } + modules_string + } + pub async fn clean_build_folder(&self) -> anyhow::Result<()> { + if self.build_path.exists() { + println!("Previous build folder found. Deleting it..."); + remove_dir_all(&self.build_path).await?; + } + create_dir(&self.build_path).await?; + Ok(()) + } + pub async fn process_file( + &self, + input: PathBuf, + output: PathBuf, + modules: Option>>> + ) -> anyhow::Result<()> { + let mut additional_rules = vec![ + dal_core::modifiers::Modifier::DarkluaRule( + Box::new(dal_core::modifiers::ModifyRelativePath { + project_root: self.local.clone(), + }) + ) + ]; + if let Some(modules) = modules { + additional_rules.push( + dal_core::modifiers::Modifier::DarkluaRule( + Box::new(dal_core::modifiers::GetLoveModules { + modules, + }) + ) + ); + } + let (transpiler, manifest) = &self.transpiler; + transpiler.process( + manifest.require_input(Some(input))?, + manifest.require_output(Some(output))?, + Some(&mut additional_rules), + self.one_file + ).await?; + Ok(()) + } + pub async fn add_luau_files(&mut self) -> anyhow::Result> { + self.bar.change_status(format!("{} {} {}", "Adding", "lua".green(), "files...")).await; + let detected_modules = if let Strategy::BuildDev = self.strategy { + None + } else { + Some(Arc::new(std::sync::Mutex::new(IndexSet::new()))) + }; + if self.local.join("main.luau").exists() && self.one_file { + self.process_file( + self.local.join("main.luau"), + self.local.join(".build").join("main.lua"), + detected_modules.as_ref().map(|x| Arc::clone(&x)) + ).await?; + if let Some(zip) = &mut self.zip { + zip.copy_zip_f_from_path( + &self.local.join(".build").join("main.lua"), + "main.lua".parse()? + ).await.unwrap(); + } + } else { + for path in glob + ::glob(&(self.local.to_string_lossy().to_string() + "/**/*.luau")) + .unwrap() + .filter_map(Result::ok) + .filter( + |path| + !path + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or("".to_string()) + .ends_with(".d.luau") + ) { + let out_path = path.strip_prefix(&self.local).unwrap(); + self.process_file( + path.clone(), + self.local.join(".build").join(out_path), + detected_modules.as_ref().map(|x| Arc::clone(&x)) + ).await?; + if let Some(zip) = &mut self.zip { + zip.copy_zip_f_from_path( + &self.local.join(".build").join(out_path).with_extension("lua"), + out_path.with_extension("lua") + ).await.unwrap(); + } + } + } + if let Some(zip) = &mut self.zip { + for path in glob + ::glob(&(self.local.to_string_lossy().to_string() + "/**/__polyfill__.lua")) + .unwrap() + .filter_map(Result::ok) { + if + path + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or("".to_string()) + .ends_with(".d.luau") + { + continue; + } + let out_path = path.strip_prefix(&self.local.join(".build")).unwrap(); + zip.copy_zip_f_from_path( + &self.local.join(".build").join(out_path).with_extension("lua"), + out_path.with_extension("lua") + ).await.unwrap(); + } + } + if let Some(modules) = detected_modules { + let inside = modules.lock().unwrap(); + Ok( + inside + .iter() + .map(|x| Modules::from_str(&uppercase_first(&x))) + .filter_map(Result::ok) + .collect() + ) + } else { + Ok(vec![]) + } + } + pub async fn add_assets(&mut self) { + self.bar.change_status("Adding asset files...".into()).await; + for path in glob + ::glob(&self.local.join("**/*").display().to_string()) + .unwrap() + .filter_map(Result::ok) + .filter(|pth| { + let ext = pth + .extension() + .map(|x| x.to_str().unwrap()) + .unwrap_or(""); + !( + pth.starts_with(self.local.join("dist")) || + allow!(ext, "lua", "luau", "toml") || + pth.is_dir() + ) + }) { + if let Some(zip) = &mut self.zip { + zip.add_zip_f_from_path(&path, &self.local).await.unwrap(); + } else { + let final_ = self.local + .join(".build") + .join(path.strip_prefix(self.local.clone()).unwrap()); + if !final_.parent().unwrap().exists() { + fs::create_dir_all(final_.parent().unwrap()).await.unwrap(); + } + fs::hard_link(&path, final_).await.unwrap(); + } + } + } + #[inline] + pub fn finish_zip(mut self) -> Option> { + self.zip.take().map(|x| x.finish()) + } +} pub async fn get_transpiler() -> (Transpiler, Manifest) { let mut manifest = Manifest { @@ -40,184 +280,84 @@ pub async fn get_transpiler() -> (Transpiler, Manifest) { "remove_unused_if_branch" ); - let polyfill = Polyfill::new(&Url::from_str(DEFAULT_POLYFILL_URL).unwrap()).await.unwrap(); - polyfill.fetch().unwrap(); + let polyfill = &Url::from_str(DEFAULT_POLYFILL_URL).unwrap(); + + let polyfill_ = if let Ok(result) = Polyfill::new(polyfill).await { + result + } else { + eprintln!("Polyfill failed cleaning cache..."); + clean_cache(polyfill).await.unwrap(); + Polyfill::new(polyfill).await.unwrap() + }; + polyfill_.fetch().unwrap(); let mut transpiler = Transpiler::default(); transpiler = transpiler.with_manifest(&manifest); - transpiler = transpiler.with_polyfill(polyfill); + transpiler = transpiler.with_polyfill(polyfill_, None); return (transpiler, manifest); } -pub async fn process_file( - config: &(Transpiler, Manifest), - local: &PathBuf, - input: PathBuf, - output: PathBuf -) -> anyhow::Result<()> { - // This is specific to each file because we require based on it - // also this receives the project root to make the right import - let mut additional_rules = vec![ - dal_core::modifiers::Modifier::DarkluaRule( - Box::new(dal_core::modifiers::ModifyRelativePath { - project_root: local.clone(), - }) - ) - ]; - - let (transpiler, manifest) = config; - transpiler.process( - manifest.require_input(Some(input)).unwrap(), - manifest.require_output(Some(output)).unwrap(), - Some(&mut additional_rules) - ).await?; - Ok(()) -} - -pub async fn add_luau_files( - config: &(Transpiler, Manifest), - local: &PathBuf, - zip: &mut Zipper -) -> anyhow::Result<()> { - for entry in glob::glob(&(local.to_string_lossy().to_string() + "/**/*.luau")).unwrap() { - if let Ok(path) = entry { - if - path - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or("".to_string()) - .ends_with(".d.luau") - { - continue; - } - let out_path = path.strip_prefix(&local).unwrap(); - process_file(config, local, path.clone(), local.join(".build").join(out_path)).await?; - zip.copy_zip_f_from_path( - &local.join(".build").join(out_path).with_extension("lua"), - out_path.with_extension("lua") - ).await.unwrap(); - } +fn uppercase_first(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().chain(c).collect(), } - for entry in glob - ::glob(&(local.to_string_lossy().to_string() + "/**/__dal_libs__.lua")) - .unwrap() { - if let Ok(path) = entry { - if - path - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or("".to_string()) - .ends_with(".d.luau") - { - continue; - } - let out_path = path.strip_prefix(&local.join(".build")).unwrap(); - zip.copy_zip_f_from_path( - &local.join(".build").join(out_path).with_extension("lua"), - out_path.with_extension("lua") - ).await.unwrap(); - } - } - Ok(()) } fn format_option(value: Option) -> String { value.map(|x| x.to_string()).unwrap_or("nil".to_string()) } -pub async fn add_assets(local: &PathBuf, zip: &mut Zipper) { - for data in glob - ::glob(&format!("{}{}", local.to_string_lossy(), "/**/*")) - .unwrap() - .filter_map(Result::ok) { - let ext = data - .extension() - .map(|x| x.to_str().unwrap()) - .unwrap_or(""); - if - data.starts_with(local.join("dist")) || - allow!(ext, "lua", "luau", "toml") || - data.is_dir() - { - continue; - } - zip.add_zip_f_from_path(&data, local).await.unwrap(); - } -} - -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub enum Strategy { /// Makes the executable BuildAndCompile, /// Just creates the love file Build, + /// Just compiles the lua files + BuildDev, } -pub async fn build(path: Option, run: Strategy) -> anyhow::Result<()> { +pub async fn build(path: Option, run: Strategy, one_file: bool) -> anyhow::Result<()> { let local = relative(path); if !local.join("kaledis.toml").exists() { println!("{}", "No Project found!".red()); return Ok(()); } + let configs = Config::from_toml_file(local.join("kaledis.toml"))?; - let configs = Config::from_toml_file(local.join("kaledis.toml")).unwrap(); - - let build_path = local.join(".build"); - - // Steam be like: - if build_path.exists() { - println!("Previous build folder found. Deleting it..."); - remove_dir_all(&build_path).await?; + if configs.project.name.len() < 1 { + eprintln!("{}", "Cannot distribute a game without a name".red()); + return Ok(()); } - create_dir(&build_path).await?; - - let transp = get_transpiler().await; - - let mut zip = Zipper::new(); - let bar = LoadingStatusBar::new("Building project...".into()); - bar.change_status(format!("{} {} {}", "Adding", "lua".green(), "files...")).await; - bar.start_animation().await; + let mut builder = Builder::new(&local, run.clone(), one_file).await?; - add_luau_files(&transp, &local, &mut zip).await?; + builder.clean_build_folder().await?; + let imported_modules = builder.add_luau_files().await?; - bar.change_status("Adding asset files...".into()).await; + builder.add_assets().await; - add_assets(&local, &mut zip).await; + let model_conf: ModelConf = (&configs).into(); - let models = { - let mut included = Vec::new(); - if configs.modules.len() < 1 && configs.exclude_modules.len() > 0 { - included = configs.exclude_modules; - } else if configs.modules.len() > 0 { - if configs.exclude_modules.len() > 0 { - println!( - "{}", - "Both modules and exclude modules used, the exclude modules will be ignored".red() - ); - } - included = configs.modules; - } - included - }; - let mut modules_string = "".to_string(); - for module in Modules::available() { - modules_string += "t.modules."; - modules_string += &module.to_string().to_lowercase(); - modules_string += "="; - modules_string += &format!( - "{}", - models - .iter() - .find(|x| **x == module) - .is_some() - ); - modules_string += "\n"; - } + builder.bar.change_status("Adding config file...".into()).await; if !(local.join("conf.luau").exists() || local.join("conf.lua").exists()) { - // TODO: make this from serialize + let modules = builder.generate_conf_modules( + if let Strategy::BuildDev = run { + Modules::iter().collect() + } else { + if configs.project.detect_modules.unwrap_or(false) { + println!("Detected Modules: {:?}", imported_modules); + imported_modules + } else { + Modules::iter().collect() + } + }, + model_conf + ); let conf_file = format!( r#" function love.conf(t) @@ -254,7 +394,9 @@ pub async fn build(path: Option, run: Strategy) -> anyhow::Result<()> { {} end "#, - format_option(configs.project.identity.map(|x| x.to_string_lossy().to_string())), + format_option( + configs.project.identity.as_ref().map(|x| x.to_string_lossy().to_string()) + ), "false", configs.project.version, configs.project.console, @@ -264,7 +406,7 @@ pub async fn build(path: Option, run: Strategy) -> anyhow::Result<()> { configs.audio.mic, configs.audio.mix_with_system, configs.window.title, - format_option(configs.window.icon.map(|x| x.to_string_lossy().to_string())), + format_option(configs.window.icon.as_ref().map(|x| x.to_string_lossy().to_string())), configs.window.width, configs.window.height, configs.window.borderless, @@ -285,23 +427,25 @@ pub async fn build(path: Option, run: Strategy) -> anyhow::Result<()> { configs.window.usedpiscale, format_option(configs.window.x), format_option(configs.window.y), - modules_string + modules ); - zip.add_zip_f_from_buf("conf.lua", conf_file.as_bytes()).await?; + if let Strategy::BuildDev = run { + let mut result = fs::File::create(builder.build_path.join("conf.lua")).await?; + result.write(conf_file.as_bytes()).await?; + } else { + if let Some(zip) = &mut builder.zip { + zip.add_zip_f_from_buf("conf.lua", conf_file.as_bytes()).await?; + } + } } else { println!("{}", "Custom config file found! Overwriting configs...".yellow()); } - bar.change_status("Adding config file...".into()).await; - // println!("{} {}", "[-]".blue(), "Adding config file..."); - - let fin = zip.finish(); match run { - Strategy::Build => { - let mut file = File::create(build_path.join("final.love")).await?; - file.write(&fin).await?; - } + Strategy::BuildDev => {} Strategy::BuildAndCompile => { + let build_path = builder.build_path.clone(); + let fin = builder.finish_zip().unwrap(); let love_executable = configs.project.love_path.join("love.exe"); let mut contents = File::open(love_executable).await?; @@ -315,12 +459,14 @@ pub async fn build(path: Option, run: Strategy) -> anyhow::Result<()> { create_dir(&dist_folder).await?; } - let mut f = File::create( - dist_folder.join(configs.project.name).with_extension("exe") - ).await?; + let new_exe = dist_folder.join(configs.project.name).with_extension("exe"); + + let mut f = File::create(&new_exe).await?; f.write(&buffer).await?; f.write(&fin).await?; + println!("Saving executable in : {}", new_exe.display().to_string()); + let l_path = configs.project.love_path; macro_rules! import_love_file { @@ -351,9 +497,16 @@ pub async fn build(path: Option, run: Strategy) -> anyhow::Result<()> { ); remove_dir_all(&build_path).await?; } + Strategy::Build => { + let build_path = builder.build_path.clone(); + let fin = builder.finish_zip().unwrap(); + + let mut file = File::create(build_path.join("final.love")).await?; + file.write(&fin).await?; + } } println!("{} {}", "[+]".green(), "Love project builded sucessfully"); - return Ok(()); + Ok(()) } diff --git a/src/commands/init.rs b/src/commands/init.rs index c1bef54..d255cdc 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -2,6 +2,7 @@ use std::{ env, fs, io::Write, path::PathBuf }; use colored::Colorize; use inquire::{ MultiSelect, Text }; +use strum::IntoEnumIterator; use crate::{ toml_conf::{ self, Modules, Project }, utils::relative }; @@ -20,7 +21,7 @@ pub fn init(path: Option) { .prompt() .unwrap(); - // TODO: give user the option to auto install + // TODO: give user the option to auto install let mut path_ = env ::var_os("PATH") .map(|path_| { @@ -55,7 +56,7 @@ pub fn init(path: Option) { .into() }); - let modules = MultiSelect::new("Select what modules you will use:", Modules::available()) + let modules = MultiSelect::new("Select what modules you will use:", Modules::iter().collect()) .with_all_selected_by_default() .prompt() .unwrap(); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index ad458b0..132fc5d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -17,11 +17,15 @@ pub enum Commands { about = "Transpiles everything, and builds a '.love' file inside a '.build' directory." )] Build { path: Option, + #[arg(short, long, help="A config that joins all files in a single one.")] + one_file: bool }, #[clap( about = "Compiles the entire project to a executable, inside a 'dist' folder." )] Compile { path: Option, + #[arg(short, long, help="A config that joins all files in a single one.")] + one_file: bool }, #[clap( about = "Watches for changes in the project and builds and executes love automatically." @@ -49,14 +53,14 @@ pub async fn handle_commands(command: Commands) { Commands::Init { path } => { init::init(path); } - Commands::Build { path } => { - build::build(path, build::Strategy::Build).await.unwrap(); + Commands::Build { path, one_file } => { + build::build(path, build::Strategy::Build, one_file).await.unwrap(); } Commands::Dev { path } => { watch::watch(path).await; } - Commands::Compile { path } => { - build::build(path, build::Strategy::BuildAndCompile).await.unwrap(); + Commands::Compile { path, one_file } => { + build::build(path, build::Strategy::BuildAndCompile, one_file).await.unwrap(); } Commands::Update { step, is_established } => { // Artificial delay to wait for the previous instance to die diff --git a/src/commands/watch.rs b/src/commands/watch.rs index ea6496b..e41ad5c 100644 --- a/src/commands/watch.rs +++ b/src/commands/watch.rs @@ -26,17 +26,17 @@ impl<'a> WatchDaemon<'a> { } } pub async fn build(&self) -> anyhow::Result<()> { - build::build(self.base_path.clone(), build::Strategy::Build).await.context("Building")?; + build::build(self.base_path.clone(), build::Strategy::BuildDev, false).await.context("Building")?; Ok(()) } pub async fn run(&self) -> anyhow::Result { - if !self.path.join(".build").join("final.love").exists() { + if !self.path.join(".build").exists() { anyhow::bail!("No bundle found."); // maybe we forgot to build it } Ok( Command::new(&self.love_executable) .current_dir(&self.love_path) - .arg(self.path.join(".build").join("final.love")) + .arg(self.path.join(".build")) .spawn() .context("Spawning the process")? ) @@ -121,7 +121,6 @@ pub async fn watch(base_path: Option) { let mut child: Option = None; while let Ok(_) = receiver.recv().await { - println!("Building project..."); if let Some(mut child) = child.take() { child.kill().await.unwrap(); } diff --git a/src/main.rs b/src/main.rs index ac043ab..77d1223 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +extern crate strum; +extern crate strum_macros; + mod toml_conf; mod commands; mod utils; @@ -6,14 +9,25 @@ mod cli_utils; use std::{ process::ExitCode, thread }; +use colored::Colorize; use commands::{ handle_commands, CLI }; use clap::Parser; use tokio::runtime; -const STACK_SIZE: usize = 4 * 1024 * 1024; +const STACK_SIZE: usize = 4 * 1024 * 1024 * 1024; + +fn print_banner() { + println!("{}", "░ ░░░░ ░░░ ░░░ ░░░░░░░░ ░░ ░░░ ░░░ ░░".purple()); + println!("{}", "▒ ▒▒▒ ▒▒▒ ▒▒▒▒ ▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒".purple()); + println!("{}", "▓ ▓▓▓▓▓ ▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓".cyan()); + println!("{}", "█ ███ ███ ██ ████████ ████████ ████ █████ ███████████ █".cyan()); + println!("{}", "█ ████ ██ ████ ██ ██ ██ ███ ███ ██".cyan()); + println!(""); +} fn run() -> ExitCode { + print_banner(); let args = CLI::parse(); let rt = runtime::Builder::new_multi_thread().enable_io().enable_time().build().unwrap(); rt.block_on(handle_commands(args.cli)); diff --git a/src/toml_conf.rs b/src/toml_conf.rs index ce471e9..12113a5 100644 --- a/src/toml_conf.rs +++ b/src/toml_conf.rs @@ -1,12 +1,11 @@ -use std::{ - fmt::Display, - fs::read_to_string, - path::{ Path, PathBuf }, -}; +use std::{ fmt::Display, fs::read_to_string, path::{ Path, PathBuf } }; use clap_serde_derive::{ clap::{ self }, serde::Serialize, ClapSerde }; use serde::Deserialize; +use strum::IntoEnumIterator; +use strum_macros::{ EnumIter, EnumString }; + #[derive(ClapSerde, Serialize, Deserialize)] #[derive(Debug)] pub struct Audio { @@ -119,7 +118,17 @@ pub struct Window { pub y: Option, } -#[derive(Debug, Deserialize, Serialize, Clone, clap_serde_derive::clap::ValueEnum, PartialEq, Eq)] +#[derive( + EnumString, + EnumIter, + Debug, + Deserialize, + Serialize, + Clone, + clap_serde_derive::clap::ValueEnum, + PartialEq, + Eq +)] pub enum Modules { /// Enable the audio module Audio, @@ -165,34 +174,12 @@ impl Display for Modules { } } -impl Modules { - pub fn available<'a>() -> Vec { - vec![ - Self::Audio, - Self::Data, - Self::Event, - Self::Font, - Self::Graphics, - Self::Image, - Self::Joystick, - Self::Keyboard, - Self::Math, - Self::Mouse, - Self::Physics, - Self::Sound, - Self::System, - Self::Thread, - Self::Timer, - Self::Touch, - Self::Video, - Self::Window - ] - } -} - #[derive(ClapSerde, Serialize, Deserialize)] #[derive(Debug)] pub struct Project { + #[arg(short, long, help = "Enable detection algorithm.")] + pub detect_modules : Option, + #[arg(short, long, help = "Save location.")] pub identity: Option, @@ -268,14 +255,13 @@ pub struct Config { #[command(flatten)] pub audio: Audio, - #[default(Modules::available())] + #[default(Modules::iter().collect())] pub modules: Vec, #[default(vec![])] pub exclude_modules: Vec, } - impl Config { pub fn from_toml_file>(path: P) -> anyhow::Result { let data = read_to_string(path)?; diff --git a/src/zip_utils.rs b/src/zip_utils.rs index 37a38a7..6eacdcb 100644 --- a/src/zip_utils.rs +++ b/src/zip_utils.rs @@ -32,16 +32,7 @@ impl Zipper { name: &Path, prefix: &PathBuf ) -> anyhow::Result<()> { - let zip = &mut self.inner; - zip.start_file_from_path( - name.strip_prefix(prefix)?, - SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored) - )?; - let mut file = File::open(name).await?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).await?; - zip.write(&buffer)?; - Ok(()) + self.copy_zip_f_from_path(name, name.strip_prefix(prefix)?.to_path_buf()).await } pub async fn copy_zip_f_from_path( &mut self, @@ -59,4 +50,25 @@ impl Zipper { zip.write(&buffer)?; Ok(()) } -} \ No newline at end of file + // pub async fn copy_glob( + // &mut self, + // base_dir: PathBuf, + // pattern: &str, + // filter: impl Fn(&PathBuf) -> bool, + // middleware: impl Fn(&PathBuf) -> Option + // ) -> anyhow::Result<()> { + // for path in glob + // ::glob(&format!("{}", base_dir.join(pattern).display().to_string()))? + // .filter_map(Result::ok) { + // if !filter(&path) { + // continue; + // } + // if let Some(output) = middleware(&path) { + // self.copy_zip_f_from_path(&path, output).await?; + // } else { + // self.add_zip_f_from_path(&path, &base_dir).await?; + // } + // } + // Ok(()) + // } +}