Merge branch 'main' into feature/sched-return

This commit is contained in:
Erica Marigold 2024-04-22 12:11:32 +05:30 committed by GitHub
commit fe2a2993db
Signed by: DevComp
GPG key ID: B5690EEEBB952194
45 changed files with 1530 additions and 246 deletions

View file

@ -8,6 +8,64 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Added a builtin API for regular expressions.
Example basic usage:
```lua
local Regex = require("@lune/regex")
local re = Regex.new("hello")
if re:isMatch("hello, world!") then
print("Matched!")
end
local caps = re:captures("hello, world! hello, again!")
print(#caps) -- 2
print(caps:get(1)) -- "hello"
print(caps:get(2)) -- "hello"
print(caps:get(3)) -- nil
```
Check out the documentation for more details.
- Added support for buffers as arguments in builtin APIs ([#148])
This includes APIs such as `fs.writeFile`, `serde.encode`, and more.
- Added support for cross-compilation of standalone binaries ([#162])
You can now compile standalone binaries for other platforms by passing
an additional `target` argument to the `build` subcommand:
```sh
lune build my-file.luau --output my-bin --target windows-x86_64
```
Currently supported targets are the same as the ones included with each
release of Lune on GitHub. Check releases for a full list of targets.
- Added `stdio.readToEnd()` for reading the entire stdin passed to Lune
- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])
- Updated to Luau version `0.622`.
### Fixed
- Fixed stack overflow for tables with circular keys ([#183])
- Fixed `net.serve` no longer accepting ipv6 addresses
- Fixed headers in `net.serve` being raw bytes instead of strings
[#148]: https://github.com/lune-org/lune/pull/148
[#162]: https://github.com/lune-org/lune/pull/162
[#183]: https://github.com/lune-org/lune/pull/183
[#186]: https://github.com/lune-org/lune/pull/186
## `0.8.3` - April 15th, 2024
### Fixed

357
Cargo.lock generated
View file

@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -110,6 +121,15 @@ version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arrayref"
version = "0.3.7"
@ -193,7 +213,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -330,6 +350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"regex-automata 0.4.6",
"serde",
]
@ -358,10 +379,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.94"
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cc"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
dependencies = [
"jobserver",
"libc",
"once_cell",
]
[[package]]
name = "cfg-if"
@ -404,6 +451,16 @@ dependencies = [
"walkdir",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "4.5.4"
@ -435,7 +492,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -446,13 +503,22 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "clipboard-win"
version = "5.3.0"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee"
checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad"
dependencies = [
"error-code",
]
[[package]]
name = "cmake"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
dependencies = [
"cc",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
@ -540,6 +606,21 @@ dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.4.0"
@ -571,6 +652,12 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "deflate64"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d"
[[package]]
name = "deranged"
version = "0.3.11"
@ -580,6 +667,17 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "derive_more"
version = "0.99.17"
@ -614,6 +712,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -807,6 +906,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"libz-ng-sys",
"miniz_oxide",
]
@ -867,7 +967,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -1003,6 +1103,15 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "home"
version = "0.5.9"
@ -1112,9 +1221,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f24ce812868d86d19daa79bf3bf9175bc44ea323391147a5e3abde2a283871b"
checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
dependencies = [
"bytes",
"futures-channel",
@ -1140,7 +1249,7 @@ dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.28",
"rustls 0.21.10",
"rustls 0.21.11",
"tokio",
"tokio-rustls 0.24.1",
]
@ -1152,7 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad"
dependencies = [
"http-body-util",
"hyper 1.3.0",
"hyper 1.3.1",
"hyper-util",
"pin-project-lite",
"tokio",
@ -1171,7 +1280,7 @@ dependencies = [
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"hyper 1.3.0",
"hyper 1.3.1",
"pin-project-lite",
"socket2",
"tokio",
@ -1243,6 +1352,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "ipnet"
version = "2.9.0"
@ -1264,6 +1382,15 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobserver"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.69"
@ -1305,6 +1432,16 @@ dependencies = [
"libc",
]
[[package]]
name = "libz-ng-sys"
version = "1.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5"
dependencies = [
"cmake",
"libc",
]
[[package]]
name = "line-wrap"
version = "0.2.0"
@ -1335,9 +1472,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "luau0-src"
version = "0.8.5+luau617"
version = "0.8.6+luau622"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "652e36b8c35d807ec76a4931fe7c62883c62cc93311fb49bf5b76084647078ea"
checksum = "07758c1f5908f7f9dd9109efaf8c66907cc38acf312db03287e7ad2a64b5de1c"
dependencies = [
"cc",
]
@ -1350,6 +1487,7 @@ dependencies = [
"async-compression",
"async-trait",
"blocking",
"bstr",
"chrono",
"chrono_lc",
"clap",
@ -1362,7 +1500,7 @@ dependencies = [
"glam",
"http 1.1.0",
"http-body-util",
"hyper 1.3.0",
"hyper 1.3.1",
"hyper-tungstenite",
"hyper-util",
"include_dir",
@ -1385,6 +1523,7 @@ dependencies = [
"regex",
"reqwest",
"rustyline",
"self_cell",
"serde",
"serde_json",
"serde_yaml",
@ -1395,6 +1534,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"urlencoding",
"zip_next",
]
[[package]]
@ -1426,6 +1566,16 @@ dependencies = [
"twox-hash",
]
[[package]]
name = "lzma-rs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
dependencies = [
"byteorder 1.5.0",
"crc",
]
[[package]]
name = "matchers"
version = "0.1.0"
@ -1670,6 +1820,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -1693,7 +1853,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -1759,9 +1919,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.80"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
@ -1782,7 +1942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -2039,7 +2199,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls 0.21.10",
"rustls 0.21.11",
"rustls-pemfile",
"serde",
"serde_json",
@ -2074,9 +2234,9 @@ dependencies = [
[[package]]
name = "rmp"
version = "0.8.12"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
dependencies = [
"byteorder 1.5.0",
"num-traits",
@ -2085,9 +2245,9 @@ dependencies = [
[[package]]
name = "rmp-serde"
version = "1.1.2"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
checksum = "938a142ab806f18b88a97b0dea523d39e0fd730a064b035726adcfc58a8a5188"
dependencies = [
"byteorder 1.5.0",
"rmp",
@ -2151,9 +2311,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.10"
version = "0.21.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4"
dependencies = [
"log",
"ring",
@ -2163,9 +2323,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.22.3"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c"
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
dependencies = [
"log",
"ring",
@ -2264,6 +2424,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "self_cell"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
[[package]]
name = "semver"
version = "0.9.0"
@ -2287,9 +2453,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.197"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [
"serde_derive",
]
@ -2306,20 +2472,20 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
name = "serde_json"
version = "1.0.115"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
"indexmap",
"itoa",
@ -2411,6 +2577,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "slab"
version = "0.4.9"
@ -2531,9 +2703,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.59"
version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [
"proc-macro2",
"quote",
@ -2581,22 +2753,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.58"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.58"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -2721,7 +2893,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -2730,7 +2902,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.10",
"rustls 0.21.11",
"tokio",
]
@ -2740,7 +2912,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.3",
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
]
@ -2753,7 +2925,7 @@ checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
dependencies = [
"futures-util",
"log",
"rustls 0.22.3",
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
@ -2799,9 +2971,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.9"
version = "0.22.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
dependencies = [
"indexmap",
"serde",
@ -2858,7 +3030,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
]
[[package]]
@ -2919,7 +3091,7 @@ dependencies = [
"httparse",
"log",
"rand",
"rustls 0.22.3",
"rustls 0.22.4",
"rustls-pki-types",
"sha1 0.10.6",
"thiserror",
@ -2937,6 +3109,12 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typenum"
version = "1.17.0"
@ -3081,7 +3259,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
"wasm-bindgen-shared",
]
@ -3115,7 +3293,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.60",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3369,3 +3547,76 @@ name = "zeroize"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
[[package]]
name = "zip"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21968e6da56f847a155a89581ba846507afa14854e041f3053edb6ddd19f807"
dependencies = [
"aes",
"arbitrary",
"byteorder 1.5.0",
"bzip2",
"constant_time_eq 0.3.0",
"crc32fast",
"crossbeam-utils",
"deflate64",
"flate2",
"hmac",
"lzma-rs",
"pbkdf2",
"sha1 0.10.6",
"time 0.3.36",
"zopfli",
"zstd",
]
[[package]]
name = "zip_next"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc8b818e7a619a85006cbdf263036c50cddd97d907887173b7ef743b4fb5b5e"
dependencies = [
"zip",
]
[[package]]
name = "zopfli"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736"
dependencies = [
"crc32fast",
"log",
"simd-adler32",
"typed-arena",
]
[[package]]
name = "zstd"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.10+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
dependencies = [
"cc",
"pkg-config",
]

View file

@ -24,8 +24,8 @@ cli = [
"dep:env_logger",
"dep:clap",
"dep:include_dir",
"dep:regex",
"dep:rustyline",
"dep:zip_next",
]
roblox = [
"dep:glam",
@ -75,6 +75,9 @@ path-clean = "1.0"
pathdiff = "0.2"
pin-project = "1.0"
urlencoding = "2.1"
bstr = "1.9"
regex = "1.10"
self_cell = "1.0"
### RUNTIME
@ -83,9 +86,8 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1.24", features = ["full", "tracing"] }
os_str_bytes = { version = "7.0", features = ["conversions"] }
mlua-luau-scheduler = { git = "https://github.com/0x5eal/mlua-luau-scheduler-exitstatus.git" }
mlua = { version = "0.9.6", features = [
mlua = { version = "0.9.7", features = [
"luau",
"luau-jit",
"async",
@ -131,11 +133,8 @@ env_logger = { optional = true, version = "0.11" }
itertools = "0.12"
clap = { optional = true, version = "4.1", features = ["derive"] }
include_dir = { optional = true, version = "0.7", features = ["glob"] }
regex = { optional = true, version = "1.7", default-features = false, features = [
"std",
"unicode-perl",
] }
rustyline = { optional = true, version = "14.0" }
zip_next = { optional = true, version = "1.1" }
### ROBLOX

View file

@ -1,73 +0,0 @@
use std::{
env::consts::EXE_EXTENSION,
path::{Path, PathBuf},
process::ExitCode,
};
use anyhow::{Context, Result};
use clap::Parser;
use console::style;
use tokio::{fs, io::AsyncWriteExt as _};
use crate::standalone::metadata::Metadata;
/// Build a standalone executable
#[derive(Debug, Clone, Parser)]
pub struct BuildCommand {
/// The path to the input file
pub input: PathBuf,
/// The path to the output file - defaults to the
/// input file path with an executable extension
#[clap(short, long)]
pub output: Option<PathBuf>,
}
impl BuildCommand {
pub async fn run(self) -> Result<ExitCode> {
let output_path = self
.output
.unwrap_or_else(|| self.input.with_extension(EXE_EXTENSION));
let input_path_displayed = self.input.display();
let output_path_displayed = output_path.display();
// Try to read the input file
let source_code = fs::read(&self.input)
.await
.context("failed to read input file")?;
// Read the contents of the lune interpreter as our starting point
println!(
"Creating standalone binary using {}",
style(input_path_displayed).green()
);
let patched_bin = Metadata::create_env_patched_bin(source_code.clone())
.await
.context("failed to create patched binary")?;
// And finally write the patched binary to the output file
println!(
"Writing standalone binary to {}",
style(output_path_displayed).blue()
);
write_executable_file_to(output_path, patched_bin).await?;
Ok(ExitCode::SUCCESS)
}
}
async fn write_executable_file_to(path: impl AsRef<Path>, bytes: impl AsRef<[u8]>) -> Result<()> {
let mut options = fs::OpenOptions::new();
options.write(true).create(true).truncate(true);
#[cfg(unix)]
{
options.mode(0o755); // Read & execute for all, write for owner
}
let mut file = options.open(path).await?;
file.write_all(bytes.as_ref()).await?;
Ok(())
}

86
src/cli/build/base_exe.rs Normal file
View file

@ -0,0 +1,86 @@
use std::{
io::{Cursor, Read},
path::PathBuf,
};
use tokio::{fs, task};
use crate::standalone::metadata::CURRENT_EXE;
use super::{
files::write_executable_file_to,
result::{BuildError, BuildResult},
target::{BuildTarget, CACHE_DIR},
};
/**
Discovers the path to the base executable to use for cross-compilation.
If the target is the same as the current system, the current executable is used.
If no binary exists at the target path, it will attempt to download it from the internet.
*/
pub async fn get_or_download_base_executable(target: BuildTarget) -> BuildResult<PathBuf> {
if target.is_current_system() {
return Ok(CURRENT_EXE.to_path_buf());
}
if target.cache_path().exists() {
return Ok(target.cache_path());
}
// The target is not cached, we must download it
println!("Requested target '{target}' does not exist in cache");
let version = env!("CARGO_PKG_VERSION");
let target_triple = format!("lune-{version}-{target}");
let release_url = format!(
"{base_url}/v{version}/{target_triple}.zip",
base_url = "https://github.com/lune-org/lune/releases/download",
);
// NOTE: This is not entirely accurate, but it is clearer for a user
println!("Downloading {target_triple}{}...", target.exe_suffix());
// Try to request to download the zip file from the target url,
// making sure transient errors are handled gracefully and
// with a different error message than "not found"
let response = reqwest::get(release_url).await?;
if !response.status().is_success() {
if response.status().as_u16() == 404 {
return Err(BuildError::ReleaseTargetNotFound(target));
}
return Err(BuildError::Download(
response.error_for_status().unwrap_err(),
));
}
// Receive the full zip file
let zip_bytes = response.bytes().await?.to_vec();
let zip_file = Cursor::new(zip_bytes);
// Look for and extract the binary file from the zip file
// NOTE: We use spawn_blocking here since reading a zip
// archive is a somewhat slow / blocking operation
let binary_file_name = format!("lune{}", target.exe_suffix());
let binary_file_handle = task::spawn_blocking(move || {
let mut archive = zip_next::ZipArchive::new(zip_file)?;
let mut binary = Vec::new();
archive
.by_name(&binary_file_name)
.or(Err(BuildError::ZippedBinaryNotFound(binary_file_name)))?
.read_to_end(&mut binary)?;
Ok::<_, BuildError>(binary)
});
let binary_file_contents = binary_file_handle.await??;
// Finally, write the extracted binary to the cache
if !CACHE_DIR.exists() {
fs::create_dir_all(CACHE_DIR.as_path()).await?;
}
write_executable_file_to(target.cache_path(), binary_file_contents).await?;
println!("Downloaded successfully and added to cache");
Ok(target.cache_path())
}

42
src/cli/build/files.rs Normal file
View file

@ -0,0 +1,42 @@
use std::path::{Path, PathBuf};
use anyhow::Result;
use tokio::{fs, io::AsyncWriteExt};
/**
Removes the source file extension from the given path, if it has one.
A source file extension is an extension such as `.lua` or `.luau`.
*/
pub fn remove_source_file_ext(path: &Path) -> PathBuf {
if path
.extension()
.is_some_and(|ext| matches!(ext.to_str(), Some("lua" | "luau")))
{
path.with_extension("")
} else {
path.to_path_buf()
}
}
/**
Writes the given bytes to a file at the specified path,
and makes sure it has permissions to be executed.
*/
pub async fn write_executable_file_to(
path: impl AsRef<Path>,
bytes: impl AsRef<[u8]>,
) -> Result<(), std::io::Error> {
let mut options = fs::OpenOptions::new();
options.write(true).create(true).truncate(true);
#[cfg(unix)]
{
options.mode(0o755); // Read & execute for all, write for owner
}
let mut file = options.open(path).await?;
file.write_all(bytes.as_ref()).await?;
Ok(())
}

83
src/cli/build/mod.rs Normal file
View file

@ -0,0 +1,83 @@
use std::{path::PathBuf, process::ExitCode};
use anyhow::{bail, Context, Result};
use clap::Parser;
use console::style;
use tokio::fs;
use crate::standalone::metadata::Metadata;
mod base_exe;
mod files;
mod result;
mod target;
use self::base_exe::get_or_download_base_executable;
use self::files::{remove_source_file_ext, write_executable_file_to};
use self::target::BuildTarget;
/// Build a standalone executable
#[derive(Debug, Clone, Parser)]
pub struct BuildCommand {
/// The path to the input file
pub input: PathBuf,
/// The path to the output file - defaults to the
/// input file path with an executable extension
#[clap(short, long)]
pub output: Option<PathBuf>,
/// The target to compile for in the format `os-arch` -
/// defaults to the os and arch of the current system
#[clap(short, long)]
pub target: Option<BuildTarget>,
}
impl BuildCommand {
pub async fn run(self) -> Result<ExitCode> {
// Derive target spec to use, or default to the current host system
let target = self.target.unwrap_or_else(BuildTarget::current_system);
// Derive paths to use, and make sure the output path is
// not the same as the input, so that we don't overwrite it
let output_path = self
.output
.clone()
.unwrap_or_else(|| remove_source_file_ext(&self.input));
let output_path = output_path.with_extension(target.exe_extension());
if output_path == self.input {
if self.output.is_some() {
bail!("output path cannot be the same as input path");
}
bail!("output path cannot be the same as input path, please specify a different output path");
}
// Try to read the given input file
// FUTURE: We should try and resolve a full require file graph using the input
// path here instead, see the notes in the `standalone` module for more details
let source_code = fs::read(&self.input)
.await
.context("failed to read input file")?;
// Derive the base executable path based on the arguments provided
let base_exe_path = get_or_download_base_executable(target).await?;
// Read the contents of the lune interpreter as our starting point
println!(
"Compiling standalone binary from {}",
style(self.input.display()).green()
);
let patched_bin = Metadata::create_env_patched_bin(base_exe_path, source_code)
.await
.context("failed to create patched binary")?;
// And finally write the patched binary to the output file
println!(
"Writing standalone binary to {}",
style(output_path.display()).blue()
);
write_executable_file_to(output_path, patched_bin).await?; // Read & execute for all, write for owner
Ok(ExitCode::SUCCESS)
}
}

24
src/cli/build/result.rs Normal file
View file

@ -0,0 +1,24 @@
use thiserror::Error;
use super::target::BuildTarget;
/**
Errors that may occur when building a standalone binary
*/
#[derive(Debug, Error)]
pub enum BuildError {
#[error("failed to find lune target '{0}' in GitHub release")]
ReleaseTargetNotFound(BuildTarget),
#[error("failed to find lune binary '{0}' in downloaded zip file")]
ZippedBinaryNotFound(String),
#[error("failed to download lune binary: {0}")]
Download(#[from] reqwest::Error),
#[error("failed to unzip lune binary: {0}")]
Unzip(#[from] zip_next::result::ZipError),
#[error("panicked while unzipping lune binary: {0}")]
UnzipJoin(#[from] tokio::task::JoinError),
#[error("io error: {0}")]
IoError(#[from] std::io::Error),
}
pub type BuildResult<T, E = BuildError> = std::result::Result<T, E>;

177
src/cli/build/target.rs Normal file
View file

@ -0,0 +1,177 @@
use std::{env::consts::ARCH, fmt, path::PathBuf, str::FromStr};
use directories::BaseDirs;
use once_cell::sync::Lazy;
const HOME_DIR: Lazy<PathBuf> = Lazy::new(|| {
BaseDirs::new()
.expect("could not find home directory")
.home_dir()
.to_path_buf()
});
pub const CACHE_DIR: Lazy<PathBuf> = Lazy::new(|| HOME_DIR.join(".lune").join("target"));
/**
A target operating system supported by Lune
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildTargetOS {
Windows,
Linux,
MacOS,
}
impl BuildTargetOS {
fn current_system() -> Self {
match std::env::consts::OS {
"windows" => Self::Windows,
"linux" => Self::Linux,
"macos" => Self::MacOS,
_ => panic!("unsupported target OS"),
}
}
fn exe_extension(self) -> &'static str {
// NOTE: We can't use the constants from std since
// they are only accessible for the current target
match self {
Self::Windows => "exe",
_ => "",
}
}
fn exe_suffix(self) -> &'static str {
match self {
Self::Windows => ".exe",
_ => "",
}
}
}
impl fmt::Display for BuildTargetOS {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Windows => write!(f, "windows"),
Self::Linux => write!(f, "linux"),
Self::MacOS => write!(f, "macos"),
}
}
}
impl FromStr for BuildTargetOS {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"win" | "windows" => Ok(Self::Windows),
"linux" => Ok(Self::Linux),
"mac" | "macos" | "darwin" => Ok(Self::MacOS),
_ => Err("invalid target OS"),
}
}
}
/**
A target architecture supported by Lune
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildTargetArch {
X86_64,
Aarch64,
}
impl BuildTargetArch {
fn current_system() -> Self {
match ARCH {
"x86_64" => Self::X86_64,
"aarch64" => Self::Aarch64,
_ => panic!("unsupported target architecture"),
}
}
}
impl fmt::Display for BuildTargetArch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::X86_64 => write!(f, "x86_64"),
Self::Aarch64 => write!(f, "aarch64"),
}
}
}
impl FromStr for BuildTargetArch {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"x86_64" | "x64" => Ok(Self::X86_64),
"aarch64" | "arm64" => Ok(Self::Aarch64),
_ => Err("invalid target architecture"),
}
}
}
/**
A full target description that Lune supports (OS + Arch)
This is used to determine the target to build for standalone binaries,
and to download the correct base executable for cross-compilation.
The target may be parsed from and displayed in the form `os-arch`.
Examples of valid targets are:
- `linux-aarch64`
- `linux-x86_64`
- `macos-aarch64`
- `macos-x86_64`
- `windows-x86_64`
*/
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuildTarget {
pub os: BuildTargetOS,
pub arch: BuildTargetArch,
}
impl BuildTarget {
pub fn current_system() -> Self {
Self {
os: BuildTargetOS::current_system(),
arch: BuildTargetArch::current_system(),
}
}
pub fn is_current_system(&self) -> bool {
self.os == BuildTargetOS::current_system() && self.arch == BuildTargetArch::current_system()
}
pub fn exe_extension(&self) -> &'static str {
self.os.exe_extension()
}
pub fn exe_suffix(&self) -> &'static str {
self.os.exe_suffix()
}
pub fn cache_path(&self) -> PathBuf {
CACHE_DIR.join(format!("{self}{}", self.os.exe_extension()))
}
}
impl fmt::Display for BuildTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}", self.os, self.arch)
}
}
impl FromStr for BuildTarget {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (left, right) = s
.split_once('-')
.ok_or("target must be in the form `os-arch`")?;
let os = left.parse()?;
let arch = right.parse()?;
Ok(Self { os, arch })
}
}

View file

@ -1,6 +1,7 @@
use std::io::ErrorKind as IoErrorKind;
use std::path::{PathBuf, MAIN_SEPARATOR};
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use tokio::fs;
@ -32,6 +33,7 @@ pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
async fn fs_read_file(lua: &Lua, path: String) -> LuaResult<LuaString> {
let bytes = fs::read(&path).await.into_lua_err()?;
lua.create_string(bytes)
}
@ -64,8 +66,8 @@ async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
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_file(_: &Lua, (path, contents): (String, BString)) -> LuaResult<()> {
fs::write(&path, contents.as_bytes()).await.into_lua_err()
}
async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> {

View file

@ -7,6 +7,7 @@ mod fs;
mod luau;
mod net;
mod process;
mod regex;
mod serde;
mod stdio;
mod task;
@ -22,6 +23,7 @@ pub enum LuneBuiltin {
Net,
Task,
Process,
Regex,
Serde,
Stdio,
#[cfg(feature = "roblox")]
@ -37,6 +39,7 @@ impl LuneBuiltin {
Self::Net => "net",
Self::Task => "task",
Self::Process => "process",
Self::Regex => "regex",
Self::Serde => "serde",
Self::Stdio => "stdio",
#[cfg(feature = "roblox")]
@ -52,6 +55,7 @@ impl LuneBuiltin {
Self::Net => net::create(lua),
Self::Task => task::create(lua),
Self::Process => process::create(lua),
Self::Regex => regex::create(lua),
Self::Serde => serde::create(lua),
Self::Stdio => stdio::create(lua),
#[cfg(feature = "roblox")]
@ -77,6 +81,7 @@ impl FromStr for LuneBuiltin {
"net" => Ok(Self::Net),
"task" => Ok(Self::Task),
"process" => Ok(Self::Process),
"regex" => Ok(Self::Regex),
"serde" => Ok(Self::Serde),
"stdio" => Ok(Self::Stdio),
#[cfg(feature = "roblox")]

View file

@ -1,12 +1,16 @@
use std::{collections::HashMap, net::Ipv4Addr};
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr},
};
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use reqwest::Method;
use super::util::table_to_hash_map;
const DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#"
return {
@ -104,10 +108,11 @@ impl FromLua<'_> for RequestConfig {
Err(_) => HashMap::new(),
};
// Extract body
let body = match tab.get::<_, LuaString>("body") {
let body = match tab.get::<_, BString>("body") {
Ok(config_body) => Some(config_body.as_bytes().to_owned()),
Err(_) => None,
};
// Convert method string into proper enum
let method = method.trim().to_ascii_uppercase();
let method = match method.as_ref() {
@ -155,7 +160,7 @@ impl FromLua<'_> for RequestConfig {
#[derive(Debug)]
pub struct ServeConfig<'a> {
pub address: Ipv4Addr,
pub address: IpAddr,
pub handle_request: LuaFunction<'a>,
pub handle_web_socket: Option<LuaFunction<'a>>,
}
@ -175,7 +180,7 @@ impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
let handle_request: Option<LuaFunction> = t.get("handleRequest")?;
let handle_web_socket: Option<LuaFunction> = t.get("handleWebSocket")?;
if handle_request.is_some() || handle_web_socket.is_some() {
let address: Ipv4Addr = match &address {
let address: IpAddr = match &address {
Some(addr) => {
let addr_str = addr.to_str()?;

View file

@ -1,5 +1,6 @@
#![allow(unused_variables)]
use bstr::BString;
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
@ -23,7 +24,7 @@ use super::serde::encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
NetClientBuilder::new()
.headers(&[("User-Agent", create_user_agent_header())])?
.headers(&[("User-Agent", create_user_agent_header(lua)?)])?
.build()?
.into_registry(lua);
TableBuilder::new(lua)?
@ -45,7 +46,7 @@ fn net_json_encode<'lua>(
.serialize_to_string(lua, val)
}
fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult<LuaValue<'lua>> {
fn net_json_decode(lua: &Lua, json: BString) -> LuaResult<LuaValue> {
EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json)
}

View file

@ -18,21 +18,30 @@ impl LuaRequest {
let path = self.head.uri.path().to_string();
let body = lua.create_string(&self.body)?;
let query: HashMap<String, String> = self
let query: HashMap<LuaString, LuaString> = self
.head
.uri
.query()
.unwrap_or_default()
.split('&')
.filter_map(|q| q.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let headers: HashMap<String, Vec<u8>> = self
.map(|(k, v)| {
let k = lua.create_string(k)?;
let v = lua.create_string(v)?;
Ok((k, v))
})
.collect::<LuaResult<_>>()?;
let headers: HashMap<LuaString, LuaString> = self
.head
.headers
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.as_bytes().to_vec()))
.collect();
.map(|(k, v)| {
let k = lua.create_string(k.as_str())?;
let v = lua.create_string(v.as_bytes())?;
Ok((k, v))
})
.collect::<LuaResult<_>>()?;
TableBuilder::new(lua)?
.with_value("method", method)?

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use bstr::{BString, ByteSlice};
use http_body_util::Full;
use hyper::{
body::Bytes,
@ -56,7 +57,7 @@ impl FromLua<'_> for LuaResponse {
LuaValue::Table(t) => {
let status: Option<u16> = t.get("status")?;
let headers: Option<LuaTable> = t.get("headers")?;
let body: Option<LuaString> = t.get("body")?;
let body: Option<BString> = t.get("body")?;
let mut headers_map = HeaderMap::new();
if let Some(headers) = headers {

View file

@ -7,12 +7,19 @@ use mlua::prelude::*;
use crate::lune::util::TableBuilder;
pub fn create_user_agent_header() -> String {
let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY")
.trim_start_matches("https://github.com/")
.split_once('/')
.unwrap();
format!("{github_owner}-{github_repo}-cli")
pub fn create_user_agent_header(lua: &Lua) -> LuaResult<String> {
let version_global = lua
.globals()
.get::<_, LuaString>("_VERSION")
.expect("Missing _VERSION global");
let version_global_str = version_global
.to_str()
.context("Invalid utf8 found in _VERSION global")?;
let (package_name, full_version) = version_global_str.split_once(' ').unwrap();
Ok(format!("{}/{}", package_name.to_lowercase(), full_version))
}
pub fn header_map_to_table(

View file

@ -3,6 +3,7 @@ use std::sync::{
Arc,
};
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use futures_util::{
@ -160,7 +161,7 @@ where
methods.add_async_method(
"send",
|_, this, (string, as_binary): (LuaString, Option<bool>)| async move {
|_, this, (string, as_binary): (BString, Option<bool>)| async move {
this.send(if as_binary.unwrap_or_default() {
WsMessage::Binary(string.as_bytes().to_vec())
} else {

View file

@ -0,0 +1,91 @@
use std::sync::Arc;
use mlua::prelude::*;
use regex::{Captures, Regex};
use self_cell::self_cell;
use super::matches::LuaMatch;
type OptionalCaptures<'a> = Option<Captures<'a>>;
self_cell! {
struct LuaCapturesInner {
owner: Arc<String>,
#[covariant]
dependent: OptionalCaptures,
}
}
/**
A wrapper over the `regex::Captures` struct that can be used from Lua.
*/
pub struct LuaCaptures {
inner: LuaCapturesInner,
}
impl LuaCaptures {
/**
Create a new `LuaCaptures` instance from a `Regex` pattern and a `String` text.
Returns `Some(_)` if captures were found, `None` if no captures were found.
*/
pub fn new(pattern: &Regex, text: String) -> Option<Self> {
let inner =
LuaCapturesInner::new(Arc::from(text), |owned| pattern.captures(owned.as_str()));
if inner.borrow_dependent().is_some() {
Some(Self { inner })
} else {
None
}
}
fn captures(&self) -> &Captures {
self.inner
.borrow_dependent()
.as_ref()
.expect("None captures should not be used")
}
fn num_captures(&self) -> usize {
// NOTE: Here we exclude the match for the entire regex
// pattern, only counting the named and numbered captures
self.captures().len() - 1
}
fn text(&self) -> Arc<String> {
Arc::clone(self.inner.borrow_owner())
}
}
impl LuaUserData for LuaCaptures {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("get", |_, this, index: usize| {
Ok(this
.captures()
.get(index)
.map(|m| LuaMatch::new(this.text(), m)))
});
methods.add_method("group", |_, this, group: String| {
Ok(this
.captures()
.name(&group)
.map(|m| LuaMatch::new(this.text(), m)))
});
methods.add_method("format", |_, this, format: String| {
let mut new = String::new();
this.captures().expand(&format, &mut new);
Ok(new)
});
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(format!("RegexCaptures({})", this.num_captures()))
});
}
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_meta_field(LuaMetaMethod::Type, "RegexCaptures");
}
}

View file

@ -0,0 +1,53 @@
use std::{ops::Range, sync::Arc};
use mlua::prelude::*;
use regex::Match;
/**
A wrapper over the `regex::Match` struct that can be used from Lua.
*/
pub struct LuaMatch {
text: Arc<String>,
start: usize,
end: usize,
}
impl LuaMatch {
/**
Create a new `LuaMatch` instance from a `String` text and a `regex::Match`.
*/
pub fn new(text: Arc<String>, matched: Match) -> Self {
Self {
text,
start: matched.start(),
end: matched.end(),
}
}
fn range(&self) -> Range<usize> {
self.start..self.end
}
fn slice(&self) -> &str {
&self.text[self.range()]
}
}
impl LuaUserData for LuaMatch {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
// NOTE: Strings are 0 based in Rust but 1 based in Luau, and end of range in Rust is exclusive
fields.add_field_method_get("start", |_, this| Ok(this.start.saturating_add(1)));
fields.add_field_method_get("finish", |_, this| Ok(this.end));
fields.add_field_method_get("len", |_, this| Ok(this.range().len()));
fields.add_field_method_get("text", |_, this| Ok(this.slice().to_string()));
fields.add_meta_field(LuaMetaMethod::Type, "RegexMatch");
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(format!("RegexMatch({})", this.slice()))
});
}
}

View file

@ -0,0 +1,21 @@
#![allow(clippy::module_inception)]
use mlua::prelude::*;
use crate::lune::util::TableBuilder;
mod captures;
mod matches;
mod regex;
use self::regex::LuaRegex;
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("new", new_regex)?
.build_readonly()
}
fn new_regex(_: &Lua, pattern: String) -> LuaResult<LuaRegex> {
LuaRegex::new(pattern)
}

View file

@ -0,0 +1,76 @@
use std::sync::Arc;
use mlua::prelude::*;
use regex::Regex;
use super::{captures::LuaCaptures, matches::LuaMatch};
/**
A wrapper over the `regex::Regex` struct that can be used from Lua.
*/
#[derive(Debug, Clone)]
pub struct LuaRegex {
inner: Regex,
}
impl LuaRegex {
/**
Create a new `LuaRegex` instance from a `String` pattern.
*/
pub fn new(pattern: String) -> LuaResult<Self> {
Regex::new(&pattern)
.map(|inner| Self { inner })
.map_err(LuaError::external)
}
}
impl LuaUserData for LuaRegex {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("isMatch", |_, this, text: String| {
Ok(this.inner.is_match(&text))
});
methods.add_method("find", |_, this, text: String| {
let arc = Arc::new(text);
Ok(this
.inner
.find(&arc)
.map(|m| LuaMatch::new(Arc::clone(&arc), m)))
});
methods.add_method("captures", |_, this, text: String| {
Ok(LuaCaptures::new(&this.inner, text))
});
methods.add_method("split", |_, this, text: String| {
Ok(this
.inner
.split(&text)
.map(|s| s.to_string())
.collect::<Vec<_>>())
});
// TODO: Determine whether it's desirable and / or feasible to support
// using a function or table for `replace` like in the lua string library
methods.add_method(
"replace",
|_, this, (haystack, replacer): (String, String)| {
Ok(this.inner.replace(&haystack, replacer).to_string())
},
);
methods.add_method(
"replaceAll",
|_, this, (haystack, replacer): (String, String)| {
Ok(this.inner.replace_all(&haystack, replacer).to_string())
},
);
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(format!("Regex({})", this.inner.as_str()))
});
}
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_meta_field(LuaMetaMethod::Type, "Regex");
}
}

View file

@ -1,3 +1,4 @@
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use serde_json::Value as JsonValue;
@ -86,11 +87,7 @@ impl EncodeDecodeConfig {
lua.create_string(bytes)
}
pub fn deserialize_from_string<'lua>(
self,
lua: &'lua Lua,
string: LuaString<'lua>,
) -> LuaResult<LuaValue<'lua>> {
pub fn deserialize_from_string(self, lua: &Lua, string: BString) -> LuaResult<LuaValue> {
let bytes = string.as_bytes();
match self.format {
EncodeDecodeFormat::Json => {

View file

@ -1,3 +1,4 @@
use bstr::BString;
use mlua::prelude::*;
pub(super) mod compress_decompress;
@ -25,26 +26,23 @@ fn serde_encode<'lua>(
config.serialize_to_string(lua, val)
}
fn serde_decode<'lua>(
lua: &'lua Lua,
(format, str): (EncodeDecodeFormat, LuaString<'lua>),
) -> LuaResult<LuaValue<'lua>> {
fn serde_decode(lua: &Lua, (format, str): (EncodeDecodeFormat, BString)) -> LuaResult<LuaValue> {
let config = EncodeDecodeConfig::from(format);
config.deserialize_from_string(lua, str)
}
async fn serde_compress<'lua>(
lua: &'lua Lua,
(format, str): (CompressDecompressFormat, LuaString<'lua>),
) -> LuaResult<LuaString<'lua>> {
async fn serde_compress(
lua: &Lua,
(format, str): (CompressDecompressFormat, BString),
) -> LuaResult<LuaString> {
let bytes = compress(format, str).await?;
lua.create_string(bytes)
}
async fn serde_decompress<'lua>(
lua: &'lua Lua,
(format, str): (CompressDecompressFormat, LuaString<'lua>),
) -> LuaResult<LuaString<'lua>> {
async fn serde_decompress(
lua: &Lua,
(format, str): (CompressDecompressFormat, BString),
) -> LuaResult<LuaString> {
let bytes = decompress(format, str).await?;
lua.create_string(bytes)
}

View file

@ -2,7 +2,7 @@ use mlua::prelude::*;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
use mlua_luau_scheduler::LuaSpawnExt;
use tokio::io::{self, AsyncWriteExt};
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use crate::lune::util::{
formatting::{
@ -21,6 +21,7 @@ pub fn create(lua: &Lua) -> LuaResult<LuaTable<'_>> {
.with_function("format", stdio_format)?
.with_async_function("write", stdio_write)?
.with_async_function("ewrite", stdio_ewrite)?
.with_async_function("readToEnd", stdio_read_to_end)?
.with_async_function("prompt", stdio_prompt)?
.build_readonly()
}
@ -53,6 +54,21 @@ async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
Ok(())
}
/*
FUTURE: Figure out how to expose some kind of "readLine" function using a buffered reader.
This is a bit tricky since we would want to be able to use **both** readLine and readToEnd
in the same script, doing something like readLine, readLine, readToEnd from lua, and
having that capture the first two lines and then read the rest of the input.
*/
async fn stdio_read_to_end(lua: &Lua, _: ()) -> LuaResult<LuaString> {
let mut input = Vec::new();
let mut stdin = io::stdin();
stdin.read_to_end(&mut input).await?;
lua.create_string(&input)
}
async fn stdio_prompt(lua: &Lua, options: PromptOptions) -> LuaResult<PromptResult> {
lua.spawn_blocking(move || prompt(options))
.await

View file

@ -13,7 +13,7 @@ pub fn create(lua: &Lua) -> LuaResult<impl IntoLua<'_>> {
// If this function runs more than once, we
// may get an already formatted lune version.
if luau_version_str.starts_with(&lune_version) {
if luau_version_str.starts_with(lune_version.as_str()) {
return Ok(luau_version_full);
}

View file

@ -5,7 +5,9 @@
clippy::match_bool,
clippy::module_name_repetitions,
clippy::multiple_crate_versions,
clippy::needless_pass_by_value
clippy::needless_pass_by_value,
clippy::declare_interior_mutable_const,
clippy::borrow_interior_mutable_const
)]
use std::process::ExitCode;

View file

@ -5,10 +5,9 @@ use mlua::Compiler as LuaCompiler;
use once_cell::sync::Lazy;
use tokio::fs;
const MAGIC: &[u8; 8] = b"cr3sc3nt";
static CURRENT_EXE: Lazy<PathBuf> =
pub const CURRENT_EXE: Lazy<PathBuf> =
Lazy::new(|| env::current_exe().expect("failed to get current exe"));
const MAGIC: &[u8; 8] = b"cr3sc3nt";
/*
TODO: Right now all we do is append the bytecode to the end
@ -49,15 +48,19 @@ impl Metadata {
/**
Creates a patched standalone binary from the given script contents.
*/
pub async fn create_env_patched_bin(script_contents: impl Into<Vec<u8>>) -> Result<Vec<u8>> {
let mut patched_bin = fs::read(CURRENT_EXE.to_path_buf()).await?;
// Compile luau input into bytecode
let bytecode = LuaCompiler::new()
pub async fn create_env_patched_bin(
base_exe_path: PathBuf,
script_contents: impl Into<Vec<u8>>,
) -> Result<Vec<u8>> {
let compiler = LuaCompiler::new()
.set_optimization_level(2)
.set_coverage_level(0)
.set_debug_level(1)
.compile(script_contents.into());
.set_debug_level(1);
let mut patched_bin = fs::read(base_exe_path).await?;
// Compile luau input into bytecode
let bytecode = compiler.compile(script_contents.into());
// Append the bytecode / metadata to the end
let meta = Self { bytecode };

View file

@ -83,6 +83,10 @@ create_tests! {
process_spawn_stdin: "process/spawn/stdin",
process_spawn_stdio: "process/spawn/stdio",
regex_general: "regex/general",
regex_metamethods: "regex/metamethods",
regex_replace: "regex/replace",
require_aliases: "require/tests/aliases",
require_async: "require/tests/async",
require_async_concurrent: "require/tests/async_concurrent",

View file

@ -50,15 +50,15 @@ assert(fs.isFile(TEMP_ROOT_PATH_2 .. "/foo/buzz"), "Missing copied file - root/f
-- Make sure the copied files are correct
assert(
fs.readFile(TEMP_ROOT_PATH_2 .. "/foo/bar/baz") == utils.binaryBlob,
fs.readFile(TEMP_ROOT_PATH_2 .. "/foo/bar/baz") == buffer.tostring(utils.binaryBlob),
"Invalid copied file - root/foo/bar/baz"
)
assert(
fs.readFile(TEMP_ROOT_PATH_2 .. "/foo/fizz") == utils.binaryBlob,
fs.readFile(TEMP_ROOT_PATH_2 .. "/foo/fizz") == buffer.tostring(utils.binaryBlob),
"Invalid copied file - root/foo/fizz"
)
assert(
fs.readFile(TEMP_ROOT_PATH_2 .. "/foo/buzz") == utils.binaryBlob,
fs.readFile(TEMP_ROOT_PATH_2 .. "/foo/buzz") == buffer.tostring(utils.binaryBlob),
"Invalid copied file - root/foo/buzz"
)

View file

@ -11,6 +11,8 @@ fs.writeDir(TEMP_ROOT_PATH)
-- Write both of our files
-- binaryBlob is of type buffer to make sure fs.writeFile
-- works with both strings and buffers
fs.writeFile(TEMP_ROOT_PATH .. "/test_binary", utils.binaryBlob)
fs.writeFile(TEMP_ROOT_PATH .. "/test_json.json", utils.jsonBlob)
@ -18,7 +20,7 @@ fs.writeFile(TEMP_ROOT_PATH .. "/test_json.json", utils.jsonBlob)
-- wrote gets us back the original strings
assert(
fs.readFile(TEMP_ROOT_PATH .. "/test_binary") == utils.binaryBlob,
fs.readFile(TEMP_ROOT_PATH .. "/test_binary") == buffer.tostring(utils.binaryBlob),
"Binary file round-trip resulted in different strings"
)

View file

@ -45,7 +45,7 @@ assert(metaFile.kind == "file", "File metadata kind was invalid")
local metaBefore = fs.metadata(TEMP_FILE_PATH)
task.wait(1)
fs.writeFile(TEMP_FILE_PATH, utils.binaryBlob .. "\n")
fs.writeFile(TEMP_FILE_PATH, buffer.tostring(utils.binaryBlob) .. "\n")
local metaAfter = fs.metadata(TEMP_FILE_PATH)
assert(

View file

@ -20,7 +20,7 @@ fs.move("bin/move_test_json.json", "bin/moved_test_json.json")
-- wrote gets us back the original strings
assert(
fs.readFile("bin/moved_test_binary") == utils.binaryBlob,
fs.readFile("bin/moved_test_binary") == buffer.tostring(utils.binaryBlob),
"Binary file round-trip resulted in different strings"
)

View file

@ -16,6 +16,6 @@ local jsonBlob = serde.encode("json", {
-- Return testing data and utils
return {
binaryBlob = binaryBlob,
binaryBlob = buffer.fromstring(binaryBlob),
jsonBlob = jsonBlob,
}

View file

@ -0,0 +1,9 @@
local net = require("@lune/net")
local runtime, version = table.unpack(_VERSION:split(" "))
local expectedUserAgent = runtime:lower() .. "/" .. version
local userAgent: string =
net.jsonDecode(net.request("https://www.whatsmyua.info/api/v1/ua").body)[1].ua.rawUa
assert(userAgent == expectedUserAgent, "Expected user agent to be " .. expectedUserAgent)

View file

@ -22,7 +22,9 @@ end)
task.wait(1)
socket.send('{"op":1,"d":null}')
local payload = '{"op":1,"d":null}'
socket.send(payload)
socket.send(buffer.fromstring(payload))
socket.close(1000)
task.cancel(delayedThread)

39
tests/regex/general.luau Normal file
View file

@ -0,0 +1,39 @@
--!nocheck
local regex = require("@lune/regex")
local re = regex.new("[0-9]+")
assert(re:isMatch("look, a number: 1241425") == true)
local mtch = re:find("1337 wow")
assert(mtch ~= nil)
assert(mtch.start == 1)
assert(mtch.finish == 4)
assert(mtch.len == 4)
assert(mtch.text == "1337")
assert(#mtch == mtch.len)
re = regex.new([[([0-9]+) (\d+) \D+ \d+ (\d)]])
local captures = re:captures("1337 125600 wow! 1984 0")
assert(captures ~= nil)
assert(#captures == 3)
assert(captures:get(0).text == "1337 125600 wow! 1984 0")
assert(captures:get(1).text == "1337")
assert(captures:get(2).text == "125600")
assert(captures:get(3).text == "0")
assert(captures:get(4) == nil)
assert(captures:format("$0") == "1337 125600 wow! 1984 0")
assert(captures:format("$3 $2 $1") == "0 125600 1337")
re = regex.new("(?P<first>[^ ]+)[ ]+(?P<last>[^ ]+)(?P<space>[ ]*)")
captures = re:captures("w1 w2 w3 w4")
assert(captures:format("$last $first$space") == "w2 w1 ")
local split = regex.new([[,]]):split("1,2,3,4")
assert(#split == 4)
assert(split[1] == "1")
assert(split[2] == "2")
assert(split[3] == "3")
assert(split[4] == "4")

View file

@ -0,0 +1,16 @@
--!nolint
local regex = require("@lune/regex")
local re = regex.new("[0-9]+")
assert(tostring(re) == "Regex([0-9]+)")
assert(typeof(re) == "Regex")
local mtch = re:find("1337 wow")
assert(tostring(mtch) == "RegexMatch(1337)")
assert(typeof(mtch) == "RegexMatch")
local re2 = regex.new("([0-9]+) ([0-9]+) wow! ([0-9]+) ([0-9]+)")
local captures = re2:captures("1337 125600 wow! 1984 0")
assert(tostring(captures) == "RegexCaptures(4)")
assert(typeof(captures) == "RegexCaptures")

53
tests/regex/replace.luau Normal file
View file

@ -0,0 +1,53 @@
local regex = require("@lune/regex")
-- Tests taken from the Regex crate
local function replace(
name: string,
pattern: string,
find: string,
replace: string,
expected: string
)
local re = regex.new(pattern)
local replaced = re:replace(find, replace)
if replaced ~= expected then
error(`test '{name}' did not return expected result (expected {expected} got {replaced})`)
end
end
local function replaceAll(
name: string,
pattern: string,
find: string,
replace: string,
expected: string
)
local re = regex.new(pattern)
local replaced = re:replaceAll(find, replace)
if replaced ~= expected then
error(`test '{name}' did not return expected result (expected {expected} got {replaced})`)
end
end
replace("first", "[0-9]", "age: 26", "Z", "age: Z6")
replace("plus", "[0-9]+", "age: 26", "Z", "age: Z")
replaceAll("all", "[0-9]", "age: 26", "Z", "age: ZZ")
replace("groups", "([^ ]+)[ ]+([^ ]+)", "w1 w2", "$2 $1", "w2 w1")
replace("double dollar", "([^ ]+)[ ]+([^ ]+)", "w1 w2", "$2 $$1", "w2 $1")
replaceAll(
"named",
"(?P<first>[^ ]+)[ ]+(?P<last>[^ ]+)(?P<space>[ ]*)",
"w1 w2 w3 w4",
"$last $first$space",
"w2 w1 w4 w3"
)
replaceAll("trim", "^[ \t]+|[ \t]+$", " \t trim me\t \t", "", "trim me")
replace("number hypen", "(.)(.)", "ab", "$1-$2", "a-b")
replaceAll("simple expand", "([a-z]) ([a-z])", "a b", "$2 $1", "b a")
replaceAll("literal dollar 1", "([a-z]+) ([a-z]+)", "a b", "$$1", "$1")
replaceAll("literal dollar 2", "([a-z]+) ([a-z]+)", "a b", "$2 $$c $1", "b $c a")
replaceAll("match at start replace with empty", "foo", "foobar", "", "bar")
replace("single empty match", "^", "bar", "foo", "foobar")

@ -1 +1 @@
Subproject commit 52f2c1a686e7b67d996005eeddf63b97b170a741
Subproject commit 655b5cc6a64024709d3662cc45ec4319c87de5a2

View file

@ -33,69 +33,60 @@ local TESTS: { Test } = {
}
local failed = false
for _, test in TESTS do
local source = fs.readFile(test.Source)
local target = fs.readFile(test.Target)
local success, compressed = pcall(serde.compress, test.Format, source)
local function testOperation(
operationName: "Compress" | "Decompress",
operation: (
format: serde.CompressDecompressFormat,
s: buffer | string
) -> string,
format: serde.CompressDecompressFormat,
source: string | buffer,
target: string
)
local success, res = pcall(operation, format, source)
if not success then
stdio.ewrite(
string.format(
"Compressing source using '%s' format threw an error!\n%s",
tostring(test.Format),
tostring(compressed)
"%sing source using '%s' format threw an error!\n%s",
operationName,
tostring(format),
tostring(res)
)
)
failed = true
continue
elseif compressed ~= target then
elseif res ~= target then
stdio.ewrite(
string.format(
"Compressing source using '%s' format did not produce target!\n",
tostring(test.Format)
"%sing source using '%s' format did not produce target!\n",
operationName,
tostring(format)
)
)
stdio.ewrite(
string.format(
"Compressed (%d chars long):\n%s\nTarget (%d chars long):\n%s\n\n",
#compressed,
tostring(compressed),
"%sed (%d chars long):\n%s\nTarget (%d chars long):\n%s\n\n",
operationName,
#res,
tostring(res),
#target,
tostring(target)
)
)
failed = true
continue
end
end
local success2, decompressed = pcall(serde.decompress, test.Format, target)
if not success2 then
stdio.ewrite(
string.format(
"Decompressing source using '%s' format threw an error!\n%s",
tostring(test.Format),
tostring(decompressed)
)
)
failed = true
continue
elseif decompressed ~= source then
stdio.ewrite(
string.format(
"Decompressing target using '%s' format did not produce source!\n",
tostring(test.Format)
)
)
stdio.ewrite(
string.format(
"Decompressed (%d chars long):\n%s\n\n",
#decompressed,
tostring(decompressed)
)
)
failed = true
continue
end
for _, test in TESTS do
local source = fs.readFile(test.Source)
local target = fs.readFile(test.Target)
-- Compression
testOperation("Compress", serde.compress, test.Format, source, target)
testOperation("Compress", serde.compress, test.Format, buffer.fromstring(source), target)
-- Decompression
testOperation("Decompress", serde.decompress, test.Format, target, source)
testOperation("Decompress", serde.decompress, test.Format, buffer.fromstring(target), source)
end
if failed then

View file

@ -144,7 +144,7 @@ end
@param path The path of the file
@param contents The contents of the file
]=]
function fs.writeFile(path: string, contents: string) end
function fs.writeFile(path: string, contents: buffer | string) end
--[=[
@within FS

View file

@ -36,7 +36,7 @@ export type FetchParamsOptions = {
export type FetchParams = {
url: string,
method: HttpMethod?,
body: string?,
body: (string | buffer)?,
query: HttpQueryMap?,
headers: HttpHeaderMap?,
options: FetchParamsOptions?,
@ -101,7 +101,7 @@ export type ServeRequest = {
export type ServeResponse = {
status: number?,
headers: { [string]: string }?,
body: string?,
body: (string | buffer)?,
}
type ServeHttpHandler = (request: ServeRequest) -> string | ServeResponse
@ -174,7 +174,7 @@ export type ServeHandle = {
export type WebSocket = {
closeCode: number?,
close: (code: number?) -> (),
send: (message: string, asBinaryMessage: boolean?) -> (),
send: (message: (string | buffer)?, asBinaryMessage: boolean?) -> (),
next: () -> string?,
}

218
types/regex.luau Normal file
View file

@ -0,0 +1,218 @@
--[=[
@class RegexMatch
A match from a regular expression.
Contains the following values:
- `start` -- The start index of the match in the original string.
- `finish` -- The end index of the match in the original string.
- `text` -- The text that was matched.
- `len` -- The length of the text that was matched.
]=]
local RegexMatch = {
start = 0,
finish = 0,
text = "",
len = 0,
}
type RegexMatch = typeof(RegexMatch)
--[=[
@class RegexCaptures
Captures from a regular expression.
]=]
local RegexCaptures = {}
--[=[
@within RegexCaptures
@tag Method
Returns the match at the given index, if one exists.
@param index -- The index of the match to get
@return RegexMatch -- The match, if one exists
]=]
function RegexCaptures.get(self: RegexCaptures, index: number): RegexMatch?
return nil :: any
end
--[=[
@within RegexCaptures
@tag Method
Returns the match for the given named match group, if one exists.
@param group -- The name of the group to get
@return RegexMatch -- The match, if one exists
]=]
function RegexCaptures.group(self: RegexCaptures, group: string): RegexMatch?
return nil :: any
end
--[=[
@within RegexCaptures
@tag Method
Formats the captures using the given format string.
### Example usage
```lua
local regex = require("@lune/regex")
local re = regex.new("(?<day>[0-9]{2})-(?<month>[0-9]{2})-(?<year>[0-9]{4})")
local caps = re:captures("On 14-03-2010, I became a Tenneessee lamb.");
assert(caps ~= nil, "Example pattern should match example text")
local formatted = caps:format("year=$year, month=$month, day=$day")
print(formatted) -- "year=2010, month=03, day=14"
```
@param format -- The format string to use
@return string -- The formatted string
]=]
function RegexCaptures.format(self: RegexCaptures, format: string): string
return nil :: any
end
export type RegexCaptures = typeof(RegexCaptures)
local Regex = {}
--[=[
@within Regex
@tag Method
Check if the given text matches the regular expression.
This method may be slightly more efficient than calling `find`
if you only need to know if the text matches the pattern.
@param text -- The text to search
@return boolean -- Whether the text matches the pattern
]=]
function Regex.isMatch(self: Regex, text: string): boolean
return nil :: any
end
--[=[
@within Regex
@tag Method
Finds the first match in the given text.
Returns `nil` if no match was found.
@param text -- The text to search
@return RegexMatch? -- The match object
]=]
function Regex.find(self: Regex, text: string): RegexMatch?
return nil :: any
end
--[=[
@within Regex
@tag Method
Finds all matches in the given text as a `RegexCaptures` object.
Returns `nil` if no matches are found.
@param text -- The text to search
@return RegexCaptures? -- The captures object
]=]
function Regex.captures(self: Regex, text: string): RegexCaptures?
return nil :: any
end
--[=[
@within Regex
@tag Method
Splits the given text using the regular expression.
@param text -- The text to split
@return { string } -- The split text
]=]
function Regex.split(self: Regex, text: string): { string }
return nil :: any
end
--[=[
@within Regex
@tag Method
Replaces the first match in the given text with the given replacer string.
@param haystack -- The text to search
@param replacer -- The string to replace matches with
@return string -- The text with the first match replaced
]=]
function Regex.replace(self: Regex, haystack: string, replacer: string): string
return nil :: any
end
--[=[
@within Regex
@tag Method
Replaces all matches in the given text with the given replacer string.
@param haystack -- The text to search
@param replacer -- The string to replace matches with
@return string -- The text with all matches replaced
]=]
function Regex.replaceAll(self: Regex, haystack: string, replacer: string): string
return nil :: any
end
export type Regex = typeof(Regex)
--[=[
@class Regex
Built-in library for regular expressions
### Example usage
```lua
local Regex = require("@lune/regex")
local re = Regex.new("hello")
if re:isMatch("hello, world!") then
print("Matched!")
end
local caps = re:captures("hello, world! hello, again!")
print(#caps) -- 2
print(caps:get(1)) -- "hello"
print(caps:get(2)) -- "hello"
print(caps:get(3)) -- nil
```
]=]
local regex = {}
--[=[
@within Regex
@tag Constructor
Creates a new `Regex` from a given string pattern.
### Errors
This constructor throws an error if the given pattern is invalid.
@param pattern -- The string pattern to use
@return Regex -- The new Regex object
]=]
function regex.new(pattern: string): Regex
return nil :: any
end
return regex

View file

@ -70,7 +70,7 @@ end
@param encoded The string to decode
@return The decoded lua value
]=]
function serde.decode(format: EncodeDecodeFormat, encoded: string): any
function serde.decode(format: EncodeDecodeFormat, encoded: buffer | string): any
return nil :: any
end
@ -93,7 +93,7 @@ end
@param s The string to compress
@return The compressed string
]=]
function serde.compress(format: CompressDecompressFormat, s: string): string
function serde.compress(format: CompressDecompressFormat, s: buffer | string): string
return nil :: any
end
@ -116,7 +116,7 @@ end
@param s The string to decompress
@return The decompressed string
]=]
function serde.decompress(format: CompressDecompressFormat, s: string): string
function serde.decompress(format: CompressDecompressFormat, s: buffer | string): string
return nil :: any
end

View file

@ -58,6 +58,9 @@ end
stdio.write("World! ")
stdio.write("All on the same line")
stdio.ewrite("\nAnd some error text, too")
-- Reading the entire input from stdin
local input = stdio.readToEnd()
```
]=]
local stdio = {}
@ -143,4 +146,16 @@ function stdio.write(s: string) end
]=]
function stdio.ewrite(s: string) end
--[=[
@within Stdio
@tag must_use
Reads the entire input from stdin.
@return The input from stdin
]=]
function stdio.readToEnd(): string
return nil :: any
end
return stdio