Compare commits

...

127 commits
v0.8.2 ... main

Author SHA1 Message Date
Micah
bb8c4bce82
Update rbx-dom dependencies (#304) 2025-04-02 23:10:56 +02:00
Filip Tibell
6902ecaa7c
Fix various new clippy lints 2025-03-24 19:44:14 +01:00
dai
dc08b91314
Fix deadlock in stdio.format calls in tostring metamethod (#288) 2025-03-24 19:34:51 +01:00
Micah
822dd19393
Add functions for getting Roblox Studio locations to roblox library (#284) 2025-03-24 19:29:22 +01:00
6cd0234a5f
Allow toggling JIT in the CLI (#265) 2025-03-24 19:26:02 +01:00
Micah
19e7f57284
Loosen Lune version string requirements (#294) 2025-03-24 19:24:36 +01:00
Qwreey
5d1401cdf6
Add process.endianness constant (#267) 2024-11-05 13:10:05 +01:00
Sasial
91af86cca2
IsA, ClassName & Parent should work if an instance is already destroyed (#271) 2024-11-05 13:02:15 +01:00
Filip Tibell
c935149c1e
Update dependencies 2024-10-17 11:43:51 +02:00
Filip Tibell
e5bda57665
Document new breaking changes in changelog 2024-10-17 11:43:13 +02:00
Filip Tibell
ef294f207c
Fix websocket example files 2024-10-17 11:27:32 +02:00
Filip Tibell
f89d02a60d
Use 4 spaces for error formatting indentation 2024-10-17 11:26:01 +02:00
Filip Tibell
d090cd2420
Remove redundant stack trace information in error formatter 2024-10-17 11:23:20 +02:00
Filip Tibell
99c17795c1
Update rokit action version and tool versions 2024-10-17 09:26:13 +02:00
Filip Tibell
138221b93e
Update websocket tests and types to use new calling convention 2024-10-16 22:00:33 +02:00
Filip Tibell
8abfc21181
Use standard method calling conventions for websockets 2024-10-16 21:55:53 +02:00
309c461e11
Implement a non-blocking child process interface (#211) 2024-10-16 21:48:12 +02:00
Filip Tibell
93fa14d832
Revert some unnecessary stylistic changes 2024-10-16 21:41:16 +02:00
df4fb9be91
Make Runtime::run Return Lua Values (#178) 2024-10-16 21:35:23 +02:00
eaac9ff53a
Migrate to Rokit as toolchain manager (#238) 2024-10-16 21:06:14 +02:00
Eli
0d2f5539b6
Add Moonwave comments for DateTime properties. (#248) 2024-10-16 21:03:58 +02:00
howmanysmall
0f4cac29aa
Fix Regex types (#250) 2024-10-16 21:03:00 +02:00
Filip Tibell
010cd36375
Version 0.8.9 2024-10-07 19:34:55 +02:00
Filip Tibell
c17da72815
Update dependencies 2024-10-07 19:33:59 +02:00
Filip Tibell
ff83c401b8
Version 0.8.8 2024-08-22 21:30:36 +02:00
Kenneth Loeffler
a007fa94a6
Update all rbx-dom dependencies to their latest versions (#245) 2024-08-22 21:24:32 +02:00
Filip Tibell
1d4d1635eb
Temporarily disable publish to crates.io in workflow 2024-08-10 13:36:19 +02:00
Filip Tibell
56f08a88aa
Fix new clippy lints 2024-08-10 13:34:13 +02:00
Filip Tibell
833d0e244b
Add version and date to changelog 2024-08-10 13:25:55 +02:00
Filip Tibell
3e09807638
Bump all crate versions 2024-08-10 13:07:56 +02:00
Filip Tibell
ea7013322f
Fix type and tostring metamethods not always being respected during table formatting 2024-08-10 13:01:13 +02:00
Filip Tibell
98b31b9f67
Update tooling 2024-08-10 12:48:51 +02:00
Filip Tibell
8364a8e4de
We no longer use selene 2024-08-10 12:48:27 +02:00
Filip Tibell
473ad80e8f
Update dependencies 2024-08-10 12:47:36 +02:00
Filip Tibell
180d20ce4a
Update changelog with missing PRs 2024-08-10 12:28:26 +02:00
Filip Tibell
b585234b08
Clarify some comments and expose more instance functions in lune-roblox 2024-07-21 23:50:55 +02:00
ZachCurtis
5379c79488
Add missing vector methods (#228) 2024-07-21 23:35:58 +02:00
Nick Winans
8aefe88104
Add compression level option to serde.compress (#224) 2024-07-06 22:38:35 +02:00
Maxwell Ruben
cb552af660
Fix readDir with trailing forward-slash on Windows (#220) 2024-07-06 22:34:12 +02:00
Filip Tibell
95c2ca0965
Fix mixed indentation in regex documentation comment 2024-06-23 14:56:31 +02:00
Filip Tibell
5167a71e6f
Improve documentation comments for serde library 2024-06-23 14:53:32 +02:00
Filip Tibell
eac34d2e7e
Re-enable crates.io publish step in release workflow 2024-06-23 14:40:33 +02:00
Filip Tibell
ff80981282
Add missing changelog entry 2024-06-23 14:39:58 +02:00
Filip Tibell
45493dc23b
Temporarily disable crates.io publish in release workflow 2024-06-23 14:09:09 +02:00
Filip Tibell
359f28133f
Add missing entry to changelog 2024-06-23 13:40:13 +02:00
Filip Tibell
c7cbda98fe
Add missing mlua feature to lune-std-luau 2024-06-23 13:28:41 +02:00
Filip Tibell
a7ac864ca5
Fix _VERSION global not being set correctly after crate refactor 2024-06-23 13:22:03 +02:00
Filip Tibell
9993e03f04
Make hash algorithm enum follow convention of other lune enums a bit better 2024-06-23 13:14:27 +02:00
Filip Tibell
997653eb4a
Pin blake3 version 2024-06-23 12:57:16 +02:00
Filip Tibell
f94fbc685a
Update changelog 2024-06-23 12:56:48 +02:00
Filip Tibell
e2e8beb45c
Bump all crate versions 2024-06-23 12:55:47 +02:00
6b38a21454
Remove lua overriding in gitattributes (#212) 2024-06-20 15:27:42 +02:00
Anthony Fuller
430d5683f0
Clarify binary file size in README (#215) 2024-06-19 17:51:09 +02:00
Filip Tibell
59a7955132
Update changelog 2024-06-05 20:23:01 +02:00
Filip Tibell
1fb1d3e7b5
Improve formatting / printing of userdata and tables with __type and / or __tostring metamethods 2024-06-05 20:18:23 +02:00
Filip Tibell
0efc2c565b
Expand test suite for stdio.format 2024-06-05 19:38:19 +02:00
Filip Tibell
c94ab0cde1
Update changelog, some other minor fixes 2024-06-05 19:21:30 +02:00
Filip Tibell
d3b9a4b9e8
Add new options for global injection and codegen to luau.load 2024-06-05 19:02:48 +02:00
Filip Tibell
3cf2be51bc
Make with_args more permissive 2024-06-05 18:52:38 +02:00
Filip Tibell
a3f0f279a8
Remove unused runtime args field 2024-06-05 18:51:37 +02:00
Filip Tibell
a94c9d6d54
Create inner runtime struct to preserve scheduler, globals, and sandboxing across runs 2024-06-05 18:50:23 +02:00
Filip Tibell
63493e78de
Get sandbox working 2024-06-05 18:33:21 +02:00
Filip Tibell
8cb7b8a13a
Fix CI 2024-06-05 17:38:19 +02:00
Filip Tibell
9d9f1685d8
Update tooling 2024-06-05 16:53:36 +02:00
Filip Tibell
91ac6b00c1
Make sure build, lint, test workflow runs for entire workspace 2024-06-05 16:53:19 +02:00
Filip Tibell
2a85532448
Move mlua-luau-scheduler into this repository 2024-06-05 16:45:53 +02:00
Micah
5a292aabc5
Implement hashing algorithms + HMac support (#193) 2024-06-05 16:30:50 +02:00
Filip Tibell
cf513c6724
Update lockfile 2024-06-01 21:50:33 +02:00
Filip Tibell
b628601cc8
Version 0.8.5 2024-06-01 21:49:16 +02:00
Filip Tibell
649bdc4c31
Bump lune-utils dependency in lune-std 2024-06-01 21:46:49 +02:00
Filip Tibell
3030158159
Bump std roblox version too 2024-06-01 21:44:55 +02:00
Filip Tibell
23456ae041
Bump versions in all changed packages 2024-06-01 21:42:36 +02:00
Filip Tibell
4f6f1835d2
Fix erroring when setting nil attributes on instances in roblox lib 2024-06-01 21:38:14 +02:00
Filip Tibell
636d0bf277
Update changelog 2024-06-01 21:26:15 +02:00
Filip Tibell
adc74f47c0
Fix panic when spawning a program that does not exist 2024-06-01 21:24:45 +02:00
Filip Tibell
1fd17ca0b3
Update changelog 2024-06-01 21:17:29 +02:00
Filip Tibell
9498620e03
Sort tables before formatting them 2024-06-01 21:08:47 +02:00
Filip Tibell
0850f41617
Improve pretty formatting for arrays 2024-06-01 21:01:51 +02:00
Filip Tibell
f2c40a4bd5
Improve formatting for empty tables 2024-06-01 20:24:48 +02:00
Filip Tibell
bfb89dec01
Fix table indentation and newline issues with new value formatter 2024-06-01 20:15:10 +02:00
Bryan Cardwell
395c36fa8b
Implement idiv support for Vector2 and Vector3 (#196) 2024-05-15 12:06:21 +02:00
Filip Tibell
7e784ba361
Fix changelog not having separated added and changed sections in latest release 2024-05-12 20:58:00 +02:00
Filip Tibell
95b81d65fe
Bump lune std crate versions for latest lune-std-datetime 2024-05-12 18:01:04 +02:00
Filip Tibell
43de7b277a
Bump chrono dependency versions 2024-05-12 17:58:08 +02:00
Filip Tibell
1d0bab5e65
Fix readme field in lune crate 2024-05-12 17:51:15 +02:00
Filip Tibell
e0f065b678
Remove unused env_logger dependency from lune crate 2024-05-12 17:13:48 +02:00
Filip Tibell
5b5c46f802
Add another missing tokio feature to lune-std-process 2024-05-12 16:22:37 +02:00
Filip Tibell
489c8a6609
Add missing tokio features to lune-std-process crate 2024-05-12 16:08:02 +02:00
Filip Tibell
a13b17fee6
Add missing mlua feature to lune-std-serde crate 2024-05-12 15:20:25 +02:00
Filip Tibell
e0b9ceb86d
Add missing repository and description fields to all manifests 2024-05-12 14:32:39 +02:00
Filip Tibell
6bc1fa2343
Fix release workflow version read 2024-05-12 14:10:50 +02:00
Filip Tibell
763b3ff6a7
Version 0.8.4 2024-05-12 14:08:12 +02:00
Filip Tibell
816b6654da
Update changelog 2024-05-12 14:07:30 +02:00
Filip Tibell
efcc3c6028
Update dependencies 2024-05-12 13:52:02 +02:00
Filip Tibell
de71558c5d
Split lune into proper crates (#188) 2024-05-12 13:30:32 +02:00
Filip Tibell
3f53fc983c
Update changelog 2024-04-21 00:03:23 +02:00
Filip Tibell
089ecb3a4c
Update dependencies 2024-04-20 23:54:34 +02:00
Filip Tibell
7a46f12c02
Fix type metamethod and test case for regex 2024-04-20 23:38:06 +02:00
Filip Tibell
96eed54a65
Port test suite for regex from fork 2024-04-20 23:30:08 +02:00
Filip Tibell
abe7217a58
Add full type definitions for new regex builtin 2024-04-20 23:19:21 +02:00
Filip Tibell
54081c1b0f
Add docs on public functions, fix captures being returned even if not found 2024-04-20 22:54:12 +02:00
Filip Tibell
12f5824da9
Port over regex implementation from fork, do some cleanup 2024-04-20 22:47:27 +02:00
Filip Tibell
e11302766b
Add scaffolding for new regex builtin 2024-04-20 22:09:48 +02:00
Filip Tibell
70f0c55d35
Do a pass over all doc comments in new build modules 2024-04-20 22:08:18 +02:00
Filip Tibell
476125cc74
Fix clippy lint in net builtin 2024-04-20 21:59:57 +02:00
Filip Tibell
b8196d6284
Organize everything neatly into files 2024-04-20 21:59:25 +02:00
Filip Tibell
a11c1558ed
Implement strict target enum for standalone builds, improve paths logic 2024-04-20 21:59:25 +02:00
Filip Tibell
cec77a9bd9
Move standalone build into its own module 2024-04-20 21:59:25 +02:00
Filip Tibell
0685e62a8f
Refactor downloading lune binary to cache, some fixes + formatting 2024-04-20 21:59:25 +02:00
Filip Tibell
53463641b8
Fix clippy lints in serde builtin 2024-04-20 21:59:25 +02:00
66ed1a0b72
Use a more descriptive User-Agent header in net.request (#186) 2024-04-20 16:55:54 +02:00
f830ce7fad
Add support for buffers as arguments in builtin APIs (#148) 2024-04-20 16:44:19 +02:00
7fb48dfa1f
Implement cross-compilation of standalone binaries (#162) 2024-04-20 16:30:47 +02:00
Filip Tibell
fa7f6c6f51
Fix doc example for new stdio API 2024-04-18 22:14:54 +02:00
Filip Tibell
753897222a
Implement stdio.readToEnd API 2024-04-18 22:10:08 +02:00
Filip Tibell
4a28499aaa
Fix net.serve no longer accepting ipv6 2024-04-18 20:56:40 +02:00
Filip Tibell
03c773fd79
Fix headers in net.serve being raw bytes instead of strings 2024-04-18 20:54:21 +02:00
fe55442dac
Fix Stack Overflow while Formatting Tables with Circular Keys (#183) 2024-04-18 12:29:23 +02:00
Filip Tibell
34fc23d024
Add native CI target for M1 macs 2024-04-15 23:40:58 +02:00
Filip Tibell
fcfb5ed3a8
Version 0.8.3 2024-04-15 23:34:51 +02:00
Filip Tibell
f36ec1483a
Update changelog 2024-04-15 23:31:16 +02:00
Filip Tibell
aa04fb345c
Update dependencies 2024-04-15 23:28:51 +02:00
Someon1e
3f79756f70
Fix require not throwing syntax error (#168) 2024-04-15 23:21:25 +02:00
Snorlax
8220216893
Solve test yielding in net_serve_requests (#177) 2024-04-11 14:04:40 +02:00
0d302cf0c1
Fix itertools dependency being marked optional even though it is mandatory (#176) 2024-04-11 13:42:21 +02:00
PhantomShift
1e3a604d0f
Fix case-sensitivity issue in requires with aliases (#173) 2024-04-07 14:44:17 +02:00
Kenneth Loeffler
a65ef2ae1b
Fix require caching by using correct tuple ordering in resolve_paths (#171) 2024-04-07 14:41:32 +02:00
274 changed files with 11232 additions and 3271 deletions

4
.gitattributes vendored
View file

@ -1,9 +1,5 @@
* text=auto
# Temporarily highlight luau as normal lua files
# until we get native linguist support for Luau
*.luau linguist-language=Lua
# Ensure all lua files use LF
*.lua eol=lf
*.luau eol=lf

View file

@ -23,11 +23,8 @@ jobs:
with:
components: rustfmt
- name: Install Just
uses: extractions/setup-just@v1
- name: Install Tooling
uses: ok-nick/setup-aftman@v0.4.2
uses: CompeyDev/setup-rokit@v0.1.2
- name: Check Formatting
run: just fmt-check
@ -40,11 +37,8 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Just
uses: extractions/setup-just@v1
- name: Install Tooling
uses: ok-nick/setup-aftman@v0.4.2
uses: CompeyDev/setup-rokit@v0.1.2
- name: Analyze
run: just analyze
@ -64,9 +58,13 @@ jobs:
cargo-target: x86_64-unknown-linux-gnu
- name: macOS x86_64
runner-os: macos-latest
runner-os: macos-13
cargo-target: x86_64-apple-darwin
timeout-minutes: 10
- name: macOS aarch64
runner-os: macos-14
cargo-target: aarch64-apple-darwin
name: CI - ${{ matrix.name }}
runs-on: ${{ matrix.runner-os }}
steps:
@ -84,17 +82,20 @@ jobs:
- name: Build
run: |
cargo build \
--workspace \
--locked --all-features \
--target ${{ matrix.cargo-target }}
- name: Lint
run: |
cargo clippy \
--workspace \
--locked --all-features \
--target ${{ matrix.cargo-target }}
- name: Test
run: |
cargo test \
--lib --workspace \
--locked --all-features \
--target ${{ matrix.cargo-target }}

View file

@ -1,24 +0,0 @@
name: Publish
on:
push:
branches:
- "main"
workflow_dispatch:
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
ignore-unpublished-changes: true

View file

@ -21,14 +21,32 @@ jobs:
uses: actions/checkout@v4
- name: Get version from manifest
uses: SebRollen/toml-action@9062fbef52816d61278d24ce53c8070440e1e8dd
uses: SebRollen/toml-action@v1.2.0
id: get_version
with:
file: Cargo.toml
file: crates/lune/Cargo.toml
field: package.version
# dry-run:
# name: Dry-run
# needs: ["init"]
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# - name: Install Rust
# uses: dtolnay/rust-toolchain@stable
# - name: Publish (dry-run)
# uses: katyo/publish-crates@v2
# with:
# dry-run: true
# check-repo: true
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
build:
needs: ["init"]
needs: ["init"] # , "dry-run"]
strategy:
fail-fast: false
matrix:
@ -70,7 +88,7 @@ jobs:
targets: ${{ matrix.cargo-target }}
- name: Install Just
uses: extractions/setup-just@v1
uses: extractions/setup-just@v2
- name: Install build tooling (aarch64-unknown-linux-gnu)
if: matrix.cargo-target == 'aarch64-unknown-linux-gnu'
@ -86,24 +104,24 @@ jobs:
run: just zip-release ${{ matrix.cargo-target }}
- name: Upload release artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: release.zip
release:
name: Release
release-github:
name: Release (GitHub)
runs-on: ubuntu-latest
needs: ["init", "build"]
needs: ["init", "build"] # , "dry-run", "build"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Just
uses: extractions/setup-just@v1
uses: extractions/setup-just@v2
- name: Download releases
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: ./releases
@ -111,7 +129,7 @@ jobs:
run: just unpack-releases "./releases"
- name: Create release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@ -120,3 +138,21 @@ jobs:
fail_on_unmatched_files: true
files: ./releases/*.zip
draft: true
# release-crates:
# name: Release (crates.io)
# runs-on: ubuntu-latest
# needs: ["init", "dry-run", "build"]
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# - name: Install Rust
# uses: dtolnay/rust-toolchain@stable
# - name: Publish crates
# uses: katyo/publish-crates@v2
# with:
# dry-run: false
# check-repo: true
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View file

@ -129,7 +129,7 @@ end
]]
print("Sending 4 pings to google 🌏")
local result = process.spawn("ping", {
local result = process.exec("ping", {
"google.com",
"-c 4",
})

View file

@ -28,8 +28,8 @@ end)
for _ = 1, 5 do
local start = os.clock()
socket.send(tostring(1))
local response = socket.next()
socket:send(tostring(1))
local response = socket:next()
local elapsed = os.clock() - start
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
task.wait(1 - elapsed)
@ -38,7 +38,7 @@ end
-- Everything went well, and we are done with the socket, so we can close it
print("Closing web socket...")
socket.close()
socket:close()
task.cancel(forceExit)
print("Done! 🌙")

View file

@ -15,9 +15,9 @@ local handle = net.serve(PORT, {
handleWebSocket = function(socket)
print("Got new web socket connection!")
repeat
local message = socket.next()
local message = socket:next()
if message ~= nil then
socket.send("Echo - " .. message)
socket:send("Echo - " .. message)
end
until message == nil
print("Web socket disconnected.")

View file

@ -8,6 +8,234 @@ 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).
## `0.9.0`
### Breaking changes
- Added two new process spawning functions - `process.create` and `process.exec`, removing the previous `process.spawn` API completely. ([#211])
To migrate from `process.spawn`, use the new `process.exec` API which retains the same behavior as the old function.
The new `process.create` function is a non-blocking process creation API and can be used to interactively
read and write stdio of the process.
```lua
local child = process.create("program", {
"cli-argument",
"other-cli-argument"
})
-- Writing to stdin
child.stdin:write("Hello from Lune!")
-- Reading from stdout
local data = child.stdout:read()
print(buffer.tostring(data))
```
- WebSocket methods in `net.socket` and `net.serve` now use standard Lua method calling convention and colon syntax.
This means `socket.send(...)` is now `socket:send(...)`, `socket.close(...)` is now `socket:close(...)`, and so on.
- `Runtime::run` now returns a more useful value instead of an `ExitCode` ([#178])
### Changed
- Documentation comments for several standard library properties have been improved ([#248], [#250])
- Error messages no longer contain redundant or duplicate stack trace information
[#178]: https://github.com/lune-org/lune/pull/178
[#211]: https://github.com/lune-org/lune/pull/211
[#248]: https://github.com/lune-org/lune/pull/248
[#250]: https://github.com/lune-org/lune/pull/250
## `0.8.9` - October 7th, 2024
### Changed
- Updated to Luau version `0.640`
## `0.8.8` - August 22nd, 2024
### Fixed
- Fixed errors when deserializing `Lighting.AttributesSerialize` by updating `rbx-dom` dependencies ([#245])
[#245]: https://github.com/lune-org/lune/pull/245
## `0.8.7` - August 10th, 2024
### Added
- Added a compression level option to `serde.compress` ([#224])
- Added missing vector methods to the `roblox` library ([#228])
### Changed
- Updated to Luau version `0.635`
- Updated to rbx-dom database version `0.634`
### Fixed
- Fixed `fs.readDir` with trailing forward-slash on Windows ([#220])
- Fixed `__type` and `__tostring` metamethods not always being respected when formatting tables
[#220]: https://github.com/lune-org/lune/pull/220
[#224]: https://github.com/lune-org/lune/pull/224
[#228]: https://github.com/lune-org/lune/pull/228
## `0.8.6` - June 23rd, 2024
### Added
- Added a builtin API for hashing and calculating HMACs as part of the `serde` library ([#193])
Basic usage:
```lua
local serde = require("@lune/serde")
local hash = serde.hash("sha256", "a message to hash")
local hmac = serde.hmac("sha256", "a message to hash", "a secret string")
print(hash)
print(hmac)
```
The returned hashes are sequences of lowercase hexadecimal digits. The following algorithms are supported:
`md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `blake3`
- Added two new options to `luau.load`:
- `codegenEnabled` - whether or not codegen should be enabled for the loaded chunk.
- `injectGlobals` - whether or not to inject globals into a passed `environment`.
By default, globals are injected and codegen is disabled.
Check the documentation for the `luau` standard library for more information.
- Implemented support for floor division operator / `__idiv` for the `Vector2` and `Vector3` types in the `roblox` standard library ([#196])
- Fixed the `_VERSION` global containing an incorrect Lune version string.
### Changed
- Sandboxing and codegen in the Luau VM is now fully enabled, resulting in up to 2x or faster code execution.
This should not result in any behavior differences in Lune, but if it does, please open an issue.
- Improved formatting of custom error objects (such as when `fs.readFile` returns an error) when printed or formatted using `stdio.format`.
### Fixed
- Fixed `__type` and `__tostring` metamethods on userdatas and tables not being respected when printed or formatted using `stdio.format`.
[#193]: https://github.com/lune-org/lune/pull/193
[#196]: https://github.com/lune-org/lune/pull/196
## `0.8.5` - June 1st, 2024
### Changed
- Improved table pretty formatting when using `print`, `warn`, and `stdio.format`:
- Keys are sorted numerically / alphabetically when possible.
- Keys of different types are put in distinct sections for mixed tables.
- Tables that are arrays no longer display their keys.
- Empty tables are no longer spread across lines.
## Fixed
- Fixed formatted values in tables not being separated by newlines.
- Fixed panicking (crashing) when using `process.spawn` with a program that does not exist.
- Fixed `instance:SetAttribute("name", nil)` throwing an error and not removing the attribute.
## `0.8.4` - May 12th, 2024
### Added
- 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
- Split the repository into modular crates instead of a monolith. ([#188])
If you previously depended on Lune as a crate, nothing about it has changed for version `0.8.4`, but now each individual sub-crate has also been published and is available for use:
- `lune` (old)
- `lune-utils`
- `lune-roblox`
- `lune-std-*` for every builtin library
When depending on the main `lune` crate, each builtin library also has a feature flag that can be toggled in the format `std-*`.
In general, this should mean that it is now much easier to make your own Lune builtin, publish your own flavor of a Lune CLI, or take advantage of all the work that has been done for Lune as a runtime when making your own Rust programs.
- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])
- Updated to Luau version `0.622`.
### Fixed
- Fixed not being able to decompress `lz4` format in high compression mode
- 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
[#188]: https://github.com/lune-org/lune/pull/188
## `0.8.3` - April 15th, 2024
### Fixed
- Fixed `require` not throwing syntax errors ([#168])
- Fixed `require` caching not working correctly ([#171])
- Fixed case-sensitivity issue in `require` with aliases ([#173])
- Fixed `itertools` dependency being marked optional even though it is mandatory ([#176])
- Fixed test cases for the `net` built-in library on Windows ([#177])
[#168]: https://github.com/lune-org/lune/pull/168
[#171]: https://github.com/lune-org/lune/pull/171
[#173]: https://github.com/lune-org/lune/pull/173
[#176]: https://github.com/lune-org/lune/pull/176
[#177]: https://github.com/lune-org/lune/pull/177
## `0.8.2` - March 12th, 2024
### Fixed

1998
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,22 @@
[package]
name = "lune"
version = "0.8.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "A standalone Luau runtime"
readme = "README.md"
keywords = ["cli", "lua", "luau", "runtime"]
categories = ["command-line-interface"]
[[bin]]
name = "lune"
path = "src/main.rs"
[lib]
name = "lune"
path = "src/lib.rs"
[features]
default = ["cli", "roblox"]
cli = [
"dep:anyhow",
"dep:env_logger",
"dep:itertools",
"dep:clap",
"dep:include_dir",
"dep:regex",
"dep:rustyline",
]
roblox = [
"dep:glam",
"dep:rand",
"dep:rbx_cookie",
"dep:rbx_binary",
"dep:rbx_dom_weak",
"dep:rbx_reflection",
"dep:rbx_reflection_database",
"dep:rbx_xml",
[workspace]
resolver = "2"
default-members = ["crates/lune"]
members = [
"crates/lune",
"crates/lune-roblox",
"crates/lune-std",
"crates/lune-std-datetime",
"crates/lune-std-fs",
"crates/lune-std-luau",
"crates/lune-std-net",
"crates/lune-std-process",
"crates/lune-std-regex",
"crates/lune-std-roblox",
"crates/lune-std-serde",
"crates/lune-std-stdio",
"crates/lune-std-task",
"crates/lune-utils",
"crates/mlua-luau-scheduler",
]
# Profile for building the release binary, with the following options set:
@ -54,99 +34,31 @@ opt-level = "z"
strip = true
lto = true
# All of the dependencies for Lune.
# Lints for all crates in the workspace
#
# Dependencies are categorized as following:
#
# 1. General dependencies with no specific features set
# 2. Large / core dependencies that have many different crates and / or features set
# 3. Dependencies for specific features of Lune, eg. the CLI or massive Roblox builtin library
#
[dependencies]
console = "0.15"
directories = "5.0"
futures-util = "0.3"
once_cell = "1.17"
thiserror = "1.0"
async-trait = "0.1"
dialoguer = "0.11"
dunce = "1.0"
lz4_flex = "0.11"
path-clean = "1.0"
pathdiff = "0.2"
pin-project = "1.0"
urlencoding = "2.1"
# 1. Error on all lints by default, then make cargo + clippy pedantic lints just warn
# 2. Selectively allow some lints that are _too_ pedantic, such as:
# - Casts between number types
# - Module naming conventions
# - Imports and multiple dependency versions
[workspace.lints.clippy]
all = { level = "deny", priority = -3 }
cargo = { level = "warn", priority = -2 }
pedantic = { level = "warn", priority = -1 }
### RUNTIME
cast_lossless = { level = "allow", priority = 1 }
cast_possible_truncation = { level = "allow", priority = 1 }
cast_possible_wrap = { level = "allow", priority = 1 }
cast_precision_loss = { level = "allow", priority = 1 }
cast_sign_loss = { level = "allow", priority = 1 }
blocking = "1.5"
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"] }
similar_names = { level = "allow", priority = 1 }
unnecessary_wraps = { level = "allow", priority = 1 }
unnested_or_patterns = { level = "allow", priority = 1 }
unreadable_literal = { level = "allow", priority = 1 }
mlua-luau-scheduler = { version = "0.0.2" }
mlua = { version = "0.9.6", features = [
"luau",
"luau-jit",
"async",
"serialize",
] }
### SERDE
async-compression = { version = "0.4", features = [
"tokio",
"brotli",
"deflate",
"gzip",
"zlib",
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
serde_yaml = "0.9"
toml = { version = "0.8", features = ["preserve_order"] }
### NET
hyper = { version = "1.1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
http = "1.0"
http-body-util = { version = "0.1" }
hyper-tungstenite = { version = "0.13" }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
] }
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
### DATETIME
chrono = "=0.4.34" # NOTE: 0.4.35 does not compile with chrono_lc
chrono_lc = "0.1"
### CLI
anyhow = { optional = true, version = "1.0" }
env_logger = { optional = true, version = "0.11" }
itertools = { optional = true, version = "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" }
### ROBLOX
glam = { optional = true, version = "0.25" }
rand = { optional = true, version = "0.8" }
rbx_cookie = { optional = true, version = "0.1.4", default-features = false }
rbx_binary = { optional = true, version = "0.7.3" }
rbx_dom_weak = { optional = true, version = "2.6.0" }
rbx_reflection = { optional = true, version = "4.4.0" }
rbx_reflection_database = { optional = true, version = "0.2.9" }
rbx_xml = { optional = true, version = "0.13.2" }
multiple_crate_versions = { level = "allow", priority = 1 }
module_inception = { level = "allow", priority = 1 }
module_name_repetitions = { level = "allow", priority = 1 }
needless_pass_by_value = { level = "allow", priority = 1 }
wildcard_imports = { level = "allow", priority = 1 }

View file

@ -33,7 +33,7 @@ Lune provides fully asynchronous APIs wherever possible, and is built in Rust
## Features
- 🌙 Strictly minimal but powerful interface that is easy to read and remember, just like Luau itself
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb) executable
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb zipped) executable
- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary
- 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port
- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances

View file

@ -1,4 +0,0 @@
[tools]
luau-lsp = "JohnnyMorganz/luau-lsp@1.27.0"
selene = "Kampfkarren/selene@0.26.1"
stylua = "JohnnyMorganz/StyLua@0.19.1"

View file

@ -0,0 +1,29 @@
[package]
name = "lune-roblox"
version = "0.1.4"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Roblox library for Lune"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
glam = "0.27"
rand = "0.8"
thiserror = "1.0"
once_cell = "1.17"
rbx_binary = "1.0.0"
rbx_dom_weak = "3.0.0"
rbx_reflection = "5.0.0"
rbx_reflection_database = "1.0.0"
rbx_xml = "1.0.0"
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -4,6 +4,15 @@ use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
use super::extension::DomValueExt;
/**
Checks if the given name is a valid attribute name.
# Errors
- If the name starts with the prefix "RBX".
- If the name contains any characters other than alphanumeric characters and underscore.
- If the name is longer than 100 characters.
*/
pub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {
let name = name.as_ref();
if name.to_ascii_uppercase().starts_with("RBX") {
@ -23,6 +32,13 @@ pub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {
}
}
/**
Checks if the given value is a valid attribute value.
# Errors
- If the value is not a valid attribute type.
*/
pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
let is_valid = matches!(
value.ty(),

View file

@ -2,7 +2,7 @@ use mlua::prelude::*;
use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
use crate::roblox::{datatypes::extension::DomValueExt, instance::Instance};
use crate::{datatypes::extension::DomValueExt, instance::Instance};
use super::*;
@ -51,7 +51,7 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
DomValue::Content(s) => Ok(LuaValue::String(
DomValue::ContentId(s) => Ok(LuaValue::String(
lua.create_string(AsRef::<str>::as_ref(s))?,
)),
@ -65,8 +65,10 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
// NOTE: Some values are either optional or default and we should handle
// that properly here since the userdata conversion above will always fail
DomValue::OptionalCFrame(None) => Ok(LuaValue::Nil),
DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => Ok(LuaValue::Nil),
DomValue::OptionalCFrame(None)
| DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => {
Ok(LuaValue::Nil)
}
_ => Err(e),
},

View file

@ -6,6 +6,7 @@ pub(crate) trait DomValueExt {
impl DomValueExt for DomType {
fn variant_name(&self) -> Option<&'static str> {
#[allow(clippy::enum_glob_use)]
use DomType::*;
Some(match self {
Attributes => "Attributes",

View file

@ -10,4 +10,4 @@ mod util;
use result::*;
pub use crate::roblox::shared::userdata::*;
pub use crate::shared::userdata::*;

View file

@ -3,7 +3,9 @@ use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::Axes as DomAxes;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, EnumItem};
@ -52,8 +54,7 @@ impl LuaExportsTable<'_> for Axes {
check(&e);
} else {
return Err(LuaError::RuntimeError(format!(
"Expected argument #{} to be an EnumItem, got userdata",
index
"Expected argument #{index} to be an EnumItem, got userdata",
)));
}
} else {

View file

@ -4,14 +4,16 @@ use mlua::prelude::*;
use rand::seq::SliceRandom;
use rbx_dom_weak::types::BrickColor as DomBrickColor;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Color3};
/**
An implementation of the [BrickColor](https://create.roblox.com/docs/reference/engine/datatypes/BrickColor) Roblox datatype.
This implements all documented properties, methods & constructors of the BrickColor class as of March 2023.
This implements all documented properties, methods & constructors of the `BrickColor` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BrickColor {

View file

@ -1,3 +1,5 @@
#![allow(clippy::items_after_statements)]
use core::fmt;
use std::ops;
@ -5,7 +7,9 @@ use glam::{EulerRot, Mat3, Mat4, Quat, Vec3};
use mlua::{prelude::*, Variadic};
use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Vector3};
@ -14,7 +18,7 @@ use super::{super::*, Vector3};
Roblox datatype, backed by [`glam::Mat4`].
This implements all documented properties, methods &
constructors of the CFrame class as of March 2023.
constructors of the `CFrame` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CFrame(pub Mat4);
@ -42,6 +46,7 @@ impl CFrame {
impl LuaExportsTable<'_> for CFrame {
const EXPORT_NAME: &'static str = "CFrame";
#[allow(clippy::too_many_lines)]
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
@ -68,8 +73,7 @@ impl LuaExportsTable<'_> for CFrame {
Ok(CFrame(Mat4::from_cols(
rx.0.extend(0.0),
ry.0.extend(0.0),
rz.map(|r| r.0)
.unwrap_or_else(|| rx.0.cross(ry.0).normalize())
rz.map_or_else(|| rx.0.cross(ry.0).normalize(), |r| r.0)
.extend(0.0),
pos.0.extend(1.0),
)))
@ -195,6 +199,7 @@ impl LuaUserData for CFrame {
});
}
#[allow(clippy::too_many_lines)]
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
@ -226,34 +231,49 @@ impl LuaUserData for CFrame {
methods.add_method(
"ToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| *this * *cf)))
Ok(rhs
.into_iter()
.map(|cf| *this * *cf)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"ToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
let inverse = this.inverse();
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| inverse * *cf)))
Ok(rhs
.into_iter()
.map(|cf| inverse * *cf)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"PointToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| *this * *v3)))
Ok(rhs
.into_iter()
.map(|v3| *this * *v3)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"PointToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
let inverse = this.inverse();
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| inverse * *v3)))
Ok(rhs
.into_iter()
.map(|v3| inverse * *v3)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"VectorToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
let result = *this - Vector3(this.position());
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3)))
Ok(rhs
.into_iter()
.map(|v3| result * *v3)
.collect::<Variadic<_>>())
},
);
methods.add_method(
@ -261,8 +281,10 @@ impl LuaUserData for CFrame {
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
let inverse = this.inverse();
let result = inverse - Vector3(inverse.position());
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3)))
Ok(rhs
.into_iter()
.map(|v3| result * *v3)
.collect::<Variadic<_>>())
},
);
#[rustfmt::skip]
@ -445,7 +467,7 @@ mod cframe_test {
Vec3::new(1.0, 2.0, 3.0).extend(1.0),
));
assert_eq!(CFrame::from(dom_cframe), cframe)
assert_eq!(CFrame::from(dom_cframe), cframe);
}
#[test]
@ -466,6 +488,6 @@ mod cframe_test {
),
);
assert_eq!(DomCFrame::from(cframe), dom_cframe)
assert_eq!(DomCFrame::from(cframe), dom_cframe);
}
}

View file

@ -1,3 +1,5 @@
#![allow(clippy::many_single_char_names)]
use core::fmt;
use std::ops;
@ -5,7 +7,9 @@ use glam::Vec3;
use mlua::prelude::*;
use rbx_dom_weak::types::{Color3 as DomColor3, Color3uint8 as DomColor3uint8};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;
@ -85,8 +89,7 @@ impl LuaExportsTable<'_> for Color3 {
b: (b as f32) / 255f32,
}),
_ => Err(LuaError::RuntimeError(format!(
"Hex color string '{}' contains invalid character",
trimmed
"Hex color string '{trimmed}' contains invalid character",
))),
}
};
@ -151,6 +154,7 @@ impl LuaUserData for Color3 {
let max = r.max(g).max(b);
let diff = max - min;
#[allow(clippy::float_cmp)]
let hue = (match max {
max if max == min => 0.0,
max if max == r => (g - b) / diff + (if g < b { 6.0 } else { 0.0 }),

View file

@ -5,14 +5,16 @@ use rbx_dom_weak::types::{
ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint,
};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Color3, ColorSequenceKeypoint};
/**
An implementation of the [ColorSequence](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence) Roblox datatype.
This implements all documented properties, methods & constructors of the ColorSequence class as of March 2023.
This implements all documented properties, methods & constructors of the `ColorSequence` class as of March 2023.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct ColorSequence {
@ -87,9 +89,9 @@ impl fmt::Display for ColorSequence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (index, keypoint) in self.keypoints.iter().enumerate() {
if index < self.keypoints.len() - 1 {
write!(f, "{}, ", keypoint)?;
write!(f, "{keypoint}, ")?;
} else {
write!(f, "{}", keypoint)?;
write!(f, "{keypoint}")?;
}
}
Ok(())
@ -102,7 +104,7 @@ impl From<DomColorSequence> for ColorSequence {
keypoints: v
.keypoints
.iter()
.cloned()
.copied()
.map(ColorSequenceKeypoint::from)
.collect(),
}
@ -115,7 +117,7 @@ impl From<ColorSequence> for DomColorSequence {
keypoints: v
.keypoints
.iter()
.cloned()
.copied()
.map(DomColorSequenceKeypoint::from)
.collect(),
}

View file

@ -3,14 +3,16 @@ use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::ColorSequenceKeypoint as DomColorSequenceKeypoint;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Color3};
/**
An implementation of the [ColorSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint) Roblox datatype.
This implements all documented properties, methods & constructors of the ColorSequenceKeypoint class as of March 2023.
This implements all documented properties, methods & constructors of the `ColorSequenceKeypoint` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ColorSequenceKeypoint {

View file

@ -8,7 +8,7 @@ use super::{super::*, Enum};
/**
An implementation of the [EnumItem](https://create.roblox.com/docs/reference/engine/datatypes/EnumItem) Roblox datatype.
This implements all documented properties, methods & constructors of the EnumItem class as of March 2023.
This implements all documented properties, methods & constructors of the `EnumItem` class as of March 2023.
*/
#[derive(Debug, Clone)]
pub struct EnumItem {

View file

@ -24,8 +24,7 @@ impl LuaUserData for Enums {
|_, _, name: String| match Enum::from_name(&name) {
Some(e) => Ok(e),
None => Err(LuaError::RuntimeError(format!(
"The enum '{}' does not exist",
name
"The enum '{name}' does not exist",
))),
},
);

View file

@ -1,9 +1,13 @@
#![allow(clippy::struct_excessive_bools)]
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::Faces as DomFaces;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, EnumItem};
@ -54,8 +58,7 @@ impl LuaExportsTable<'_> for Faces {
check(&e);
} else {
return Err(LuaError::RuntimeError(format!(
"Expected argument #{} to be an EnumItem, got userdata",
index
"Expected argument #{index} to be an EnumItem, got userdata",
)));
}
} else {

View file

@ -6,7 +6,9 @@ use rbx_dom_weak::types::{
Font as DomFont, FontStyle as DomFontStyle, FontWeight as DomFontWeight,
};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, EnumItem};
@ -62,7 +64,7 @@ impl LuaExportsTable<'_> for Font {
let font_from_name =
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font {
family: format!("rbxasset://fonts/families/{}.json", file),
family: format!("rbxasset://fonts/families/{file}.json"),
weight: weight.unwrap_or_default(),
style: style.unwrap_or_default(),
cached_id: None,
@ -72,7 +74,7 @@ impl LuaExportsTable<'_> for Font {
let font_from_id =
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font {
family: format!("rbxassetid://{}", id),
family: format!("rbxassetid://{id}"),
weight: weight.unwrap_or_default(),
style: style.unwrap_or_default(),
cached_id: None,
@ -206,7 +208,7 @@ pub(crate) enum FontWeight {
}
impl FontWeight {
pub(crate) fn as_u16(&self) -> u16 {
pub(crate) fn as_u16(self) -> u16 {
match self {
Self::Thin => 100,
Self::ExtraLight => 200,
@ -288,12 +290,11 @@ impl<'lua> FromLua<'lua> for FontWeight {
if value.parent.desc.name == "FontWeight" {
if let Ok(value) = FontWeight::from_str(&value.name) {
return Ok(value);
} else {
message = Some(format!(
"Found unknown Enum.FontWeight value '{}'",
value.name
));
}
message = Some(format!(
"Found unknown Enum.FontWeight value '{}'",
value.name
));
} else {
message = Some(format!(
"Expected Enum.FontWeight, got Enum.{}",
@ -316,7 +317,7 @@ impl<'lua> IntoLua<'lua> for FontWeight {
None => Err(LuaError::ToLuaConversionError {
from: "FontWeight",
to: "EnumItem",
message: Some(format!("Found unknown Enum.FontWeight value '{}'", self)),
message: Some(format!("Found unknown Enum.FontWeight value '{self}'")),
}),
}
}
@ -329,7 +330,7 @@ pub(crate) enum FontStyle {
}
impl FontStyle {
pub(crate) fn as_u8(&self) -> u8 {
pub(crate) fn as_u8(self) -> u8 {
match self {
Self::Normal => 0,
Self::Italic => 1,
@ -383,12 +384,11 @@ impl<'lua> FromLua<'lua> for FontStyle {
if value.parent.desc.name == "FontStyle" {
if let Ok(value) = FontStyle::from_str(&value.name) {
return Ok(value);
} else {
message = Some(format!(
"Found unknown Enum.FontStyle value '{}'",
value.name
));
}
message = Some(format!(
"Found unknown Enum.FontStyle value '{}'",
value.name
));
} else {
message = Some(format!(
"Expected Enum.FontStyle, got Enum.{}",
@ -411,7 +411,7 @@ impl<'lua> IntoLua<'lua> for FontStyle {
None => Err(LuaError::ToLuaConversionError {
from: "FontStyle",
to: "EnumItem",
message: Some(format!("Found unknown Enum.FontStyle value '{}'", self)),
message: Some(format!("Found unknown Enum.FontStyle value '{self}'")),
}),
}
}

View file

@ -3,14 +3,16 @@ use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::NumberRange as DomNumberRange;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;
/**
An implementation of the [NumberRange](https://create.roblox.com/docs/reference/engine/datatypes/NumberRange) Roblox datatype.
This implements all documented properties, methods & constructors of the NumberRange class as of March 2023.
This implements all documented properties, methods & constructors of the `NumberRange` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NumberRange {

View file

@ -5,14 +5,16 @@ use rbx_dom_weak::types::{
NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint,
};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, NumberSequenceKeypoint};
/**
An implementation of the [NumberSequence](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequence) Roblox datatype.
This implements all documented properties, methods & constructors of the NumberSequence class as of March 2023.
This implements all documented properties, methods & constructors of the `NumberSequence` class as of March 2023.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct NumberSequence {
@ -91,9 +93,9 @@ impl fmt::Display for NumberSequence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (index, keypoint) in self.keypoints.iter().enumerate() {
if index < self.keypoints.len() - 1 {
write!(f, "{}, ", keypoint)?;
write!(f, "{keypoint}, ")?;
} else {
write!(f, "{}", keypoint)?;
write!(f, "{keypoint}")?;
}
}
Ok(())
@ -106,7 +108,7 @@ impl From<DomNumberSequence> for NumberSequence {
keypoints: v
.keypoints
.iter()
.cloned()
.copied()
.map(NumberSequenceKeypoint::from)
.collect(),
}
@ -119,7 +121,7 @@ impl From<NumberSequence> for DomNumberSequence {
keypoints: v
.keypoints
.iter()
.cloned()
.copied()
.map(DomNumberSequenceKeypoint::from)
.collect(),
}

View file

@ -3,14 +3,16 @@ use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::NumberSequenceKeypoint as DomNumberSequenceKeypoint;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;
/**
An implementation of the [NumberSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequenceKeypoint) Roblox datatype.
This implements all documented properties, methods & constructors of the NumberSequenceKeypoint class as of March 2023.
This implements all documented properties, methods & constructors of the `NumberSequenceKeypoint` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NumberSequenceKeypoint {

View file

@ -3,14 +3,16 @@ use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::CustomPhysicalProperties as DomCustomPhysicalProperties;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, EnumItem};
/**
An implementation of the [PhysicalProperties](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties) Roblox datatype.
This implements all documented properties, methods & constructors of the PhysicalProperties class as of March 2023.
This implements all documented properties, methods & constructors of the `PhysicalProperties` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PhysicalProperties {

View file

@ -4,7 +4,9 @@ use glam::Vec3;
use mlua::prelude::*;
use rbx_dom_weak::types::Ray as DomRay;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Vector3};

View file

@ -5,7 +5,9 @@ use glam::Vec2;
use mlua::prelude::*;
use rbx_dom_weak::types::Rect as DomRect;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Vector2};

View file

@ -4,7 +4,9 @@ use glam::{Mat4, Vec3};
use mlua::prelude::*;
use rbx_dom_weak::types::Region3 as DomRegion3;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, CFrame, Vector3};

View file

@ -4,7 +4,9 @@ use glam::IVec3;
use mlua::prelude::*;
use rbx_dom_weak::types::Region3int16 as DomRegion3int16;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Vector3int16};

View file

@ -4,14 +4,16 @@ use std::ops;
use mlua::prelude::*;
use rbx_dom_weak::types::UDim as DomUDim;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;
/**
An implementation of the [UDim](https://create.roblox.com/docs/reference/engine/datatypes/UDim) Roblox datatype.
This implements all documented properties, methods & constructors of the UDim class as of March 2023.
This implements all documented properties, methods & constructors of the `UDim` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UDim {

View file

@ -1,3 +1,5 @@
#![allow(clippy::items_after_statements)]
use core::fmt;
use std::ops;
@ -5,14 +7,16 @@ use glam::Vec2;
use mlua::prelude::*;
use rbx_dom_weak::types::UDim2 as DomUDim2;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, UDim};
/**
An implementation of the [UDim2](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) Roblox datatype.
This implements all documented properties, methods & constructors of the UDim2 class as of March 2023.
This implements all documented properties, methods & constructors of the `UDim2` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UDim2 {

View file

@ -5,7 +5,9 @@ use glam::{Vec2, Vec3};
use mlua::prelude::*;
use rbx_dom_weak::types::Vector2 as DomVector2;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;
@ -50,6 +52,9 @@ impl LuaUserData for Vector2 {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
Ok(this.0.angle_between(rhs.0))
});
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);
@ -58,6 +63,14 @@ impl LuaUserData for Vector2 {
methods.add_method("Dot", |_, this, rhs: LuaUserDataRef<Vector2>| {
Ok(this.0.dot(rhs.0))
});
methods.add_method(
"FuzzyEq",
|_, this, (rhs, epsilon): (LuaUserDataRef<Vector2>, f32)| {
let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;
let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;
Ok(eq_x && eq_y)
},
);
methods.add_method(
"Lerp",
|_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {
@ -70,6 +83,10 @@ impl LuaUserData for Vector2 {
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector2>| {
Ok(Vector2(this.0.min(rhs.0)))
});
methods.add_method("Abs", |_, this, ()| Ok(Vector2(this.0.abs())));
methods.add_method("Ceil", |_, this, ()| Ok(Vector2(this.0.ceil())));
methods.add_method("Floor", |_, this, ()| Ok(Vector2(this.0.floor())));
methods.add_method("Sign", |_, this, ()| Ok(Vector2(this.0.signum())));
// Metamethods
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
@ -78,6 +95,7 @@ impl LuaUserData for Vector2 {
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);
}
}
@ -136,6 +154,20 @@ impl ops::Div<f32> for Vector2 {
}
}
impl IDiv for Vector2 {
type Output = Vector2;
fn idiv(self, rhs: Self) -> Self::Output {
Self((self.0 / rhs.0).floor())
}
}
impl IDiv<f32> for Vector2 {
type Output = Vector2;
fn idiv(self, rhs: f32) -> Self::Output {
Self((self.0 / rhs).floor())
}
}
impl From<DomVector2> for Vector2 {
fn from(v: DomVector2) -> Self {
Vector2(Vec2 { x: v.x, y: v.y })

View file

@ -5,7 +5,9 @@ use glam::IVec2;
use mlua::prelude::*;
use rbx_dom_weak::types::Vector2int16 as DomVector2int16;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;

View file

@ -5,10 +5,9 @@ use glam::Vec3;
use mlua::prelude::*;
use rbx_dom_weak::types::Vector3 as DomVector3;
use crate::{
lune::util::TableBuilder,
roblox::{datatypes::util::round_float_decimal, exports::LuaExportsTable},
};
use lune_utils::TableBuilder;
use crate::{datatypes::util::round_float_decimal, exports::LuaExportsTable};
use super::{super::*, EnumItem};
@ -37,8 +36,7 @@ impl LuaExportsTable<'_> for Vector3 {
"Z" => Vector3(Vec3::Z),
name => {
return Err(LuaError::RuntimeError(format!(
"Axis '{}' is not known",
name
"Axis '{name}' is not known",
)))
}
})
@ -61,8 +59,7 @@ impl LuaExportsTable<'_> for Vector3 {
"Back" => Vector3(Vec3::Z),
name => {
return Err(LuaError::RuntimeError(format!(
"NormalId '{}' is not known",
name
"NormalId '{name}' is not known",
)))
}
})
@ -136,6 +133,10 @@ impl LuaUserData for Vector3 {
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector3>| {
Ok(Vector3(this.0.min(rhs.0)))
});
methods.add_method("Abs", |_, this, ()| Ok(Vector3(this.0.abs())));
methods.add_method("Ceil", |_, this, ()| Ok(Vector3(this.0.ceil())));
methods.add_method("Floor", |_, this, ()| Ok(Vector3(this.0.floor())));
methods.add_method("Sign", |_, this, ()| Ok(Vector3(this.0.signum())));
// Metamethods
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
@ -144,6 +145,7 @@ impl LuaUserData for Vector3 {
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);
}
}
@ -202,6 +204,20 @@ impl ops::Div<f32> for Vector3 {
}
}
impl IDiv for Vector3 {
type Output = Vector3;
fn idiv(self, rhs: Self) -> Self::Output {
Self((self.0 / rhs.0).floor())
}
}
impl IDiv<f32> for Vector3 {
type Output = Vector3;
fn idiv(self, rhs: f32) -> Self::Output {
Self((self.0 / rhs).floor())
}
}
impl From<DomVector3> for Vector3 {
fn from(v: DomVector3) -> Self {
Vector3(Vec3 {

View file

@ -5,7 +5,9 @@ use glam::IVec3;
use mlua::prelude::*;
use rbx_dom_weak::types::Vector3int16 as DomVector3int16;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::super::*;

View file

@ -2,7 +2,7 @@ use std::path::Path;
use rbx_dom_weak::WeakDom;
use crate::roblox::shared::instance::class_is_a_service;
use crate::shared::instance::class_is_a_service;
/**
A document kind specifier.
@ -58,13 +58,14 @@ impl DocumentKind {
Returns `None` if the given dom is empty and as such can not have its kind inferred.
*/
#[must_use]
pub fn from_weak_dom(dom: &WeakDom) -> Option<Self> {
let mut has_top_level_child = false;
let mut has_top_level_service = false;
for child_ref in dom.root().children() {
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
has_top_level_child = true;
if class_is_a_service(&child_inst.class).unwrap_or(false) {
if class_is_a_service(child_inst.class).unwrap_or(false) {
has_top_level_service = true;
break;
}

View file

@ -15,7 +15,7 @@ pub use kind::*;
use postprocessing::*;
use crate::roblox::instance::{data_model, Instance};
use crate::instance::{data_model, Instance};
pub type DocumentResult<T> = Result<T, DocumentError>;
@ -78,6 +78,7 @@ impl Document {
| Model | Binary | `rbxm` |
| Model | Xml | `rbxmx` |
*/
#[must_use]
#[rustfmt::skip]
pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> &'static str {
match (kind, format) {
@ -113,6 +114,10 @@ impl Document {
Note that detection of model vs place file is heavily dependent on the structure
of the file, and a model file with services in it will detect as a place file, so
if possible using [`Document::from_bytes`] with an explicit kind should be preferred.
# Errors
Errors if the given bytes are not a valid roblox file.
*/
pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> {
let (format, dom) = Self::from_bytes_inner(bytes)?;
@ -125,6 +130,10 @@ impl Document {
This will automatically handle and detect if the document
should be decoded using a roblox binary or roblox xml format.
# Errors
Errors if the given bytes are not a valid roblox file or not of the given kind.
*/
pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> DocumentResult<Self> {
let (format, dom) = Self::from_bytes_inner(bytes)?;
@ -138,6 +147,10 @@ impl Document {
This will use the same format that the document was created
with, meaning if the document is a binary document the output
will be binary, and vice versa for xml and other future formats.
# Errors
Errors if the document can not be encoded.
*/
pub fn to_bytes(&self) -> DocumentResult<Vec<u8>> {
self.to_bytes_with_format(self.format)
@ -146,6 +159,10 @@ impl Document {
/**
Encodes the document as a vector of bytes, to
be written to a file or sent over the network.
# Errors
Errors if the document can not be encoded.
*/
pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult<Vec<u8>> {
let mut bytes = Vec::new();
@ -172,6 +189,7 @@ impl Document {
/**
Gets the kind this document was created with.
*/
#[must_use]
pub fn kind(&self) -> DocumentKind {
self.kind
}
@ -179,6 +197,7 @@ impl Document {
/**
Gets the format this document was created with.
*/
#[must_use]
pub fn format(&self) -> DocumentFormat {
self.format
}
@ -186,14 +205,17 @@ impl Document {
/**
Gets the file extension for this document.
*/
#[must_use]
pub fn extension(&self) -> &'static str {
Self::canonical_extension(self.kind, self.format)
}
/**
Creates a DataModel instance out of this place document.
Creates a `DataModel` instance out of this place document.
Will error if the document is not a place.
# Errors
Errors if the document is not a place.
*/
pub fn into_data_model_instance(mut self) -> DocumentResult<Instance> {
if self.kind != DocumentKind::Place {
@ -219,7 +241,9 @@ impl Document {
/**
Creates an array of instances out of this model document.
Will error if the document is not a model.
# Errors
Errors if the document is not a model.
*/
pub fn into_instance_array(mut self) -> DocumentResult<Vec<Instance>> {
if self.kind != DocumentKind::Model {
@ -237,9 +261,11 @@ impl Document {
}
/**
Creates a place document out of a DataModel instance.
Creates a place document out of a `DataModel` instance.
Will error if the instance is not a DataModel.
# Errors
Errors if the instance is not a `DataModel`.
*/
pub fn from_data_model_instance(i: Instance) -> DocumentResult<Self> {
if i.get_class_name() != data_model::CLASS_NAME {
@ -266,7 +292,9 @@ impl Document {
/**
Creates a model document out of an array of instances.
Will error if any of the instances is a DataModel.
# Errors
Errors if any of the instances is a `DataModel`.
*/
pub fn from_instance_array(v: Vec<Instance>) -> DocumentResult<Self> {
for i in &v {

View file

@ -1,9 +1,9 @@
use rbx_dom_weak::{
types::{Ref as DomRef, VariantType as DomType},
Instance as DomInstance, WeakDom,
ustr, Instance as DomInstance, WeakDom,
};
use crate::roblox::shared::instance::class_is_a;
use crate::shared::instance::class_is_a;
pub fn postprocess_dom_for_place(_dom: &mut WeakDom) {
// Nothing here yet
@ -18,8 +18,8 @@ pub fn postprocess_dom_for_model(dom: &mut WeakDom) {
remove_matching_prop(inst, DomType::UniqueId, "HistoryId");
// Similar story with ScriptGuid - this is used
// in the studio-only cloud script drafts feature
if class_is_a(&inst.class, "LuaSourceContainer").unwrap_or(false) {
inst.properties.remove("ScriptGuid");
if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) {
inst.properties.remove(&ustr("ScriptGuid"));
}
});
}
@ -41,7 +41,8 @@ where
}
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
if inst.properties.get(name).map_or(false, |u| u.ty() == ty) {
let name = &ustr(name);
if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {
inst.properties.remove(name);
}
}

View file

@ -1,3 +1,5 @@
#![allow(clippy::items_after_statements)]
use mlua::prelude::*;
use rbx_dom_weak::{
@ -5,7 +7,7 @@ use rbx_dom_weak::{
Instance as DomInstance,
};
use crate::roblox::{
use crate::{
datatypes::{
attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value},
conversion::{DomValueToLua, LuaToDomValue},
@ -17,6 +19,7 @@ use crate::roblox::{
use super::{data_model, registry::InstanceRegistry, Instance};
#[allow(clippy::too_many_lines)]
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
ensure_not_destroyed(this)?;
@ -68,7 +71,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
"FindFirstAncestorWhichIsA",
|lua, this, class_name: String| {
ensure_not_destroyed(this)?;
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false))
.into_lua(lua)
},
);
@ -101,7 +104,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|lua, this, (class_name, recursive): (String, Option<bool>)| {
ensure_not_destroyed(this)?;
let predicate =
|child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false);
|child: &DomInstance| class_is_a(child.class, &class_name).unwrap_or(false);
if matches!(recursive, Some(true)) {
this.find_descendant(predicate).into_lua(lua)
} else {
@ -110,8 +113,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
},
);
m.add_method("IsA", |_, this, class_name: String| {
ensure_not_destroyed(this)?;
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
Ok(class_is_a(this.class_name, class_name).unwrap_or(false))
});
m.add_method(
"IsAncestorOf",
@ -142,7 +144,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
ensure_not_destroyed(this)?;
let attributes = this.get_attributes();
let tab = lua.create_table_with_capacity(0, attributes.len())?;
for (key, value) in attributes.into_iter() {
for (key, value) in attributes {
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
}
Ok(tab)
@ -152,13 +154,18 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
ensure_not_destroyed(this)?;
ensure_valid_attribute_name(&attribute_name)?;
match lua_value.lua_to_dom_value(lua, None) {
Ok(dom_value) => {
ensure_valid_attribute_value(&dom_value)?;
this.set_attribute(attribute_name, dom_value);
Ok(())
if lua_value.is_nil() || lua_value.is_null() {
this.remove_attribute(attribute_name);
Ok(())
} else {
match lua_value.lua_to_dom_value(lua, None) {
Ok(dom_value) => {
ensure_valid_attribute_value(&dom_value)?;
this.set_attribute(attribute_name, dom_value);
Ok(())
}
Err(e) => Err(e.into()),
}
Err(e) => Err(e.into()),
}
},
);
@ -209,26 +216,26 @@ fn instance_property_get<'lua>(
this: &Instance,
prop_name: String,
) -> LuaResult<LuaValue<'lua>> {
ensure_not_destroyed(this)?;
match prop_name.as_str() {
"ClassName" => return this.get_class_name().into_lua(lua),
"Name" => {
return this.get_name().into_lua(lua);
}
"Parent" => {
return this.get_parent().into_lua(lua);
}
_ => {}
}
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
ensure_not_destroyed(this)?;
if prop_name.as_str() == "Name" {
return this.get_name().into_lua(lua);
}
if let Some(info) = find_property_info(this.class_name, &prop_name) {
if let Some(prop) = this.get_property(&prop_name) {
if let DomValue::Enum(enum_value) = prop {
let enum_name = info.enum_name.ok_or_else(|| {
LuaError::RuntimeError(format!(
"Failed to get property '{}' - encountered unknown enum",
prop_name
"Failed to get property '{prop_name}' - encountered unknown enum",
))
})?;
EnumItem::from_enum_name_and_value(&enum_name, enum_value.to_u32())
@ -246,8 +253,7 @@ fn instance_property_get<'lua>(
EnumItem::from_enum_name_and_value(&enum_name, enum_value)
.ok_or_else(|| {
LuaError::RuntimeError(format!(
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
prop_name, enum_name, enum_value
"Failed to get property '{prop_name}' - Enum.{enum_name} does not contain numeric value {enum_value}",
))
})?
.into_lua(lua)
@ -258,26 +264,23 @@ fn instance_property_get<'lua>(
Ok(LuaValue::Nil)
} else {
Err(LuaError::RuntimeError(format!(
"Failed to get property '{}' - missing default value",
prop_name
"Failed to get property '{prop_name}' - missing default value",
)))
}
} else {
Err(LuaError::RuntimeError(format!(
"Failed to get property '{}' - malformed property info",
prop_name
"Failed to get property '{prop_name}' - malformed property info",
)))
}
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
getter.call(this.clone())
getter.call(*this)
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
Ok(LuaValue::Function(method))
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
"{prop_name} is not a valid member of {this}",
)))
}
}
@ -318,13 +321,13 @@ fn instance_property_set<'lua>(
}
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
let parent = Parent::from_lua(prop_value, lua)?;
this.set_parent(parent.map(|p| p.clone()));
this.set_parent(parent.map(|p| *p));
return Ok(());
}
_ => {}
}
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
if let Some(info) = find_property_info(this.class_name, &prop_name) {
if let Some(enum_name) = info.enum_name {
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
@ -347,16 +350,14 @@ fn instance_property_set<'lua>(
}
} else {
Err(LuaError::RuntimeError(format!(
"Failed to set property '{}' - malformed property info",
prop_name
"Failed to set property '{prop_name}' - malformed property info",
)))
}
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
setter.call((this.clone(), prop_value))
setter.call((*this, prop_value))
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
"{prop_name} is not a valid member of {this}",
)))
}
}

View file

@ -1,6 +1,6 @@
use mlua::prelude::*;
use crate::roblox::shared::{
use crate::shared::{
classes::{
add_class_restricted_getter, add_class_restricted_method,
get_or_create_property_ref_instance,
@ -26,40 +26,39 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
### See Also
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
}
/**
Gets or creates a service for this DataModel.
Gets or creates a service for this `DataModel`.
### See Also
* [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> LuaResult<Instance> {
if matches!(class_is_a_service(&service_name), None | Some(false)) {
Err(LuaError::RuntimeError(format!(
"'{}' is not a valid service name",
service_name
"'{service_name}' is not a valid service name",
)))
} else if let Some(service) = this.find_child(|child| child.class == service_name) {
Ok(service)
} else {
let service = Instance::new_orphaned(service_name);
service.set_parent(Some(this.clone()));
service.set_parent(Some(*this));
Ok(service)
}
}
/**
Gets a service for this DataModel, if it exists.
Gets a service for this `DataModel`, if it exists.
### See Also
* [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn data_model_find_service(
_: &Lua,
@ -68,8 +67,7 @@ fn data_model_find_service(
) -> LuaResult<Option<Instance>> {
if matches!(class_is_a_service(&service_name), None | Some(false)) {
Err(LuaError::RuntimeError(format!(
"'{}' is not a valid service name",
service_name
"'{service_name}' is not a valid service name",
)))
} else if let Some(service) = this.find_child(|child| child.class == service_name) {
Ok(Some(service))

View file

@ -1,3 +1,5 @@
#![allow(clippy::missing_panics_doc)]
use std::{
collections::{BTreeMap, VecDeque},
fmt,
@ -9,13 +11,14 @@ use mlua::prelude::*;
use once_cell::sync::Lazy;
use rbx_dom_weak::{
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
ustr, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, Ustr, WeakDom,
};
use lune_utils::TableBuilder;
use crate::{
lune::util::TableBuilder,
roblox::exports::LuaExportsTable,
roblox::shared::instance::{class_exists, class_is_a},
exports::LuaExportsTable,
shared::instance::{class_exists, class_is_a},
};
pub(crate) mod base;
@ -31,10 +34,10 @@ const PROPERTY_NAME_TAGS: &str = "Tags";
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Instance {
pub(crate) dom_ref: DomRef,
pub(crate) class_name: String,
pub(crate) class_name: Ustr,
}
impl Instance {
@ -42,47 +45,37 @@ impl Instance {
Creates a new `Instance` from an existing dom object ref.
Panics if the instance does not exist in the internal dom,
or if the given dom object ref points to the dom root.
or if the given dom object ref points to the internal dom root.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/
pub(crate) fn new(dom_ref: DomRef) -> Self {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let instance = dom
.get_by_ref(dom_ref)
.expect("Failed to find instance in document");
if instance.referent() == dom.root_ref() {
panic!("Instances can not be created from dom roots")
}
Self {
dom_ref,
class_name: instance.class.clone(),
}
#[must_use]
pub fn new(dom_ref: DomRef) -> Self {
Self::new_opt(dom_ref).expect("Failed to find instance in document")
}
/**
Creates a new `Instance` from a dom object ref, if the instance exists.
Panics if the given dom object ref points to the dom root.
Panics if the given dom object ref points to the internal dom root.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/
pub(crate) fn new_opt(dom_ref: DomRef) -> Option<Self> {
#[must_use]
pub fn new_opt(dom_ref: DomRef) -> Option<Self> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
if let Some(instance) = dom.get_by_ref(dom_ref) {
if instance.referent() == dom.root_ref() {
panic!("Instances can not be created from dom roots")
}
assert!(
!(instance.referent() == dom.root_ref()),
"Instances can not be created from dom roots"
);
Some(Self {
dom_ref,
class_name: instance.class.clone(),
class_name: instance.class,
})
} else {
None
@ -92,24 +85,25 @@ impl Instance {
/**
Creates a new orphaned `Instance` with a given class name.
An orphaned instance is an instance at the root of a weak dom.
An orphaned instance is an instance at the root of Lune's internal weak dom.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/
pub(crate) fn new_orphaned(class_name: impl AsRef<str>) -> Self {
#[must_use]
pub fn new_orphaned(class_name: impl AsRef<str>) -> Self {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let class_name = class_name.as_ref();
let instance = DomInstanceBuilder::new(class_name.to_string());
let instance = DomInstanceBuilder::new(class_name);
let dom_root = dom.root_ref();
let dom_ref = dom.insert(dom_root, instance);
Self {
dom_ref,
class_name: class_name.to_string(),
class_name: ustr(class_name),
}
}
@ -117,10 +111,11 @@ impl Instance {
Creates a new orphaned `Instance` by transferring
it from an external weak dom to the internal one.
An orphaned instance is an instance at the root of a weak dom.
An orphaned instance is an instance at the root of Lune's internal weak dom.
Panics if the given dom ref is the root dom ref of the external weak dom.
*/
#[must_use]
pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let dom_root = dom.root_ref();
@ -146,6 +141,12 @@ impl Instance {
cloned
}
/**
Clones multiple instances to an external weak dom.
This will place the instances as children of the
root of the weak dom, and return their referents.
*/
pub fn clone_multiple_into_external_dom(
referents: &[DomRef],
external_dom: &mut WeakDom,
@ -154,7 +155,7 @@ impl Instance {
let cloned = dom.clone_multiple_into_external(referents, external_dom);
for referent in cloned.iter() {
for referent in &cloned {
external_dom.transfer_within(*referent, external_dom.root_ref());
}
@ -169,9 +170,10 @@ impl Instance {
### See Also
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn clone_instance(&self) -> Instance {
#[must_use]
pub fn clone_instance(&self) -> Self {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let new_ref = dom.clone_within(self.dom_ref);
drop(dom); // Self::new needs mutex handle, drop it first
@ -192,7 +194,7 @@ impl Instance {
### See Also
* [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn destroy(&mut self) -> bool {
if self.is_destroyed() {
@ -219,7 +221,7 @@ impl Instance {
### See Also
* [`Instance::Destroy`] for more info about what happens when an instance gets destroyed
* [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn clear_all_children(&mut self) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -239,10 +241,10 @@ impl Instance {
### See Also
* [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
class_is_a(&self.class_name, class_name).unwrap_or(false)
class_is_a(self.class_name, class_name).unwrap_or(false)
}
/**
@ -252,8 +254,9 @@ impl Instance {
### See Also
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
#[must_use]
pub fn get_class_name(&self) -> &str {
self.class_name.as_str()
}
@ -263,7 +266,7 @@ impl Instance {
### See Also
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_name(&self) -> String {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -279,14 +282,14 @@ impl Instance {
### See Also
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn set_name(&self, name: impl Into<String>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
dom.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document")
.name = name.into()
.name = name.into();
}
/**
@ -294,15 +297,12 @@ impl Instance {
### See Also
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_parent(&self) -> Option<Instance> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let parent_ref = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
.parent();
let parent_ref = dom.get_by_ref(self.dom_ref)?.parent();
if parent_ref == dom.root_ref() {
None
@ -317,18 +317,16 @@ impl Instance {
If the provided parent is [`None`] the instance will become orphaned.
An orphaned instance is an instance at the root of a weak dom.
An orphaned instance is an instance at the root of Lune's internal weak dom.
### See Also
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn set_parent(&self, parent: Option<Instance>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let parent_ref = parent
.map(|parent| parent.dom_ref)
.unwrap_or_else(|| dom.root_ref());
let parent_ref = parent.map_or_else(|| dom.root_ref(), |parent| parent.dom_ref);
dom.transfer_within(self.dom_ref, parent_ref);
}
@ -343,7 +341,7 @@ impl Instance {
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
.properties
.get(name.as_ref())
.get(&ustr(name.as_ref()))
.cloned()
}
@ -360,7 +358,7 @@ impl Instance {
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document")
.properties
.insert(name.as_ref().to_string(), value);
.insert(ustr(name.as_ref()), value);
}
/**
@ -368,7 +366,7 @@ impl Instance {
### See Also
* [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -376,7 +374,7 @@ impl Instance {
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
{
attributes.get(name.as_ref()).cloned()
} else {
@ -389,7 +387,7 @@ impl Instance {
### See Also
* [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -397,7 +395,7 @@ impl Instance {
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
{
attributes.clone().into_iter().collect()
} else {
@ -410,7 +408,7 @@ impl Instance {
### See Also
* [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -424,36 +422,59 @@ impl Instance {
value => value,
};
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES)
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
{
attributes.insert(name.as_ref().to_string(), value);
} else {
let mut attributes = DomAttributes::new();
attributes.insert(name.as_ref().to_string(), value);
inst.properties.insert(
PROPERTY_NAME_ATTRIBUTES.to_string(),
ustr(PROPERTY_NAME_ATTRIBUTES),
DomValue::Attributes(attributes),
);
}
}
/**
Removes an attribute from the instance.
Note that this does not have an equivalent in the Roblox engine API,
but separating this from `set_attribute` lets `set_attribute` be more
ergonomic and not require an `Option<DomValue>` for the value argument.
The equivalent in the Roblox engine API would be `instance:SetAttribute(name, nil)`.
*/
pub fn remove_attribute(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
{
attributes.remove(name.as_ref());
if attributes.is_empty() {
inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES));
}
}
}
/**
Adds a tag to the instance.
### See Also
* [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn add_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
tags.push(name.as_ref());
} else {
inst.properties.insert(
PROPERTY_NAME_TAGS.to_string(),
ustr(PROPERTY_NAME_TAGS),
DomValue::Tags(vec![name.as_ref().to_string()].into()),
);
}
@ -464,14 +485,14 @@ impl Instance {
### See Also
* [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_tags(&self) -> Vec<String> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
tags.iter().map(ToString::to_string).collect()
} else {
Vec::new()
@ -483,14 +504,14 @@ impl Instance {
### See Also
* [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn has_tag(&self, name: impl AsRef<str>) -> bool {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
let name = name.as_ref();
tags.iter().any(|tag| tag == name)
} else {
@ -503,21 +524,19 @@ impl Instance {
### See Also
* [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn remove_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
let name = name.as_ref();
let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
new_tags.retain(|tag| tag != name);
inst.properties.insert(
PROPERTY_NAME_TAGS.to_string(),
DomValue::Tags(new_tags.into()),
);
inst.properties
.insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into()));
}
}
@ -529,7 +548,7 @@ impl Instance {
### See Also
* [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_children(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -552,7 +571,7 @@ impl Instance {
### See Also
* [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_descendants(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -586,7 +605,7 @@ impl Instance {
### See Also
* [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_full_name(&self) -> String {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -663,9 +682,8 @@ impl Instance {
if predicate(ancestor) {
drop(dom); // Self::new needs mutex handle, drop it first
return Some(Self::new(ancestor_ref));
} else {
ancestor_ref = ancestor.parent();
}
ancestor_ref = ancestor.parent();
}
None
@ -677,7 +695,7 @@ impl Instance {
### See Also
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
where
@ -699,9 +717,8 @@ impl Instance {
let queue_ref = queue_item.referent();
drop(dom); // Self::new needs mutex handle, drop it first
return Some(Self::new(queue_ref));
} else {
queue.extend(queue_item.children())
}
queue.extend(queue_item.children());
}
None
@ -717,8 +734,7 @@ impl LuaExportsTable<'_> for Instance {
Instance::new_orphaned(class_name).into_lua(lua)
} else {
Err(LuaError::RuntimeError(format!(
"Failed to create Instance - '{}' is not a valid class name",
class_name
"Failed to create Instance - '{class_name}' is not a valid class name",
)))
}
};
@ -756,7 +772,7 @@ impl LuaUserData for Instance {
impl Hash for Instance {
fn hash<H: Hasher>(&self, state: &mut H) {
self.dom_ref.hash(state)
self.dom_ref.hash(state);
}
}

View file

@ -51,6 +51,13 @@ impl InstanceRegistry {
.expect("Missing InstanceRegistry in app data")
}
/**
Inserts a method into the instance registry.
# Errors
- If the method already exists in the registry.
*/
pub fn insert_method<'lua>(
lua: &'lua Lua,
class_name: &str,
@ -80,6 +87,13 @@ impl InstanceRegistry {
Ok(())
}
/**
Inserts a property getter into the instance registry.
# Errors
- If the property already exists in the registry.
*/
pub fn insert_property_getter<'lua>(
lua: &'lua Lua,
class_name: &str,
@ -109,6 +123,13 @@ impl InstanceRegistry {
Ok(())
}
/**
Inserts a property setter into the instance registry.
# Errors
- If the property already exists in the registry.
*/
pub fn insert_property_setter<'lua>(
lua: &'lua Lua,
class_name: &str,
@ -138,6 +159,12 @@ impl InstanceRegistry {
Ok(())
}
/**
Finds a method in the instance registry.
Returns `None` if the method is not found.
*/
#[must_use]
pub fn find_method<'lua>(
lua: &'lua Lua,
instance: &Instance,
@ -159,6 +186,12 @@ impl InstanceRegistry {
})
}
/**
Finds a property getter in the instance registry.
Returns `None` if the property getter is not found.
*/
#[must_use]
pub fn find_property_getter<'lua>(
lua: &'lua Lua,
instance: &Instance,
@ -180,6 +213,12 @@ impl InstanceRegistry {
})
}
/**
Finds a property setter in the instance registry.
Returns `None` if the property setter is not found.
*/
#[must_use]
pub fn find_property_setter<'lua>(
lua: &'lua Lua,
instance: &Instance,
@ -202,6 +241,16 @@ impl InstanceRegistry {
}
}
/**
Gets the class name chain for a given class name.
The chain starts with the given class name and ends with the root class.
# Panics
Panics if the class name is not valid.
*/
#[must_use]
pub fn class_name_chain(class_name: &str) -> Vec<&str> {
let db = rbx_reflection_database::get();

View file

@ -1,7 +1,7 @@
use mlua::prelude::*;
use rbx_dom_weak::types::{MaterialColors, TerrainMaterials, Variant};
use crate::roblox::{
use crate::{
datatypes::types::{Color3, EnumItem},
shared::classes::{add_class_restricted_method, add_class_restricted_method_mut},
};
@ -23,13 +23,12 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M)
CLASS_NAME,
"SetMaterialColor",
terrain_set_material_color,
)
);
}
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
if let Some(Variant::MaterialColors(material_colors)) = instance.get_property("MaterialColors")
{
material_colors
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
inner
} else {
MaterialColors::default()
}
@ -40,7 +39,7 @@ fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
### See Also
* [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> LuaResult<Color3> {
let material_colors = get_or_create_material_colors(this);
@ -65,7 +64,7 @@ fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> L
### See Also
* [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn terrain_set_material_color(
_: &Lua,

View file

@ -1,8 +1,6 @@
use mlua::prelude::*;
use crate::roblox::shared::classes::{
add_class_restricted_getter, get_or_create_property_ref_instance,
};
use crate::shared::classes::{add_class_restricted_getter, get_or_create_property_ref_instance};
use super::Instance;
@ -18,7 +16,7 @@ pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
### See Also
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
get_or_create_property_ref_instance(this, "Terrain", "Terrain")
@ -29,7 +27,7 @@ fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
### See Also
* [`CurrentCamera`](https://create.roblox.com/docs/reference/engine/classes/Workspace#CurrentCamera)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn workspace_get_camera(_: &Lua, this: &Instance) -> LuaResult<Instance> {
get_or_create_property_ref_instance(this, "CurrentCamera", "Camera")

View file

@ -1,6 +1,8 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
use crate::lune::util::TableBuilder;
use lune_utils::TableBuilder;
pub mod datatypes;
pub mod document;
@ -46,6 +48,16 @@ fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
])
}
/**
Creates a table containing all the Roblox datatypes, classes, and singletons.
Note that this is not guaranteed to contain any value unless indexed directly,
it may be optimized to use lazy initialization in the future.
# Errors
Errors when out of memory or when a value cannot be created.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
// FUTURE: We can probably create these lazily as users
// index the main exports (this return value) table and

View file

@ -7,7 +7,7 @@ use rbx_dom_weak::types::Variant as DomVariant;
use rbx_reflection::{ClassDescriptor, DataType};
use super::{property::DatabaseProperty, utils::*};
use crate::roblox::datatypes::{
use crate::datatypes::{
conversion::DomValueToLua, types::EnumItem, userdata_impl_eq, userdata_impl_to_string,
};
@ -28,6 +28,7 @@ impl DatabaseClass {
/**
Get the name of this class.
*/
#[must_use]
pub fn get_name(&self) -> String {
self.0.name.to_string()
}
@ -37,6 +38,7 @@ impl DatabaseClass {
May be `None` if no parent class exists.
*/
#[must_use]
pub fn get_superclass(&self) -> Option<String> {
let sup = self.0.superclass.as_ref()?;
Some(sup.to_string())
@ -45,6 +47,7 @@ impl DatabaseClass {
/**
Get all known properties for this class.
*/
#[must_use]
pub fn get_properties(&self) -> HashMap<String, DatabaseProperty> {
self.0
.properties
@ -56,6 +59,7 @@ impl DatabaseClass {
/**
Get all default values for properties of this class.
*/
#[must_use]
pub fn get_defaults(&self) -> HashMap<String, DomVariant> {
self.0
.default_properties
@ -71,7 +75,12 @@ impl DatabaseClass {
to players at runtime, and top-level class categories.
*/
pub fn get_tags_str(&self) -> Vec<&'static str> {
self.0.tags.iter().map(class_tag_to_str).collect::<Vec<_>>()
self.0
.tags
.iter()
.copied()
.map(class_tag_to_str)
.collect::<Vec<_>>()
}
}
@ -135,14 +144,12 @@ fn make_enum_value(inner: DbClass, name: impl AsRef<str>, value: u32) -> LuaResu
let name = name.as_ref();
let enum_name = find_enum_name(inner, name).ok_or_else(|| {
LuaError::RuntimeError(format!(
"Failed to get default property '{}' - No enum descriptor was found",
name
"Failed to get default property '{name}' - No enum descriptor was found",
))
})?;
EnumItem::from_enum_name_and_value(&enum_name, value).ok_or_else(|| {
LuaError::RuntimeError(format!(
"Failed to get default property '{}' - Enum.{} does not contain numeric value {}",
name, enum_name, value
"Failed to get default property '{name}' - Enum.{enum_name} does not contain numeric value {value}",
))
})
}

View file

@ -4,7 +4,7 @@ use mlua::prelude::*;
use rbx_reflection::EnumDescriptor;
use crate::roblox::datatypes::{userdata_impl_eq, userdata_impl_to_string};
use crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};
type DbEnum = &'static EnumDescriptor<'static>;
@ -23,6 +23,7 @@ impl DatabaseEnum {
/**
Get the name of this enum.
*/
#[must_use]
pub fn get_name(&self) -> String {
self.0.name.to_string()
}
@ -31,8 +32,9 @@ impl DatabaseEnum {
Get all known members of this enum.
Note that this is a direct map of name -> enum values,
and does not actually use the EnumItem datatype itself.
and does not actually use the `EnumItem` datatype itself.
*/
#[must_use]
pub fn get_items(&self) -> HashMap<String, u32> {
self.0
.items

View file

@ -4,7 +4,7 @@ use mlua::prelude::*;
use rbx_reflection::ReflectionDatabase;
use crate::roblox::datatypes::userdata_impl_eq;
use crate::datatypes::userdata_impl_eq;
mod class;
mod enums;
@ -30,6 +30,7 @@ impl Database {
/**
Creates a new database struct, referencing the bundled reflection database.
*/
#[must_use]
pub fn new() -> Self {
Self::default()
}
@ -40,6 +41,7 @@ impl Database {
This will follow the format `x.y.z.w`, which most
commonly looks something like `0.567.0.123456789`.
*/
#[must_use]
pub fn get_version(&self) -> String {
let [x, y, z, w] = self.0.version;
format!("{x}.{y}.{z}.{w}")
@ -48,15 +50,17 @@ impl Database {
/**
Retrieves a list of all currently known enum names.
*/
#[must_use]
pub fn get_enum_names(&self) -> Vec<String> {
self.0.enums.keys().map(|e| e.to_string()).collect()
self.0.enums.keys().map(ToString::to_string).collect()
}
/**
Retrieves a list of all currently known class names.
*/
#[must_use]
pub fn get_class_names(&self) -> Vec<String> {
self.0.classes.keys().map(|e| e.to_string()).collect()
self.0.classes.keys().map(ToString::to_string).collect()
}
/**
@ -108,14 +112,17 @@ impl Database {
impl LuaUserData for Database {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Version", |_, this| Ok(this.get_version()))
fields.add_field_method_get("Version", |_, this| Ok(this.get_version()));
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_method("GetEnumNames", |_, this, _: ()| Ok(this.get_enum_names()));
methods.add_method("GetClassNames", |_, this, _: ()| Ok(this.get_class_names()));
methods.add_method("GetEnumNames", |_, this, (): ()| Ok(this.get_enum_names()));
methods.add_method(
"GetClassNames",
|_, this, (): ()| Ok(this.get_class_names()),
);
methods.add_method("GetEnum", |_, this, name: String| Ok(this.get_enum(name)));
methods.add_method("GetClass", |_, this, name: String| Ok(this.get_class(name)));
methods.add_method("FindEnum", |_, this, name: String| Ok(this.find_enum(name)));

View file

@ -5,7 +5,7 @@ use mlua::prelude::*;
use rbx_reflection::{ClassDescriptor, PropertyDescriptor};
use super::utils::*;
use crate::roblox::datatypes::{userdata_impl_eq, userdata_impl_to_string};
use crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};
type DbClass = &'static ClassDescriptor<'static>;
type DbProp = &'static PropertyDescriptor<'static>;
@ -25,6 +25,7 @@ impl DatabaseProperty {
/**
Get the name of this property.
*/
#[must_use]
pub fn get_name(&self) -> String {
self.1.name.to_string()
}
@ -36,6 +37,7 @@ impl DatabaseProperty {
For enums this will be a string formatted as `Enum.EnumName`.
*/
#[must_use]
pub fn get_datatype_name(&self) -> String {
data_type_to_str(self.1.data_type.clone())
}
@ -45,8 +47,9 @@ impl DatabaseProperty {
All properties are writable and readable in Lune even if scriptability is not.
*/
#[must_use]
pub fn get_scriptability_str(&self) -> &'static str {
scriptability_to_str(&self.1.scriptability)
scriptability_to_str(self.1.scriptability)
}
/**
@ -59,6 +62,7 @@ impl DatabaseProperty {
self.1
.tags
.iter()
.copied()
.map(property_tag_to_str)
.collect::<Vec<_>>()
}

View file

@ -1,6 +1,6 @@
use rbx_reflection::{ClassTag, DataType, PropertyTag, Scriptability};
use crate::roblox::datatypes::extension::DomValueExt;
use crate::datatypes::extension::DomValueExt;
pub fn data_type_to_str(data_type: DataType) -> String {
match data_type {
@ -17,7 +17,7 @@ pub fn data_type_to_str(data_type: DataType) -> String {
NOTE: Remember to add any new strings here to typedefs too!
*/
pub fn scriptability_to_str(scriptability: &Scriptability) -> &'static str {
pub fn scriptability_to_str(scriptability: Scriptability) -> &'static str {
match scriptability {
Scriptability::None => "None",
Scriptability::Custom => "Custom",
@ -28,7 +28,7 @@ pub fn scriptability_to_str(scriptability: &Scriptability) -> &'static str {
}
}
pub fn property_tag_to_str(tag: &PropertyTag) -> &'static str {
pub fn property_tag_to_str(tag: PropertyTag) -> &'static str {
match tag {
PropertyTag::Deprecated => "Deprecated",
PropertyTag::Hidden => "Hidden",
@ -41,7 +41,7 @@ pub fn property_tag_to_str(tag: &PropertyTag) -> &'static str {
}
}
pub fn class_tag_to_str(tag: &ClassTag) -> &'static str {
pub fn class_tag_to_str(tag: ClassTag) -> &'static str {
match tag {
ClassTag::Deprecated => "Deprecated",
ClassTag::NotBrowsable => "NotBrowsable",

View file

@ -2,7 +2,7 @@ use mlua::prelude::*;
use rbx_dom_weak::types::Variant as DomValue;
use crate::roblox::instance::Instance;
use crate::instance::Instance;
use super::instance::class_is_a;
@ -20,8 +20,7 @@ pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Insta
field_getter(lua, this)
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
field_name, class_name
"{field_name} is not a valid member of {class_name}",
)))
}
});
@ -42,8 +41,7 @@ pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Insta
field_getter(lua, this, value)
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
field_name, class_name
"{field_name} is not a valid member of {class_name}",
)))
}
});
@ -64,8 +62,7 @@ pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Inst
method(lua, this, args)
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
method_name, class_name
"{method_name} is not a valid member of {class_name}",
)))
}
});
@ -92,8 +89,7 @@ pub(crate) fn add_class_restricted_method_mut<
method(lua, this, args)
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
method_name, class_name
"{method_name} is not a valid member of {class_name}",
)))
}
});
@ -102,7 +98,7 @@ pub(crate) fn add_class_restricted_method_mut<
/**
Gets or creates the instance child with the given reference prop name and class name.
Note that the class name here must be an exact match, it is not checked using IsA.
Note that the class name here must be an exact match, it is not checked using `IsA`.
The instance may be in one of several states but this function will guarantee that the
property reference is correct and that the instance exists after it has been called:
@ -126,7 +122,7 @@ pub(crate) fn get_or_create_property_ref_instance(
Ok(inst)
} else {
let inst = Instance::new_orphaned(class_name);
inst.set_parent(Some(this.clone()));
inst.set_parent(Some(*this));
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
Ok(inst)
}

View file

@ -60,12 +60,12 @@ pub(crate) fn find_property_info(
value_type: Some(*value_type),
..Default::default()
},
_ => Default::default(),
_ => PropertyInfo::default(),
});
break;
} else if let Some(sup) = &class.superclass {
// No property found, we should look at the superclass
class_name = Cow::Borrowed(sup)
class_name = Cow::Borrowed(sup);
} else {
break;
}
@ -87,7 +87,7 @@ pub(crate) fn find_property_info(
break;
} else if let Some(sup) = &class.superclass {
// No default value found, we should look at the superclass
class_name = Cow::Borrowed(sup)
class_name = Cow::Borrowed(sup);
} else {
break;
}

View file

@ -1,3 +1,5 @@
#![allow(clippy::missing_errors_doc)]
use std::{any::type_name, cell::RefCell, fmt, ops};
use mlua::prelude::*;
@ -5,21 +7,29 @@ use mlua::prelude::*;
// Utility functions
type ListWriter = dyn Fn(&mut fmt::Formatter<'_>, bool, &str) -> fmt::Result;
#[must_use]
pub fn make_list_writer() -> Box<ListWriter> {
let first = RefCell::new(true);
Box::new(move |f, flag, literal| {
if flag {
if first.take() {
write!(f, "{}", literal)?;
write!(f, "{literal}")?;
} else {
write!(f, ", {}", literal)?;
write!(f, ", {literal}")?;
}
}
Ok::<_, fmt::Error>(())
})
}
// Userdata metamethod implementations
/*
Userdata metamethod implementations
Note that many of these return [`LuaResult`] even though they don't
return any errors - this is for consistency reasons and to make it
easier to add these blanket implementations to [`LuaUserData`] impls.
*/
pub fn userdata_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
where
@ -139,6 +149,37 @@ where
})
}
pub trait IDiv<Rhs = Self> {
type Output;
#[must_use]
fn idiv(self, rhs: Rhs) -> Self::Output;
}
pub fn userdata_impl_idiv_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
where
D: LuaUserData + IDiv<D, Output = D> + IDiv<f32, Output = D> + Copy + 'static,
{
match &rhs {
LuaValue::Number(n) => return Ok(datatype.idiv(*n as f32)),
LuaValue::Integer(i) => return Ok(datatype.idiv(*i as f32)),
LuaValue::UserData(ud) => {
if let Ok(vec) = ud.borrow::<D>() {
return Ok(datatype.idiv(*vec));
}
}
_ => {}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: type_name::<D>(),
message: Some(format!(
"Expected {} or number, got {}",
type_name::<D>(),
rhs.type_name()
)),
})
}
pub fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
where
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,

View file

@ -0,0 +1,22 @@
[package]
name = "lune-std-datetime"
version = "0.1.3"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - DateTime"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
thiserror = "1.0"
chrono = "0.4.38"
chrono_lc = "0.1.6"
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -6,31 +6,8 @@ use chrono::prelude::*;
use chrono::DateTime as ChronoDateTime;
use chrono_lc::LocaleDate;
use crate::lune::util::TableBuilder;
mod error;
mod values;
use error::*;
use values::*;
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("fromIsoDate", |_, iso_date: String| {
Ok(DateTime::from_iso_date(iso_date)?)
})?
.with_function("fromLocalTime", |_, values| {
Ok(DateTime::from_local_time(&values)?)
})?
.with_function("fromUniversalTime", |_, values| {
Ok(DateTime::from_universal_time(&values)?)
})?
.with_function("fromUnixTimestamp", |_, timestamp| {
Ok(DateTime::from_unix_timestamp_float(timestamp)?)
})?
.with_function("now", |_, ()| Ok(DateTime::now()))?
.build_readonly()
}
use crate::result::{DateTimeError, DateTimeResult};
use crate::values::DateTimeValues;
const DEFAULT_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
const DEFAULT_LOCALE: &str = "en";
@ -49,6 +26,7 @@ impl DateTime {
See [`chrono::DateTime::now`] for additional details.
*/
#[must_use]
pub fn now() -> Self {
Self { inner: Utc::now() }
}
@ -66,6 +44,10 @@ impl DateTime {
```
See [`chrono::DateTime::from_timestamp`] for additional details.
# Errors
Returns an error if the input value is out of range.
*/
pub fn from_unix_timestamp_float(unix_timestamp: f64) -> DateTimeResult<Self> {
let whole = unix_timestamp.trunc() as i64;
@ -84,6 +66,10 @@ impl DateTime {
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
for additional details and cases where this constructor may return an error.
# Errors
Returns an error if the date or time values are invalid.
*/
pub fn from_universal_time(values: &DateTimeValues) -> DateTimeResult<Self> {
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
@ -108,6 +94,10 @@ impl DateTime {
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
for additional details and cases where this constructor may return an error.
# Errors
Returns an error if the date or time values are invalid or ambiguous.
*/
pub fn from_local_time(values: &DateTimeValues) -> DateTimeResult<Self> {
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
@ -138,6 +128,7 @@ impl DateTime {
See [`chrono_lc::DateTime::formatl`] for additional details.
*/
#[must_use]
pub fn format_string_local(&self, format: Option<&str>, locale: Option<&str>) -> String {
self.inner
.with_timezone(&Local)
@ -156,6 +147,7 @@ impl DateTime {
See [`chrono_lc::DateTime::formatl`] for additional details.
*/
#[must_use]
pub fn format_string_universal(&self, format: Option<&str>, locale: Option<&str>) -> String {
self.inner
.with_timezone(&Utc)
@ -171,6 +163,10 @@ impl DateTime {
`1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.
See [`chrono::DateTime::parse_from_rfc3339`] for additional details.
# Errors
Returns an error if the input string is not a valid RFC 3339 date-time.
*/
pub fn from_iso_date(iso_date: impl AsRef<str>) -> DateTimeResult<Self> {
let inner = ChronoDateTime::parse_from_rfc3339(iso_date.as_ref())?.with_timezone(&Utc);
@ -181,6 +177,7 @@ impl DateTime {
Extracts individual date & time values from this
`DateTime`, using the current local time zone.
*/
#[must_use]
pub fn to_local_time(self) -> DateTimeValues {
DateTimeValues::from(self.inner.with_timezone(&Local))
}
@ -189,6 +186,7 @@ impl DateTime {
Extracts individual date & time values from this
`DateTime`, using the universal (UTC) time zone.
*/
#[must_use]
pub fn to_universal_time(self) -> DateTimeValues {
DateTimeValues::from(self.inner.with_timezone(&Utc))
}
@ -198,6 +196,7 @@ impl DateTime {
See [`chrono::DateTime::to_rfc3339`] for additional details.
*/
#[must_use]
pub fn to_iso_date(self) -> String {
self.inner.to_rfc3339()
}

View file

@ -0,0 +1,36 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
use lune_utils::TableBuilder;
mod date_time;
mod result;
mod values;
pub use self::date_time::DateTime;
/**
Creates the `datetime` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("fromIsoDate", |_, iso_date: String| {
Ok(DateTime::from_iso_date(iso_date)?)
})?
.with_function("fromLocalTime", |_, values| {
Ok(DateTime::from_local_time(&values)?)
})?
.with_function("fromUniversalTime", |_, values| {
Ok(DateTime::from_universal_time(&values)?)
})?
.with_function("fromUnixTimestamp", |_, timestamp| {
Ok(DateTime::from_unix_timestamp_float(timestamp)?)
})?
.with_function("now", |_, ()| Ok(DateTime::now()))?
.build_readonly()
}

View file

@ -2,9 +2,9 @@ use mlua::prelude::*;
use chrono::prelude::*;
use crate::lune::util::TableBuilder;
use lune_utils::TableBuilder;
use super::error::{DateTimeError, DateTimeResult};
use super::result::{DateTimeError, DateTimeResult};
#[derive(Debug, Clone, Copy)]
pub struct DateTimeValues {
@ -60,10 +60,10 @@ where
}
}
/**
Conversion methods between DateTimeValues and plain lua tables
/*
Conversion methods between `DateTimeValues` and plain lua tables
Note that the IntoLua implementation here uses a read-only table,
Note that the `IntoLua` implementation here uses a read-only table,
since we generally want to convert into lua when we know we have
a fixed point in time, and we guarantee that it doesn't change
*/
@ -117,9 +117,9 @@ impl IntoLua<'_> for DateTimeValues {
}
}
/**
Conversion methods between chrono's timezone-aware DateTime to
and from our non-timezone-aware DateTimeValues values struct
/*
Conversion methods between chrono's timezone-aware `DateTime` to
and from our non-timezone-aware `DateTimeValues` values struct
*/
impl<T: TimeZone> From<DateTime<T>> for DateTimeValues {

View file

@ -0,0 +1,23 @@
[package]
name = "lune-std-fs"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - FS"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
bstr = "1.9"
tokio = { version = "1", default-features = false, features = ["fs"] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }
lune-std-datetime = { version = "0.1.2", path = "../lune-std-datetime" }

View file

@ -13,7 +13,7 @@ pub struct CopyContents {
pub files: Vec<(usize, PathBuf)>,
}
async fn get_contents_at(root: PathBuf, _options: FsWriteOptions) -> LuaResult<CopyContents> {
async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyContents> {
let mut dirs = Vec::new();
let mut files = Vec::new();
@ -53,11 +53,11 @@ async fn get_contents_at(root: PathBuf, _options: FsWriteOptions) -> LuaResult<C
// Ensure that all directory and file paths are relative to the root path
// SAFETY: Since we only ever push dirs and files relative to the root, unwrap is safe
for (_, dir) in dirs.iter_mut() {
*dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf()
for (_, dir) in &mut dirs {
*dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf();
}
for (_, file) in files.iter_mut() {
*file = file.strip_prefix(&normalized_root).unwrap().to_path_buf()
for (_, file) in &mut files {
*file = file.strip_prefix(&normalized_root).unwrap().to_path_buf();
}
// FUTURE: Deduplicate paths such that these directories:

View file

@ -1,20 +1,30 @@
use std::io::ErrorKind as IoErrorKind;
use std::path::{PathBuf, MAIN_SEPARATOR};
#![allow(clippy::cargo_common_metadata)]
use std::io::ErrorKind as IoErrorKind;
use std::path::PathBuf;
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use tokio::fs;
use crate::lune::util::TableBuilder;
use lune_utils::TableBuilder;
mod copy;
mod metadata;
mod options;
use copy::copy;
use metadata::FsMetadata;
use options::FsWriteOptions;
use self::copy::copy;
use self::metadata::FsMetadata;
use self::options::FsWriteOptions;
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
/**
Creates the `fs` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_async_function("readFile", fs_read_file)?
.with_async_function("readDir", fs_read_dir)?
@ -32,6 +42,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)
}
@ -39,33 +50,20 @@ async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
let mut dir_strings = Vec::new();
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
if let Some(dir_path_str) = dir_entry.path().to_str() {
dir_strings.push(dir_path_str.to_owned());
if let Some(dir_name_str) = dir_entry.file_name().to_str() {
dir_strings.push(dir_name_str.to_owned());
} else {
return Err(LuaError::RuntimeError(format!(
"File path could not be converted into a string: '{}'",
dir_entry.path().display()
"File name could not be converted into a string: '{}'",
dir_entry.file_name().to_string_lossy()
)));
}
}
let mut dir_string_prefix = path;
if !dir_string_prefix.ends_with(MAIN_SEPARATOR) {
dir_string_prefix.push(MAIN_SEPARATOR);
}
let dir_strings_no_prefix = dir_strings
.iter()
.map(|inner_path| {
inner_path
.trim()
.trim_start_matches(&dir_string_prefix)
.to_owned()
})
.collect::<Vec<_>>();
Ok(dir_strings_no_prefix)
Ok(dir_strings)
}
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

@ -8,7 +8,7 @@ use std::{
use mlua::prelude::*;
use crate::lune::builtins::datetime::DateTime;
use lune_std_datetime::DateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FsMetadataKind {

View file

@ -0,0 +1,18 @@
[package]
name = "lune-std-luau"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Luau"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau", "luau-jit"] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -0,0 +1,90 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
use lune_utils::{jit::JitStatus, TableBuilder};
mod options;
use self::options::{LuauCompileOptions, LuauLoadOptions};
const BYTECODE_ERROR_BYTE: u8 = 0;
/**
Creates the `luau` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("compile", compile_source)?
.with_function("load", load_source)?
.build_readonly()
}
fn compile_source<'lua>(
lua: &'lua Lua,
(source, options): (LuaString<'lua>, LuauCompileOptions),
) -> LuaResult<LuaString<'lua>> {
let bytecode = options.into_compiler().compile(source);
match bytecode.first() {
Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError(
String::from_utf8_lossy(&bytecode).into_owned(),
)),
Some(_) => lua.create_string(bytecode),
None => panic!("Compiling resulted in empty bytecode"),
}
}
fn load_source<'lua>(
lua: &'lua Lua,
(source, options): (LuaString<'lua>, LuauLoadOptions),
) -> LuaResult<LuaFunction<'lua>> {
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
let env_changed = options.environment.is_some();
if let Some(custom_environment) = options.environment {
let environment = lua.create_table()?;
// Inject all globals into the environment
if options.inject_globals {
for pair in lua.globals().pairs() {
let (key, value): (LuaValue, LuaValue) = pair?;
environment.set(key, value)?;
}
if let Some(global_metatable) = lua.globals().get_metatable() {
environment.set_metatable(Some(global_metatable));
}
} else if let Some(custom_metatable) = custom_environment.get_metatable() {
// Since we don't need to set the global metatable,
// we can just set a custom metatable if it exists
environment.set_metatable(Some(custom_metatable));
}
// Inject the custom environment
for pair in custom_environment.pairs() {
let (key, value): (LuaValue, LuaValue) = pair?;
environment.set(key, value)?;
}
chunk = chunk.set_environment(environment);
}
// Enable JIT if codegen is enabled and the environment hasn't
// changed, otherwise disable JIT since it'll fall back anyways
lua.enable_jit(options.codegen_enabled && !env_changed);
let function = chunk.into_function()?;
lua.enable_jit(
lua.app_data_ref::<JitStatus>()
.ok_or(LuaError::runtime(
"Failed to get current JitStatus ref from AppData",
))?
.enabled(),
);
Ok(function)
}

View file

@ -1,8 +1,14 @@
#![allow(clippy::struct_field_names)]
use mlua::prelude::*;
use mlua::Compiler as LuaCompiler;
const DEFAULT_DEBUG_NAME: &str = "luau.load(...)";
/**
Options for compiling Lua source code.
*/
#[derive(Debug, Clone, Copy)]
pub struct LuauCompileOptions {
pub(crate) optimization_level: u8,
pub(crate) coverage_level: u8,
@ -76,6 +82,8 @@ impl<'lua> FromLua<'lua> for LuauCompileOptions {
pub struct LuauLoadOptions<'lua> {
pub(crate) debug_name: String,
pub(crate) environment: Option<LuaTable<'lua>>,
pub(crate) inject_globals: bool,
pub(crate) codegen_enabled: bool,
}
impl Default for LuauLoadOptions<'_> {
@ -83,6 +91,8 @@ impl Default for LuauLoadOptions<'_> {
Self {
debug_name: DEFAULT_DEBUG_NAME.to_string(),
environment: None,
inject_globals: true,
codegen_enabled: false,
}
}
}
@ -102,11 +112,21 @@ impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
options.environment = Some(environment);
}
if let Some(inject_globals) = t.get("injectGlobals")? {
options.inject_globals = inject_globals;
}
if let Some(codegen_enabled) = t.get("codegenEnabled")? {
options.codegen_enabled = codegen_enabled;
}
options
}
LuaValue::String(s) => Self {
debug_name: s.to_string_lossy().to_string(),
environment: None,
inject_globals: true,
codegen_enabled: false,
},
_ => {
return Err(LuaError::FromLuaConversionError {

View file

@ -0,0 +1,39 @@
[package]
name = "lune-std-net"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Net"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
bstr = "1.9"
futures-util = "0.3"
hyper = { version = "1.1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
http = "1.0"
http-body-util = { version = "0.1" }
hyper-tungstenite = { version = "0.13" }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
] }
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
urlencoding = "2.1"
tokio = { version = "1", default-features = false, features = [
"sync",
"net",
"macros",
] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }
lune-std-serde = { version = "0.1.2", path = "../lune-std-serde" }

View file

@ -4,10 +4,8 @@ use mlua::prelude::*;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_ENCODING};
use crate::lune::{
builtins::serde::compress_decompress::{decompress, CompressDecompressFormat},
util::TableBuilder,
};
use lune_std_serde::{decompress, CompressDecompressFormat};
use lune_utils::TableBuilder;
use super::{config::RequestConfig, util::header_map_to_table};
@ -103,7 +101,7 @@ impl NetClient {
.and_then(|(_, value)| value.to_str().ok())
.and_then(CompressDecompressFormat::detect_from_header_str);
if let Some(format) = decompress_format {
res_bytes = decompress(format, res_bytes).await?;
res_bytes = decompress(res_bytes, format).await?;
res_decompressed = true;
}
}

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 {
@ -79,7 +83,7 @@ impl FromLua<'_> for RequestConfig {
query: HashMap::new(),
headers: HashMap::new(),
body: None,
options: Default::default(),
options: RequestConfigOptions::default(),
})
} else if let LuaValue::Table(tab) = value {
// If we got a table we are able to configure the entire request
@ -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)]
#![allow(clippy::cargo_common_metadata)]
use bstr::BString;
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
@ -9,7 +10,7 @@ mod server;
mod util;
mod websocket;
use crate::lune::util::TableBuilder;
use lune_utils::TableBuilder;
use self::{
client::{NetClient, NetClientBuilder},
@ -19,11 +20,18 @@ use self::{
websocket::NetWebSocket,
};
use super::serde::encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
use lune_std_serde::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
/**
Creates the `net` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(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)?
@ -41,12 +49,13 @@ fn net_json_encode<'lua>(
lua: &'lua Lua,
(val, pretty): (LuaValue<'lua>, Option<bool>),
) -> LuaResult<LuaString<'lua>> {
EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()))
.serialize_to_string(lua, val)
let config = EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()));
encode(val, lua, config)
}
fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult<LuaValue<'lua>> {
EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json)
fn net_json_decode(lua: &Lua, json: BString) -> LuaResult<LuaValue> {
let config = EncodeDecodeConfig::from(EncodeDecodeFormat::Json);
decode(json, lua, config)
}
async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
@ -56,9 +65,9 @@ async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
res.await?.into_lua_table(lua)
}
async fn net_socket(lua: &Lua, url: String) -> LuaResult<LuaTable> {
async fn net_socket(lua: &Lua, url: String) -> LuaResult<LuaValue> {
let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
NetWebSocket::new(ws).into_lua_table(lua)
NetWebSocket::new(ws).into_lua(lua)
}
async fn net_serve<'lua>(

View file

@ -10,7 +10,7 @@ use tokio::{net::TcpListener, pin};
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
use crate::lune::util::TableBuilder;
use lune_utils::TableBuilder;
use super::config::ServeConfig;
@ -81,7 +81,7 @@ pub async fn serve<'lua>(
// Wait for either a new connection or a shutdown signal
tokio::select! {
_ = fut_accept => {}
() = fut_accept => {}
res = fut_shutdown => {
// NOTE: We will only get a RecvError here if the serve handle is dropped,
// this means lua has garbage collected it and the user does not want
@ -97,8 +97,8 @@ pub async fn serve<'lua>(
TableBuilder::new(lua)?
.with_value("ip", addr.ip().to_string())?
.with_value("port", addr.port())?
.with_function("stop", move |lua, _: ()| match shutdown_tx.send(true) {
Ok(_) => Ok(()),
.with_function("stop", move |_, (): ()| match shutdown_tx.send(true) {
Ok(()) => Ok(()),
Err(_) => Err(LuaError::runtime("Server already stopped")),
})?
.build_readonly()

View file

@ -4,7 +4,7 @@ use http::request::Parts;
use mlua::prelude::*;
use crate::lune::util::TableBuilder;
use lune_utils::TableBuilder;
pub(super) struct LuaRequest {
pub(super) _remote_addr: SocketAddr,
@ -18,21 +18,32 @@ impl LuaRequest {
let path = self.head.uri.path().to_string();
let body = lua.create_string(&self.body)?;
let query: HashMap<String, String> = self
#[allow(clippy::mutable_key_type)]
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<_>>()?;
#[allow(clippy::mutable_key_type)]
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

@ -40,13 +40,13 @@ impl Service<Request<Incoming>> for Svc {
lua.spawn_local(async move {
let sock = sock.await.unwrap();
let lua_sock = NetWebSocket::new(sock);
let lua_tab = lua_sock.into_lua_table(&lua_inner).unwrap();
let lua_val = lua_sock.into_lua(&lua_inner).unwrap();
let handler_websocket: LuaFunction =
keys.websocket_handler(&lua_inner).unwrap().unwrap();
lua_inner
.push_thread_back(handler_websocket, lua_tab)
.push_thread_back(handler_websocket, lua_val)
.unwrap();
});

View file

@ -5,14 +5,21 @@ use reqwest::header::HeaderMap;
use mlua::prelude::*;
use crate::lune::util::TableBuilder;
use lune_utils::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(
@ -21,7 +28,7 @@ pub fn header_map_to_table(
remove_content_headers: bool,
) -> LuaResult<LuaTable> {
let mut res_headers: HashMap<String, Vec<String>> = HashMap::new();
for (name, value) in headers.iter() {
for (name, value) in &headers {
let name = name.as_str();
let value = value.to_str().unwrap().to_owned();
if let Some(existing) = res_headers.get_mut(name) {

View file

@ -3,6 +3,7 @@ use std::sync::{
Arc,
};
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use futures_util::{
@ -22,29 +23,6 @@ use hyper_tungstenite::{
WebSocketStream,
};
use crate::lune::util::TableBuilder;
// Wrapper implementation for compatibility and changing colon syntax to dot syntax
const WEB_SOCKET_IMPL_LUA: &str = r#"
return freeze(setmetatable({
close = function(...)
return websocket:close(...)
end,
send = function(...)
return websocket:send(...)
end,
next = function(...)
return websocket:next(...)
end,
}, {
__index = function(self, key)
if key == "closeCode" then
return websocket.closeCode
end
end,
}))
"#;
#[derive(Debug)]
pub struct NetWebSocket<T> {
close_code_exists: Arc<AtomicBool>,
@ -124,25 +102,6 @@ where
let mut ws = self.write_stream.lock().await;
ws.close().await.into_lua_err()
}
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?;
let table_freeze = lua
.globals()
.get::<_, LuaTable>("table")?
.get::<_, LuaFunction>("freeze")?;
let env = TableBuilder::new(lua)?
.with_value("websocket", self.clone())?
.with_value("setmetatable", setmetatable)?
.with_value("freeze", table_freeze)?
.build_readonly()?;
lua.load(WEB_SOCKET_IMPL_LUA)
.set_name("websocket")
.set_environment(env)
.eval()
}
}
impl<T> LuaUserData for NetWebSocket<T>
@ -154,13 +113,13 @@ where
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("close", |lua, this, code: Option<u16>| async move {
methods.add_async_method("close", |_, this, code: Option<u16>| async move {
this.close(code).await
});
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 {
@ -171,7 +130,7 @@ where
},
);
methods.add_async_method("next", |lua, this, _: ()| async move {
methods.add_async_method("next", |lua, this, (): ()| async move {
let msg = this.next().await?;
if let Some(WsMessage::Close(Some(frame))) = msg.as_ref() {

View file

@ -0,0 +1,34 @@
[package]
name = "lune-std-process"
version = "0.1.3"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Process"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
directories = "5.0"
pin-project = "1.0"
os_str_bytes = { version = "7.0", features = ["conversions"] }
bstr = "1.9"
bytes = "1.6.0"
tokio = { version = "1", default-features = false, features = [
"io-std",
"io-util",
"process",
"rt",
"sync",
] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -0,0 +1,289 @@
#![allow(clippy::cargo_common_metadata)]
use std::{
cell::RefCell,
env::{
self,
consts::{ARCH, OS},
},
path::MAIN_SEPARATOR,
process::Stdio,
rc::Rc,
sync::Arc,
};
use mlua::prelude::*;
use lune_utils::TableBuilder;
use mlua_luau_scheduler::{Functions, LuaSpawnExt};
use options::ProcessSpawnOptionsStdio;
use os_str_bytes::RawOsString;
use stream::{ChildProcessReader, ChildProcessWriter};
use tokio::{io::AsyncWriteExt, process::Child, sync::RwLock};
mod options;
mod stream;
mod tee_writer;
mod wait_for_child;
use self::options::ProcessSpawnOptions;
use self::wait_for_child::wait_for_child;
use lune_utils::path::get_current_dir;
/**
Creates the `process` standard library module.
# Errors
Errors when out of memory.
*/
#[allow(clippy::missing_panics_doc)]
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
let mut cwd_str = get_current_dir()
.to_str()
.expect("cwd should be valid UTF-8")
.to_string();
if !cwd_str.ends_with(MAIN_SEPARATOR) {
cwd_str.push(MAIN_SEPARATOR);
}
// Create constants for OS & processor architecture
let os = lua.create_string(OS.to_lowercase())?;
let arch = lua.create_string(ARCH.to_lowercase())?;
let endianness = lua.create_string(if cfg!(target_endian = "big") {
"big"
} else {
"little"
})?;
// Create readonly args array
let args_vec = lua
.app_data_ref::<Vec<String>>()
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
.clone();
let args_tab = TableBuilder::new(lua)?
.with_sequential_values(args_vec)?
.build_readonly()?;
// Create proxied table for env that gets & sets real env vars
let env_tab = TableBuilder::new(lua)?
.with_metatable(
TableBuilder::new(lua)?
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
.build_readonly()?,
)?
.build_readonly()?;
// Create our process exit function, the scheduler crate provides this
let fns = Functions::new(lua)?;
let process_exit = fns.exit;
// Create the full process table
TableBuilder::new(lua)?
.with_value("os", os)?
.with_value("arch", arch)?
.with_value("endianness", endianness)?
.with_value("args", args_tab)?
.with_value("cwd", cwd_str)?
.with_value("env", env_tab)?
.with_value("exit", process_exit)?
.with_async_function("exec", process_exec)?
.with_function("create", process_create)?
.build_readonly()
}
fn process_env_get<'lua>(
lua: &'lua Lua,
(_, key): (LuaValue<'lua>, String),
) -> LuaResult<LuaValue<'lua>> {
match env::var_os(key) {
Some(value) => {
let raw_value = RawOsString::new(value);
Ok(LuaValue::String(
lua.create_string(raw_value.to_raw_bytes())?,
))
}
None => Ok(LuaValue::Nil),
}
}
fn process_env_set<'lua>(
_: &'lua Lua,
(_, key, value): (LuaValue<'lua>, String, Option<String>),
) -> LuaResult<()> {
// Make sure key is valid, otherwise set_var will panic
if key.is_empty() {
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
} else if key.contains('=') {
Err(LuaError::RuntimeError(
"Key must not contain the equals character '='".to_string(),
))
} else if key.contains('\0') {
Err(LuaError::RuntimeError(
"Key must not contain the NUL character".to_string(),
))
} else if let Some(value) = value {
// Make sure value is valid, otherwise set_var will panic
if value.contains('\0') {
Err(LuaError::RuntimeError(
"Value must not contain the NUL character".to_string(),
))
} else {
env::set_var(&key, &value);
Ok(())
}
} else {
env::remove_var(&key);
Ok(())
}
}
fn process_env_iter<'lua>(
lua: &'lua Lua,
(_, ()): (LuaValue<'lua>, ()),
) -> LuaResult<LuaFunction<'lua>> {
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
lua.create_function_mut(move |lua, (): ()| match vars.next() {
Some((key, value)) => {
let raw_key = RawOsString::new(key);
let raw_value = RawOsString::new(value);
Ok((
LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
))
}
None => Ok((LuaValue::Nil, LuaValue::Nil)),
})
}
async fn process_exec(
lua: &Lua,
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
) -> LuaResult<LuaTable> {
let res = lua
.spawn(async move {
let cmd = spawn_command_with_stdin(program, args, options.clone()).await?;
wait_for_child(cmd, options.stdio.stdout, options.stdio.stderr).await
})
.await?;
/*
NOTE: If an exit code was not given by the child process,
we default to 1 if it yielded any error output, otherwise 0
An exit code may be missing if the process was terminated by
some external signal, which is the only time we use this default
*/
let code = res
.status
.code()
.unwrap_or(i32::from(!res.stderr.is_empty()));
// Construct and return a readonly lua table with results
TableBuilder::new(lua)?
.with_value("ok", code == 0)?
.with_value("code", code)?
.with_value("stdout", lua.create_string(&res.stdout)?)?
.with_value("stderr", lua.create_string(&res.stderr)?)?
.build_readonly()
}
#[allow(clippy::await_holding_refcell_ref)]
fn process_create(
lua: &Lua,
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
) -> LuaResult<LuaTable> {
// We do not want the user to provide stdio options for process.create,
// so we reset the options, regardless of what the user provides us
let mut spawn_options = options.clone();
spawn_options.stdio = ProcessSpawnOptionsStdio::default();
let (code_tx, code_rx) = tokio::sync::broadcast::channel(4);
let code_rx_rc = Rc::new(RefCell::new(code_rx));
let child = spawn_command(program, args, spawn_options)?;
let child_arc = Arc::new(RwLock::new(child));
let child_arc_clone = Arc::clone(&child_arc);
let mut child_lock = tokio::task::block_in_place(|| child_arc_clone.blocking_write());
let stdin = child_lock.stdin.take().unwrap();
let stdout = child_lock.stdout.take().unwrap();
let stderr = child_lock.stderr.take().unwrap();
let child_arc_inner = Arc::clone(&child_arc);
// Spawn a background task to wait for the child to exit and send the exit code
let status_handle = tokio::spawn(async move {
let res = child_arc_inner.write().await.wait().await;
if let Ok(output) = res {
let code = output.code().unwrap_or_default();
code_tx
.send(code)
.expect("ExitCode receiver was unexpectedly dropped");
}
});
TableBuilder::new(lua)?
.with_value("stdout", ChildProcessReader(stdout))?
.with_value("stderr", ChildProcessReader(stderr))?
.with_value("stdin", ChildProcessWriter(stdin))?
.with_async_function("kill", move |_, ()| {
// First, stop the status task so the RwLock is dropped
status_handle.abort();
let child_arc_clone = Arc::clone(&child_arc);
// Then get another RwLock to write to the child process and kill it
async move { Ok(child_arc_clone.write().await.kill().await?) }
})?
.with_async_function("status", move |lua, ()| {
let code_rx_rc_clone = Rc::clone(&code_rx_rc);
async move {
// Exit code of 9 corresponds to SIGKILL, which should be the only case where
// the receiver gets suddenly dropped
let code = code_rx_rc_clone.borrow_mut().recv().await.unwrap_or(9);
TableBuilder::new(lua)?
.with_value("code", code)?
.with_value("ok", code == 0)?
.build_readonly()
}
})?
.build_readonly()
}
async fn spawn_command_with_stdin(
program: String,
args: Option<Vec<String>>,
mut options: ProcessSpawnOptions,
) -> LuaResult<Child> {
let stdin = options.stdio.stdin.take();
let mut child = spawn_command(program, args, options)?;
if let Some(stdin) = stdin {
let mut child_stdin = child.stdin.take().unwrap();
child_stdin.write_all(&stdin).await.into_lua_err()?;
}
Ok(child)
}
fn spawn_command(
program: String,
args: Option<Vec<String>>,
options: ProcessSpawnOptions,
) -> LuaResult<Child> {
let stdout = options.stdio.stdout;
let stderr = options.stdio.stderr;
let child = options
.into_command(program, args)
.stdin(Stdio::piped())
.stdout(stdout.as_stdio())
.stderr(stderr.as_stdio())
.spawn()?;
Ok(child)
}

View file

@ -1,6 +1,5 @@
use std::{fmt, process::Stdio, str::FromStr};
use itertools::Itertools;
use mlua::prelude::*;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
@ -55,6 +54,7 @@ impl FromStr for ProcessSpawnOptionsStdioKind {
ProcessSpawnOptionsStdioKind::all()
.iter()
.map(|k| format!("'{k}'"))
.collect::<Vec<_>>()
.join(", ")
)))
}

View file

@ -56,7 +56,7 @@ impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
"Invalid value for option 'cwd' - failed to get home directory",
)
})?;
cwd = user_dirs.home_dir().join(stripped)
cwd = user_dirs.home_dir().join(stripped);
}
if !cwd.exists() {
return Err(LuaError::runtime(

View file

@ -0,0 +1,58 @@
use bstr::BString;
use bytes::BytesMut;
use mlua::prelude::*;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
const CHUNK_SIZE: usize = 8;
#[derive(Debug, Clone)]
pub struct ChildProcessReader<R: AsyncRead>(pub R);
#[derive(Debug, Clone)]
pub struct ChildProcessWriter<W: AsyncWrite>(pub W);
impl<R: AsyncRead + Unpin> ChildProcessReader<R> {
pub async fn read(&mut self, chunk_size: Option<usize>) -> LuaResult<Vec<u8>> {
let mut buf = BytesMut::with_capacity(chunk_size.unwrap_or(CHUNK_SIZE));
self.0.read_buf(&mut buf).await?;
Ok(buf.to_vec())
}
pub async fn read_to_end(&mut self) -> LuaResult<Vec<u8>> {
let mut buf = vec![];
self.0.read_to_end(&mut buf).await?;
Ok(buf)
}
}
impl<R: AsyncRead + Unpin + 'static> LuaUserData for ChildProcessReader<R> {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method_mut("read", |lua, this, chunk_size: Option<usize>| async move {
let buf = this.read(chunk_size).await?;
if buf.is_empty() {
return Ok(LuaValue::Nil);
}
Ok(LuaValue::String(lua.create_string(buf)?))
});
methods.add_async_method_mut("readToEnd", |lua, this, ()| async {
Ok(lua.create_string(this.read_to_end().await?))
});
}
}
impl<W: AsyncWrite + Unpin> ChildProcessWriter<W> {
pub async fn write(&mut self, data: BString) -> LuaResult<()> {
self.0.write_all(data.as_ref()).await?;
Ok(())
}
}
impl<W: AsyncWrite + Unpin + 'static> LuaUserData for ChildProcessWriter<W> {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method_mut("write", |_, this, data| async { this.write(data).await });
}
}

View file

@ -33,7 +33,7 @@ where
}
}
impl<'a, W> AsyncWrite for AsyncTeeWriter<'a, W>
impl<W> AsyncWrite for AsyncTeeWriter<'_, W>
where
W: AsyncWrite + Unpin,
{

View file

@ -24,8 +24,7 @@ where
R: AsyncRead + Unpin,
{
Ok(match kind {
ProcessSpawnOptionsStdioKind::None => Vec::new(),
ProcessSpawnOptionsStdioKind::Forward => Vec::new(),
ProcessSpawnOptionsStdioKind::None | ProcessSpawnOptionsStdioKind::Forward => Vec::new(),
ProcessSpawnOptionsStdioKind::Default => {
let mut read_from =
read_from.expect("read_from must be Some when stdio kind is Default");

View file

@ -0,0 +1,21 @@
[package]
name = "lune-std-regex"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - RegEx"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
regex = "1.10"
self_cell = "1.0"
lune-utils = { version = "0.1.3", path = "../lune-utils" }

Some files were not shown because too many files have changed in this diff Show more