Compare commits

..

No commits in common. "main" and "v0.7.6" have entirely different histories.
main ... v0.7.6

364 changed files with 8638 additions and 20678 deletions

7
.gitattributes vendored
View file

@ -1,8 +1,9 @@
* 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
# Ensure all txt files within tests use LF
tests/**/*.txt eol=lf

View file

@ -5,97 +5,30 @@ on:
pull_request:
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Install Tooling
uses: CompeyDev/setup-rokit@v0.1.2
- name: Check Formatting
run: just fmt-check
analyze:
needs: ["fmt"]
name: Analyze and lint Luau files
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Tooling
uses: CompeyDev/setup-rokit@v0.1.2
- name: Analyze
run: just analyze
ci:
needs: ["fmt"]
strategy:
fail-fast: false
matrix:
include:
- name: Windows x86_64
runner-os: windows-latest
cargo-target: x86_64-pc-windows-msvc
name: CI
runs-on: ubuntu-latest
- name: Linux x86_64
runner-os: ubuntu-latest
cargo-target: x86_64-unknown-linux-gnu
- name: macOS x86_64
runner-os: macos-13
cargo-target: x86_64-apple-darwin
- name: macOS aarch64
runner-os: macos-14
cargo-target: aarch64-apple-darwin
name: CI - ${{ matrix.name }}
runs-on: ${{ matrix.runner-os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: true
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
targets: ${{ matrix.cargo-target }}
components: rustfmt, clippy
- name: Rustfmt
run: cargo fmt -- --check
- name: Build
run: |
cargo build \
--workspace \
--locked --all-features \
--target ${{ matrix.cargo-target }}
run: cargo build --locked
- name: Lint
run: |
cargo clippy \
--workspace \
--locked --all-features \
--target ${{ matrix.cargo-target }}
- name: Clippy
run: cargo clippy
- name: Test
run: |
cargo test \
--lib --workspace \
--locked --all-features \
--target ${{ matrix.cargo-target }}
run: cargo test --lib

24
.github/workflows/publish.yaml vendored Normal file
View file

@ -0,0 +1,24 @@
name: Publish
on:
push:
branches:
- "main"
workflow_dispatch:
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- 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

@ -6,9 +6,8 @@ on:
permissions:
contents: write
defaults:
run:
shell: bash
env:
CARGO_TARGET_DIR: output
jobs:
init:
@ -18,37 +17,18 @@ jobs:
version: ${{ steps.get_version.outputs.value }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Get version from manifest
uses: SebRollen/toml-action@v1.2.0
uses: SebRollen/toml-action@9062fbef52816d61278d24ce53c8070440e1e8dd
id: get_version
with:
file: crates/lune/Cargo.toml
file: 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"] # , "dry-run"]
needs: ["init"]
strategy:
fail-fast: false
matrix:
include:
- name: Windows x86_64
@ -80,17 +60,14 @@ jobs:
runs-on: ${{ matrix.runner-os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.cargo-target }}
- name: Install Just
uses: extractions/setup-just@v2
- name: Install build tooling (aarch64-unknown-linux-gnu)
- name: Install tooling (aarch64-unknown-linux-gnu)
if: matrix.cargo-target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update -y
@ -98,61 +75,79 @@ jobs:
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- name: Build binary
run: just build --locked --release --target ${{ matrix.cargo-target }}
shell: bash
run: |
cargo build \
--locked --release --all-features \
--target ${{ matrix.cargo-target }}
- name: Create release archive
run: just zip-release ${{ matrix.cargo-target }}
- name: Create binary archive
shell: bash
run: |
mkdir -p staging
if [ "${{ matrix.runner-os }}" = "windows-latest" ]; then
cp "output/${{ matrix.cargo-target }}/release/lune.exe" staging/
cd staging
7z a ../release.zip *
else
cp "output/${{ matrix.cargo-target }}/release/lune" staging/
cd staging
chmod +x lune
zip ../release.zip *
fi
- name: Upload release artifact
uses: actions/upload-artifact@v4
- name: Upload binary artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.artifact-name }}
path: release.zip
release-github:
name: Release (GitHub)
release:
name: Release
runs-on: ubuntu-latest
needs: ["init", "build"] # , "dry-run", "build"]
needs: ["init", "build"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Just
uses: extractions/setup-just@v2
- name: Download releases
uses: actions/download-artifact@v4
- name: Download binaries
uses: actions/download-artifact@v3
with:
path: ./releases
path: ./binaries
- name: Unpack releases
run: just unpack-releases "./releases"
- name: Discover binaries
shell: bash
run: |
cd ./binaries
echo ""
echo "Binaries dir:"
ls -lhrt
echo ""
echo "Searching for zipped releases..."
for DIR in * ; do
if [ -d "$DIR" ]; then
cd "$DIR"
for FILE in * ; do
if [ ! -d "$FILE" ]; then
if [ "$FILE" = "release.zip" ]; then
echo "Found zipped release '$DIR'"
mv "$FILE" "../$DIR.zip"
rm -rf "../$DIR/"
fi
fi
done
cd ..
fi
done
echo ""
echo "Binaries dir:"
ls -lhrt
cd ..
- name: Create release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: ${{ needs.init.outputs.version }}
tag_name: v${{ needs.init.outputs.version }}
fail_on_unmatched_files: true
files: ./releases/*.zip
files: ./binaries/*.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 }}

7
.gitignore vendored
View file

@ -6,14 +6,7 @@
# Autogenerated dirs
/bin
/out
/target
/staging
/**/bin
/**/out
/**/target
/**/staging
# Autogenerated files

132
.justfile
View file

@ -1,129 +1,11 @@
EXT := if os() == "windows" { ".exe" } else { "" }
CWD := invocation_directory()
BIN_NAME := "lune"
# Default hidden recipe for listing other recipes + cwd
[no-cd]
[no-exit-message]
[private]
default:
#!/usr/bin/env bash
set -euo pipefail
printf "Current directory:\n {{CWD}}\n"
just --list
# Builds the Lune CLI binary
[no-exit-message]
build *ARGS:
#!/usr/bin/env bash
set -euo pipefail
cargo build --bin {{BIN_NAME}} {{ARGS}}
# Run an individual test using the Lune CLI
run-test TEST_NAME:
cargo run -- "tests/{{TEST_NAME}}"
# Run an individual file using the Lune CLI
[no-exit-message]
run FILE_PATH:
#!/usr/bin/env bash
set -euo pipefail
cargo run --bin {{BIN_NAME}} -- run "{{FILE_PATH}}"
run-file FILE_NAME:
cargo run -- "{{FILE_NAME}}"
# Run tests for the Lune library
[no-exit-message]
test *ARGS:
#!/usr/bin/env bash
set -euo pipefail
cargo test --lib -- {{ARGS}}
# Run tests for the Lune binary
[no-exit-message]
test-bin *ARGS:
#!/usr/bin/env bash
set -euo pipefail
cargo test --bin {{BIN_NAME}} -- {{ARGS}}
# Apply formatting for all Rust & Luau files
[no-exit-message]
fmt:
#!/usr/bin/env bash
set -euo pipefail
stylua .lune scripts tests types \
--glob "tests/**/*.luau" \
--glob "!tests/roblox/rbx-test-files/**"
cargo fmt
# Check formatting for all Rust & Luau files
[no-exit-message]
fmt-check:
#!/usr/bin/env bash
set -euo pipefail
stylua .lune scripts tests types \
--glob "tests/**/*.luau" \
--glob "!tests/roblox/rbx-test-files/**"
cargo fmt --check
# Analyze and lint Luau files using luau-lsp
[no-exit-message]
analyze:
#!/usr/bin/env bash
set -euo pipefail
luau-lsp analyze \
--settings=".vscode/settings.json" \
--ignore="tests/roblox/rbx-test-files/**" \
.lune scripts tests types
# Zips up the built binary into a single zip file
[no-exit-message]
zip-release TARGET_TRIPLE:
#!/usr/bin/env bash
set -euo pipefail
rm -rf staging
rm -rf release.zip
mkdir -p staging
cp "target/{{TARGET_TRIPLE}}/release/{{BIN_NAME}}{{EXT}}" staging/
cd staging
if [ "{{os_family()}}" = "windows" ]; then
7z a ../release.zip *
else
chmod +x {{BIN_NAME}}
zip ../release.zip *
fi
cd "{{CWD}}"
rm -rf staging
# Used in GitHub workflow to move per-matrix release zips
[no-exit-message]
[private]
unpack-releases RELEASES_DIR:
#!/usr/bin/env bash
set -euo pipefail
#
if [ ! -d "{{RELEASES_DIR}}" ]; then
echo "Releases directory is missing"
exit 1
fi
#
cd "{{RELEASES_DIR}}"
echo ""
echo "Releases dir:"
ls -lhrt
echo ""
echo "Searching for zipped releases..."
#
for DIR in * ; do
if [ -d "$DIR" ]; then
cd "$DIR"
for FILE in * ; do
if [ ! -d "$FILE" ]; then
if [ "$FILE" = "release.zip" ]; then
echo "Found zipped release '$DIR'"
mv "$FILE" "../$DIR.zip"
rm -rf "../$DIR/"
fi
fi
done
cd ..
fi
done
#
echo ""
echo "Releases dir:"
ls -lhrt
test:
cargo test --lib

View file

@ -7,10 +7,5 @@
"typeErrors": true,
"globals": [
"warn"
],
"aliases": {
"lune": "./types/",
"tests": "./tests",
"require-tests": "./tests/require/tests"
}
]
}

View file

@ -129,7 +129,7 @@ end
]]
print("Sending 4 pings to google 🌏")
local result = process.exec("ping", {
local result = process.spawn("ping", {
"google.com",
"-c 4",
})
@ -171,7 +171,7 @@ local apiResult = net.request({
method = "PATCH",
headers = {
["Content-Type"] = "application/json",
} :: { [string]: string },
},
body = net.jsonEncode({
title = "foo",
body = "bar",

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.")

10
.vscode/settings.json vendored
View file

@ -1,17 +1,21 @@
{
// Luau - disable Roblox features, enable Lune typedefs & requires
"luau-lsp.sourcemap.enabled": false,
"luau-lsp.types.roblox": false,
"luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": {
"@lune/": "./types/",
"@tests/": "./tests/",
"@require-tests/": "./tests/require/tests/"
"@lune/": "~/.lune/.typedefs/0.7.5/"
},
// Luau - ignore type defs file in docs dir and dev scripts we use
"luau-lsp.ignoreGlobs": [
"docs/*.d.luau",
"packages/lib-roblox/scripts/*.luau",
"tests/roblox/rbx-test-files/**/*.lua",
"tests/roblox/rbx-test-files/**/*.luau"
],
// Rust
"rust-analyzer.check.command": "clippy",
// Formatting
"editor.formatOnSave": true,
"stylua.searchParentDirectories": true,
"prettier.tabWidth": 2,

View file

@ -8,572 +8,6 @@ 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
- Fixed REPL panicking after the first evaluation / run.
- Fixed globals reloading on each run in the REPL, causing unnecessary slowdowns.
- Fixed `net.serve` requests no longer being plain tables in Lune `0.8.1`, breaking usage of things such as `table.clone`.
## `0.8.1` - March 11th, 2024
### Added
- Added the ability to specify an address in `net.serve`. ([#142])
### Changed
- Update to Luau version `0.616`.
- Major performance improvements when using a large amount of threads / asynchronous Lune APIs. ([#165])
- Minor performance improvements and less overhead for `net.serve` and `net.socket`. ([#165])
### Fixed
- Fixed `fs.copy` not working with empty dirs. ([#155])
- Fixed stack overflow when printing tables with cyclic references. ([#158])
- Fixed not being able to yield in `net.serve` handlers without blocking other requests. ([#165])
- Fixed various scheduler issues / panics. ([#165])
[#142]: https://github.com/lune-org/lune/pull/142
[#155]: https://github.com/lune-org/lune/pull/155
[#158]: https://github.com/lune-org/lune/pull/158
[#165]: https://github.com/lune-org/lune/pull/165
## `0.8.0` - January 14th, 2024
### Breaking Changes
- The Lune CLI now uses subcommands instead of flag options: <br/>
- `lune script_name arg1 arg2 arg3` -> `lune run script_name arg1 arg2 arg3`
- `lune --list` -> `lune list`
- `lune --setup` -> `lune setup`
This unfortunately hurts ergonomics for quickly running scripts but is a necessary change to allow us to add more commands, such as the new `build` subcommand.
- The `createdAt`, `modifiedAt`, and `accessedAt` properties returned from `fs.metadata` are now `DateTime` values instead of numbers.
- The `Lune` struct has been renamed to `Runtime` in the Lune rust crate.
### Added
- Added support for compiling single Lune scripts into standalone executables! ([#140])
Example usage:
```lua
-- my_cool_script.luau
print("Hello, standalone!")
```
```sh
> lune build my_cool_script.luau
# Creates `my_cool_script.exe` (Windows) or `my_cool_script` (macOS / Linux)
```
```sh
> ./my_cool_script.exe # Windows
> ./my_cool_script # macOS / Linux
> "Hello, standalone!"
```
To compile scripts that use `require` and reference multiple files, a bundler such as [darklua](https://github.com/seaofvoices/darklua) should preferrably be used. You may also distribute files alongside the standalone binary, they will still be able to be `require`-d. This limitation will be lifted in the future and Lune will automatically bundle any referenced scripts.
- Added support for path aliases using `.luaurc` config files!
For full documentation and reference, check out the [official Luau RFC](https://rfcs.luau-lang.org/require-by-string-aliases.html), but here's a quick example:
```jsonc
// .luaurc
{
"aliases": {
"modules": "./some/long/path/to/modules"
}
}
```
```lua
-- ./some/long/path/to/modules/foo.luau
return { World = "World!" }
-- ./anywhere/you/want/my_script.luau
local mod = require("@modules/foo")
print("Hello, " .. mod.World)
```
- Added support for multiple values for a single query, and multiple values for a single header, in `net.request`. This is a part of the HTTP specification that is not widely used but that may be useful in certain cases. To clarify:
- Single values remain unchanged and will work exactly the same as before. <br/>
```lua
-- https://example.com/?foo=bar&baz=qux
local net = require("@lune/net")
net.request({
url = "example.com",
query = {
foo = "bar",
baz = "qux",
}
})
```
- Multiple values _on a single query / header_ are represented as an ordered array of strings. <br/>
```lua
-- https://example.com/?foo=first&foo=second&foo=third&bar=baz
local net = require("@lune/net")
net.request({
url = "example.com",
query = {
foo = { "first", "second", "third" },
bar = "baz",
}
})
```
[#140]: https://github.com/lune-org/lune/pull/140
### Changed
- Update to Luau version `0.606`.
### Fixed
- Fixed the `print` and `warn` global functions yielding the thread, preventing them from being used in places such as the callback to `table.sort`.
- Fixed the `overwrite` option for `fs.move` not correctly removing existing files / directories. ([#133])
[#133]: https://github.com/lune-org/lune/pull/133
## `0.7.11` - October 29th, 2023
### Changed
- Update to Luau version `0.601`.
### Fixed
- Fixed `roblox.getAuthCookie` not being compatible with the latest cookie format by upgrading rbx_cookie.
## `0.7.10` - October 25th, 2023
### Added
- Added the `GetDebugId` instance method to the `roblox` built-in. This will return the internal id used by the instance, and as the name implies, it should be primarily used for _debugging_ purposes and cases where you need a globally unique identifier for an instance. It is guaranteed to be a 32-digit hexadecimal string.
### Fixed
- Fixed issues with `SecurityCapabilities` on instances in the `roblox` built-in by upgrading rbx-dom.
## `0.7.9` - October 21st, 2023
### Added
- Added `implementProperty` and `implementMethod` to the `roblox` built-in library to fill in missing functionality that Lune does not aim to implement itself.
Example usage:
```lua
local roblox = require("@lune/roblox")
local part = roblox.Instance.new("Part")
roblox.implementMethod("BasePart", "TestMethod", function(_, ...)
print("Tried to call TestMethod with", ...)
end)
part:TestMethod("Hello", "world!")
```
### Changed
- Update to Luau version `0.599`.
- Stdio options when using `process.spawn` can now be set with more granularity, allowing stderr & stdout to be disabled individually and completely to improve memory usage when they are not being used.
## `0.7.8` - October 5th, 2023
### Added
- Added a new `datetime` built-in library for handling date & time values, parsing, formatting, and more. ([#94])
Example usage:
```lua
local DateTime = require("@lune/datetime")
-- Creates a DateTime for the current exact moment in time
local now = DateTime.now()
-- Formats the current moment in time as an ISO 8601 string
print(now:toIsoDate())
-- Formats the current moment in time, using the local
-- time, the French locale, and the specified time string
print(now:formatLocalTime("%A, %d %B %Y", "fr"))
-- Returns a specific moment in time as a DateTime instance
local someDayInTheFuture = DateTime.fromLocalTime({
year = 3033,
month = 8,
day = 26,
hour = 16,
minute = 56,
second = 28,
millisecond = 892,
})
-- Extracts the current local date & time as separate values (same values as above table)
print(now:toLocalTime())
-- Returns a DateTime instance from a given float, where the whole
-- denotes the seconds and the fraction denotes the milliseconds
-- Note that the fraction for millis here is completely optional
DateTime.fromUnixTimestamp(871978212313.321)
-- Extracts the current universal (UTC) date & time as separate values
print(now:toUniversalTime())
```
- Added support for passing `stdin` in `process.spawn` ([#106])
- Added support for setting a custom environment in load options for `luau.load`, not subject to `getfenv` / `setfenv` deoptimizations
- Added [Terrain:GetMaterialColor](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor) and [Terrain:SetMaterialColor](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor) ([#93])
- Added support for a variable number of arguments for CFrame methods ([#85])
### Changed
- Update to Luau version `0.596`.
- Update to rbx-dom database version `0.596`.
- `process.spawn` now uses `powershell` instead of `/bin/bash` as the shell on Windows, with `shell = true`.
- CFrame and Vector3 values are now rounded to the nearest 2 ^ 16 decimal place to reduce floating point errors and diff noise. Note that this does not affect intermediate calculations done in lua, and only happens when a property value is set on an Instance.
### Fixed
- Fixed the `process` built-in library not loading correctly when using Lune in REPL mode.
- Fixed list subcommand not listing global scripts without a local `.lune` / `lune` directory present.
- Fixed `net.serve` stopping when the returned `ServeHandle` is garbage collected.
- Fixed missing trailing newline when using the `warn` global.
- Fixed constructor for `CFrame` in the `roblox` built-in library not parsing the 12-arg overload correctly. ([#102])
- Fixed various functions for `CFrame` in the `roblox` built-in library being incorrect, specifically row-column ordering and some flipped signs. ([#103])
- Fixed cross-service Instance references disappearing when using the `roblox` built-in library ([#117])
[#85]: https://github.com/lune-org/lune/pull/85
[#93]: https://github.com/lune-org/lune/pull/93
[#94]: https://github.com/lune-org/lune/pull/94
[#102]: https://github.com/lune-org/lune/pull/102
[#103]: https://github.com/lune-org/lune/pull/103
[#106]: https://github.com/lune-org/lune/pull/106
[#117]: https://github.com/lune-org/lune/pull/117
## `0.7.7` - August 23rd, 2023
### Added
- Added a [REPL](https://en.wikipedia.org/wiki/Readevalprint_loop) to Lune. ([#83])
This allows you to run scripts within Lune without writing files!
Example usage, inside your favorite terminal:
```bash
# 1. Run the Lune executable, without any arguments
lune
# 2. You will be shown the current Lune version and a blank prompt arrow:
Lune v0.7.7
>
# 3. Start typing, and hit enter when you want to run your script!
# Your script will run until completion and output things along the way.
> print(2 + 3)
5
> print("Hello, lune changelog!")
Hello, lune changelog!
# 4. You can also set variables that will get preserved between runs.
# Note that local variables do not get preserved here.
> myVariable = 123
> print(myVariable)
123
# 5. Press either of these key combinations to exit the REPL:
# - Ctrl + D
# - Ctrl + C
```
- Added a new `luau` built-in library for manually compiling and loading Luau source code. ([#82])
Example usage:
```lua
local luau = require("@lune/luau")
local bytecode = luau.compile("print('Hello, World!')")
local callableFn = luau.load(bytecode)
callableFn()
-- Additionally, we can skip the bytecode generation and
-- load a callable function directly from the code itself.
local callableFn2 = luau.load("print('Hello, World!')")
callableFn2()
```
### Changed
- Update to Luau version `0.591`.
- Lune's internal task scheduler and `require` functionality has been completely rewritten. <br/>
The new scheduler is much more stable, conforms to a larger test suite, and has a few additional benefits:
- Built-in libraries are now lazily loaded, meaning nothing gets allocated until the built-in library gets loaded using `require("@lune/builtin-name")`. This also improves startup times slightly.
- Spawned processes using `process.spawn` now run on different thread(s), freeing up resources for the main thread where luau runs.
- Serving requests using `net.serve` now processes requests on background threads, also freeing up resources. In the future, this will also allow us to offload heavy tasks such as compression/decompression to background threads.
- Groundwork for custom / user-defined require aliases has been implemented, as well as absolute / cwd-relative requires. These will both be exposed as options and be made available to use some time in the future.
- When using the `serde` built-in library, keys are now sorted during serialization. This means that the output of `encode` is now completely deterministic, and wont cause issues when committing generated files to git etc.
### Fixed
- Fixed not being able to pass arguments to the thread using `coroutine.resume`. ([#86])
- Fixed a large number of long-standing issues, from the task scheduler rewrite:
- Fixed `require` hanging indefinitely when the module being require-d uses an async function in its main body.
- Fixed background tasks (such as `net.serve`) not keeping Lune alive even if there are no lua threads to run.
- Fixed spurious panics and error messages such as `Tried to resume next queued future but none are queued`.
- Fixed not being able to catch non-string errors properly, errors were accidentally being wrapped in an opaque `userdata` type.
[#82]: https://github.com/lune-org/lune/pull/82
[#83]: https://github.com/lune-org/lune/pull/83
[#86]: https://github.com/lune-org/lune/pull/86
## `0.7.6` - August 9th, 2023
### Changed
@ -667,9 +101,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed not being able to read & write to WebSocket objects at the same time. ([#68])
- Fixed tab character at the start of a script causing it not to parse correctly. ([#72])
[#62]: https://github.com/lune-org/lune/pull/62
[#68]: https://github.com/lune-org/lune/pull/66
[#72]: https://github.com/lune-org/lune/pull/72
[#62]: https://github.com/filiptibell/lune/pull/62
[#68]: https://github.com/filiptibell/lune/pull/66
[#72]: https://github.com/filiptibell/lune/pull/72
## `0.7.4` - July 7th, 2023
@ -687,7 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- When using `roblox.serializeModel`, Lune will no longer keep internal unique ids. <br/>
This is consistent with what Roblox does and prevents Lune from always generating a new and unique file. <br/>
This previously caused unnecessary diffs when using git or other kinds of source control. ([Relevant issue](https://github.com/lune-org/lune/issues/61))
This previously caused unnecessary diffs when using git or other kinds of source control. ([Relevant issue](https://github.com/filiptibell/lune/issues/61))
## `0.7.2` - June 28th, 2023
@ -712,7 +146,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed `closeCode` erroring when being accessed on websockets. ([#57])
- Fixed issues with `UniqueId` when using the `roblox` builtin by downgrading `rbx-dom`.
[#57]: https://github.com/lune-org/lune/pull/57
[#57]: https://github.com/filiptibell/lune/pull/57
## `0.7.0` - June 12th, 2023
@ -781,7 +215,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed `stdio.write` and `stdio.ewrite` not being flushed and causing output to be interleaved. ([#47])
- Fixed `typeof` returning `userdata` for roblox types such as `Instance`, `Vector3`, ...
[#47]: https://github.com/lune-org/lune/pull/47
[#47]: https://github.com/filiptibell/lune/pull/47
## `0.6.7` - May 14th, 2023

View file

@ -1,71 +0,0 @@
<!-- markdownlint-disable MD001 -->
<!-- markdownlint-disable MD033 -->
# Contributing
---
### Reporting a Bug
- Make sure the bug has not already been reported by searching on GitHub under [Issues](https://github.com/lune-org/lune/issues).
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/lune-org/lune/issues/new). Be sure to include a **title and description**, as much relevant information as possible, and if applicable, a **code sample** or a **test case** demonstrating the expected behavior.
---
### Contributing - Bug Fixes
1. Make sure an [issue](https://github.com/lune-org/lune/issues) has been created for the bug first, so that it can be tracked and searched for in the repository history. This is not mandatory for small fixes.
2. Open a new GitHub pull request for it. A pull request for a bug fix must include:
- A clear and concise description of the bug it is fixing.
- A new test file ensuring there are no regressions after the bug has been fixed.
- A link to the relevant issue, or a `Fixes #issue` line, if an issue exists.
### Contributing - Features
1. Make sure an [issue](https://github.com/lune-org/lune/issues) has been created for the feature first, so that it can be tracked and searched for in the repository history. If you are making changes to an existing feature, and no issue exists, one should be created for the proposed changes.
2. Any API design or considerations should first be brought up and discussed in the relevant issue, to prevent long review times on pull requests and unnecessary work for maintainers.
3. Familiarize yourself with the codebase and the tools you will be using. Some important parts include:
- The [mlua](https://crates.io/crates/mlua) library, which we use to interface with Luau.
- Any [built-in libraries](https://github.com/lune-org/lune/tree/main/src/lune/builtins) that are relevant for your new feature. If you are making a new built-in library, refer to existing ones for structure and implementation details.
- Our toolchain, notably [StyLua](https://github.com/JohnnyMorganz/StyLua), [rustfmt](https://github.com/rust-lang/rustfmt), and [clippy](https://github.com/rust-lang/rust-clippy). If you do not use these tools there is a decent chance CI will fail on your pull request, blocking it from getting approved.
4. Write some code!
5. Open a new GitHub pull request. A pull request for a feature must include:
- A clear and concise description of the new feature or changes to the feature.
- Test files for any added or changed functionality.
- A link to the relevant issue, or a `Closes #issue` line.
### Contributing - Formatting & Cosmetic Changes
Changes that are purely cosmetic, and do not add to the stability, functionality, or testability of Lune, will generally not be accepted unless there has been previous discussion about the changes being made.
### Contributing - Documentation
#### Documentation Site
Check out the [docs](https://github.com/lune-org/docs) repository and its contribution guidelines.
#### Type Definitions
If type definitions for built-in libraries need improvements:
1. Check out the [types](https://github.com/lune-org/lune/tree/main/types) directory at the root of the repository.
2. Make the desired changes, and verify that they have the desired outcome.
3. Open a new GitHub pull request for your changes.
---
### Publishing a Release
The Lune release process is semi-automated, and takes care of most things for you. Here's how to create a new release:
1. Make sure the changelog is up to date and contains all of the changes since the last release.
2. Add the release date in the changelog + set a new version number in `Cargo.toml`.
3. Commit and push changes from step 2 to GitHub. This will automatically publish the Lune library to [crates.io](https://crates.io) when the version number changes.
4. Trigger the [release](https://github.com/lune-org/lune/actions/workflows/release.yaml) workflow on GitHub manually, and wait for it to finish. Find the new pending release in the [Releases](https://github.com/lune-org/lune/releases) section.
5. Add in changes from the changelog for the new pending release into the description, hit "accept" on creating a new version tag, and publish 🚀
---
If you have any questions, check out the `#lune` channel in the [Roblox OSS discord](https://discord.gg/H9WqmFAB5Y), where most of our realtime discussion takes place!
Thank you for contributing to Lune! 🌙

2775
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,22 +1,41 @@
[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",
[package]
name = "lune"
version = "0.7.6"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/filiptibell/lune"
description = "A Luau script runner"
readme = "README.md"
keywords = ["cli", "lua", "luau", "scripts"]
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",
]
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",
]
# Profile for building the release binary, with the following options set:
@ -34,31 +53,82 @@ opt-level = "z"
strip = true
lto = true
# Lints for all crates in the workspace
# All of the dependencies for Lune.
#
# 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 }
# 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.10"
dunce = "1.0"
lz4_flex = "0.11"
pin-project = "1.0"
os_str_bytes = "6.4"
urlencoding = "2.1"
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 }
### RUNTIME
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 = { version = "0.9.0-beta.3", features = [
"luau",
"luau-jit",
"serialize",
] }
tokio = { version = "1.24", features = ["full"] }
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 }
### 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.7", features = ["preserve_order"] }
### NET
hyper = { version = "0.14", features = ["full"] }
hyper-tungstenite = { version = "0.11" }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
] }
tokio-tungstenite = { version = "0.20", features = ["rustls-tls-webpki-roots"] }
### CLI
anyhow = { optional = true, version = "1.0" }
env_logger = { optional = true, version = "0.10" }
itertools = { optional = true, version = "0.11" }
clap = { optional = true, version = "4.1", features = ["derive"] }
include_dir = { optional = true, version = "0.7.3", features = ["glob"] }
regex = { optional = true, version = "1.7", default-features = false, features = [
"std",
"unicode-perl",
] }
### ROBLOX
glam = { optional = true, version = "0.24" }
rand = { optional = true, version = "0.8" }
rbx_cookie = { optional = true, version = "0.1.3", default-features = false }
rbx_binary = { optional = true, version = "0.7.1" }
rbx_dom_weak = { optional = true, version = "2.5.0" }
rbx_reflection = { optional = true, version = "4.3.0" }
rbx_reflection_database = { optional = true, version = "0.2.7" }
rbx_xml = { optional = true, version = "0.13.1" }

View file

@ -1,47 +1,44 @@
<!-- markdownlint-disable MD033 -->
<!-- markdownlint-disable MD041 -->
<img align="right" width="250" src="assets/logo/tilt_svg.svg" alt="Lune logo" />
<h1 align="center">Lune</h1>
<div align="center">
<h1> Lune 🌙 </h1>
<div>
<a href="https://crates.io/crates/lune">
<img src="https://img.shields.io/crates/v/lune.svg?label=Version" alt="Current Lune library version" />
</a>
<a href="https://github.com/lune-org/lune/actions">
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/lune-org/lune/ci.yaml" alt="CI status" />
<a href="https://github.com/filiptibell/lune/actions">
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/filiptibell/lune/ci.yaml" alt="CI status" />
</a>
<a href="https://github.com/lune-org/lune/actions">
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/lune-org/lune/release.yaml" alt="Release status" />
<a href="https://github.com/filiptibell/lune/actions">
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/filiptibell/lune/release.yaml" alt="Release status" />
</a>
<a href="https://github.com/lune-org/lune/blob/main/LICENSE.txt">
<img src="https://img.shields.io/github/license/lune-org/lune.svg?label=License&color=informational" alt="Lune license" />
<a href="https://github.com/filiptibell/lune/blob/main/LICENSE.txt">
<img src="https://img.shields.io/github/license/filiptibell/lune.svg?label=License&color=informational" alt="Lune license" />
</a>
</div>
</div>
<br/>
---
A standalone [Luau](https://luau-lang.org) runtime.
A standalone [Luau](https://luau-lang.org) script runtime.
Write and run programs, similar to runtimes for other languages such as [Node](https://nodejs.org), [Deno](https://deno.land), [Bun](https://bun.sh), or [Luvit](https://luvit.io) for vanilla Lua.
Write and run scripts, similar to runtimes for other languages such as [Node](https://nodejs.org) / [Deno](https://deno.land), or [Luvit](https://luvit.io) for vanilla Lua.
Lune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for speed, safety and correctness.
Lune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for optimal safety and correctness.
## 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 zipped) executable
- 🌙 A 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 (~4mb) 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
- 🏡 A familiar scripting 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
## Non-goals
- Making programs short and terse - proper autocomplete / intellisense make using Lune just as quick, and readability is important
- Running full Roblox games outside of Roblox - there is some compatibility, but Lune is meant for different purposes
- Making scripts short and terse - proper autocomplete / intellisense make scripting using Lune just as quick, and readability is important
- Running full Roblox game scripts outside of Roblox - there is some compatibility, but Lune is meant for different purposes
## Where do I start?

4
aftman.toml Normal file
View file

@ -0,0 +1,4 @@
[tools]
luau-lsp = "JohnnyMorganz/luau-lsp@1.20.0"
selene = "Kampfkarren/selene@0.24.0"
stylua = "JohnnyMorganz/StyLua@0.17.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

View file

@ -1,69 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_i_6_19)">
<g clip-path="url(#clip0_6_19)">
<rect x="2" y="2" width="16" height="16" rx="1" fill="url(#paint0_linear_6_19)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.875 0.125L18.125 0.125V1.875H19.875V0.125ZM19.875 2.125H18.125V3.875H19.875V2.125ZM19.875 4.125H18.125V5.875H19.875V4.125ZM19.875 6.125H18.125V7.875H19.875V6.125ZM19.875 8.125H18.125V9.875H19.875V8.125ZM19.875 10.125H18.125V11.875H19.875V10.125ZM19.875 12.125H18.125V13.875H19.875V12.125ZM19.875 14.125H18.125V15.875H19.875V14.125ZM19.875 16.125H18.125V17.875H19.875V16.125ZM19.875 18.125H18.125V19.875H19.875V18.125ZM20 20V20.125H4.76837e-07V20H-0.125L-0.125 -5.21541e-07L4.76837e-07 -5.1409e-07V-0.125L20 -0.125V2.92063e-06H20.125V20H20ZM0.125 0.125L0.125 1.875H1.87499L1.87499 0.125L0.125 0.125ZM2.12499 0.125V1.875H3.87499V0.125L2.12499 0.125ZM4.12499 0.125V1.875H5.87499V0.125L4.12499 0.125ZM6.12499 0.125V1.875H7.87499V0.125L6.12499 0.125ZM8.12499 0.125V1.875H9.87499V0.125L8.12499 0.125ZM10.125 0.125V1.875H11.875V0.125L10.125 0.125ZM12.125 0.125V1.875H13.875V0.125L12.125 0.125ZM14.125 0.125V1.875H15.875V0.125L14.125 0.125ZM16.125 0.125V1.875H17.875V0.125L16.125 0.125ZM17.875 2.125H16.125V3.875H17.875V2.125ZM17.875 4.125H16.125V5.875H17.875V4.125ZM17.875 6.125H16.125V7.875H17.875V6.125ZM17.875 8.125H16.125V9.875H17.875V8.125ZM17.875 10.125H16.125V11.875H17.875V10.125ZM17.875 12.125H16.125V13.875H17.875V12.125ZM17.875 14.125H16.125V15.875H17.875V14.125ZM17.875 16.125H16.125V17.875H17.875V16.125ZM17.875 18.125H16.125V19.875H17.875V18.125ZM15.875 19.875V18.125H14.125V19.875H15.875ZM13.875 19.875V18.125H12.125V19.875H13.875ZM11.875 19.875V18.125H10.125V19.875H11.875ZM9.87499 19.875V18.125H8.12499V19.875H9.87499ZM7.87499 19.875V18.125H6.12499V19.875H7.87499ZM5.87499 19.875V18.125H4.12499V19.875H5.87499ZM3.87499 19.875V18.125H2.12499V19.875H3.87499ZM1.87499 19.875L1.87499 18.125H0.124999L0.124999 19.875H1.87499ZM0.124999 17.875H1.87499V16.125H0.124999L0.124999 17.875ZM0.124999 15.875H1.87499L1.87499 14.125H0.124999L0.124999 15.875ZM0.124999 13.875H1.87499L1.87499 12.125H0.124999L0.124999 13.875ZM0.124999 11.875H1.87499L1.87499 10.125H0.125L0.124999 11.875ZM0.125 9.875H1.87499V8.125H0.125L0.125 9.875ZM0.125 7.875H1.87499L1.87499 6.125H0.125L0.125 7.875ZM0.125 5.875H1.87499L1.87499 4.125H0.125L0.125 5.875ZM0.125 3.875H1.87499V2.125H0.125L0.125 3.875ZM2.12499 2.125L2.12499 3.875H3.87499L3.87499 2.125H2.12499ZM4.12499 2.125V3.875H5.87499V2.125H4.12499ZM6.12499 2.125V3.875H7.87499V2.125H6.12499ZM8.12499 2.125V3.875H9.87499V2.125H8.12499ZM10.125 2.125V3.875H11.875V2.125H10.125ZM12.125 2.125V3.875H13.875V2.125H12.125ZM14.125 2.125V3.875H15.875V2.125H14.125ZM15.875 4.125H14.125V5.875H15.875V4.125ZM15.875 6.125H14.125V7.875H15.875V6.125ZM15.875 8.125H14.125V9.875H15.875V8.125ZM15.875 10.125H14.125V11.875H15.875V10.125ZM15.875 12.125H14.125V13.875H15.875V12.125ZM15.875 14.125H14.125V15.875H15.875V14.125ZM15.875 16.125H14.125V17.875H15.875V16.125ZM13.875 17.875V16.125H12.125V17.875H13.875ZM11.875 17.875V16.125H10.125V17.875H11.875ZM9.87499 17.875V16.125H8.12499V17.875H9.87499ZM7.87499 17.875V16.125H6.12499V17.875H7.87499ZM5.87499 17.875V16.125H4.12499V17.875H5.87499ZM3.87499 17.875L3.87499 16.125H2.12499L2.12499 17.875H3.87499ZM2.12499 15.875H3.87499V14.125H2.12499V15.875ZM2.12499 13.875H3.87499L3.87499 12.125H2.12499L2.12499 13.875ZM2.12499 11.875H3.87499V10.125H2.12499V11.875ZM2.12499 9.875H3.87499V8.125H2.12499V9.875ZM2.12499 7.875H3.87499L3.87499 6.125H2.12499L2.12499 7.875ZM2.12499 5.875H3.87499V4.125H2.12499V5.875ZM4.12499 4.125L4.12499 5.875H5.87499L5.87499 4.125H4.12499ZM6.12499 4.125L6.12499 5.875H7.87499L7.87499 4.125H6.12499ZM8.12499 4.125V5.875H9.87499V4.125H8.12499ZM10.125 4.125V5.875H11.875V4.125H10.125ZM12.125 4.125V5.875H13.875V4.125H12.125ZM13.875 6.125H12.125V7.875H13.875V6.125ZM13.875 8.125H12.125V9.875H13.875V8.125ZM13.875 10.125H12.125V11.875H13.875V10.125ZM13.875 12.125H12.125V13.875H13.875V12.125ZM13.875 14.125H12.125V15.875H13.875V14.125ZM11.875 15.875V14.125H10.125V15.875H11.875ZM9.87499 15.875V14.125H8.12499V15.875H9.87499ZM7.87499 15.875L7.87499 14.125H6.12499L6.12499 15.875H7.87499ZM5.87499 15.875L5.87499 14.125H4.12499L4.12499 15.875H5.87499ZM4.12499 13.875H5.87499V12.125H4.12499V13.875ZM4.12499 11.875H5.87499V10.125H4.12499V11.875ZM4.12499 9.875H5.87499V8.125H4.12499V9.875ZM4.12499 7.875H5.87499V6.125H4.12499V7.875ZM6.12499 6.125V7.875H7.87499V6.125H6.12499ZM8.12499 6.125V7.875H9.87499V6.125H8.12499ZM10.125 6.125V7.875H11.875V6.125H10.125ZM11.875 8.125H10.125V9.875H11.875V8.125ZM11.875 10.125H10.125V11.875H11.875V10.125ZM11.875 12.125H10.125V13.875H11.875V12.125ZM9.87499 13.875V12.125H8.12499V13.875H9.87499ZM7.87499 13.875V12.125H6.12499V13.875H7.87499ZM6.12499 11.875H7.87499V10.125H6.12499V11.875ZM6.12499 9.875H7.87499V8.125H6.12499V9.875ZM8.12499 8.125V9.875H9.87499V8.125H8.12499ZM9.87499 10.125H8.12499V11.875H9.87499V10.125Z" fill="url(#paint1_linear_6_19)" fill-opacity="0.05"/>
<g filter="url(#filter1_di_6_19)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.375 6.25C14.2725 6.25 15 5.52246 15 4.625C15 4.4376 14.9683 4.2576 14.9099 4.09009C15.5446 4.31125 16 4.91494 16 5.625C16 6.52246 15.2725 7.25 14.375 7.25C13.6649 7.25 13.0613 6.79458 12.8401 6.15991C13.0076 6.21828 13.1876 6.25 13.375 6.25Z" fill="white"/>
</g>
<g filter="url(#filter2_di_6_19)">
<path d="M6.625 15.54V16H4.49219V15.54H6.625ZM4.60352 11.7344V16H4.03809V11.7344H4.60352ZM9.00098 15.2676V12.8301H9.5459V16H9.02734L9.00098 15.2676ZM9.10352 14.5996L9.3291 14.5938C9.3291 14.8047 9.30664 15 9.26172 15.1797C9.21875 15.3574 9.14844 15.5117 9.05078 15.6426C8.95312 15.7734 8.8252 15.876 8.66699 15.9502C8.50879 16.0225 8.31641 16.0586 8.08984 16.0586C7.93555 16.0586 7.79395 16.0361 7.66504 15.9912C7.53809 15.9463 7.42871 15.877 7.33691 15.7832C7.24512 15.6895 7.17383 15.5674 7.12305 15.417C7.07422 15.2666 7.0498 15.0859 7.0498 14.875V12.8301H7.5918V14.8809C7.5918 15.0234 7.60742 15.1416 7.63867 15.2354C7.67188 15.3271 7.71582 15.4004 7.77051 15.4551C7.82715 15.5078 7.88965 15.5449 7.95801 15.5664C8.02832 15.5879 8.10059 15.5986 8.1748 15.5986C8.40527 15.5986 8.58789 15.5547 8.72266 15.4668C8.85742 15.377 8.9541 15.2568 9.0127 15.1064C9.07324 14.9541 9.10352 14.7852 9.10352 14.5996ZM10.9141 13.5068V16H10.3721V12.8301H10.8848L10.9141 13.5068ZM10.7852 14.2949L10.5596 14.2861C10.5615 14.0693 10.5938 13.8691 10.6562 13.6855C10.7188 13.5 10.8066 13.3389 10.9199 13.2021C11.0332 13.0654 11.168 12.96 11.3242 12.8857C11.4824 12.8096 11.6572 12.7715 11.8486 12.7715C12.0049 12.7715 12.1455 12.793 12.2705 12.8359C12.3955 12.877 12.502 12.9434 12.5898 13.0352C12.6797 13.127 12.748 13.2461 12.7949 13.3926C12.8418 13.5371 12.8652 13.7139 12.8652 13.9229V16H12.3203V13.917C12.3203 13.751 12.2959 13.6182 12.2471 13.5186C12.1982 13.417 12.127 13.3438 12.0332 13.2988C11.9395 13.252 11.8242 13.2285 11.6875 13.2285C11.5527 13.2285 11.4297 13.2568 11.3184 13.3135C11.209 13.3701 11.1143 13.4482 11.0342 13.5479C10.9561 13.6475 10.8945 13.7617 10.8496 13.8906C10.8066 14.0176 10.7852 14.1523 10.7852 14.2949ZM15.0039 16.0586C14.7832 16.0586 14.583 16.0215 14.4033 15.9473C14.2256 15.8711 14.0723 15.7646 13.9434 15.6279C13.8164 15.4912 13.7188 15.3291 13.6504 15.1416C13.582 14.9541 13.5479 14.749 13.5479 14.5264V14.4033C13.5479 14.1455 13.5859 13.916 13.6621 13.7148C13.7383 13.5117 13.8418 13.3398 13.9727 13.1992C14.1035 13.0586 14.252 12.9521 14.418 12.8799C14.584 12.8076 14.7559 12.7715 14.9336 12.7715C15.1602 12.7715 15.3555 12.8105 15.5195 12.8887C15.6855 12.9668 15.8213 13.0762 15.9268 13.2168C16.0322 13.3555 16.1104 13.5195 16.1611 13.709C16.2119 13.8965 16.2373 14.1016 16.2373 14.3242V14.5674H13.8701V14.125H15.6953V14.084C15.6875 13.9434 15.6582 13.8066 15.6074 13.6738C15.5586 13.541 15.4805 13.4316 15.373 13.3457C15.2656 13.2598 15.1191 13.2168 14.9336 13.2168C14.8105 13.2168 14.6973 13.2432 14.5938 13.2959C14.4902 13.3467 14.4014 13.4229 14.3271 13.5244C14.2529 13.626 14.1953 13.75 14.1543 13.8965C14.1133 14.043 14.0928 14.2119 14.0928 14.4033V14.5264C14.0928 14.6768 14.1133 14.8184 14.1543 14.9512C14.1973 15.082 14.2588 15.1973 14.3389 15.2969C14.4209 15.3965 14.5195 15.4746 14.6348 15.5312C14.752 15.5879 14.8848 15.6162 15.0332 15.6162C15.2246 15.6162 15.3867 15.5771 15.5195 15.499C15.6523 15.4209 15.7686 15.3164 15.8682 15.1855L16.1963 15.4463C16.1279 15.5498 16.041 15.6484 15.9355 15.7422C15.8301 15.8359 15.7002 15.9121 15.5459 15.9707C15.3936 16.0293 15.2129 16.0586 15.0039 16.0586Z" fill="white"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_i_6_19" x="2" y="1.5" width="16.5" height="16.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.5" dy="-0.5"/>
<feGaussianBlur stdDeviation="0.25"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_19"/>
</filter>
<filter id="filter1_di_6_19" x="10.8401" y="3.09009" width="6.15991" height="6.15991" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_19"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_19" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_19"/>
</filter>
<filter id="filter2_di_6_19" x="2.03809" y="10.7344" width="15.1992" height="7.32422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_19"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_19" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_19"/>
</filter>
<linearGradient id="paint0_linear_6_19" x1="10" y1="2" x2="10" y2="18" gradientUnits="userSpaceOnUse">
<stop stop-color="#E168FF"/>
<stop offset="1" stop-color="#C848E9"/>
</linearGradient>
<linearGradient id="paint1_linear_6_19" x1="10" y1="-0.125" x2="10" y2="20.125" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.5"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<clipPath id="clip0_6_19">
<rect x="2" y="2" width="16" height="16" rx="1" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

View file

@ -1,64 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_i_6_67)">
<g clip-path="url(#clip0_6_67)">
<rect x="2" y="2" width="16" height="16" rx="1" fill="url(#paint0_linear_6_67)"/>
<g filter="url(#filter1_di_6_67)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.375 6.25C14.2725 6.25 15 5.52246 15 4.625C15 4.43759 14.9683 4.2576 14.9099 4.09009C15.5446 4.31125 16 4.91494 16 5.625C16 6.52246 15.2725 7.25 14.375 7.25C13.6649 7.25 13.0613 6.79458 12.8401 6.15991C13.0076 6.21828 13.1876 6.25 13.375 6.25Z" fill="white"/>
</g>
<g filter="url(#filter2_di_6_67)">
<path d="M6.625 15.54V16H4.49219V15.54H6.625ZM4.60352 11.7344V16H4.03809V11.7344H4.60352ZM9.00098 15.2676V12.8301H9.5459V16H9.02734L9.00098 15.2676ZM9.10352 14.5996L9.3291 14.5938C9.3291 14.8047 9.30664 15 9.26172 15.1797C9.21875 15.3574 9.14844 15.5117 9.05078 15.6426C8.95312 15.7734 8.8252 15.876 8.66699 15.9502C8.50879 16.0225 8.31641 16.0586 8.08984 16.0586C7.93555 16.0586 7.79395 16.0361 7.66504 15.9912C7.53809 15.9463 7.42871 15.877 7.33691 15.7832C7.24512 15.6895 7.17383 15.5674 7.12305 15.417C7.07422 15.2666 7.0498 15.0859 7.0498 14.875V12.8301H7.5918V14.8809C7.5918 15.0234 7.60742 15.1416 7.63867 15.2354C7.67188 15.3271 7.71582 15.4004 7.77051 15.4551C7.82715 15.5078 7.88965 15.5449 7.95801 15.5664C8.02832 15.5879 8.10059 15.5986 8.1748 15.5986C8.40527 15.5986 8.58789 15.5547 8.72266 15.4668C8.85742 15.377 8.9541 15.2568 9.0127 15.1064C9.07324 14.9541 9.10352 14.7852 9.10352 14.5996ZM10.9141 13.5068V16H10.3721V12.8301H10.8848L10.9141 13.5068ZM10.7852 14.2949L10.5596 14.2861C10.5615 14.0693 10.5938 13.8691 10.6562 13.6855C10.7188 13.5 10.8066 13.3389 10.9199 13.2021C11.0332 13.0654 11.168 12.96 11.3242 12.8857C11.4824 12.8096 11.6572 12.7715 11.8486 12.7715C12.0049 12.7715 12.1455 12.793 12.2705 12.8359C12.3955 12.877 12.502 12.9434 12.5898 13.0352C12.6797 13.127 12.748 13.2461 12.7949 13.3926C12.8418 13.5371 12.8652 13.7139 12.8652 13.9229V16H12.3203V13.917C12.3203 13.751 12.2959 13.6182 12.2471 13.5186C12.1982 13.417 12.127 13.3438 12.0332 13.2988C11.9395 13.252 11.8242 13.2285 11.6875 13.2285C11.5527 13.2285 11.4297 13.2568 11.3184 13.3135C11.209 13.3701 11.1143 13.4482 11.0342 13.5479C10.9561 13.6475 10.8945 13.7617 10.8496 13.8906C10.8066 14.0176 10.7852 14.1523 10.7852 14.2949ZM15.0039 16.0586C14.7832 16.0586 14.583 16.0215 14.4033 15.9473C14.2256 15.8711 14.0723 15.7646 13.9434 15.6279C13.8164 15.4912 13.7188 15.3291 13.6504 15.1416C13.582 14.9541 13.5479 14.749 13.5479 14.5264V14.4033C13.5479 14.1455 13.5859 13.916 13.6621 13.7148C13.7383 13.5117 13.8418 13.3398 13.9727 13.1992C14.1035 13.0586 14.252 12.9521 14.418 12.8799C14.584 12.8076 14.7559 12.7715 14.9336 12.7715C15.1602 12.7715 15.3555 12.8105 15.5195 12.8887C15.6855 12.9668 15.8213 13.0762 15.9268 13.2168C16.0322 13.3555 16.1104 13.5195 16.1611 13.709C16.2119 13.8965 16.2373 14.1016 16.2373 14.3242V14.5674H13.8701V14.125H15.6953V14.084C15.6875 13.9434 15.6582 13.8066 15.6074 13.6738C15.5586 13.541 15.4805 13.4316 15.373 13.3457C15.2656 13.2598 15.1191 13.2168 14.9336 13.2168C14.8105 13.2168 14.6973 13.2432 14.5938 13.2959C14.4902 13.3467 14.4014 13.4229 14.3271 13.5244C14.2529 13.626 14.1953 13.75 14.1543 13.8965C14.1133 14.043 14.0928 14.2119 14.0928 14.4033V14.5264C14.0928 14.6768 14.1133 14.8184 14.1543 14.9512C14.1973 15.082 14.2588 15.1973 14.3389 15.2969C14.4209 15.3965 14.5195 15.4746 14.6348 15.5312C14.752 15.5879 14.8848 15.6162 15.0332 15.6162C15.2246 15.6162 15.3867 15.5771 15.5195 15.499C15.6523 15.4209 15.7686 15.3164 15.8682 15.1855L16.1963 15.4463C16.1279 15.5498 16.041 15.6484 15.9355 15.7422C15.8301 15.8359 15.7002 15.9121 15.5459 15.9707C15.3936 16.0293 15.2129 16.0586 15.0039 16.0586Z" fill="white"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_i_6_67" x="2" y="1.5" width="16.5" height="16.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.5" dy="-0.5"/>
<feGaussianBlur stdDeviation="0.25"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_67"/>
</filter>
<filter id="filter1_di_6_67" x="10.8401" y="3.09009" width="6.15991" height="6.15991" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_67"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_67" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_67"/>
</filter>
<filter id="filter2_di_6_67" x="2.03809" y="10.7344" width="15.1992" height="7.32422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_67"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_67" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_67"/>
</filter>
<linearGradient id="paint0_linear_6_67" x1="10" y1="2" x2="10" y2="18" gradientUnits="userSpaceOnUse">
<stop stop-color="#E168FF"/>
<stop offset="1" stop-color="#C848E9"/>
</linearGradient>
<clipPath id="clip0_6_67">
<rect x="2" y="2" width="16" height="16" rx="1" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

View file

@ -1,74 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_97)">
<g filter="url(#filter0_i_6_97)">
<g clip-path="url(#clip1_6_97)">
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="url(#paint0_linear_6_97)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.875 0.125H18.125V1.875H19.875V0.125ZM19.875 2.125H18.125V3.875H19.875V2.125ZM19.875 4.125H18.125V5.875H19.875V4.125ZM19.875 6.125H18.125V7.875H19.875V6.125ZM19.875 8.125H18.125V9.875H19.875V8.125ZM19.875 10.125H18.125V11.875H19.875V10.125ZM19.875 12.125H18.125V13.875H19.875V12.125ZM19.875 14.125H18.125V15.875H19.875V14.125ZM19.875 16.125H18.125V17.875H19.875V16.125ZM19.875 18.125H18.125V19.875H19.875V18.125ZM20 20V20.125H4.76837e-07V20H-0.125V-5.21541e-07L4.76837e-07 -5.1409e-07V-0.125H20V2.92063e-06H20.125V20H20ZM0.125 0.125L0.125 1.875H1.87499L1.87499 0.125H0.125ZM2.12499 0.125V1.875H3.87499V0.125H2.12499ZM4.12499 0.125V1.875H5.87499V0.125H4.12499ZM6.12499 0.125V1.875H7.87499V0.125H6.12499ZM8.12499 0.125V1.875H9.87499V0.125H8.12499ZM10.125 0.125V1.875H11.875V0.125H10.125ZM12.125 0.125V1.875H13.875V0.125H12.125ZM14.125 0.125V1.875H15.875V0.125H14.125ZM16.125 0.125V1.875H17.875V0.125H16.125ZM17.875 2.125H16.125V3.875H17.875V2.125ZM17.875 4.125H16.125V5.875H17.875V4.125ZM17.875 6.125H16.125V7.875H17.875V6.125ZM17.875 8.125H16.125V9.875H17.875V8.125ZM17.875 10.125H16.125V11.875H17.875V10.125ZM17.875 12.125H16.125V13.875H17.875V12.125ZM17.875 14.125H16.125V15.875H17.875V14.125ZM17.875 16.125H16.125V17.875H17.875V16.125ZM17.875 18.125H16.125V19.875H17.875V18.125ZM15.875 19.875V18.125H14.125V19.875H15.875ZM13.875 19.875V18.125H12.125V19.875H13.875ZM11.875 19.875V18.125H10.125V19.875H11.875ZM9.87499 19.875V18.125H8.12499V19.875H9.87499ZM7.87499 19.875V18.125H6.12499V19.875H7.87499ZM5.87499 19.875V18.125H4.12499V19.875H5.87499ZM3.87499 19.875V18.125H2.12499V19.875H3.87499ZM1.87499 19.875L1.87499 18.125H0.124999L0.124999 19.875H1.87499ZM0.124999 17.875H1.87499V16.125H0.124999L0.124999 17.875ZM0.124999 15.875H1.87499L1.87499 14.125H0.124999L0.124999 15.875ZM0.124999 13.875H1.87499L1.87499 12.125H0.124999L0.124999 13.875ZM0.124999 11.875H1.87499V10.125H0.125L0.124999 11.875ZM0.125 9.875H1.87499V8.125H0.125L0.125 9.875ZM0.125 7.875H1.87499L1.87499 6.125H0.125L0.125 7.875ZM0.125 5.875H1.87499L1.87499 4.125H0.125L0.125 5.875ZM0.125 3.875H1.87499V2.125H0.125L0.125 3.875ZM2.12499 2.125L2.12499 3.875H3.87499L3.87499 2.125H2.12499ZM4.12499 2.125V3.875H5.87499V2.125H4.12499ZM6.12499 2.125V3.875H7.87499V2.125H6.12499ZM8.12499 2.125V3.875H9.87499V2.125H8.12499ZM10.125 2.125V3.875H11.875V2.125H10.125ZM12.125 2.125V3.875H13.875V2.125H12.125ZM14.125 2.125V3.875H15.875V2.125H14.125ZM15.875 4.125H14.125V5.875H15.875V4.125ZM15.875 6.125H14.125V7.875H15.875V6.125ZM15.875 8.125H14.125V9.875H15.875V8.125ZM15.875 10.125H14.125V11.875H15.875V10.125ZM15.875 12.125H14.125V13.875H15.875V12.125ZM15.875 14.125H14.125V15.875H15.875V14.125ZM15.875 16.125H14.125V17.875H15.875V16.125ZM13.875 17.875V16.125H12.125V17.875H13.875ZM11.875 17.875V16.125H10.125V17.875H11.875ZM9.87499 17.875V16.125H8.12499V17.875H9.87499ZM7.87499 17.875V16.125H6.12499V17.875H7.87499ZM5.87499 17.875V16.125H4.12499V17.875H5.87499ZM3.87499 17.875L3.87499 16.125H2.12499L2.12499 17.875H3.87499ZM2.12499 15.875H3.87499V14.125H2.12499V15.875ZM2.12499 13.875H3.87499L3.87499 12.125H2.12499L2.12499 13.875ZM2.12499 11.875H3.87499V10.125H2.12499V11.875ZM2.12499 9.875H3.87499V8.125H2.12499V9.875ZM2.12499 7.875H3.87499L3.87499 6.125H2.12499L2.12499 7.875ZM2.12499 5.875H3.87499V4.125H2.12499V5.875ZM4.12499 4.125L4.12499 5.875H5.87499L5.87499 4.125H4.12499ZM6.12499 4.125L6.12499 5.875H7.87499L7.87499 4.125H6.12499ZM8.12499 4.125V5.875H9.87499V4.125H8.12499ZM10.125 4.125V5.875H11.875V4.125H10.125ZM12.125 4.125V5.875H13.875V4.125H12.125ZM13.875 6.125H12.125V7.875H13.875V6.125ZM13.875 8.125H12.125V9.875H13.875V8.125ZM13.875 10.125H12.125V11.875H13.875V10.125ZM13.875 12.125H12.125V13.875H13.875V12.125ZM13.875 14.125H12.125V15.875H13.875V14.125ZM11.875 15.875V14.125H10.125V15.875H11.875ZM9.87499 15.875V14.125H8.12499V15.875H9.87499ZM7.87499 15.875L7.87499 14.125H6.12499L6.12499 15.875H7.87499ZM5.87499 15.875L5.87499 14.125H4.12499L4.12499 15.875H5.87499ZM4.12499 13.875H5.87499V12.125H4.12499V13.875ZM4.12499 11.875H5.87499V10.125H4.12499V11.875ZM4.12499 9.875H5.87499V8.125H4.12499V9.875ZM4.12499 7.875H5.87499V6.125H4.12499V7.875ZM6.12499 6.125V7.875H7.87499V6.125H6.12499ZM8.12499 6.125V7.875H9.87499V6.125H8.12499ZM10.125 6.125V7.875H11.875V6.125H10.125ZM11.875 8.125H10.125V9.875H11.875V8.125ZM11.875 10.125H10.125V11.875H11.875V10.125ZM11.875 12.125H10.125V13.875H11.875V12.125ZM9.87499 13.875V12.125H8.12499V13.875H9.87499ZM7.87499 13.875V12.125H6.12499V13.875H7.87499ZM6.12499 11.875H7.87499V10.125H6.12499V11.875ZM6.12499 9.875H7.87499V8.125H6.12499V9.875ZM8.12499 8.125V9.875H9.87499V8.125H8.12499ZM9.87499 10.125H8.12499V11.875H9.87499V10.125Z" fill="url(#paint1_linear_6_97)" fill-opacity="0.05"/>
<g filter="url(#filter1_di_6_97)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2306 7.25129C15.0975 7.48357 15.9885 6.96913 16.2208 6.10224C16.2693 5.92123 16.2852 5.73915 16.2722 5.56225C16.828 5.94013 17.1117 6.64112 16.9279 7.32699C16.6956 8.19387 15.8046 8.70832 14.9377 8.47604C14.2518 8.29226 13.7866 7.69611 13.7372 7.02583C13.8839 7.12556 14.0496 7.20279 14.2306 7.25129Z" fill="white"/>
</g>
<g filter="url(#filter2_di_6_97)">
<path d="M5.30613 14.4778L5.18709 14.922L3.12695 14.37L3.24599 13.9257L5.30613 14.4778ZM4.33851 10.2786L3.23448 14.3988L2.68832 14.2525L3.79234 10.1322L4.33851 10.2786ZM7.67167 14.8295L8.30254 12.4751L8.82889 12.6161L8.00846 15.678L7.50757 15.5438L7.67167 14.8295ZM7.9436 14.2109L8.16301 14.2636C8.10842 14.4673 8.03617 14.6502 7.94627 14.8121C7.85877 14.9727 7.75092 15.1035 7.62272 15.2046C7.49452 15.3058 7.34441 15.3717 7.17239 15.4024C7.00087 15.4313 6.80569 15.4164 6.58685 15.3578C6.43781 15.3178 6.30685 15.2595 6.19396 15.1827C6.08296 15.1065 5.99526 15.0112 5.93085 14.8969C5.86645 14.7826 5.82918 14.6462 5.81906 14.4878C5.81081 14.3299 5.83399 14.1491 5.88859 13.9453L6.41785 11.9701L6.94138 12.1104L6.41059 14.0913C6.37369 14.229 6.3582 14.3472 6.36412 14.4458C6.37244 14.5431 6.39593 14.6252 6.4346 14.6922C6.47566 14.7578 6.52642 14.8098 6.58689 14.8482C6.64925 14.8872 6.71627 14.9163 6.78796 14.9355C7.01058 14.9951 7.19835 14.9999 7.35127 14.9499C7.5047 14.898 7.62917 14.807 7.72469 14.6769C7.8226 14.5454 7.89557 14.3901 7.9436 14.2109ZM9.97528 13.6239L9.33 16.0321L8.80648 15.8919L9.62691 12.8299L10.1221 12.9626L9.97528 13.6239ZM9.64679 14.3518L9.43117 14.2849C9.48917 14.076 9.57211 13.891 9.68 13.7298C9.78839 13.5668 9.91499 13.4339 10.0598 13.3311C10.2046 13.2284 10.3621 13.1614 10.5322 13.1301C10.7047 13.0975 10.8834 13.106 11.0683 13.1555C11.2193 13.1959 11.3495 13.2531 11.4591 13.327C11.5693 13.3989 11.6549 13.4906 11.716 13.602C11.7791 13.714 11.8143 13.8467 11.8216 14.0004C11.8295 14.1521 11.8064 14.3289 11.7523 14.5308L11.2147 16.5371L10.6883 16.3961L11.2275 14.3841C11.2704 14.2237 11.2812 14.0891 11.2598 13.9802C11.239 13.8695 11.1891 13.7803 11.1101 13.7127C11.0317 13.6431 10.9265 13.5906 10.7944 13.5553C10.6642 13.5204 10.538 13.5159 10.4158 13.5418C10.2955 13.5682 10.1838 13.6191 10.0807 13.6946C9.97944 13.7706 9.89045 13.8651 9.81369 13.9779C9.73933 14.0895 9.6837 14.2141 9.64679 14.3518ZM13.2653 17.1473C13.0521 17.0901 12.8684 17.0025 12.714 16.8843C12.5621 16.7647 12.4415 16.6222 12.3524 16.4568C12.2651 16.2919 12.2128 16.11 12.1953 15.9112C12.1778 15.7124 12.1978 15.5055 12.2555 15.2904L12.2873 15.1715C12.354 14.9225 12.4502 14.7107 12.5759 14.5361C12.702 14.3596 12.8465 14.2204 13.0093 14.1184C13.1721 14.0164 13.343 13.952 13.5221 13.9252C13.7011 13.8984 13.8765 13.908 14.0482 13.954C14.267 14.0126 14.4456 14.1009 14.5838 14.2188C14.724 14.3372 14.8268 14.478 14.8922 14.6411C14.9582 14.8024 14.9912 14.9811 14.9912 15.1772C14.9918 15.3715 14.9632 15.5761 14.9056 15.7912L14.8426 16.0261L12.5561 15.4134L12.6706 14.9861L14.4336 15.4585L14.4442 15.4189C14.4731 15.281 14.4802 15.1414 14.4655 15C14.4527 14.859 14.4056 14.7332 14.324 14.6223C14.2425 14.5115 14.1121 14.4321 13.9329 14.3841C13.8141 14.3522 13.6978 14.3484 13.5842 14.3725C13.471 14.3948 13.3655 14.4454 13.2675 14.5243C13.1695 14.6032 13.0818 14.708 13.0043 14.8389C12.9267 14.9698 12.8632 15.1277 12.8137 15.3126L12.7818 15.4314C12.7429 15.5767 12.726 15.7188 12.7313 15.8577C12.7389 15.9952 12.7685 16.1224 12.8201 16.2394C12.8735 16.3568 12.9486 16.4578 13.0452 16.5423C13.1438 16.6274 13.2647 16.6891 13.4081 16.7275C13.593 16.7771 13.7597 16.7813 13.9082 16.7402C14.0567 16.6991 14.196 16.6283 14.3261 16.5276L14.5756 16.8644C14.4827 16.9467 14.3732 17.0195 14.2471 17.0828C14.121 17.146 13.9758 17.186 13.8116 17.2026C13.6493 17.2198 13.4672 17.2013 13.2653 17.1473Z" fill="white"/>
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_i_6_97" x="0.202041" y="-0.297958" width="20.0959" height="20.0959" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.5" dy="-0.5"/>
<feGaussianBlur stdDeviation="0.25"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_97"/>
</filter>
<filter id="filter1_di_6_97" x="11.7372" y="4.56225" width="6.24645" height="5.96956" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_97"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_97" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_97"/>
</filter>
<filter id="filter2_di_6_97" x="0.673153" y="9.13222" width="15.9027" height="10.3343" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_97"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_97" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_97"/>
</filter>
<linearGradient id="paint0_linear_6_97" x1="12.3431" y1="0.202042" x2="12.3431" y2="16.202" gradientUnits="userSpaceOnUse">
<stop stop-color="#E168FF"/>
<stop offset="1" stop-color="#C848E9"/>
</linearGradient>
<linearGradient id="paint1_linear_6_97" x1="10" y1="-0.125" x2="10" y2="20.125" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.5"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<clipPath id="clip0_6_97">
<rect width="20" height="20" fill="white"/>
</clipPath>
<clipPath id="clip1_6_97">
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

View file

@ -1,69 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_77)">
<g filter="url(#filter0_i_6_77)">
<g clip-path="url(#clip1_6_77)">
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="url(#paint0_linear_6_77)"/>
<g filter="url(#filter1_di_6_77)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2306 7.25129C15.0975 7.48357 15.9885 6.96913 16.2208 6.10224C16.2693 5.92123 16.2852 5.73915 16.2722 5.56225C16.828 5.94013 17.1117 6.64112 16.9279 7.32699C16.6956 8.19387 15.8046 8.70832 14.9377 8.47604C14.2518 8.29226 13.7866 7.69611 13.7372 7.02583C13.8839 7.12556 14.0496 7.20279 14.2306 7.25129Z" fill="white"/>
</g>
<g filter="url(#filter2_di_6_77)">
<path d="M5.30613 14.4778L5.18709 14.922L3.12695 14.37L3.24599 13.9257L5.30613 14.4778ZM4.33851 10.2786L3.23448 14.3988L2.68832 14.2525L3.79234 10.1322L4.33851 10.2786ZM7.67167 14.8295L8.30254 12.4751L8.82889 12.6161L8.00846 15.678L7.50757 15.5438L7.67167 14.8295ZM7.9436 14.2109L8.16301 14.2636C8.10842 14.4673 8.03617 14.6502 7.94627 14.8121C7.85877 14.9727 7.75092 15.1035 7.62272 15.2046C7.49452 15.3058 7.34441 15.3717 7.17239 15.4024C7.00087 15.4313 6.80569 15.4164 6.58685 15.3578C6.43781 15.3178 6.30685 15.2595 6.19396 15.1827C6.08296 15.1065 5.99526 15.0112 5.93085 14.8969C5.86645 14.7826 5.82918 14.6462 5.81906 14.4878C5.81081 14.3299 5.83399 14.1491 5.88859 13.9453L6.41785 11.9701L6.94138 12.1104L6.41059 14.0913C6.37369 14.229 6.3582 14.3472 6.36412 14.4458C6.37244 14.5431 6.39593 14.6252 6.4346 14.6922C6.47566 14.7578 6.52642 14.8098 6.58689 14.8482C6.64925 14.8872 6.71627 14.9163 6.78796 14.9355C7.01058 14.9951 7.19835 14.9999 7.35127 14.9499C7.5047 14.898 7.62917 14.807 7.72469 14.6769C7.8226 14.5454 7.89557 14.3901 7.9436 14.2109ZM9.97528 13.6239L9.33 16.0321L8.80648 15.8919L9.62691 12.8299L10.1221 12.9626L9.97528 13.6239ZM9.64679 14.3518L9.43117 14.2849C9.48917 14.076 9.57211 13.891 9.68 13.7298C9.78839 13.5668 9.91499 13.4339 10.0598 13.3311C10.2046 13.2284 10.3621 13.1614 10.5322 13.1301C10.7047 13.0975 10.8834 13.106 11.0683 13.1555C11.2193 13.1959 11.3495 13.2531 11.4591 13.327C11.5693 13.3989 11.6549 13.4906 11.716 13.602C11.7791 13.714 11.8143 13.8467 11.8216 14.0004C11.8295 14.1521 11.8064 14.3289 11.7523 14.5308L11.2147 16.5371L10.6883 16.3961L11.2275 14.3841C11.2704 14.2237 11.2812 14.0891 11.2598 13.9802C11.239 13.8695 11.1891 13.7803 11.1101 13.7127C11.0317 13.6431 10.9265 13.5906 10.7944 13.5553C10.6642 13.5204 10.538 13.5159 10.4158 13.5418C10.2955 13.5682 10.1838 13.6191 10.0807 13.6946C9.97944 13.7706 9.89045 13.8651 9.81369 13.9779C9.73933 14.0895 9.6837 14.2141 9.64679 14.3518ZM13.2653 17.1473C13.0521 17.0901 12.8684 17.0025 12.714 16.8843C12.5621 16.7647 12.4415 16.6222 12.3524 16.4568C12.2651 16.2919 12.2128 16.11 12.1953 15.9112C12.1778 15.7124 12.1978 15.5055 12.2555 15.2904L12.2873 15.1715C12.354 14.9225 12.4502 14.7107 12.5759 14.5361C12.702 14.3596 12.8465 14.2204 13.0093 14.1184C13.1721 14.0164 13.343 13.952 13.5221 13.9252C13.7011 13.8984 13.8765 13.908 14.0482 13.954C14.267 14.0126 14.4456 14.1009 14.5838 14.2188C14.724 14.3372 14.8268 14.478 14.8922 14.6411C14.9582 14.8024 14.9912 14.9811 14.9912 15.1772C14.9918 15.3715 14.9632 15.5761 14.9056 15.7912L14.8426 16.0261L12.5561 15.4134L12.6706 14.9861L14.4336 15.4585L14.4442 15.4189C14.4731 15.281 14.4802 15.1414 14.4655 15C14.4527 14.859 14.4056 14.7332 14.324 14.6223C14.2425 14.5115 14.1121 14.4321 13.9329 14.3841C13.8141 14.3522 13.6978 14.3484 13.5842 14.3725C13.471 14.3948 13.3655 14.4454 13.2675 14.5243C13.1695 14.6032 13.0818 14.708 13.0043 14.8389C12.9267 14.9698 12.8632 15.1277 12.8137 15.3126L12.7818 15.4314C12.7429 15.5767 12.726 15.7188 12.7313 15.8577C12.7389 15.9952 12.7685 16.1224 12.8201 16.2394C12.8735 16.3568 12.9486 16.4578 13.0452 16.5423C13.1438 16.6274 13.2647 16.6891 13.4081 16.7275C13.593 16.7771 13.7597 16.7813 13.9082 16.7402C14.0567 16.6991 14.196 16.6283 14.3261 16.5276L14.5756 16.8644C14.4827 16.9467 14.3732 17.0195 14.2471 17.0828C14.121 17.146 13.9758 17.186 13.8116 17.2026C13.6493 17.2198 13.4672 17.2013 13.2653 17.1473Z" fill="white"/>
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_i_6_77" x="0.202041" y="-0.297958" width="20.0959" height="20.0959" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.5" dy="-0.5"/>
<feGaussianBlur stdDeviation="0.25"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_77"/>
</filter>
<filter id="filter1_di_6_77" x="11.7372" y="4.56224" width="6.24645" height="5.96957" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_77"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_77" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_77"/>
</filter>
<filter id="filter2_di_6_77" x="0.673153" y="9.13222" width="15.9027" height="10.3343" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.5" dy="0.5"/>
<feGaussianBlur stdDeviation="0.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_77"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_77" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.25" dy="-0.25"/>
<feGaussianBlur stdDeviation="0.375"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_77"/>
</filter>
<linearGradient id="paint0_linear_6_77" x1="12.3431" y1="0.202042" x2="12.3431" y2="16.202" gradientUnits="userSpaceOnUse">
<stop stop-color="#E168FF"/>
<stop offset="1" stop-color="#C848E9"/>
</linearGradient>
<clipPath id="clip0_6_77">
<rect width="20" height="20" fill="white"/>
</clipPath>
<clipPath id="clip1_6_77">
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -1,29 +0,0 @@
[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

@ -1,61 +0,0 @@
use super::*;
pub(crate) trait DomValueExt {
fn variant_name(&self) -> Option<&'static str>;
}
impl DomValueExt for DomType {
fn variant_name(&self) -> Option<&'static str> {
#[allow(clippy::enum_glob_use)]
use DomType::*;
Some(match self {
Attributes => "Attributes",
Axes => "Axes",
BinaryString => "BinaryString",
Bool => "Bool",
BrickColor => "BrickColor",
CFrame => "CFrame",
Color3 => "Color3",
Color3uint8 => "Color3uint8",
ColorSequence => "ColorSequence",
Content => "Content",
ContentId => "ContentId",
Enum => "Enum",
EnumItem => "EnumItem",
Faces => "Faces",
Float32 => "Float32",
Float64 => "Float64",
Font => "Font",
Int32 => "Int32",
Int64 => "Int64",
MaterialColors => "MaterialColors",
NumberRange => "NumberRange",
NumberSequence => "NumberSequence",
PhysicalProperties => "PhysicalProperties",
Ray => "Ray",
Rect => "Rect",
Ref => "Ref",
Region3 => "Region3",
Region3int16 => "Region3int16",
SharedString => "SharedString",
String => "String",
Tags => "Tags",
UDim => "UDim",
UDim2 => "UDim2",
UniqueId => "UniqueId",
Vector2 => "Vector2",
Vector2int16 => "Vector2int16",
Vector3 => "Vector3",
Vector3int16 => "Vector3int16",
OptionalCFrame => "OptionalCFrame",
SecurityCapabilities => "SecurityCapabilities",
_ => return None,
})
}
}
impl DomValueExt for DomValue {
fn variant_name(&self) -> Option<&'static str> {
self.ty().variant_name()
}
}

View file

@ -1,493 +0,0 @@
#![allow(clippy::items_after_statements)]
use core::fmt;
use std::ops;
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 lune_utils::TableBuilder;
use crate::exports::LuaExportsTable;
use super::{super::*, Vector3};
/**
An implementation of the [CFrame](https://create.roblox.com/docs/reference/engine/datatypes/CFrame)
Roblox datatype, backed by [`glam::Mat4`].
This implements all documented properties, methods &
constructors of the `CFrame` class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CFrame(pub Mat4);
impl CFrame {
pub const IDENTITY: Self = Self(Mat4::IDENTITY);
fn position(&self) -> Vec3 {
self.0.w_axis.truncate()
}
fn orientation(&self) -> Mat3 {
Mat3::from_cols(
self.0.x_axis.truncate(),
self.0.y_axis.truncate(),
self.0.z_axis.truncate(),
)
}
fn inverse(&self) -> Self {
Self(self.0.inverse())
}
}
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)))
};
let cframe_from_axis_angle =
|_, (v, r): (LuaUserDataRef<Vector3>, f32)| Ok(CFrame(Mat4::from_axis_angle(v.0, r)));
let cframe_from_euler_angles_xyz = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
};
let cframe_from_euler_angles_yxz = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
};
let cframe_from_matrix = |_,
(pos, rx, ry, rz): (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
Option<LuaUserDataRef<Vector3>>,
)| {
Ok(CFrame(Mat4::from_cols(
rx.0.extend(0.0),
ry.0.extend(0.0),
rz.map_or_else(|| rx.0.cross(ry.0).normalize(), |r| r.0)
.extend(0.0),
pos.0.extend(1.0),
)))
};
let cframe_from_orientation = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
};
let cframe_look_at = |_,
(from, to, up): (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
Option<LuaUserDataRef<Vector3>>,
)| {
Ok(CFrame(look_at(
from.0,
to.0,
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
)))
};
// Dynamic args constructor
type ArgsPos<'lua> = LuaUserDataRef<'lua, Vector3>;
type ArgsLook<'lua> = (
LuaUserDataRef<'lua, Vector3>,
LuaUserDataRef<'lua, Vector3>,
Option<LuaUserDataRef<'lua, Vector3>>,
);
type ArgsPosXYZ = (f32, f32, f32);
type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32);
type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32);
let cframe_new = |lua, args: LuaMultiValue| match args.len() {
0 => Ok(CFrame(Mat4::IDENTITY)),
1 => match ArgsPos::from_lua_multi(args, lua) {
Ok(pos) => Ok(CFrame(Mat4::from_translation(pos.0))),
Err(err) => Err(err),
},
3 => {
if let Ok((from, to, up)) = ArgsLook::from_lua_multi(args.clone(), lua) {
Ok(CFrame(look_at(
from.0,
to.0,
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
)))
} else if let Ok((x, y, z)) = ArgsPosXYZ::from_lua_multi(args, lua) {
Ok(CFrame(Mat4::from_translation(Vec3::new(x, y, z))))
} else {
// TODO: Make this error message better
Err(LuaError::RuntimeError(
"Invalid arguments to constructor".to_string(),
))
}
}
7 => match ArgsPosXYZQuat::from_lua_multi(args, lua) {
Ok((x, y, z, qx, qy, qz, qw)) => Ok(CFrame(Mat4::from_rotation_translation(
Quat::from_array([qx, qy, qz, qw]),
Vec3::new(x, y, z),
))),
Err(err) => Err(err),
},
12 => match ArgsMatrix::from_lua_multi(args, lua) {
Ok((x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)) => {
Ok(CFrame(Mat4::from_cols_array_2d(&[
[r00, r10, r20, 0.0],
[r01, r11, r21, 0.0],
[r02, r12, r22, 0.0],
[x, y, z, 1.0],
])))
}
Err(err) => Err(err),
},
_ => Err(LuaError::RuntimeError(format!(
"Invalid number of arguments: expected 0, 1, 3, 7, or 12, got {}",
args.len()
))),
};
TableBuilder::new(lua)?
.with_function("Angles", cframe_angles)?
.with_value("identity", CFrame(Mat4::IDENTITY))?
.with_function("fromAxisAngle", cframe_from_axis_angle)?
.with_function("fromEulerAnglesXYZ", cframe_from_euler_angles_xyz)?
.with_function("fromEulerAnglesYXZ", cframe_from_euler_angles_yxz)?
.with_function("fromMatrix", cframe_from_matrix)?
.with_function("fromOrientation", cframe_from_orientation)?
.with_function("lookAt", cframe_look_at)?
.with_function("new", cframe_new)?
.build_readonly()
}
}
impl LuaUserData for CFrame {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Position", |_, this| Ok(Vector3(this.position())));
fields.add_field_method_get("Rotation", |_, this| {
Ok(CFrame(Mat4::from_cols(
this.0.x_axis,
this.0.y_axis,
this.0.z_axis,
Vec3::ZERO.extend(1.0),
)))
});
fields.add_field_method_get("X", |_, this| Ok(this.position().x));
fields.add_field_method_get("Y", |_, this| Ok(this.position().y));
fields.add_field_method_get("Z", |_, this| Ok(this.position().z));
fields.add_field_method_get("XVector", |_, this| Ok(Vector3(this.orientation().x_axis)));
fields.add_field_method_get("YVector", |_, this| Ok(Vector3(this.orientation().y_axis)));
fields.add_field_method_get("ZVector", |_, this| Ok(Vector3(this.orientation().z_axis)));
fields.add_field_method_get("RightVector", |_, this| {
Ok(Vector3(this.orientation().x_axis))
});
fields.add_field_method_get("UpVector", |_, this| Ok(Vector3(this.orientation().y_axis)));
fields.add_field_method_get("LookVector", |_, this| {
Ok(Vector3(-this.orientation().z_axis))
});
}
#[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()));
methods.add_method(
"Lerp",
|_, this, (goal, alpha): (LuaUserDataRef<CFrame>, f32)| {
let quat_this = Quat::from_mat4(&this.0);
let quat_goal = Quat::from_mat4(&goal.0);
let translation = this
.0
.w_axis
.truncate()
.lerp(goal.0.w_axis.truncate(), alpha);
let rotation = quat_this.slerp(quat_goal, alpha);
Ok(CFrame(Mat4::from_rotation_translation(
rotation,
translation,
)))
},
);
methods.add_method("Orthonormalize", |_, this, ()| {
let rotation = Quat::from_mat4(&this.0);
let translation = this.0.w_axis.truncate();
Ok(CFrame(Mat4::from_rotation_translation(
rotation.normalize(),
translation,
)))
});
methods.add_method(
"ToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
Ok(rhs
.into_iter()
.map(|cf| *this * *cf)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"ToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
let inverse = this.inverse();
Ok(rhs
.into_iter()
.map(|cf| inverse * *cf)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"PointToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
Ok(rhs
.into_iter()
.map(|v3| *this * *v3)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"PointToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
let inverse = this.inverse();
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(rhs
.into_iter()
.map(|v3| result * *v3)
.collect::<Variadic<_>>())
},
);
methods.add_method(
"VectorToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
let inverse = this.inverse();
let result = inverse - Vector3(inverse.position());
Ok(rhs
.into_iter()
.map(|v3| result * *v3)
.collect::<Variadic<_>>())
},
);
#[rustfmt::skip]
methods.add_method("GetComponents", |_, this, ()| {
let pos = this.position();
let transposed = this.orientation().transpose();
Ok((
pos.x, pos.y, pos.z,
transposed.x_axis.x, transposed.x_axis.y, transposed.x_axis.z,
transposed.y_axis.x, transposed.y_axis.y, transposed.y_axis.z,
transposed.z_axis.x, transposed.z_axis.y, transposed.z_axis.z,
))
});
methods.add_method("ToEulerAnglesXYZ", |_, this, ()| {
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::XYZ))
});
methods.add_method("ToEulerAnglesYXZ", |_, this, ()| {
let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);
Ok((rx, ry, rz))
});
methods.add_method("ToOrientation", |_, this, ()| {
let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);
Ok((rx, ry, rz))
});
methods.add_method("ToAxisAngle", |_, this, ()| {
let (axis, angle) = Quat::from_mat4(&this.0).to_axis_angle();
Ok((Vector3(axis), angle))
});
// Metamethods
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_meta_method(LuaMetaMethod::Mul, |lua, this, rhs: LuaValue| {
if let LuaValue::UserData(ud) = &rhs {
if let Ok(cf) = ud.borrow::<CFrame>() {
return lua.create_userdata(*this * *cf);
} else if let Ok(vec) = ud.borrow::<Vector3>() {
return lua.create_userdata(*this * *vec);
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: "userdata",
message: Some(format!(
"Expected CFrame or Vector3, got {}",
rhs.type_name()
)),
})
});
methods.add_meta_method(
LuaMetaMethod::Add,
|_, this, vec: LuaUserDataRef<Vector3>| Ok(*this + *vec),
);
methods.add_meta_method(
LuaMetaMethod::Sub,
|_, this, vec: LuaUserDataRef<Vector3>| Ok(*this - *vec),
);
}
}
impl fmt::Display for CFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pos = self.position();
let transposed = self.orientation().transpose();
write!(
f,
"{}, {}, {}, {}",
Vector3(pos),
Vector3(transposed.x_axis),
Vector3(transposed.y_axis),
Vector3(transposed.z_axis)
)
}
}
impl ops::Mul for CFrame {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
CFrame(self.0 * rhs.0)
}
}
impl ops::Mul<Vector3> for CFrame {
type Output = Vector3;
fn mul(self, rhs: Vector3) -> Self::Output {
Vector3(self.0.project_point3(rhs.0))
}
}
impl ops::Add<Vector3> for CFrame {
type Output = Self;
fn add(self, rhs: Vector3) -> Self::Output {
CFrame(Mat4::from_cols(
self.0.x_axis,
self.0.y_axis,
self.0.z_axis,
self.0.w_axis + rhs.0.extend(0.0),
))
}
}
impl ops::Sub<Vector3> for CFrame {
type Output = Self;
fn sub(self, rhs: Vector3) -> Self::Output {
CFrame(Mat4::from_cols(
self.0.x_axis,
self.0.y_axis,
self.0.z_axis,
self.0.w_axis - rhs.0.extend(0.0),
))
}
}
impl From<DomCFrame> for CFrame {
fn from(v: DomCFrame) -> Self {
let transposed = v.orientation.transpose();
CFrame(Mat4::from_cols(
Vector3::from(transposed.x).0.extend(0.0),
Vector3::from(transposed.y).0.extend(0.0),
Vector3::from(transposed.z).0.extend(0.0),
Vector3::from(v.position).0.extend(1.0),
))
}
}
impl From<CFrame> for DomCFrame {
fn from(v: CFrame) -> Self {
let transposed = v.orientation().transpose();
DomCFrame {
position: DomVector3::from(Vector3(v.position())),
orientation: DomMatrix3::new(
DomVector3::from(Vector3(transposed.x_axis)),
DomVector3::from(Vector3(transposed.y_axis)),
DomVector3::from(Vector3(transposed.z_axis)),
),
}
}
}
/**
Creates a matrix at the position `from`, looking towards `to`.
[`glam`] does provide functions such as [`look_at_lh`], [`look_at_rh`] and more but
they all create view matrices for camera transforms which is not what we want here.
*/
fn look_at(from: Vec3, to: Vec3, up: Vec3) -> Mat4 {
let dir = (to - from).normalize();
let xaxis = up.cross(dir).normalize();
let yaxis = dir.cross(xaxis).normalize();
Mat4::from_cols(
Vec3::new(xaxis.x, yaxis.x, dir.x).extend(0.0),
Vec3::new(xaxis.y, yaxis.y, dir.y).extend(0.0),
Vec3::new(xaxis.z, yaxis.z, dir.z).extend(0.0),
from.extend(1.0),
)
}
#[cfg(test)]
mod cframe_test {
use glam::{Mat4, Vec3};
use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};
use super::CFrame;
#[test]
fn dom_cframe_from_cframe() {
let dom_cframe = DomCFrame::new(
DomVector3::new(1.0, 2.0, 3.0),
DomMatrix3::new(
DomVector3::new(1.0, 2.0, 3.0),
DomVector3::new(1.0, 2.0, 3.0),
DomVector3::new(1.0, 2.0, 3.0),
),
);
let cframe = CFrame(Mat4::from_cols(
Vec3::new(1.0, 1.0, 1.0).extend(0.0),
Vec3::new(2.0, 2.0, 2.0).extend(0.0),
Vec3::new(3.0, 3.0, 3.0).extend(0.0),
Vec3::new(1.0, 2.0, 3.0).extend(1.0),
));
assert_eq!(CFrame::from(dom_cframe), cframe);
}
#[test]
fn cframe_from_dom_cframe() {
let cframe = CFrame(Mat4::from_cols(
Vec3::new(1.0, 2.0, 3.0).extend(0.0),
Vec3::new(1.0, 2.0, 3.0).extend(0.0),
Vec3::new(1.0, 2.0, 3.0).extend(0.0),
Vec3::new(1.0, 2.0, 3.0).extend(1.0),
));
let dom_cframe = DomCFrame::new(
DomVector3::new(1.0, 2.0, 3.0),
DomMatrix3::new(
DomVector3::new(1.0, 1.0, 1.0),
DomVector3::new(2.0, 2.0, 2.0),
DomVector3::new(3.0, 3.0, 3.0),
),
);
assert_eq!(DomCFrame::from(cframe), dom_cframe);
}
}

View file

@ -1,125 +0,0 @@
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::{
ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint,
};
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.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct ColorSequence {
pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
}
impl LuaExportsTable<'_> for ColorSequence {
const EXPORT_NAME: &'static str = "ColorSequence";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsColor<'lua> = LuaUserDataRef<'lua, Color3>;
type ArgsColors<'lua> = (LuaUserDataRef<'lua, Color3>, LuaUserDataRef<'lua, Color3>);
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, ColorSequenceKeypoint>>;
let color_sequence_new = |lua, args: LuaMultiValue| {
if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
Ok(ColorSequence {
keypoints: vec![
ColorSequenceKeypoint {
time: 0.0,
color: *color,
},
ColorSequenceKeypoint {
time: 1.0,
color: *color,
},
],
})
} else if let Ok((c0, c1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
Ok(ColorSequence {
keypoints: vec![
ColorSequenceKeypoint {
time: 0.0,
color: *c0,
},
ColorSequenceKeypoint {
time: 1.0,
color: *c1,
},
],
})
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
Ok(ColorSequence {
keypoints: keypoints.iter().map(|k| **k).collect(),
})
} else {
// FUTURE: Better error message here using given arg types
Err(LuaError::RuntimeError(
"Invalid arguments to constructor".to_string(),
))
}
};
TableBuilder::new(lua)?
.with_function("new", color_sequence_new)?
.build_readonly()
}
}
impl LuaUserData for ColorSequence {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
}
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);
}
}
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}, ")?;
} else {
write!(f, "{keypoint}")?;
}
}
Ok(())
}
}
impl From<DomColorSequence> for ColorSequence {
fn from(v: DomColorSequence) -> Self {
Self {
keypoints: v
.keypoints
.iter()
.copied()
.map(ColorSequenceKeypoint::from)
.collect(),
}
}
}
impl From<ColorSequence> for DomColorSequence {
fn from(v: ColorSequence) -> Self {
Self {
keypoints: v
.keypoints
.iter()
.copied()
.map(DomColorSequenceKeypoint::from)
.collect(),
}
}
}

View file

@ -1,120 +0,0 @@
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::{Content as DomContent, ContentType};
use lune_utils::TableBuilder;
use crate::{exports::LuaExportsTable, instance::Instance};
use super::{super::*, EnumItem};
/**
An implementation of the [Content](https://create.roblox.com/docs/reference/engine/datatypes/Content) Roblox datatype.
This implements all documented properties, methods & constructors of the Content type as of April 2025.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct Content(ContentType);
impl LuaExportsTable<'_> for Content {
const EXPORT_NAME: &'static str = "Content";
fn create_exports_table(lua: &'_ Lua) -> LuaResult<LuaTable<'_>> {
let from_uri = |_, uri: String| Ok(Self(ContentType::Uri(uri)));
let from_object = |_, obj: LuaUserDataRef<Instance>| {
let database = rbx_reflection_database::get();
let instance_descriptor = database
.classes
.get("Instance")
.expect("the reflection database should always have Instance in it");
let param_descriptor = database.classes.get(obj.get_class_name()).expect(
"you should not be able to construct an Instance that is not known to Lune",
);
if database.has_superclass(param_descriptor, instance_descriptor) {
Err(LuaError::runtime("the provided object is a descendant class of 'Instance', expected one that was only an 'Object'"))
} else {
Ok(Content(ContentType::Object(obj.dom_ref)))
}
};
TableBuilder::new(lua)?
.with_value("none", Content(ContentType::None))?
.with_function("fromUri", from_uri)?
.with_function("fromObject", from_object)?
.build_readonly()
}
}
impl LuaUserData for Content {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("SourceType", |_, this| {
let variant_name = match &this.0 {
ContentType::None => "None",
ContentType::Uri(_) => "Uri",
ContentType::Object(_) => "Object",
other => {
return Err(LuaError::runtime(format!(
"cannot get SourceType: unknown ContentType variant '{other:?}'"
)))
}
};
Ok(EnumItem::from_enum_name_and_name(
"ContentSourceType",
variant_name,
))
});
fields.add_field_method_get("Uri", |_, this| {
if let ContentType::Uri(uri) = &this.0 {
Ok(Some(uri.to_owned()))
} else {
Ok(None)
}
});
fields.add_field_method_get("Object", |_, this| {
if let ContentType::Object(referent) = &this.0 {
Ok(Instance::new_opt(*referent))
} else {
Ok(None)
}
});
}
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);
}
}
impl fmt::Display for Content {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Regardless of the actual content of the Content, Roblox just emits
// `Content` when casting it to a string. We do not do that.
write!(f, "Content(")?;
match &self.0 {
ContentType::None => write!(f, "None")?,
ContentType::Uri(uri) => write!(f, "Uri={uri}")?,
ContentType::Object(_) => write!(f, "Object")?,
other => write!(f, "UnknownType({other:?})")?,
}
write!(f, ")")
}
}
impl From<DomContent> for Content {
fn from(value: DomContent) -> Self {
Self(value.value().clone())
}
}
impl From<Content> for DomContent {
fn from(value: Content) -> Self {
match value.0 {
ContentType::None => Self::none(),
ContentType::Uri(uri) => Self::from_uri(uri),
ContentType::Object(referent) => Self::from_referent(referent),
other => unimplemented!("unknown variant of ContentType: {other:?}"),
}
}
}

View file

@ -1,129 +0,0 @@
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::{
NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint,
};
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.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct NumberSequence {
pub(crate) keypoints: Vec<NumberSequenceKeypoint>,
}
impl LuaExportsTable<'_> for NumberSequence {
const EXPORT_NAME: &'static str = "NumberSequence";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsColor = f32;
type ArgsColors = (f32, f32);
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, NumberSequenceKeypoint>>;
let number_sequence_new = |lua, args: LuaMultiValue| {
if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {
Ok(NumberSequence {
keypoints: vec![
NumberSequenceKeypoint {
time: 0.0,
value,
envelope: 0.0,
},
NumberSequenceKeypoint {
time: 1.0,
value,
envelope: 0.0,
},
],
})
} else if let Ok((v0, v1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
Ok(NumberSequence {
keypoints: vec![
NumberSequenceKeypoint {
time: 0.0,
value: v0,
envelope: 0.0,
},
NumberSequenceKeypoint {
time: 1.0,
value: v1,
envelope: 0.0,
},
],
})
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
Ok(NumberSequence {
keypoints: keypoints.iter().map(|k| **k).collect(),
})
} else {
// FUTURE: Better error message here using given arg types
Err(LuaError::RuntimeError(
"Invalid arguments to constructor".to_string(),
))
}
};
TableBuilder::new(lua)?
.with_function("new", number_sequence_new)?
.build_readonly()
}
}
impl LuaUserData for NumberSequence {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
}
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);
}
}
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}, ")?;
} else {
write!(f, "{keypoint}")?;
}
}
Ok(())
}
}
impl From<DomNumberSequence> for NumberSequence {
fn from(v: DomNumberSequence) -> Self {
Self {
keypoints: v
.keypoints
.iter()
.copied()
.map(NumberSequenceKeypoint::from)
.collect(),
}
}
}
impl From<NumberSequence> for DomNumberSequence {
fn from(v: NumberSequence) -> Self {
Self {
keypoints: v
.keypoints
.iter()
.copied()
.map(DomNumberSequenceKeypoint::from)
.collect(),
}
}
}

View file

@ -1,16 +0,0 @@
// HACK: We round to the nearest Very Small Decimal
// to reduce writing out floating point accumulation
// errors to files (mostly relevant for xml formats)
const ROUNDING: usize = 65_536; // 2 ^ 16
pub fn round_float_decimal(value: f32) -> f32 {
let place = ROUNDING as f32;
// Round only the fractional part, we do not want to
// lose any float precision in case a user for some
// reason has very very large float numbers in files
let whole = value.trunc();
let fract = (value.fract() * place).round() / place;
whole + fract
}

View file

@ -1,68 +0,0 @@
use mlua::prelude::*;
/**
Trait for any item that should be exported as part of the `roblox` built-in library.
This may be an enum or a struct that should export constants and/or constructs.
### Example usage
```rs
use mlua::prelude::*;
struct MyType(usize);
impl MyType {
pub fn new(n: usize) -> Self {
Self(n)
}
}
impl LuaExportsTable<'_> for MyType {
const EXPORT_NAME: &'static str = "MyType";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let my_type_new = |lua, n: Option<usize>| {
Self::new(n.unwrap_or_default())
};
TableBuilder::new(lua)?
.with_function("new", my_type_new)?
.build_readonly()
}
}
impl LuaUserData for MyType {
// ...
}
```
*/
pub trait LuaExportsTable<'lua> {
const EXPORT_NAME: &'static str;
fn create_exports_table(lua: &'lua Lua) -> LuaResult<LuaTable<'lua>>;
}
/**
Exports a single item that implements the [`LuaExportsTable`] trait.
Returns the name of the export, as well as the export table.
### Example usage
```rs
let lua: mlua::Lua::new();
let (name1, table1) = export::<Type1>(lua)?;
let (name2, table2) = export::<Type2>(lua)?;
```
*/
pub fn export<'lua, T>(lua: &'lua Lua) -> LuaResult<(&'static str, LuaValue<'lua>)>
where
T: LuaExportsTable<'lua>,
{
Ok((
T::EXPORT_NAME,
<T as LuaExportsTable>::create_exports_table(lua)?.into_lua(lua)?,
))
}

View file

@ -1,274 +0,0 @@
use std::{
borrow::Borrow,
collections::HashMap,
sync::{Arc, Mutex},
};
use mlua::{prelude::*, AppDataRef};
use thiserror::Error;
use super::Instance;
type InstanceRegistryMap = HashMap<String, HashMap<String, LuaRegistryKey>>;
#[derive(Debug, Clone, Error)]
pub enum InstanceRegistryError {
#[error("class name '{0}' is not valid")]
InvalidClassName(String),
#[error("class '{class_name}' already registered method '{method_name}'")]
MethodAlreadyExists {
class_name: String,
method_name: String,
},
#[error("class '{class_name}' already registered property '{property_name}'")]
PropertyAlreadyExists {
class_name: String,
property_name: String,
},
}
#[derive(Debug, Clone)]
pub struct InstanceRegistry {
getters: Arc<Mutex<InstanceRegistryMap>>,
setters: Arc<Mutex<InstanceRegistryMap>>,
methods: Arc<Mutex<InstanceRegistryMap>>,
}
impl InstanceRegistry {
// NOTE: We lazily create the instance registry instead
// of always creating it together with the roblox builtin
// since it is less commonly used and it simplifies some app
// data borrowing relationship problems we'd otherwise have
fn get_or_create(lua: &Lua) -> AppDataRef<'_, Self> {
if lua.app_data_ref::<Self>().is_none() {
lua.set_app_data(Self {
getters: Arc::new(Mutex::new(HashMap::new())),
setters: Arc::new(Mutex::new(HashMap::new())),
methods: Arc::new(Mutex::new(HashMap::new())),
});
}
lua.app_data_ref::<Self>()
.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,
method_name: &str,
method: LuaFunction<'lua>,
) -> Result<(), InstanceRegistryError> {
let registry = Self::get_or_create(lua);
let mut methods = registry
.methods
.lock()
.expect("Failed to lock instance registry methods");
let class_methods = methods.entry(class_name.to_string()).or_default();
if class_methods.contains_key(method_name) {
return Err(InstanceRegistryError::MethodAlreadyExists {
class_name: class_name.to_string(),
method_name: method_name.to_string(),
});
}
let key = lua
.create_registry_value(method)
.expect("Failed to store method in lua registry");
class_methods.insert(method_name.to_string(), key);
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,
property_name: &str,
property_getter: LuaFunction<'lua>,
) -> Result<(), InstanceRegistryError> {
let registry = Self::get_or_create(lua);
let mut getters = registry
.getters
.lock()
.expect("Failed to lock instance registry getters");
let class_getters = getters.entry(class_name.to_string()).or_default();
if class_getters.contains_key(property_name) {
return Err(InstanceRegistryError::PropertyAlreadyExists {
class_name: class_name.to_string(),
property_name: property_name.to_string(),
});
}
let key = lua
.create_registry_value(property_getter)
.expect("Failed to store getter in lua registry");
class_getters.insert(property_name.to_string(), key);
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,
property_name: &str,
property_setter: LuaFunction<'lua>,
) -> Result<(), InstanceRegistryError> {
let registry = Self::get_or_create(lua);
let mut setters = registry
.setters
.lock()
.expect("Failed to lock instance registry getters");
let class_setters = setters.entry(class_name.to_string()).or_default();
if class_setters.contains_key(property_name) {
return Err(InstanceRegistryError::PropertyAlreadyExists {
class_name: class_name.to_string(),
property_name: property_name.to_string(),
});
}
let key = lua
.create_registry_value(property_setter)
.expect("Failed to store getter in lua registry");
class_setters.insert(property_name.to_string(), key);
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,
method_name: &str,
) -> Option<LuaFunction<'lua>> {
let registry = Self::get_or_create(lua);
let methods = registry
.methods
.lock()
.expect("Failed to lock instance registry methods");
class_name_chain(&instance.class_name)
.iter()
.find_map(|&class_name| {
methods
.get(class_name)
.and_then(|class_methods| class_methods.get(method_name))
.map(|key| lua.registry_value::<LuaFunction>(key).unwrap())
})
}
/**
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,
property_name: &str,
) -> Option<LuaFunction<'lua>> {
let registry = Self::get_or_create(lua);
let getters = registry
.getters
.lock()
.expect("Failed to lock instance registry getters");
class_name_chain(&instance.class_name)
.iter()
.find_map(|&class_name| {
getters
.get(class_name)
.and_then(|class_getters| class_getters.get(property_name))
.map(|key| lua.registry_value::<LuaFunction>(key).unwrap())
})
}
/**
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,
property_name: &str,
) -> Option<LuaFunction<'lua>> {
let registry = Self::get_or_create(lua);
let setters = registry
.setters
.lock()
.expect("Failed to lock instance registry setters");
class_name_chain(&instance.class_name)
.iter()
.find_map(|&class_name| {
setters
.get(class_name)
.and_then(|class_setters| class_setters.get(property_name))
.map(|key| lua.registry_value::<LuaFunction>(key).unwrap())
})
}
}
/**
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();
let mut list = vec![class_name];
let mut current_name = class_name;
loop {
let class_descriptor = db
.classes
.get(current_name)
.expect("Got invalid class name");
if let Some(sup) = &class_descriptor.superclass {
current_name = sup.borrow();
list.push(current_name);
} else {
break;
}
}
list
}

View file

@ -1,93 +0,0 @@
use mlua::prelude::*;
use rbx_dom_weak::types::{MaterialColors, TerrainMaterials, Variant};
use crate::{
datatypes::types::{Color3, EnumItem},
shared::classes::{add_class_restricted_method, add_class_restricted_method_mut},
};
use super::Instance;
pub const CLASS_NAME: &str = "Terrain";
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M) {
add_class_restricted_method(
methods,
CLASS_NAME,
"GetMaterialColor",
terrain_get_material_color,
);
add_class_restricted_method_mut(
methods,
CLASS_NAME,
"SetMaterialColor",
terrain_set_material_color,
);
}
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
inner
} else {
MaterialColors::default()
}
}
/**
Returns the color of the given terrain material.
### See Also
* [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)
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);
if &material.parent.desc.name != "Material" {
return Err(LuaError::RuntimeError(format!(
"Expected Enum.Material, got Enum.{}",
&material.parent.desc.name
)));
}
let terrain_material = material
.name
.parse::<TerrainMaterials>()
.map_err(|err| LuaError::RuntimeError(err.to_string()))?;
Ok(material_colors.get_color(terrain_material).into())
}
/**
Sets the color of the given terrain material.
### See Also
* [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)
on the Roblox Developer Hub
*/
fn terrain_set_material_color(
_: &Lua,
this: &mut Instance,
args: (EnumItem, Color3),
) -> LuaResult<()> {
let mut material_colors = get_or_create_material_colors(this);
let material = args.0;
let color = args.1;
if &material.parent.desc.name != "Material" {
return Err(LuaError::RuntimeError(format!(
"Expected Enum.Material, got Enum.{}",
&material.parent.desc.name
)));
}
let terrain_material = material
.name
.parse::<TerrainMaterials>()
.map_err(|err| LuaError::RuntimeError(err.to_string()))?;
material_colors.set_color(terrain_material, color.into());
this.set_property("MaterialColors", Variant::MaterialColors(material_colors));
Ok(())
}

View file

@ -1,72 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
use lune_utils::TableBuilder;
pub mod datatypes;
pub mod document;
pub mod instance;
pub mod reflection;
pub(crate) mod exports;
pub(crate) mod shared;
use exports::export;
fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
use datatypes::types::*;
use instance::Instance;
Ok(vec![
// Datatypes
export::<Axes>(lua)?,
export::<BrickColor>(lua)?,
export::<CFrame>(lua)?,
export::<Color3>(lua)?,
export::<ColorSequence>(lua)?,
export::<ColorSequenceKeypoint>(lua)?,
export::<Content>(lua)?,
export::<Faces>(lua)?,
export::<Font>(lua)?,
export::<NumberRange>(lua)?,
export::<NumberSequence>(lua)?,
export::<NumberSequenceKeypoint>(lua)?,
export::<PhysicalProperties>(lua)?,
export::<Ray>(lua)?,
export::<Rect>(lua)?,
export::<UDim>(lua)?,
export::<UDim2>(lua)?,
export::<Region3>(lua)?,
export::<Region3int16>(lua)?,
export::<Vector2>(lua)?,
export::<Vector2int16>(lua)?,
export::<Vector3>(lua)?,
export::<Vector3int16>(lua)?,
// Classes
export::<Instance>(lua)?,
// Singletons
("Enum", Enums.into_lua(lua)?),
])
}
/**
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
// save some memory and startup time. The full exports
// table is quite big and probably won't get any smaller
// since we impl all roblox constructors for each datatype.
let exports = create_all_exports(lua)?;
TableBuilder::new(lua)?
.with_values(exports)?
.build_readonly()
}

View file

@ -1,22 +0,0 @@
[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

@ -1,250 +0,0 @@
use std::cmp::Ordering;
use mlua::prelude::*;
use chrono::prelude::*;
use chrono::DateTime as ChronoDateTime;
use chrono_lc::LocaleDate;
use crate::result::{DateTimeError, DateTimeResult};
use crate::values::DateTimeValues;
const DEFAULT_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
const DEFAULT_LOCALE: &str = "en";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct DateTime {
// NOTE: We store this as the UTC time zone since it is the most commonly
// used and getting the generics right for TimeZone is somewhat tricky,
// but none of the method implementations below should rely on this tz
inner: ChronoDateTime<Utc>,
}
impl DateTime {
/**
Creates a new `DateTime` struct representing the current moment in time.
See [`chrono::DateTime::now`] for additional details.
*/
#[must_use]
pub fn now() -> Self {
Self { inner: Utc::now() }
}
/**
Creates a new `DateTime` struct from the given `unix_timestamp`,
which is a float of seconds passed since the UNIX epoch.
This is somewhat unconventional, but fits our Luau interface and dynamic types quite well.
To use this method the same way you would use a more traditional `from_unix_timestamp`
that takes a `u64` of seconds or similar type, casting the value is sufficient:
```rust ignore
DateTime::from_unix_timestamp_float(123456789u64 as f64)
```
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;
let fract = unix_timestamp.fract();
let nanos = (fract * 1_000_000_000f64)
.round()
.clamp(u32::MIN as f64, u32::MAX as f64) as u32;
let inner = ChronoDateTime::<Utc>::from_timestamp(whole, nanos)
.ok_or(DateTimeError::OutOfRangeUnspecified)?;
Ok(Self { inner })
}
/**
Transforms individual date & time values into a new
`DateTime` struct, using the universal (UTC) time zone.
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)
.ok_or(DateTimeError::InvalidDate)?;
let time = NaiveTime::from_hms_milli_opt(
values.hour,
values.minute,
values.second,
values.millisecond,
)
.ok_or(DateTimeError::InvalidTime)?;
let inner = Utc.from_utc_datetime(&NaiveDateTime::new(date, time));
Ok(Self { inner })
}
/**
Transforms individual date & time values into a new
`DateTime` struct, using the current local time zone.
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)
.ok_or(DateTimeError::InvalidDate)?;
let time = NaiveTime::from_hms_milli_opt(
values.hour,
values.minute,
values.second,
values.millisecond,
)
.ok_or(DateTimeError::InvalidTime)?;
let inner = Local
.from_local_datetime(&NaiveDateTime::new(date, time))
.single()
.ok_or(DateTimeError::Ambiguous)?
.with_timezone(&Utc);
Ok(Self { inner })
}
/**
Formats the `DateTime` using the universal (UTC) time
zone, the given format string, and the given locale.
`format` and `locale` default to `"%Y-%m-%d %H:%M:%S"` and `"en"` respectively.
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)
.formatl(
format.unwrap_or(DEFAULT_FORMAT),
locale.unwrap_or(DEFAULT_LOCALE),
)
.to_string()
}
/**
Formats the `DateTime` using the universal (UTC) time
zone, the given format string, and the given locale.
`format` and `locale` default to `"%Y-%m-%d %H:%M:%S"` and `"en"` respectively.
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)
.formatl(
format.unwrap_or(DEFAULT_FORMAT),
locale.unwrap_or(DEFAULT_LOCALE),
)
.to_string()
}
/**
Parses a time string in the ISO 8601 format, such as
`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);
Ok(Self { inner })
}
/**
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))
}
/**
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))
}
/**
Formats a time string in the ISO 8601 format, such as `1996-12-19T16:39:57-08:00`.
See [`chrono::DateTime::to_rfc3339`] for additional details.
*/
#[must_use]
pub fn to_iso_date(self) -> String {
self.inner.to_rfc3339()
}
}
impl LuaUserData for DateTime {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("unixTimestamp", |_, this| Ok(this.inner.timestamp()));
fields.add_field_method_get("unixTimestampMillis", |_, this| {
Ok(this.inner.timestamp_millis())
});
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Metamethods to compare DateTime as instants in time
methods.add_meta_method(
LuaMetaMethod::Eq,
|_, this: &Self, other: LuaUserDataRef<Self>| Ok(this.eq(&other)),
);
methods.add_meta_method(
LuaMetaMethod::Lt,
|_, this: &Self, other: LuaUserDataRef<Self>| {
Ok(matches!(this.cmp(&other), Ordering::Less))
},
);
methods.add_meta_method(
LuaMetaMethod::Le,
|_, this: &Self, other: LuaUserDataRef<Self>| {
Ok(matches!(this.cmp(&other), Ordering::Less | Ordering::Equal))
},
);
// Normal methods
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date()));
methods.add_method(
"formatUniversalTime",
|_, this, (format, locale): (Option<String>, Option<String>)| {
Ok(this.format_string_universal(format.as_deref(), locale.as_deref()))
},
);
methods.add_method(
"formatLocalTime",
|_, this, (format, locale): (Option<String>, Option<String>)| {
Ok(this.format_string_local(format.as_deref(), locale.as_deref()))
},
);
methods.add_method("toUniversalTime", |_, this: &Self, ()| {
Ok(this.to_universal_time())
});
methods.add_method("toLocalTime", |_, this: &Self, ()| Ok(this.to_local_time()));
}
}

View file

@ -1,36 +0,0 @@
#![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

@ -1,32 +0,0 @@
use mlua::prelude::*;
use thiserror::Error;
pub type DateTimeResult<T, E = DateTimeError> = Result<T, E>;
#[derive(Debug, Clone, Error)]
pub enum DateTimeError {
#[error("invalid date")]
InvalidDate,
#[error("invalid time")]
InvalidTime,
#[error("ambiguous date or time")]
Ambiguous,
#[error("date or time is outside allowed range")]
OutOfRangeUnspecified,
#[error("{name} must be within range {min} -> {max}, got {value}")]
OutOfRange {
name: &'static str,
value: String,
min: String,
max: String,
},
#[error(transparent)]
ParseError(#[from] chrono::ParseError),
}
impl From<DateTimeError> for LuaError {
fn from(value: DateTimeError) -> Self {
LuaError::runtime(value.to_string())
}
}

View file

@ -1,170 +0,0 @@
use mlua::prelude::*;
use chrono::prelude::*;
use lune_utils::TableBuilder;
use super::result::{DateTimeError, DateTimeResult};
#[derive(Debug, Clone, Copy)]
pub struct DateTimeValues {
pub year: i32,
pub month: u32,
pub day: u32,
pub hour: u32,
pub minute: u32,
pub second: u32,
pub millisecond: u32,
}
impl DateTimeValues {
/**
Verifies that all of the date & time values are within allowed ranges:
| Name | Range |
|---------------|----------------|
| `year` | `1400 -> 9999` |
| `month` | `1 -> 12` |
| `day` | `1 -> 31` |
| `hour` | `0 -> 23` |
| `minute` | `0 -> 59` |
| `second` | `0 -> 60` |
| `millisecond` | `0 -> 999` |
*/
pub fn verify(self) -> DateTimeResult<Self> {
verify_in_range("year", self.year, 1400, 9999)?;
verify_in_range("month", self.month, 1, 12)?;
verify_in_range("day", self.day, 1, 31)?;
verify_in_range("hour", self.hour, 0, 23)?;
verify_in_range("minute", self.minute, 0, 59)?;
verify_in_range("second", self.second, 0, 60)?;
verify_in_range("millisecond", self.millisecond, 0, 999)?;
Ok(self)
}
}
fn verify_in_range<T>(name: &'static str, value: T, min: T, max: T) -> DateTimeResult<T>
where
T: PartialOrd + std::fmt::Display,
{
assert!(max > min);
if value < min || value > max {
Err(DateTimeError::OutOfRange {
name,
min: min.to_string(),
max: max.to_string(),
value: value.to_string(),
})
} else {
Ok(value)
}
}
/*
Conversion methods between `DateTimeValues` and plain lua tables
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
*/
impl FromLua<'_> for DateTimeValues {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if !value.is_table() {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "DateTimeValues",
message: Some("value must be a table".to_string()),
});
};
let value = value.as_table().unwrap();
let values = Self {
year: value.get("year")?,
month: value.get("month")?,
day: value.get("day")?,
hour: value.get("hour")?,
minute: value.get("minute")?,
second: value.get("second")?,
millisecond: value.get("millisecond").unwrap_or(0),
};
match values.verify() {
Ok(dt) => Ok(dt),
Err(e) => Err(LuaError::FromLuaConversionError {
from: "table",
to: "DateTimeValues",
message: Some(e.to_string()),
}),
}
}
}
impl IntoLua<'_> for DateTimeValues {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
let tab = TableBuilder::new(lua)?
.with_value("year", self.year)?
.with_values(vec![
("month", self.month),
("day", self.day),
("hour", self.hour),
("minute", self.minute),
("second", self.second),
("millisecond", self.millisecond),
])?
.build_readonly()?;
Ok(LuaValue::Table(tab))
}
}
/*
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 {
fn from(value: DateTime<T>) -> Self {
Self {
year: value.year(),
month: value.month(),
day: value.day(),
hour: value.hour(),
minute: value.minute(),
second: value.second(),
millisecond: value.timestamp_subsec_millis(),
}
}
}
impl TryFrom<DateTimeValues> for DateTime<Utc> {
type Error = DateTimeError;
fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {
Utc.with_ymd_and_hms(
value.year,
value.month,
value.day,
value.hour,
value.minute,
value.second,
)
.single()
.ok_or(DateTimeError::Ambiguous)
}
}
impl TryFrom<DateTimeValues> for DateTime<Local> {
type Error = DateTimeError;
fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {
Local
.with_ymd_and_hms(
value.year,
value.month,
value.day,
value.hour,
value.minute,
value.second,
)
.single()
.ok_or(DateTimeError::Ambiguous)
}
}

View file

@ -1,23 +0,0 @@
[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

@ -1,18 +0,0 @@
[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

@ -1,90 +0,0 @@
#![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,143 +0,0 @@
#![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,
pub(crate) debug_level: u8,
}
impl LuauCompileOptions {
pub fn into_compiler(self) -> LuaCompiler {
LuaCompiler::default()
.set_optimization_level(self.optimization_level)
.set_coverage_level(self.coverage_level)
.set_debug_level(self.debug_level)
}
}
impl Default for LuauCompileOptions {
fn default() -> Self {
// NOTE: This is the same as LuaCompiler::default() values, but they are
// not accessible from outside of mlua so we need to recreate them here.
Self {
optimization_level: 1,
coverage_level: 0,
debug_level: 1,
}
}
}
impl<'lua> FromLua<'lua> for LuauCompileOptions {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
Ok(match value {
LuaValue::Nil => Self::default(),
LuaValue::Table(t) => {
let mut options = Self::default();
let get_and_check = |name: &'static str| -> LuaResult<Option<u8>> {
match t.get(name)? {
Some(n @ (0..=2)) => Ok(Some(n)),
Some(n) => Err(LuaError::runtime(format!(
"'{name}' must be one of: 0, 1, or 2 - got {n}"
))),
None => Ok(None),
}
};
if let Some(optimization_level) = get_and_check("optimizationLevel")? {
options.optimization_level = optimization_level;
}
if let Some(coverage_level) = get_and_check("coverageLevel")? {
options.coverage_level = coverage_level;
}
if let Some(debug_level) = get_and_check("debugLevel")? {
options.debug_level = debug_level;
}
options
}
_ => {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "CompileOptions",
message: Some(format!(
"Invalid compile options - expected table, got {}",
value.type_name()
)),
})
}
})
}
}
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<'_> {
fn default() -> Self {
Self {
debug_name: DEFAULT_DEBUG_NAME.to_string(),
environment: None,
inject_globals: true,
codegen_enabled: false,
}
}
}
impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
Ok(match value {
LuaValue::Nil => Self::default(),
LuaValue::Table(t) => {
let mut options = Self::default();
if let Some(debug_name) = t.get("debugName")? {
options.debug_name = debug_name;
}
if let Some(environment) = t.get("environment")? {
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 {
from: value.type_name(),
to: "LoadOptions",
message: Some(format!(
"Invalid load options - expected string or table, got {}",
value.type_name()
)),
})
}
})
}
}

View file

@ -1,39 +0,0 @@
[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

@ -1,163 +0,0 @@
use std::str::FromStr;
use mlua::prelude::*;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_ENCODING};
use lune_std_serde::{decompress, CompressDecompressFormat};
use lune_utils::TableBuilder;
use super::{config::RequestConfig, util::header_map_to_table};
const REGISTRY_KEY: &str = "NetClient";
pub struct NetClientBuilder {
builder: reqwest::ClientBuilder,
}
impl NetClientBuilder {
pub fn new() -> NetClientBuilder {
Self {
builder: reqwest::ClientBuilder::new(),
}
}
pub fn headers<K, V>(mut self, headers: &[(K, V)]) -> LuaResult<Self>
where
K: AsRef<str>,
V: AsRef<[u8]>,
{
let mut map = HeaderMap::new();
for (key, val) in headers {
let hkey = HeaderName::from_str(key.as_ref()).into_lua_err()?;
let hval = HeaderValue::from_bytes(val.as_ref()).into_lua_err()?;
map.insert(hkey, hval);
}
self.builder = self.builder.default_headers(map);
Ok(self)
}
pub fn build(self) -> LuaResult<NetClient> {
let client = self.builder.build().into_lua_err()?;
Ok(NetClient { inner: client })
}
}
#[derive(Debug, Clone)]
pub struct NetClient {
inner: reqwest::Client,
}
impl NetClient {
pub fn from_registry(lua: &Lua) -> Self {
lua.named_registry_value(REGISTRY_KEY)
.expect("Failed to get NetClient from lua registry")
}
pub fn into_registry(self, lua: &Lua) {
lua.set_named_registry_value(REGISTRY_KEY, self)
.expect("Failed to store NetClient in lua registry");
}
pub async fn request(&self, config: RequestConfig) -> LuaResult<NetClientResponse> {
// Create and send the request
let mut request = self.inner.request(config.method, config.url);
for (query, values) in config.query {
request = request.query(
&values
.iter()
.map(|v| (query.as_str(), v))
.collect::<Vec<_>>(),
);
}
for (header, values) in config.headers {
for value in values {
request = request.header(header.as_str(), value);
}
}
let res = request
.body(config.body.unwrap_or_default())
.send()
.await
.into_lua_err()?;
// Extract status, headers
let res_status = res.status().as_u16();
let res_status_text = res.status().canonical_reason();
let res_headers = res.headers().clone();
// Read response bytes
let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec();
let mut res_decompressed = false;
// Check for extra options, decompression
if config.options.decompress {
let decompress_format = res_headers
.iter()
.find(|(name, _)| {
name.as_str()
.eq_ignore_ascii_case(CONTENT_ENCODING.as_str())
})
.and_then(|(_, value)| value.to_str().ok())
.and_then(CompressDecompressFormat::detect_from_header_str);
if let Some(format) = decompress_format {
res_bytes = decompress(res_bytes, format).await?;
res_decompressed = true;
}
}
Ok(NetClientResponse {
ok: (200..300).contains(&res_status),
status_code: res_status,
status_message: res_status_text.unwrap_or_default().to_string(),
headers: res_headers,
body: res_bytes,
body_decompressed: res_decompressed,
})
}
}
impl LuaUserData for NetClient {}
impl FromLua<'_> for NetClient {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::UserData(ud) = value {
if let Ok(ctx) = ud.borrow::<NetClient>() {
return Ok(ctx.clone());
}
}
unreachable!("NetClient should only be used from registry")
}
}
impl From<&Lua> for NetClient {
fn from(value: &Lua) -> Self {
value
.named_registry_value(REGISTRY_KEY)
.expect("Missing require context in lua registry")
}
}
pub struct NetClientResponse {
ok: bool,
status_code: u16,
status_message: String,
headers: HeaderMap,
body: Vec<u8>,
body_decompressed: bool,
}
impl NetClientResponse {
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_value("ok", self.ok)?
.with_value("statusCode", self.status_code)?
.with_value("statusMessage", self.status_message)?
.with_value(
"headers",
header_map_to_table(lua, self.headers, self.body_decompressed)?,
)?
.with_value("body", lua.create_string(&self.body)?)?
.build_readonly()
}
}

View file

@ -1,231 +0,0 @@
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: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#"
return {
status = 426,
body = "Upgrade Required",
headers = {
Upgrade = "websocket",
},
}
"#;
// Net request config
#[derive(Debug, Clone)]
pub struct RequestConfigOptions {
pub decompress: bool,
}
impl Default for RequestConfigOptions {
fn default() -> Self {
Self { decompress: true }
}
}
impl<'lua> FromLua<'lua> for RequestConfigOptions {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::Nil = value {
// Nil means default options
Ok(Self::default())
} else if let LuaValue::Table(tab) = value {
// Table means custom options
let decompress = match tab.get::<_, Option<bool>>("decompress") {
Ok(decomp) => Ok(decomp.unwrap_or(true)),
Err(_) => Err(LuaError::RuntimeError(
"Invalid option value for 'decompress' in request config options".to_string(),
)),
}?;
Ok(Self { decompress })
} else {
// Anything else is invalid
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "RequestConfigOptions",
message: Some(format!(
"Invalid request config options - expected table or nil, got {}",
value.type_name()
)),
})
}
}
}
#[derive(Debug, Clone)]
pub struct RequestConfig {
pub url: String,
pub method: Method,
pub query: HashMap<String, Vec<String>>,
pub headers: HashMap<String, Vec<String>>,
pub body: Option<Vec<u8>>,
pub options: RequestConfigOptions,
}
impl FromLua<'_> for RequestConfig {
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
// If we just got a string we assume its a GET request to a given url
if let LuaValue::String(s) = value {
Ok(Self {
url: s.to_string_lossy().to_string(),
method: Method::GET,
query: HashMap::new(),
headers: HashMap::new(),
body: None,
options: RequestConfigOptions::default(),
})
} else if let LuaValue::Table(tab) = value {
// If we got a table we are able to configure the entire request
// Extract url
let url = match tab.get::<_, LuaString>("url") {
Ok(config_url) => Ok(config_url.to_string_lossy().to_string()),
Err(_) => Err(LuaError::runtime("Missing 'url' in request config")),
}?;
// Extract method
let method = match tab.get::<_, LuaString>("method") {
Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(),
Err(_) => "GET".to_string(),
};
// Extract query
let query = match tab.get::<_, LuaTable>("query") {
Ok(tab) => table_to_hash_map(tab, "query")?,
Err(_) => HashMap::new(),
};
// Extract headers
let headers = match tab.get::<_, LuaTable>("headers") {
Ok(tab) => table_to_hash_map(tab, "headers")?,
Err(_) => HashMap::new(),
};
// Extract 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() {
"GET" => Ok(Method::GET),
"POST" => Ok(Method::POST),
"PUT" => Ok(Method::PUT),
"DELETE" => Ok(Method::DELETE),
"HEAD" => Ok(Method::HEAD),
"OPTIONS" => Ok(Method::OPTIONS),
"PATCH" => Ok(Method::PATCH),
_ => Err(LuaError::RuntimeError(format!(
"Invalid request config method '{}'",
&method
))),
}?;
// Parse any extra options given
let options = match tab.get::<_, LuaValue>("options") {
Ok(opts) => RequestConfigOptions::from_lua(opts, lua)?,
Err(_) => RequestConfigOptions::default(),
};
// All good, validated and we got what we need
Ok(Self {
url,
method,
query,
headers,
body,
options,
})
} else {
// Anything else is invalid
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "RequestConfig",
message: Some(format!(
"Invalid request config - expected string or table, got {}",
value.type_name()
)),
})
}
}
}
// Net serve config
#[derive(Debug)]
pub struct ServeConfig<'a> {
pub address: IpAddr,
pub handle_request: LuaFunction<'a>,
pub handle_web_socket: Option<LuaFunction<'a>>,
}
impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::Function(f) = &value {
// Single function = request handler, rest is default
Ok(ServeConfig {
handle_request: f.clone(),
handle_web_socket: None,
address: DEFAULT_IP_ADDRESS,
})
} else if let LuaValue::Table(t) = &value {
// Table means custom options
let address: Option<LuaString> = t.get("address")?;
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: IpAddr = match &address {
Some(addr) => {
let addr_str = addr.to_str()?;
addr_str
.trim_start_matches("http://")
.trim_start_matches("https://")
.parse()
.map_err(|_e| LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ServeConfig",
message: Some(format!(
"IP address format is incorrect - \
expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', \
got '{addr_str}'"
)),
})?
}
None => DEFAULT_IP_ADDRESS,
};
Ok(Self {
address,
handle_request: handle_request.unwrap_or_else(|| {
lua.load(WEB_SOCKET_UPDGRADE_REQUEST_HANDLER)
.into_function()
.expect("Failed to create default http responder function")
}),
handle_web_socket,
})
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ServeConfig",
message: Some(String::from(
"Invalid serve config - expected table with 'handleRequest' or 'handleWebSocket' function",
)),
})
}
} else {
// Anything else is invalid
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ServeConfig",
message: None,
})
}
}
}

View file

@ -1,102 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use bstr::BString;
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
mod client;
mod config;
mod server;
mod util;
mod websocket;
use lune_utils::TableBuilder;
use self::{
client::{NetClient, NetClientBuilder},
config::{RequestConfig, ServeConfig},
server::serve,
util::create_user_agent_header,
websocket::NetWebSocket,
};
use lune_std_serde::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
/**
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(lua)?)])?
.build()?
.into_registry(lua);
TableBuilder::new(lua)?
.with_function("jsonEncode", net_json_encode)?
.with_function("jsonDecode", net_json_decode)?
.with_async_function("request", net_request)?
.with_async_function("socket", net_socket)?
.with_async_function("serve", net_serve)?
.with_function("urlEncode", net_url_encode)?
.with_function("urlDecode", net_url_decode)?
.build_readonly()
}
fn net_json_encode<'lua>(
lua: &'lua Lua,
(val, pretty): (LuaValue<'lua>, Option<bool>),
) -> LuaResult<LuaString<'lua>> {
let config = EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()));
encode(val, lua, config)
}
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> {
let client = NetClient::from_registry(lua);
// NOTE: We spawn the request as a background task to free up resources in lua
let res = lua.spawn(async move { client.request(config).await });
res.await?.into_lua_table(lua)
}
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(lua)
}
async fn net_serve<'lua>(
lua: &'lua Lua,
(port, config): (u16, ServeConfig<'lua>),
) -> LuaResult<LuaTable<'lua>> {
serve(lua, port, config).await
}
fn net_url_encode<'lua>(
lua: &'lua Lua,
(lua_string, as_binary): (LuaString<'lua>, Option<bool>),
) -> LuaResult<LuaValue<'lua>> {
if matches!(as_binary, Some(true)) {
urlencoding::encode_binary(lua_string.as_bytes()).into_lua(lua)
} else {
urlencoding::encode(lua_string.to_str()?).into_lua(lua)
}
}
fn net_url_decode<'lua>(
lua: &'lua Lua,
(lua_string, as_binary): (LuaString<'lua>, Option<bool>),
) -> LuaResult<LuaValue<'lua>> {
if matches!(as_binary, Some(true)) {
urlencoding::decode_binary(lua_string.as_bytes()).into_lua(lua)
} else {
urlencoding::decode(lua_string.to_str()?)
.map_err(|e| LuaError::RuntimeError(format!("Encountered invalid encoding - {e}")))?
.into_lua(lua)
}
}

View file

@ -1,61 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use mlua::prelude::*;
#[derive(Debug, Clone, Copy)]
pub(super) struct SvcKeys {
key_request: &'static str,
key_websocket: Option<&'static str>,
}
impl SvcKeys {
pub(super) fn new<'lua>(
lua: &'lua Lua,
handle_request: LuaFunction<'lua>,
handle_websocket: Option<LuaFunction<'lua>>,
) -> LuaResult<Self> {
static SERVE_COUNTER: AtomicUsize = AtomicUsize::new(0);
let count = SERVE_COUNTER.fetch_add(1, Ordering::Relaxed);
// NOTE: We leak strings here, but this is an acceptable tradeoff since programs
// generally only start one or a couple of servers and they are usually never dropped.
// Leaking here lets us keep this struct Copy and access the request handler callbacks
// very performantly, significantly reducing the per-request overhead of the server.
let key_request: &'static str =
Box::leak(format!("__net_serve_request_{count}").into_boxed_str());
let key_websocket: Option<&'static str> = if handle_websocket.is_some() {
Some(Box::leak(
format!("__net_serve_websocket_{count}").into_boxed_str(),
))
} else {
None
};
lua.set_named_registry_value(key_request, handle_request)?;
if let Some(key) = key_websocket {
lua.set_named_registry_value(key, handle_websocket.unwrap())?;
}
Ok(Self {
key_request,
key_websocket,
})
}
pub(super) fn has_websocket_handler(&self) -> bool {
self.key_websocket.is_some()
}
pub(super) fn request_handler<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaFunction<'lua>> {
lua.named_registry_value(self.key_request)
}
pub(super) fn websocket_handler<'lua>(
&self,
lua: &'lua Lua,
) -> LuaResult<Option<LuaFunction<'lua>>> {
self.key_websocket
.map(|key| lua.named_registry_value(key))
.transpose()
}
}

View file

@ -1,105 +0,0 @@
use std::{
net::SocketAddr,
rc::{Rc, Weak},
};
use hyper::server::conn::http1;
use hyper_util::rt::TokioIo;
use tokio::{net::TcpListener, pin};
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
use lune_utils::TableBuilder;
use super::config::ServeConfig;
mod keys;
mod request;
mod response;
mod service;
use keys::SvcKeys;
use service::Svc;
pub async fn serve<'lua>(
lua: &'lua Lua,
port: u16,
config: ServeConfig<'lua>,
) -> LuaResult<LuaTable<'lua>> {
let addr: SocketAddr = (config.address, port).into();
let listener = TcpListener::bind(addr).await?;
let (lua_svc, lua_inner) = {
let rc = lua
.app_data_ref::<Weak<Lua>>()
.expect("Missing weak lua ref")
.upgrade()
.expect("Lua was dropped unexpectedly");
(Rc::clone(&rc), rc)
};
let keys = SvcKeys::new(lua, config.handle_request, config.handle_web_socket)?;
let svc = Svc {
lua: lua_svc,
addr,
keys,
};
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
lua.spawn_local(async move {
let mut shutdown_rx_outer = shutdown_rx.clone();
loop {
// Create futures for accepting new connections and shutting down
let fut_shutdown = shutdown_rx_outer.changed();
let fut_accept = async {
let stream = match listener.accept().await {
Err(_) => return,
Ok((s, _)) => s,
};
let io = TokioIo::new(stream);
let svc = svc.clone();
let mut shutdown_rx_inner = shutdown_rx.clone();
lua_inner.spawn_local(async move {
let conn = http1::Builder::new()
.keep_alive(true) // Web sockets need this
.serve_connection(io, svc)
.with_upgrades();
// NOTE: Because we need to use keep_alive for websockets, we need to
// also manually poll this future and handle the shutdown signal here
pin!(conn);
tokio::select! {
_ = conn.as_mut() => {}
_ = shutdown_rx_inner.changed() => {
conn.as_mut().graceful_shutdown();
}
}
});
};
// Wait for either a new connection or a shutdown signal
tokio::select! {
() = 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
// to manually stop the server using the serve handle. Run forever.
if res.is_ok() {
break;
}
}
}
}
});
TableBuilder::new(lua)?
.with_value("ip", addr.ip().to_string())?
.with_value("port", addr.port())?
.with_function("stop", move |_, (): ()| match shutdown_tx.send(true) {
Ok(()) => Ok(()),
Err(_) => Err(LuaError::runtime("Server already stopped")),
})?
.build_readonly()
}

View file

@ -1,56 +0,0 @@
use std::{collections::HashMap, net::SocketAddr};
use http::request::Parts;
use mlua::prelude::*;
use lune_utils::TableBuilder;
pub(super) struct LuaRequest {
pub(super) _remote_addr: SocketAddr,
pub(super) head: Parts,
pub(super) body: Vec<u8>,
}
impl LuaRequest {
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
let method = self.head.method.as_str().to_string();
let path = self.head.uri.path().to_string();
let body = lua.create_string(&self.body)?;
#[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)| {
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)| {
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)?
.with_value("path", path)?
.with_value("query", query)?
.with_value("headers", headers)?
.with_value("body", body)?
.build()
}
}

View file

@ -1,89 +0,0 @@
use std::str::FromStr;
use bstr::{BString, ByteSlice};
use http_body_util::Full;
use hyper::{
body::Bytes,
header::{HeaderName, HeaderValue},
HeaderMap, Response,
};
use mlua::prelude::*;
#[derive(Debug, Clone, Copy)]
pub(super) enum LuaResponseKind {
PlainText,
Table,
}
pub(super) struct LuaResponse {
pub(super) kind: LuaResponseKind,
pub(super) status: u16,
pub(super) headers: HeaderMap,
pub(super) body: Option<Vec<u8>>,
}
impl LuaResponse {
pub(super) fn into_response(self) -> LuaResult<Response<Full<Bytes>>> {
Ok(match self.kind {
LuaResponseKind::PlainText => Response::builder()
.status(200)
.header("Content-Type", "text/plain")
.body(Full::new(Bytes::from(self.body.unwrap())))
.into_lua_err()?,
LuaResponseKind::Table => {
let mut response = Response::builder()
.status(self.status)
.body(Full::new(Bytes::from(self.body.unwrap_or_default())))
.into_lua_err()?;
response.headers_mut().extend(self.headers);
response
}
})
}
}
impl FromLua<'_> for LuaResponse {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
match value {
// Plain strings from the handler are plaintext responses
LuaValue::String(s) => Ok(Self {
kind: LuaResponseKind::PlainText,
status: 200,
headers: HeaderMap::new(),
body: Some(s.as_bytes().to_vec()),
}),
// Tables are more detailed responses with potential status, headers, body
LuaValue::Table(t) => {
let status: Option<u16> = t.get("status")?;
let headers: Option<LuaTable> = t.get("headers")?;
let body: Option<BString> = t.get("body")?;
let mut headers_map = HeaderMap::new();
if let Some(headers) = headers {
for pair in headers.pairs::<String, LuaString>() {
let (h, v) = pair?;
let name = HeaderName::from_str(&h).into_lua_err()?;
let value = HeaderValue::from_bytes(v.as_bytes()).into_lua_err()?;
headers_map.insert(name, value);
}
}
let body_bytes = body.map(|s| s.as_bytes().to_vec());
Ok(Self {
kind: LuaResponseKind::Table,
status: status.unwrap_or(200),
headers: headers_map,
body: body_bytes,
})
}
// Anything else is an error
value => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "NetServeResponse",
message: None,
}),
}
}
}

View file

@ -1,82 +0,0 @@
use std::{future::Future, net::SocketAddr, pin::Pin, rc::Rc};
use http_body_util::{BodyExt, Full};
use hyper::{
body::{Bytes, Incoming},
service::Service,
Request, Response,
};
use hyper_tungstenite::{is_upgrade_request, upgrade};
use mlua::prelude::*;
use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
use super::{
super::websocket::NetWebSocket, keys::SvcKeys, request::LuaRequest, response::LuaResponse,
};
#[derive(Debug, Clone)]
pub(super) struct Svc {
pub(super) lua: Rc<Lua>,
pub(super) addr: SocketAddr,
pub(super) keys: SvcKeys,
}
impl Service<Request<Incoming>> for Svc {
type Response = Response<Full<Bytes>>;
type Error = LuaError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn call(&self, req: Request<Incoming>) -> Self::Future {
let lua = self.lua.clone();
let addr = self.addr;
let keys = self.keys;
if keys.has_websocket_handler() && is_upgrade_request(&req) {
Box::pin(async move {
let (res, sock) = upgrade(req, None).into_lua_err()?;
let lua_inner = lua.clone();
lua.spawn_local(async move {
let sock = sock.await.unwrap();
let lua_sock = NetWebSocket::new(sock);
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_val)
.unwrap();
});
Ok(res)
})
} else {
let (head, body) = req.into_parts();
Box::pin(async move {
let handler_request: LuaFunction = keys.request_handler(&lua).unwrap();
let body = body.collect().await.into_lua_err()?;
let body = body.to_bytes().to_vec();
let lua_req = LuaRequest {
_remote_addr: addr,
head,
body,
};
let lua_req_table = lua_req.into_lua_table(&lua)?;
let thread_id = lua.push_thread_back(handler_request, lua_req_table)?;
lua.track_thread(thread_id);
lua.wait_for_thread(thread_id).await;
let thread_res = lua
.get_thread_result(thread_id)
.expect("Missing handler thread result")?;
LuaResponse::from_lua_multi(thread_res, &lua)?.into_response()
})
}
}
}

View file

@ -1,94 +0,0 @@
use std::collections::HashMap;
use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH};
use reqwest::header::HeaderMap;
use mlua::prelude::*;
use lune_utils::TableBuilder;
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(
lua: &Lua,
headers: HeaderMap,
remove_content_headers: bool,
) -> LuaResult<LuaTable> {
let mut res_headers: HashMap<String, Vec<String>> = HashMap::new();
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) {
existing.push(value);
} else {
res_headers.insert(name.to_owned(), vec![value]);
}
}
if remove_content_headers {
let content_encoding_header_str = CONTENT_ENCODING.as_str();
let content_length_header_str = CONTENT_LENGTH.as_str();
res_headers.retain(|name, _| {
name != content_encoding_header_str && name != content_length_header_str
});
}
let mut builder = TableBuilder::new(lua)?;
for (name, mut values) in res_headers {
if values.len() == 1 {
let value = values.pop().unwrap().into_lua(lua)?;
builder = builder.with_value(name, value)?;
} else {
let values = TableBuilder::new(lua)?
.with_sequential_values(values)?
.build_readonly()?
.into_lua(lua)?;
builder = builder.with_value(name, values)?;
}
}
builder.build_readonly()
}
pub fn table_to_hash_map(
tab: LuaTable,
tab_origin_key: &'static str,
) -> LuaResult<HashMap<String, Vec<String>>> {
let mut map = HashMap::new();
for pair in tab.pairs::<String, LuaValue>() {
let (key, value) = pair?;
match value {
LuaValue::String(s) => {
map.insert(key, vec![s.to_str()?.to_owned()]);
}
LuaValue::Table(t) => {
let mut values = Vec::new();
for value in t.sequence_values::<LuaString>() {
values.push(value?.to_str()?.to_owned());
}
map.insert(key, values);
}
_ => {
return Err(LuaError::runtime(format!(
"Value for '{tab_origin_key}' must be a string or array of strings",
)))
}
}
}
Ok(map)
}

View file

@ -1,149 +0,0 @@
use std::sync::{
atomic::{AtomicBool, AtomicU16, Ordering},
Arc,
};
use bstr::{BString, ByteSlice};
use mlua::prelude::*;
use futures_util::{
stream::{SplitSink, SplitStream},
SinkExt, StreamExt,
};
use tokio::{
io::{AsyncRead, AsyncWrite},
sync::Mutex as AsyncMutex,
};
use hyper_tungstenite::{
tungstenite::{
protocol::{frame::coding::CloseCode as WsCloseCode, CloseFrame as WsCloseFrame},
Message as WsMessage,
},
WebSocketStream,
};
#[derive(Debug)]
pub struct NetWebSocket<T> {
close_code_exists: Arc<AtomicBool>,
close_code_value: Arc<AtomicU16>,
read_stream: Arc<AsyncMutex<SplitStream<WebSocketStream<T>>>>,
write_stream: Arc<AsyncMutex<SplitSink<WebSocketStream<T>, WsMessage>>>,
}
impl<T> Clone for NetWebSocket<T> {
fn clone(&self) -> Self {
Self {
close_code_exists: Arc::clone(&self.close_code_exists),
close_code_value: Arc::clone(&self.close_code_value),
read_stream: Arc::clone(&self.read_stream),
write_stream: Arc::clone(&self.write_stream),
}
}
}
impl<T> NetWebSocket<T>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
pub fn new(value: WebSocketStream<T>) -> Self {
let (write, read) = value.split();
Self {
close_code_exists: Arc::new(AtomicBool::new(false)),
close_code_value: Arc::new(AtomicU16::new(0)),
read_stream: Arc::new(AsyncMutex::new(read)),
write_stream: Arc::new(AsyncMutex::new(write)),
}
}
fn get_close_code(&self) -> Option<u16> {
if self.close_code_exists.load(Ordering::Relaxed) {
Some(self.close_code_value.load(Ordering::Relaxed))
} else {
None
}
}
fn set_close_code(&self, code: u16) {
self.close_code_exists.store(true, Ordering::Relaxed);
self.close_code_value.store(code, Ordering::Relaxed);
}
pub async fn send(&self, msg: WsMessage) -> LuaResult<()> {
let mut ws = self.write_stream.lock().await;
ws.send(msg).await.into_lua_err()
}
pub async fn next(&self) -> LuaResult<Option<WsMessage>> {
let mut ws = self.read_stream.lock().await;
ws.next().await.transpose().into_lua_err()
}
pub async fn close(&self, code: Option<u16>) -> LuaResult<()> {
if self.close_code_exists.load(Ordering::Relaxed) {
return Err(LuaError::runtime("Socket has already been closed"));
}
self.send(WsMessage::Close(Some(WsCloseFrame {
code: match code {
Some(code) if (1000..=4999).contains(&code) => WsCloseCode::from(code),
Some(code) => {
return Err(LuaError::runtime(format!(
"Close code must be between 1000 and 4999, got {code}"
)))
}
None => WsCloseCode::Normal,
},
reason: "".into(),
})))
.await?;
let mut ws = self.write_stream.lock().await;
ws.close().await.into_lua_err()
}
}
impl<T> LuaUserData for NetWebSocket<T>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("closeCode", |_, this| Ok(this.get_close_code()));
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("close", |_, this, code: Option<u16>| async move {
this.close(code).await
});
methods.add_async_method(
"send",
|_, 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 {
let s = string.to_str().into_lua_err()?;
WsMessage::Text(s.to_string())
})
.await
},
);
methods.add_async_method("next", |lua, this, (): ()| async move {
let msg = this.next().await?;
if let Some(WsMessage::Close(Some(frame))) = msg.as_ref() {
this.set_close_code(frame.code.into());
}
Ok(match msg {
Some(WsMessage::Binary(bin)) => LuaValue::String(lua.create_string(bin)?),
Some(WsMessage::Text(txt)) => LuaValue::String(lua.create_string(txt)?),
Some(WsMessage::Close(_)) | None => LuaValue::Nil,
// Ignore ping/pong/frame messages, they are handled by tungstenite
msg => unreachable!("Unhandled message: {:?}", msg),
})
});
}
}

View file

@ -1,34 +0,0 @@
[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

@ -1,289 +0,0 @@
#![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,80 +0,0 @@
use std::{fmt, process::Stdio, str::FromStr};
use mlua::prelude::*;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ProcessSpawnOptionsStdioKind {
// TODO: We need better more obvious names
// for these, but that is a breaking change
#[default]
Default,
Forward,
Inherit,
None,
}
impl ProcessSpawnOptionsStdioKind {
pub fn all() -> &'static [Self] {
&[Self::Default, Self::Forward, Self::Inherit, Self::None]
}
pub fn as_stdio(self) -> Stdio {
match self {
Self::None => Stdio::null(),
Self::Forward => Stdio::inherit(),
_ => Stdio::piped(),
}
}
}
impl fmt::Display for ProcessSpawnOptionsStdioKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match *self {
Self::Default => "default",
Self::Forward => "forward",
Self::Inherit => "inherit",
Self::None => "none",
};
f.write_str(s)
}
}
impl FromStr for ProcessSpawnOptionsStdioKind {
type Err = LuaError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.trim().to_ascii_lowercase().as_str() {
"default" => Self::Default,
"forward" => Self::Forward,
"inherit" => Self::Inherit,
"none" => Self::None,
_ => {
return Err(LuaError::RuntimeError(format!(
"Invalid spawn options stdio kind - got '{}', expected one of {}",
s,
ProcessSpawnOptionsStdioKind::all()
.iter()
.map(|k| format!("'{k}'"))
.collect::<Vec<_>>()
.join(", ")
)))
}
})
}
}
impl<'lua> FromLua<'lua> for ProcessSpawnOptionsStdioKind {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
match value {
LuaValue::Nil => Ok(Self::default()),
LuaValue::String(s) => s.to_str()?.parse(),
_ => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ProcessSpawnOptionsStdioKind",
message: Some(format!(
"Invalid spawn options stdio kind - expected string, got {}",
value.type_name()
)),
}),
}
}
}

View file

@ -1,177 +0,0 @@
use std::{
collections::HashMap,
env::{self},
path::PathBuf,
};
use directories::UserDirs;
use mlua::prelude::*;
use tokio::process::Command;
mod kind;
mod stdio;
pub(super) use kind::*;
pub(super) use stdio::*;
#[derive(Debug, Clone, Default)]
pub(super) struct ProcessSpawnOptions {
pub cwd: Option<PathBuf>,
pub envs: HashMap<String, String>,
pub shell: Option<String>,
pub stdio: ProcessSpawnOptionsStdio,
}
impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
let mut this = Self::default();
let value = match value {
LuaValue::Nil => return Ok(this),
LuaValue::Table(t) => t,
_ => {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ProcessSpawnOptions",
message: Some(format!(
"Invalid spawn options - expected table, got {}",
value.type_name()
)),
})
}
};
/*
If we got a working directory to use:
1. Substitute leading tilde (~) for the users home dir
2. Make sure it exists
*/
match value.get("cwd")? {
LuaValue::Nil => {}
LuaValue::String(s) => {
let mut cwd = PathBuf::from(s.to_str()?);
if let Ok(stripped) = cwd.strip_prefix("~") {
let user_dirs = UserDirs::new().ok_or_else(|| {
LuaError::runtime(
"Invalid value for option 'cwd' - failed to get home directory",
)
})?;
cwd = user_dirs.home_dir().join(stripped);
}
if !cwd.exists() {
return Err(LuaError::runtime(
"Invalid value for option 'cwd' - path does not exist",
));
};
this.cwd = Some(cwd);
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'cwd' - expected string, got '{}'",
value.type_name()
)))
}
}
/*
If we got environment variables, make sure they are strings
*/
match value.get("env")? {
LuaValue::Nil => {}
LuaValue::Table(e) => {
for pair in e.pairs::<String, String>() {
let (k, v) = pair.context("Environment variables must be strings")?;
this.envs.insert(k, v);
}
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'env' - expected table, got '{}'",
value.type_name()
)))
}
}
/*
If we got a shell to use:
1. When given as a string, use that literally
2. When set to true, use a default shell for the platform
*/
match value.get("shell")? {
LuaValue::Nil => {}
LuaValue::String(s) => this.shell = Some(s.to_string_lossy().to_string()),
LuaValue::Boolean(true) => {
this.shell = match env::consts::FAMILY {
"unix" => Some("/bin/sh".to_string()),
"windows" => Some("powershell".to_string()),
_ => None,
};
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'",
value.type_name()
)))
}
}
/*
If we got options for stdio handling, parse those as well - note that
we accept a separate "stdin" value here for compatibility with older
scripts, but the user should preferrably pass it in the stdio table
*/
this.stdio = value.get("stdio")?;
match value.get("stdin")? {
LuaValue::Nil => {}
LuaValue::String(s) => this.stdio.stdin = Some(s.as_bytes().to_vec()),
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'stdin' - expected 'string', got '{}'",
value.type_name()
)))
}
}
Ok(this)
}
}
impl ProcessSpawnOptions {
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command {
let mut program = program.into();
// Run a shell using the command param if wanted
let pargs = match self.shell {
None => args,
Some(shell) => {
let shell_args = match args {
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
None => vec!["-c".to_string(), program.to_string()],
};
program = shell.to_string();
Some(shell_args)
}
};
// Create command with the wanted options
let mut cmd = match pargs {
None => Command::new(program),
Some(args) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
};
// Set dir to run in and env variables
if let Some(cwd) = self.cwd {
cmd.current_dir(cwd);
}
if !self.envs.is_empty() {
cmd.envs(self.envs);
}
cmd
}
}

View file

@ -1,56 +0,0 @@
use mlua::prelude::*;
use super::kind::ProcessSpawnOptionsStdioKind;
#[derive(Debug, Clone, Default)]
pub struct ProcessSpawnOptionsStdio {
pub stdout: ProcessSpawnOptionsStdioKind,
pub stderr: ProcessSpawnOptionsStdioKind,
pub stdin: Option<Vec<u8>>,
}
impl From<ProcessSpawnOptionsStdioKind> for ProcessSpawnOptionsStdio {
fn from(value: ProcessSpawnOptionsStdioKind) -> Self {
Self {
stdout: value,
stderr: value,
..Default::default()
}
}
}
impl<'lua> FromLua<'lua> for ProcessSpawnOptionsStdio {
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
match value {
LuaValue::Nil => Ok(Self::default()),
LuaValue::String(s) => {
Ok(ProcessSpawnOptionsStdioKind::from_lua(LuaValue::String(s), lua)?.into())
}
LuaValue::Table(t) => {
let mut this = Self::default();
if let Some(stdin) = t.get("stdin")? {
this.stdin = stdin;
}
if let Some(stdout) = t.get("stdout")? {
this.stdout = stdout;
}
if let Some(stderr) = t.get("stderr")? {
this.stderr = stderr;
}
Ok(this)
}
_ => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ProcessSpawnOptionsStdio",
message: Some(format!(
"Invalid spawn options stdio - expected string or table, got {}",
value.type_name()
)),
}),
}
}
}

View file

@ -1,58 +0,0 @@
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

@ -1,73 +0,0 @@
use std::process::ExitStatus;
use mlua::prelude::*;
use tokio::{
io::{self, AsyncRead, AsyncReadExt},
process::Child,
task,
};
use super::{options::ProcessSpawnOptionsStdioKind, tee_writer::AsyncTeeWriter};
#[derive(Debug, Clone)]
pub(super) struct WaitForChildResult {
pub status: ExitStatus,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
async fn read_with_stdio_kind<R>(
read_from: Option<R>,
kind: ProcessSpawnOptionsStdioKind,
) -> LuaResult<Vec<u8>>
where
R: AsyncRead + Unpin,
{
Ok(match kind {
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");
let mut buffer = Vec::new();
read_from.read_to_end(&mut buffer).await.into_lua_err()?;
buffer
}
ProcessSpawnOptionsStdioKind::Inherit => {
let mut read_from =
read_from.expect("read_from must be Some when stdio kind is Inherit");
let mut stdout = io::stdout();
let mut tee = AsyncTeeWriter::new(&mut stdout);
io::copy(&mut read_from, &mut tee).await.into_lua_err()?;
tee.into_vec()
}
})
}
pub(super) async fn wait_for_child(
mut child: Child,
stdout_kind: ProcessSpawnOptionsStdioKind,
stderr_kind: ProcessSpawnOptionsStdioKind,
) -> LuaResult<WaitForChildResult> {
let stdout_opt = child.stdout.take();
let stderr_opt = child.stderr.take();
let stdout_task = task::spawn(read_with_stdio_kind(stdout_opt, stdout_kind));
let stderr_task = task::spawn(read_with_stdio_kind(stderr_opt, stderr_kind));
let status = child.wait().await.expect("Child process failed to start");
let stdout_buffer = stdout_task.await.into_lua_err()??;
let stderr_buffer = stderr_task.await.into_lua_err()??;
Ok(WaitForChildResult {
status,
stdout: stdout_buffer,
stderr: stderr_buffer,
})
}

View file

@ -1,21 +0,0 @@
[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" }

View file

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

View file

@ -1,28 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
use lune_utils::TableBuilder;
mod captures;
mod matches;
mod regex;
use self::regex::LuaRegex;
/**
Creates the `regex` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("new", new_regex)?
.build_readonly()
}
fn new_regex(_: &Lua, pattern: String) -> LuaResult<LuaRegex> {
LuaRegex::new(pattern)
}

View file

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

View file

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

View file

@ -1,24 +0,0 @@
[package]
name = "lune-std-roblox"
version = "0.1.4"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Roblox"
[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" }
once_cell = "1.17"
rbx_cookie = { version = "0.1.4", default-features = false }
roblox_install = "1.0.0"
lune-utils = { version = "0.1.3", path = "../lune-utils" }
lune-roblox = { version = "0.1.4", path = "../lune-roblox" }

View file

@ -1,178 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
use once_cell::sync::OnceCell;
use lune_roblox::{
document::{Document, DocumentError, DocumentFormat, DocumentKind},
instance::{registry::InstanceRegistry, Instance},
reflection::Database as ReflectionDatabase,
};
static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new();
use lune_utils::TableBuilder;
use roblox_install::RobloxStudio;
/**
Creates the `roblox` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
let mut roblox_constants = Vec::new();
let roblox_module = lune_roblox::module(lua)?;
for pair in roblox_module.pairs::<LuaValue, LuaValue>() {
roblox_constants.push(pair?);
}
TableBuilder::new(lua)?
.with_values(roblox_constants)?
.with_async_function("deserializePlace", deserialize_place)?
.with_async_function("deserializeModel", deserialize_model)?
.with_async_function("serializePlace", serialize_place)?
.with_async_function("serializeModel", serialize_model)?
.with_function("getAuthCookie", get_auth_cookie)?
.with_function("getReflectionDatabase", get_reflection_database)?
.with_function("implementProperty", implement_property)?
.with_function("implementMethod", implement_method)?
.with_function("studioApplicationPath", studio_application_path)?
.with_function("studioContentPath", studio_content_path)?
.with_function("studioPluginPath", studio_plugin_path)?
.with_function("studioBuiltinPluginPath", studio_builtin_plugin_path)?
.build_readonly()
}
async fn deserialize_place<'lua>(
lua: &'lua Lua,
contents: LuaString<'lua>,
) -> LuaResult<LuaValue<'lua>> {
let bytes = contents.as_bytes().to_vec();
let fut = lua.spawn_blocking(move || {
let doc = Document::from_bytes(bytes, DocumentKind::Place)?;
let data_model = doc.into_data_model_instance()?;
Ok::<_, DocumentError>(data_model)
});
fut.await.into_lua_err()?.into_lua(lua)
}
async fn deserialize_model<'lua>(
lua: &'lua Lua,
contents: LuaString<'lua>,
) -> LuaResult<LuaValue<'lua>> {
let bytes = contents.as_bytes().to_vec();
let fut = lua.spawn_blocking(move || {
let doc = Document::from_bytes(bytes, DocumentKind::Model)?;
let instance_array = doc.into_instance_array()?;
Ok::<_, DocumentError>(instance_array)
});
fut.await.into_lua_err()?.into_lua(lua)
}
async fn serialize_place<'lua>(
lua: &'lua Lua,
(data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option<bool>),
) -> LuaResult<LuaString<'lua>> {
let data_model = *data_model;
let fut = lua.spawn_blocking(move || {
let doc = Document::from_data_model_instance(data_model)?;
let bytes = doc.to_bytes_with_format(match as_xml {
Some(true) => DocumentFormat::Xml,
_ => DocumentFormat::Binary,
})?;
Ok::<_, DocumentError>(bytes)
});
let bytes = fut.await.into_lua_err()?;
lua.create_string(bytes)
}
async fn serialize_model<'lua>(
lua: &'lua Lua,
(instances, as_xml): (Vec<LuaUserDataRef<'lua, Instance>>, Option<bool>),
) -> LuaResult<LuaString<'lua>> {
let instances = instances.iter().map(|i| **i).collect();
let fut = lua.spawn_blocking(move || {
let doc = Document::from_instance_array(instances)?;
let bytes = doc.to_bytes_with_format(match as_xml {
Some(true) => DocumentFormat::Xml,
_ => DocumentFormat::Binary,
})?;
Ok::<_, DocumentError>(bytes)
});
let bytes = fut.await.into_lua_err()?;
lua.create_string(bytes)
}
fn get_auth_cookie(_: &Lua, raw: Option<bool>) -> LuaResult<Option<String>> {
if matches!(raw, Some(true)) {
Ok(rbx_cookie::get_value())
} else {
Ok(rbx_cookie::get())
}
}
fn get_reflection_database(_: &Lua, _: ()) -> LuaResult<ReflectionDatabase> {
Ok(*REFLECTION_DATABASE.get_or_init(ReflectionDatabase::new))
}
fn implement_property(
lua: &Lua,
(class_name, property_name, property_getter, property_setter): (
String,
String,
LuaFunction,
Option<LuaFunction>,
),
) -> LuaResult<()> {
let property_setter = if let Some(setter) = property_setter {
setter
} else {
let property_name = property_name.clone();
lua.create_function(move |_, _: LuaMultiValue| {
Err::<(), _>(LuaError::runtime(format!(
"Property '{property_name}' is read-only"
)))
})?
};
InstanceRegistry::insert_property_getter(lua, &class_name, &property_name, property_getter)
.into_lua_err()?;
InstanceRegistry::insert_property_setter(lua, &class_name, &property_name, property_setter)
.into_lua_err()?;
Ok(())
}
fn implement_method(
lua: &Lua,
(class_name, method_name, method): (String, String, LuaFunction),
) -> LuaResult<()> {
InstanceRegistry::insert_method(lua, &class_name, &method_name, method).into_lua_err()?;
Ok(())
}
fn studio_application_path(_: &Lua, _: ()) -> LuaResult<String> {
RobloxStudio::locate()
.map(|rs| rs.application_path().display().to_string())
.map_err(LuaError::external)
}
fn studio_content_path(_: &Lua, _: ()) -> LuaResult<String> {
RobloxStudio::locate()
.map(|rs| rs.content_path().display().to_string())
.map_err(LuaError::external)
}
fn studio_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {
RobloxStudio::locate()
.map(|rs| rs.plugins_path().display().to_string())
.map_err(LuaError::external)
}
fn studio_builtin_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {
RobloxStudio::locate()
.map(|rs| rs.built_in_plugins_path().display().to_string())
.map_err(LuaError::external)
}

View file

@ -1,47 +0,0 @@
[package]
name = "lune-std-serde"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Serde"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau", "serialize"] }
async-compression = { version = "0.4", features = [
"tokio",
"brotli",
"deflate",
"gzip",
"zlib",
] }
bstr = "1.9"
lz4 = "1.26"
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"] }
digest = "0.10.7"
hmac = "0.12.1"
md-5 = "0.10.6"
sha1 = "0.10.6"
sha2 = "0.10.8"
sha3 = "0.10.8"
# This feature MIGHT break due to the unstable nature of the digest crate.
# Check before updating it.
blake3 = { version = "=1.5.0", features = ["traits-preview"] }
tokio = { version = "1", default-features = false, features = [
"rt",
"io-util",
] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -1,158 +0,0 @@
use mlua::prelude::*;
use serde_json::Value as JsonValue;
use serde_yaml::Value as YamlValue;
use toml::Value as TomlValue;
// NOTE: These are options for going from other format -> lua ("serializing" lua values)
const LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new()
.set_array_metatable(false)
.serialize_none_to_null(false)
.serialize_unit_to_null(false);
// NOTE: These are options for going from lua -> other format ("deserializing" lua values)
const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new()
.sort_keys(true)
.deny_recursive_tables(false)
.deny_unsupported_types(true);
/**
An encoding and decoding format supported by Lune.
Encode / decode in this case is synonymous with serialize / deserialize.
*/
#[derive(Debug, Clone, Copy)]
pub enum EncodeDecodeFormat {
Json,
Yaml,
Toml,
}
impl<'lua> FromLua<'lua> for EncodeDecodeFormat {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::String(s) = &value {
match s.to_string_lossy().to_ascii_lowercase().trim() {
"json" => Ok(Self::Json),
"yaml" => Ok(Self::Yaml),
"toml" => Ok(Self::Toml),
kind => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "EncodeDecodeFormat",
message: Some(format!(
"Invalid format '{kind}', valid formats are: json, yaml, toml"
)),
}),
}
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "EncodeDecodeFormat",
message: None,
})
}
}
}
/**
Configuration for encoding and decoding values.
Encoding / decoding in this case is synonymous with serialize / deserialize.
*/
#[derive(Debug, Clone, Copy)]
pub struct EncodeDecodeConfig {
pub format: EncodeDecodeFormat,
pub pretty: bool,
}
impl From<EncodeDecodeFormat> for EncodeDecodeConfig {
fn from(format: EncodeDecodeFormat) -> Self {
Self {
format,
pretty: false,
}
}
}
impl From<(EncodeDecodeFormat, bool)> for EncodeDecodeConfig {
fn from(value: (EncodeDecodeFormat, bool)) -> Self {
Self {
format: value.0,
pretty: value.1,
}
}
}
/**
Encodes / serializes the given value into a string, using the specified configuration.
# Errors
Errors when the encoding fails.
*/
pub fn encode<'lua>(
value: LuaValue<'lua>,
lua: &'lua Lua,
config: EncodeDecodeConfig,
) -> LuaResult<LuaString<'lua>> {
let bytes = match config.format {
EncodeDecodeFormat::Json => {
let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
if config.pretty {
serde_json::to_vec_pretty(&serialized).into_lua_err()?
} else {
serde_json::to_vec(&serialized).into_lua_err()?
}
}
EncodeDecodeFormat::Yaml => {
let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
let mut writer = Vec::with_capacity(128);
serde_yaml::to_writer(&mut writer, &serialized).into_lua_err()?;
writer
}
EncodeDecodeFormat::Toml => {
let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
let s = if config.pretty {
toml::to_string_pretty(&serialized).into_lua_err()?
} else {
toml::to_string(&serialized).into_lua_err()?
};
s.as_bytes().to_vec()
}
};
lua.create_string(bytes)
}
/**
Decodes / deserializes the given string into a value, using the specified configuration.
# Errors
Errors when the decoding fails.
*/
pub fn decode(
bytes: impl AsRef<[u8]>,
lua: &Lua,
config: EncodeDecodeConfig,
) -> LuaResult<LuaValue> {
let bytes = bytes.as_ref();
match config.format {
EncodeDecodeFormat::Json => {
let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::Yaml => {
let value: YamlValue = serde_yaml::from_slice(bytes).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::Toml => {
if let Ok(s) = String::from_utf8(bytes.to_vec()) {
let value: TomlValue = toml::from_str(&s).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
} else {
Err(LuaError::RuntimeError(
"TOML must be valid utf-8".to_string(),
))
}
}
}
}

View file

@ -1,260 +0,0 @@
use std::fmt::Write;
use bstr::BString;
use md5::Md5;
use mlua::prelude::*;
use blake3::Hasher as Blake3;
use sha1::Sha1;
use sha2::{Sha224, Sha256, Sha384, Sha512};
use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};
pub struct HashOptions {
algorithm: HashAlgorithm,
message: BString,
secret: Option<BString>,
// seed: Option<BString>,
}
#[derive(Debug, Clone, Copy)]
enum HashAlgorithm {
Md5,
Sha1,
// SHA-2 variants
Sha2_224,
Sha2_256,
Sha2_384,
Sha2_512,
// SHA-3 variants
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
// Blake3
Blake3,
}
impl HashAlgorithm {
pub const ALL: [Self; 11] = [
Self::Md5,
Self::Sha1,
Self::Sha2_224,
Self::Sha2_256,
Self::Sha2_384,
Self::Sha2_512,
Self::Sha3_224,
Self::Sha3_256,
Self::Sha3_384,
Self::Sha3_512,
Self::Blake3,
];
pub const fn name(self) -> &'static str {
match self {
Self::Md5 => "md5",
Self::Sha1 => "sha1",
Self::Sha2_224 => "sha224",
Self::Sha2_256 => "sha256",
Self::Sha2_384 => "sha384",
Self::Sha2_512 => "sha512",
Self::Sha3_224 => "sha3-224",
Self::Sha3_256 => "sha3-256",
Self::Sha3_384 => "sha3-384",
Self::Sha3_512 => "sha3-512",
Self::Blake3 => "blake3",
}
}
}
impl HashOptions {
/**
Computes the hash for the `message` using whatever `algorithm` is
contained within this struct and returns it as a string of hex digits.
*/
#[inline]
#[must_use = "hashing a message is useless without using the resulting hash"]
pub fn hash(self) -> String {
use digest::Digest;
let message = self.message;
let bytes = match self.algorithm {
HashAlgorithm::Md5 => Md5::digest(message).to_vec(),
HashAlgorithm::Sha1 => Sha1::digest(message).to_vec(),
HashAlgorithm::Sha2_224 => Sha224::digest(message).to_vec(),
HashAlgorithm::Sha2_256 => Sha256::digest(message).to_vec(),
HashAlgorithm::Sha2_384 => Sha384::digest(message).to_vec(),
HashAlgorithm::Sha2_512 => Sha512::digest(message).to_vec(),
HashAlgorithm::Sha3_224 => Sha3_224::digest(message).to_vec(),
HashAlgorithm::Sha3_256 => Sha3_256::digest(message).to_vec(),
HashAlgorithm::Sha3_384 => Sha3_384::digest(message).to_vec(),
HashAlgorithm::Sha3_512 => Sha3_512::digest(message).to_vec(),
HashAlgorithm::Blake3 => Blake3::digest(message).to_vec(),
};
// We don't want to return raw binary data generally, since that's not
// what most people want a hash for. So we have to make a hex string.
bytes
.iter()
.fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
})
}
/**
Computes the HMAC for the `message` using whatever `algorithm` and
`secret` are contained within this struct. The computed value is
returned as a string of hex digits.
# Errors
If the `secret` is not provided or is otherwise invalid.
*/
#[inline]
pub fn hmac(self) -> LuaResult<String> {
use hmac::{Hmac, Mac, SimpleHmac};
let secret = self
.secret
.ok_or_else(|| LuaError::FromLuaConversionError {
from: "nil",
to: "string or buffer",
message: Some("Argument #3 missing or nil".to_string()),
})?;
/*
These macros exist to remove what would ultimately be dozens of
repeating lines. Essentially, there's several step to processing
HMacs, which expands into the 3 lines you see below. However,
the Hmac struct is specialized towards eager block-based processes.
In order to support anything else, like blake3, there's a second
type named `SimpleHmac`. This results in duplicate macros like
there are below.
*/
macro_rules! hmac {
($Type:ty) => {{
let mut mac: Hmac<$Type> = Hmac::new_from_slice(&secret).into_lua_err()?;
mac.update(&self.message);
mac.finalize().into_bytes().to_vec()
}};
}
macro_rules! hmac_no_blocks {
($Type:ty) => {{
let mut mac: SimpleHmac<$Type> =
SimpleHmac::new_from_slice(&secret).into_lua_err()?;
mac.update(&self.message);
mac.finalize().into_bytes().to_vec()
}};
}
let bytes = match self.algorithm {
HashAlgorithm::Md5 => hmac!(Md5),
HashAlgorithm::Sha1 => hmac!(Sha1),
HashAlgorithm::Sha2_224 => hmac!(Sha224),
HashAlgorithm::Sha2_256 => hmac!(Sha256),
HashAlgorithm::Sha2_384 => hmac!(Sha384),
HashAlgorithm::Sha2_512 => hmac!(Sha512),
HashAlgorithm::Sha3_224 => hmac!(Sha3_224),
HashAlgorithm::Sha3_256 => hmac!(Sha3_256),
HashAlgorithm::Sha3_384 => hmac!(Sha3_384),
HashAlgorithm::Sha3_512 => hmac!(Sha3_512),
HashAlgorithm::Blake3 => hmac_no_blocks!(Blake3),
};
Ok(bytes
.iter()
.fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
}))
}
}
impl<'lua> FromLua<'lua> for HashAlgorithm {
fn from_lua(value: LuaValue<'lua>, _lua: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::String(str) = value {
/*
Casing tends to vary for algorithms, so rather than force
people to remember it we'll just accept any casing.
*/
let str = str.to_str()?.to_ascii_lowercase();
match str.as_str() {
"md5" => Ok(Self::Md5),
"sha1" => Ok(Self::Sha1),
"sha2-224" | "sha2_224" | "sha224" => Ok(Self::Sha2_224),
"sha2-256" | "sha2_256" | "sha256" => Ok(Self::Sha2_256),
"sha2-384" | "sha2_384" | "sha384" => Ok(Self::Sha2_384),
"sha2-512" | "sha2_512" | "sha512" => Ok(Self::Sha2_512),
"sha3-224" | "sha3_224" => Ok(Self::Sha3_224),
"sha3-256" | "sha3_256" => Ok(Self::Sha3_256),
"sha3-384" | "sha3_384" => Ok(Self::Sha3_384),
"sha3-512" | "sha3_512" => Ok(Self::Sha3_512),
"blake3" => Ok(Self::Blake3),
_ => Err(LuaError::FromLuaConversionError {
from: "string",
to: "HashAlgorithm",
message: Some(format!(
"Invalid hashing algorithm '{str}', valid kinds are:\n{}",
HashAlgorithm::ALL
.into_iter()
.map(HashAlgorithm::name)
.collect::<Vec<_>>()
.join(", ")
)),
}),
}
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "HashAlgorithm",
message: None,
})
}
}
}
impl<'lua> FromLuaMulti<'lua> for HashOptions {
fn from_lua_multi(mut values: LuaMultiValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
let algorithm = values
.pop_front()
.map(|value| HashAlgorithm::from_lua(value, lua))
.transpose()?
.ok_or_else(|| LuaError::FromLuaConversionError {
from: "nil",
to: "HashAlgorithm",
message: Some("Argument #1 missing or nil".to_string()),
})?;
let message = values
.pop_front()
.map(|value| BString::from_lua(value, lua))
.transpose()?
.ok_or_else(|| LuaError::FromLuaConversionError {
from: "nil",
to: "string or buffer",
message: Some("Argument #2 missing or nil".to_string()),
})?;
let secret = values
.pop_front()
.map(|value| BString::from_lua(value, lua))
.transpose()?;
// let seed = values
// .pop_front()
// .map(|value| BString::from_lua(value, lua))
// .transpose()?;
Ok(HashOptions {
algorithm,
message,
secret,
// seed,
})
}
}

View file

@ -1,69 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use bstr::BString;
use mlua::prelude::*;
use lune_utils::TableBuilder;
mod compress_decompress;
mod encode_decode;
mod hash;
pub use self::compress_decompress::{compress, decompress, CompressDecompressFormat};
pub use self::encode_decode::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
pub use self::hash::HashOptions;
/**
Creates the `serde` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("encode", serde_encode)?
.with_function("decode", serde_decode)?
.with_async_function("compress", serde_compress)?
.with_async_function("decompress", serde_decompress)?
.with_function("hash", hash_message)?
.with_function("hmac", hmac_message)?
.build_readonly()
}
fn serde_encode<'lua>(
lua: &'lua Lua,
(format, value, pretty): (EncodeDecodeFormat, LuaValue<'lua>, Option<bool>),
) -> LuaResult<LuaString<'lua>> {
let config = EncodeDecodeConfig::from((format, pretty.unwrap_or_default()));
encode(value, lua, config)
}
fn serde_decode(lua: &Lua, (format, bs): (EncodeDecodeFormat, BString)) -> LuaResult<LuaValue> {
let config = EncodeDecodeConfig::from(format);
decode(bs, lua, config)
}
async fn serde_compress(
lua: &Lua,
(format, bs, level): (CompressDecompressFormat, BString, Option<i32>),
) -> LuaResult<LuaString> {
let bytes = compress(bs, format, level).await?;
lua.create_string(bytes)
}
async fn serde_decompress(
lua: &Lua,
(format, bs): (CompressDecompressFormat, BString),
) -> LuaResult<LuaString> {
let bytes = decompress(bs, format).await?;
lua.create_string(bytes)
}
fn hash_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {
lua.create_string(options.hash())
}
fn hmac_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {
lua.create_string(options.hmac()?)
}

View file

@ -1,25 +0,0 @@
[package]
name = "lune-std-stdio"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Stdio"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
dialoguer = "0.11"
mlua = { version = "0.9.9", features = ["luau"] }
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
tokio = { version = "1", default-features = false, features = [
"io-std",
"io-util",
] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -1,85 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use lune_utils::fmt::{pretty_format_multi_value, ValueFormatConfig};
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
use tokio::io::{stderr, stdin, stdout, AsyncReadExt, AsyncWriteExt};
use lune_utils::TableBuilder;
mod prompt;
mod style_and_color;
use self::prompt::{prompt, PromptOptions, PromptResult};
use self::style_and_color::{ColorKind, StyleKind};
const FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new()
.with_max_depth(4)
.with_colors_enabled(false);
/**
Creates the `stdio` standard library module.
# Errors
Errors when out of memory.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("color", stdio_color)?
.with_function("style", stdio_style)?
.with_function("format", stdio_format)?
.with_async_function("write", stdio_write)?
.with_async_function("ewrite", stdio_ewrite)?
.with_async_function("readToEnd", stdio_read_to_end)?
.with_async_function("prompt", stdio_prompt)?
.build_readonly()
}
fn stdio_color(lua: &Lua, color: ColorKind) -> LuaResult<LuaValue> {
color.ansi_escape_sequence().into_lua(lua)
}
fn stdio_style(lua: &Lua, style: StyleKind) -> LuaResult<LuaValue> {
style.ansi_escape_sequence().into_lua(lua)
}
fn stdio_format(_: &Lua, args: LuaMultiValue) -> LuaResult<String> {
Ok(pretty_format_multi_value(&args, &FORMAT_CONFIG))
}
async fn stdio_write(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
let mut stdout = stdout();
stdout.write_all(s.as_bytes()).await?;
stdout.flush().await?;
Ok(())
}
async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
let mut stderr = stderr();
stderr.write_all(s.as_bytes()).await?;
stderr.flush().await?;
Ok(())
}
/*
FUTURE: Figure out how to expose some kind of "readLine" function using a buffered reader.
This is a bit tricky since we would want to be able to use **both** readLine and readToEnd
in the same script, doing something like readLine, readLine, readToEnd from lua, and
having that capture the first two lines and then read the rest of the input.
*/
async fn stdio_read_to_end(lua: &Lua, (): ()) -> LuaResult<LuaString> {
let mut input = Vec::new();
let mut stdin = stdin();
stdin.read_to_end(&mut input).await?;
lua.create_string(&input)
}
async fn stdio_prompt(lua: &Lua, options: PromptOptions) -> LuaResult<PromptResult> {
lua.spawn_blocking(move || prompt(options))
.await
.into_lua_err()
}

View file

@ -1,195 +0,0 @@
use std::str::FromStr;
use mlua::prelude::*;
const ESCAPE_SEQ_RESET: &str = "\x1b[0m";
/**
A color kind supported by the `stdio` standard library.
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorKind {
Reset,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl ColorKind {
pub const ALL: [Self; 9] = [
Self::Reset,
Self::Black,
Self::Red,
Self::Green,
Self::Yellow,
Self::Blue,
Self::Magenta,
Self::Cyan,
Self::White,
];
/**
Returns the human-friendly name of this color kind.
*/
pub fn name(self) -> &'static str {
match self {
Self::Reset => "reset",
Self::Black => "black",
Self::Red => "red",
Self::Green => "green",
Self::Yellow => "yellow",
Self::Blue => "blue",
Self::Magenta => "magenta",
Self::Cyan => "cyan",
Self::White => "white",
}
}
/**
Returns the ANSI escape sequence for the color kind.
*/
pub fn ansi_escape_sequence(self) -> &'static str {
match self {
Self::Reset => ESCAPE_SEQ_RESET,
Self::Black => "\x1b[30m",
Self::Red => "\x1b[31m",
Self::Green => "\x1b[32m",
Self::Yellow => "\x1b[33m",
Self::Blue => "\x1b[34m",
Self::Magenta => "\x1b[35m",
Self::Cyan => "\x1b[36m",
Self::White => "\x1b[37m",
}
}
}
impl FromStr for ColorKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.trim().to_ascii_lowercase().as_str() {
"reset" => Self::Reset,
"black" => Self::Black,
"red" => Self::Red,
"green" => Self::Green,
"yellow" => Self::Yellow,
"blue" => Self::Blue,
// NOTE: Previous versions of Lune had this color as "purple" instead
// of "magenta", so we keep this here for backwards compatibility.
"magenta" | "purple" => Self::Magenta,
"cyan" => Self::Cyan,
"white" => Self::White,
_ => return Err(()),
})
}
}
impl FromLua<'_> for ColorKind {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::String(s) = value {
let s = s.to_str()?;
match s.parse() {
Ok(color) => Ok(color),
Err(()) => Err(LuaError::FromLuaConversionError {
from: "string",
to: "ColorKind",
message: Some(format!(
"Invalid color kind '{s}'\nValid kinds are: {}",
Self::ALL
.iter()
.map(|kind| kind.name())
.collect::<Vec<_>>()
.join(", ")
)),
}),
}
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ColorKind",
message: None,
})
}
}
}
/**
A style kind supported by the `stdio` standard library.
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StyleKind {
Reset,
Bold,
Dim,
}
impl StyleKind {
pub const ALL: [Self; 3] = [Self::Reset, Self::Bold, Self::Dim];
/**
Returns the human-friendly name for this style kind.
*/
pub fn name(self) -> &'static str {
match self {
Self::Reset => "reset",
Self::Bold => "bold",
Self::Dim => "dim",
}
}
/**
Returns the ANSI escape sequence for this style kind.
*/
pub fn ansi_escape_sequence(self) -> &'static str {
match self {
Self::Reset => ESCAPE_SEQ_RESET,
Self::Bold => "\x1b[1m",
Self::Dim => "\x1b[2m",
}
}
}
impl FromStr for StyleKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.trim().to_ascii_lowercase().as_str() {
"reset" => Self::Reset,
"bold" => Self::Bold,
"dim" => Self::Dim,
_ => return Err(()),
})
}
}
impl FromLua<'_> for StyleKind {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::String(s) = value {
let s = s.to_str()?;
match s.parse() {
Ok(style) => Ok(style),
Err(()) => Err(LuaError::FromLuaConversionError {
from: "string",
to: "StyleKind",
message: Some(format!(
"Invalid style kind '{s}'\nValid kinds are: {}",
Self::ALL
.iter()
.map(|kind| kind.name())
.collect::<Vec<_>>()
.join(", ")
)),
}),
}
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "StyleKind",
message: None,
})
}
}
}

View file

@ -1,21 +0,0 @@
[package]
name = "lune-std-task"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library - Task"
[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" }
tokio = { version = "1", default-features = false, features = ["time"] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }

View file

@ -1,60 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use std::time::Duration;
use mlua::prelude::*;
use mlua_luau_scheduler::Functions;
use tokio::time::{sleep, Instant};
use lune_utils::TableBuilder;
/**
Creates the `task` standard library module.
# Errors
Errors when out of memory, or if default Lua globals are missing.
*/
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
let fns = Functions::new(lua)?;
// Create wait & delay functions
let task_wait = lua.create_async_function(wait)?;
let task_delay_env = TableBuilder::new(lua)?
.with_value("select", lua.globals().get::<_, LuaFunction>("select")?)?
.with_value("spawn", fns.spawn.clone())?
.with_value("defer", fns.defer.clone())?
.with_value("wait", task_wait.clone())?
.build_readonly()?;
let task_delay = lua
.load(DELAY_IMPL_LUA)
.set_name("task.delay")
.set_environment(task_delay_env)
.into_function()?;
TableBuilder::new(lua)?
.with_value("cancel", fns.cancel)?
.with_value("defer", fns.defer)?
.with_value("delay", task_delay)?
.with_value("spawn", fns.spawn)?
.with_value("wait", task_wait)?
.build_readonly()
}
const DELAY_IMPL_LUA: &str = r"
return defer(function(...)
wait(select(1, ...))
spawn(select(2, ...))
end, ...)
";
async fn wait(_: &Lua, secs: Option<f64>) -> LuaResult<f64> {
let duration = Duration::from_secs_f64(secs.unwrap_or_default());
let before = Instant::now();
sleep(duration).await;
let after = Instant::now();
Ok((after - before).as_secs_f64())
}

View file

@ -1,59 +0,0 @@
[package]
name = "lune-std"
version = "0.1.5"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Lune standard library"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[features]
default = [
"datetime",
"fs",
"luau",
"net",
"process",
"regex",
"roblox",
"serde",
"stdio",
"task",
]
datetime = ["dep:lune-std-datetime"]
fs = ["dep:lune-std-fs"]
luau = ["dep:lune-std-luau"]
net = ["dep:lune-std-net"]
process = ["dep:lune-std-process"]
regex = ["dep:lune-std-regex"]
roblox = ["dep:lune-std-roblox"]
serde = ["dep:lune-std-serde"]
stdio = ["dep:lune-std-stdio"]
task = ["dep:lune-std-task"]
[dependencies]
mlua = { version = "0.9.9", features = ["luau"] }
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", default-features = false, features = ["fs", "sync"] }
lune-utils = { version = "0.1.3", path = "../lune-utils" }
lune-std-datetime = { optional = true, version = "0.1.3", path = "../lune-std-datetime" }
lune-std-fs = { optional = true, version = "0.1.2", path = "../lune-std-fs" }
lune-std-luau = { optional = true, version = "0.1.2", path = "../lune-std-luau" }
lune-std-net = { optional = true, version = "0.1.2", path = "../lune-std-net" }
lune-std-process = { optional = true, version = "0.1.3", path = "../lune-std-process" }
lune-std-regex = { optional = true, version = "0.1.2", path = "../lune-std-regex" }
lune-std-roblox = { optional = true, version = "0.1.4", path = "../lune-std-roblox" }
lune-std-serde = { optional = true, version = "0.1.2", path = "../lune-std-serde" }
lune-std-stdio = { optional = true, version = "0.1.2", path = "../lune-std-stdio" }
lune-std-task = { optional = true, version = "0.1.2", path = "../lune-std-task" }

View file

@ -1,92 +0,0 @@
use std::str::FromStr;
use mlua::prelude::*;
/**
A standard global provided by Lune.
*/
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum LuneStandardGlobal {
GTable,
Print,
Require,
Version,
Warn,
}
impl LuneStandardGlobal {
/**
All available standard globals.
*/
pub const ALL: &'static [Self] = &[
Self::GTable,
Self::Print,
Self::Require,
Self::Version,
Self::Warn,
];
/**
Gets the name of the global, such as `_G` or `require`.
*/
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::GTable => "_G",
Self::Print => "print",
Self::Require => "require",
Self::Version => "_VERSION",
Self::Warn => "warn",
}
}
/**
Creates the Lua value for the global.
# Errors
If the global could not be created.
*/
#[rustfmt::skip]
#[allow(unreachable_patterns)]
pub fn create<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
let res = match self {
Self::GTable => crate::globals::g_table::create(lua),
Self::Print => crate::globals::print::create(lua),
Self::Require => crate::globals::require::create(lua),
Self::Version => crate::globals::version::create(lua),
Self::Warn => crate::globals::warn::create(lua),
};
match res {
Ok(v) => Ok(v),
Err(e) => Err(e.context(format!(
"Failed to create standard global '{}'",
self.name()
))),
}
}
}
impl FromStr for LuneStandardGlobal {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let low = s.trim().to_ascii_lowercase();
Ok(match low.as_str() {
"_g" => Self::GTable,
"print" => Self::Print,
"require" => Self::Require,
"_version" => Self::Version,
"warn" => Self::Warn,
_ => {
return Err(format!(
"Unknown standard global '{low}'\nValid globals are: {}",
Self::ALL
.iter()
.map(Self::name)
.collect::<Vec<_>>()
.join(", ")
))
}
})
}
}

View file

@ -1,5 +0,0 @@
use mlua::prelude::*;
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
lua.create_table()?.into_lua(lua)
}

View file

@ -1,5 +0,0 @@
pub mod g_table;
pub mod print;
pub mod require;
pub mod version;
pub mod warn;

View file

@ -1,19 +0,0 @@
use std::io::Write;
use lune_utils::fmt::{pretty_format_multi_value, ValueFormatConfig};
use mlua::prelude::*;
const FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new()
.with_max_depth(4)
.with_colors_enabled(true);
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
let f = lua.create_function(|_, args: LuaMultiValue| {
let formatted = format!("{}\n", pretty_format_multi_value(&args, &FORMAT_CONFIG));
let mut stdout = std::io::stdout();
stdout.write_all(formatted.as_bytes())?;
stdout.flush()?;
Ok(())
})?;
f.into_lua(lua)
}

View file

@ -1,73 +0,0 @@
use mlua::prelude::*;
use lune_utils::path::{clean_path_and_make_absolute, diff_path, get_current_dir};
use crate::luaurc::LuauRc;
use super::context::*;
pub(super) async fn require<'lua, 'ctx>(
lua: &'lua Lua,
ctx: &'ctx RequireContext,
source: &str,
alias: &str,
path: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
let alias = alias.to_ascii_lowercase();
let parent = clean_path_and_make_absolute(source)
.parent()
.expect("how did a root path end up here..")
.to_path_buf();
// Try to gather the first luaurc and / or error we
// encounter to display better error messages to users
let mut first_luaurc = None;
let mut first_error = None;
let predicate = |rc: &LuauRc| {
if first_luaurc.is_none() {
first_luaurc.replace(rc.clone());
}
if let Err(e) = rc.validate() {
if first_error.is_none() {
first_error.replace(e);
}
false
} else {
rc.find_alias(&alias).is_some()
}
};
// Try to find a luaurc that contains the alias we're searching for
let luaurc = LuauRc::read_recursive(parent, predicate)
.await
.ok_or_else(|| {
if let Some(error) = first_error {
LuaError::runtime(format!("error while parsing .luaurc file: {error}"))
} else if let Some(luaurc) = first_luaurc {
LuaError::runtime(format!(
"failed to find alias '{alias}' - known aliases:\n{}",
luaurc
.aliases()
.iter()
.map(|(name, path)| format!(" {name} > {path}"))
.collect::<Vec<_>>()
.join("\n")
))
} else {
LuaError::runtime(format!("failed to find alias '{alias}' (no .luaurc)"))
}
})?;
// We now have our aliased path, our path require function just needs it
// in a slightly different format with both absolute + relative to cwd
let abs_path = luaurc.find_alias(&alias).unwrap().join(path);
let rel_path = diff_path(&abs_path, get_current_dir()).ok_or_else(|| {
LuaError::runtime(format!("failed to find relative path for alias '{alias}'"))
})?;
super::path::require_abs_rel(lua, ctx, abs_path, rel_path).await
}

View file

@ -1,289 +0,0 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Arc,
};
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSchedulerExt;
use tokio::{
fs::read,
sync::{
broadcast::{self, Sender},
Mutex as AsyncMutex,
},
};
use lune_utils::path::{clean_path, clean_path_and_make_absolute};
use crate::library::LuneStandardLibrary;
/**
Context containing cached results for all `require` operations.
The cache uses absolute paths, so any given relative
path will first be transformed into an absolute path.
*/
#[derive(Debug, Clone)]
pub(super) struct RequireContext {
libraries: Arc<AsyncMutex<HashMap<LuneStandardLibrary, LuaResult<LuaRegistryKey>>>>,
results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
}
impl RequireContext {
/**
Creates a new require context for the given [`Lua`] struct.
Note that this require context is global and only one require
context should be created per [`Lua`] struct, creating more
than one context may lead to undefined require-behavior.
*/
pub fn new() -> Self {
Self {
libraries: Arc::new(AsyncMutex::new(HashMap::new())),
results: Arc::new(AsyncMutex::new(HashMap::new())),
pending: Arc::new(AsyncMutex::new(HashMap::new())),
}
}
/**
Resolves the given `source` and `path` into require paths
to use, based on the current require context settings.
This will resolve path segments such as `./`, `../`, ..., and
if the resolved path is not an absolute path, will create an
absolute path by prepending the current working directory.
*/
pub fn resolve_paths(
source: impl AsRef<str>,
path: impl AsRef<str>,
) -> LuaResult<(PathBuf, PathBuf)> {
let path = PathBuf::from(source.as_ref())
.parent()
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
.join(path.as_ref());
let abs_path = clean_path_and_make_absolute(&path);
let rel_path = clean_path(path);
Ok((abs_path, rel_path))
}
/**
Checks if the given path has a cached require result.
*/
pub fn is_cached(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
let is_cached = self
.results
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.contains_key(abs_path.as_ref());
Ok(is_cached)
}
/**
Checks if the given path is currently being used in `require`.
*/
pub fn is_pending(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
let is_pending = self
.pending
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.contains_key(abs_path.as_ref());
Ok(is_pending)
}
/**
Gets the resulting value from the require cache.
Will panic if the path has not been cached, use [`is_cached`] first.
*/
pub fn get_from_cache<'lua>(
&self,
lua: &'lua Lua,
abs_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>> {
let results = self
.results
.try_lock()
.expect("RequireContext may not be used from multiple threads");
let cached = results
.get(abs_path.as_ref())
.expect("Path does not exist in results cache");
match cached {
Err(e) => Err(e.clone()),
Ok(k) => {
let multi_vec = lua
.registry_value::<Vec<LuaValue>>(k)
.expect("Missing require result in lua registry");
Ok(LuaMultiValue::from_vec(multi_vec))
}
}
}
/**
Waits for the resulting value from the require cache.
Will panic if the path has not been cached, use [`is_cached`] first.
*/
pub async fn wait_for_cache<'lua>(
&self,
lua: &'lua Lua,
abs_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>> {
let mut thread_recv = {
let pending = self
.pending
.try_lock()
.expect("RequireContext may not be used from multiple threads");
let thread_id = pending
.get(abs_path.as_ref())
.expect("Path is not currently pending require");
thread_id.subscribe()
};
thread_recv.recv().await.into_lua_err()?;
self.get_from_cache(lua, abs_path.as_ref())
}
async fn load(
&self,
lua: &Lua,
abs_path: impl AsRef<Path>,
rel_path: impl AsRef<Path>,
) -> LuaResult<LuaRegistryKey> {
let abs_path = abs_path.as_ref();
let rel_path = rel_path.as_ref();
// Read the file at the given path, try to parse and
// load it into a new lua thread that we can schedule
let file_contents = read(&abs_path).await?;
let file_thread = lua
.load(file_contents)
.set_name(rel_path.to_string_lossy().to_string());
// Schedule the thread to run, wait for it to finish running
let thread_id = lua.push_thread_back(file_thread, ())?;
lua.track_thread(thread_id);
lua.wait_for_thread(thread_id).await;
let thread_res = lua.get_thread_result(thread_id).unwrap();
// Return the result of the thread, storing any lua value(s) in the registry
match thread_res {
Err(e) => Err(e),
Ok(v) => {
let multi_vec = v.into_vec();
let multi_key = lua
.create_registry_value(multi_vec)
.expect("Failed to store require result in registry - out of memory");
Ok(multi_key)
}
}
}
/**
Loads (requires) the file at the given path.
*/
pub async fn load_with_caching<'lua>(
&self,
lua: &'lua Lua,
abs_path: impl AsRef<Path>,
rel_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>> {
let abs_path = abs_path.as_ref();
let rel_path = rel_path.as_ref();
// Set this abs path as currently pending
let (broadcast_tx, _) = broadcast::channel(1);
self.pending
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.insert(abs_path.to_path_buf(), broadcast_tx);
// Try to load at this abs path
let load_res = self.load(lua, abs_path, rel_path).await;
let load_val = match &load_res {
Err(e) => Err(e.clone()),
Ok(k) => {
let multi_vec = lua
.registry_value::<Vec<LuaValue>>(k)
.expect("Failed to fetch require result from registry");
Ok(LuaMultiValue::from_vec(multi_vec))
}
};
// NOTE: We use the async lock and not try_lock here because
// some other thread may be wanting to insert into the require
// cache at the same time, and that's not an actual error case
self.results
.lock()
.await
.insert(abs_path.to_path_buf(), load_res);
// Remove the pending thread id from the require context,
// broadcast a message to let any listeners know that this
// path has now finished the require process and is cached
let broadcast_tx = self
.pending
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.remove(abs_path)
.expect("Pending require broadcaster was unexpectedly removed");
broadcast_tx.send(()).ok();
load_val
}
/**
Loads (requires) the library with the given name.
*/
pub fn load_library<'lua>(
&self,
lua: &'lua Lua,
name: impl AsRef<str>,
) -> LuaResult<LuaMultiValue<'lua>> {
let library: LuneStandardLibrary = match name.as_ref().parse() {
Err(e) => return Err(LuaError::runtime(e)),
Ok(b) => b,
};
let mut cache = self
.libraries
.try_lock()
.expect("RequireContext may not be used from multiple threads");
if let Some(res) = cache.get(&library) {
return match res {
Err(e) => return Err(e.clone()),
Ok(key) => {
let multi_vec = lua
.registry_value::<Vec<LuaValue>>(key)
.expect("Missing library result in lua registry");
Ok(LuaMultiValue::from_vec(multi_vec))
}
};
};
let result = library.module(lua);
cache.insert(
library,
match result.clone() {
Err(e) => Err(e),
Ok(multi) => {
let multi_vec = multi.into_vec();
let multi_key = lua
.create_registry_value(multi_vec)
.expect("Failed to store require result in registry - out of memory");
Ok(multi_key)
}
},
);
result
}
}

View file

@ -1,14 +0,0 @@
use mlua::prelude::*;
use super::context::*;
pub(super) fn require<'lua, 'ctx>(
lua: &'lua Lua,
ctx: &'ctx RequireContext,
name: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
ctx.load_library(lua, name)
}

View file

@ -1,93 +0,0 @@
use mlua::prelude::*;
use lune_utils::TableBuilder;
mod context;
use context::RequireContext;
mod alias;
mod library;
mod path;
const REQUIRE_IMPL: &str = r"
return require(source(), ...)
";
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
lua.set_app_data(RequireContext::new());
/*
Require implementation needs a few workarounds:
- Async functions run outside of the lua resumption cycle,
so the current lua thread, as well as its stack/debug info
is not available, meaning we have to use a normal function
- Using the async require function directly in another lua function
would mean yielding across the metamethod/c-call boundary, meaning
we have to first load our two functions into a normal lua chunk
and then load that new chunk into our final require function
Also note that we inspect the stack at level 2:
1. The current c / rust function
2. The wrapper lua chunk defined above
3. The lua chunk we are require-ing from
*/
let require_fn = lua.create_async_function(require)?;
let get_source_fn = lua.create_function(move |lua, (): ()| match lua.inspect_stack(2) {
None => Err(LuaError::runtime(
"Failed to get stack info for require source",
)),
Some(info) => match info.source().source {
None => Err(LuaError::runtime(
"Stack info is missing source for require",
)),
Some(source) => lua.create_string(source.as_bytes()),
},
})?;
let require_env = TableBuilder::new(lua)?
.with_value("source", get_source_fn)?
.with_value("require", require_fn)?
.build_readonly()?;
lua.load(REQUIRE_IMPL)
.set_name("require")
.set_environment(require_env)
.into_function()?
.into_lua(lua)
}
async fn require<'lua>(
lua: &'lua Lua,
(source, path): (LuaString<'lua>, LuaString<'lua>),
) -> LuaResult<LuaMultiValue<'lua>> {
let source = source
.to_str()
.into_lua_err()
.context("Failed to parse require source as string")?
.to_string();
let path = path
.to_str()
.into_lua_err()
.context("Failed to parse require path as string")?
.to_string();
let context = lua
.app_data_ref()
.expect("Failed to get RequireContext from app data");
if let Some(builtin_name) = path.strip_prefix("@lune/").map(str::to_ascii_lowercase) {
library::require(lua, &context, &builtin_name)
} else if let Some(aliased_path) = path.strip_prefix('@') {
let (alias, path) = aliased_path.split_once('/').ok_or(LuaError::runtime(
"Require with custom alias must contain '/' delimiter",
))?;
alias::require(lua, &context, &source, alias, path).await
} else {
path::require(lua, &context, &source, &path).await
}
}

View file

@ -1,129 +0,0 @@
use std::path::{Path, PathBuf};
use mlua::prelude::*;
use mlua::Error::ExternalError;
use super::context::*;
pub(super) async fn require<'lua, 'ctx>(
lua: &'lua Lua,
ctx: &'ctx RequireContext,
source: &str,
path: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
let (abs_path, rel_path) = RequireContext::resolve_paths(source, path)?;
require_abs_rel(lua, ctx, abs_path, rel_path).await
}
pub(super) async fn require_abs_rel<'lua, 'ctx>(
lua: &'lua Lua,
ctx: &'ctx RequireContext,
abs_path: PathBuf, // Absolute to filesystem
rel_path: PathBuf, // Relative to CWD (for displaying)
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
// 1. Try to require the exact path
match require_inner(lua, ctx, &abs_path, &rel_path).await {
Ok(res) => return Ok(res),
Err(err) => {
if !is_file_not_found_error(&err) {
return Err(err);
}
}
}
// 2. Try to require the path with an added "luau" extension
// 3. Try to require the path with an added "lua" extension
for extension in ["luau", "lua"] {
match require_inner(
lua,
ctx,
&append_extension(&abs_path, extension),
&append_extension(&rel_path, extension),
)
.await
{
Ok(res) => return Ok(res),
Err(err) => {
if !is_file_not_found_error(&err) {
return Err(err);
}
}
}
}
// We didn't find any direct file paths, look
// for directories with "init" files in them...
let abs_init = abs_path.join("init");
let rel_init = rel_path.join("init");
// 4. Try to require the init path with an added "luau" extension
// 5. Try to require the init path with an added "lua" extension
for extension in ["luau", "lua"] {
match require_inner(
lua,
ctx,
&append_extension(&abs_init, extension),
&append_extension(&rel_init, extension),
)
.await
{
Ok(res) => return Ok(res),
Err(err) => {
if !is_file_not_found_error(&err) {
return Err(err);
}
}
}
}
// Nothing left to try, throw an error
Err(LuaError::runtime(format!(
"No file exists at the path '{}'",
rel_path.display()
)))
}
async fn require_inner<'lua, 'ctx>(
lua: &'lua Lua,
ctx: &'ctx RequireContext,
abs_path: impl AsRef<Path>,
rel_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
let abs_path = abs_path.as_ref();
let rel_path = rel_path.as_ref();
if ctx.is_cached(abs_path)? {
ctx.get_from_cache(lua, abs_path)
} else if ctx.is_pending(abs_path)? {
ctx.wait_for_cache(lua, &abs_path).await
} else {
ctx.load_with_caching(lua, &abs_path, &rel_path).await
}
}
fn append_extension(path: impl Into<PathBuf>, ext: &'static str) -> PathBuf {
let mut new = path.into();
match new.extension() {
// FUTURE: There's probably a better way to do this than converting to a lossy string
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
None => new.set_extension(ext),
};
new
}
fn is_file_not_found_error(err: &LuaError) -> bool {
if let ExternalError(err) = err {
err.as_ref().downcast_ref::<std::io::Error>().is_some()
} else {
false
}
}

View file

@ -1,35 +0,0 @@
use mlua::prelude::*;
use lune_utils::get_version_string;
struct Version(String);
impl LuaUserData for Version {}
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
let v = match lua.app_data_ref::<Version>() {
Some(v) => v.0.to_string(),
None => env!("CARGO_PKG_VERSION").to_string(),
};
let s = get_version_string(v);
lua.create_string(s)?.into_lua(lua)
}
/**
Overrides the version string to be used by the `_VERSION` global.
The global will be a string in the format `Lune x.y.z+luau`,
where `x.y.z` is the string passed to this function.
The version string passed should be the version of the Lune runtime,
obtained from `env!("CARGO_PKG_VERSION")` or a similar mechanism.
# Panics
Panics if the version string is empty or contains invalid characters.
*/
pub fn set_global_version(lua: &Lua, version: impl Into<String>) {
let v = version.into();
let _ = get_version_string(&v); // Validate version string
lua.set_app_data(Version(v));
}

View file

@ -1,23 +0,0 @@
use std::io::Write;
use lune_utils::fmt::{pretty_format_multi_value, Label, ValueFormatConfig};
use mlua::prelude::*;
const FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new()
.with_max_depth(4)
.with_colors_enabled(true);
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
let f = lua.create_function(|_, args: LuaMultiValue| {
let formatted = format!(
"{}\n{}\n",
Label::Warn,
pretty_format_multi_value(&args, &FORMAT_CONFIG)
);
let mut stdout = std::io::stdout();
stdout.write_all(formatted.as_bytes())?;
stdout.flush()?;
Ok(())
})?;
f.into_lua(lua)
}

View file

@ -1,29 +0,0 @@
#![allow(clippy::cargo_common_metadata)]
use mlua::prelude::*;
mod global;
mod globals;
mod library;
mod luaurc;
pub use self::global::LuneStandardGlobal;
pub use self::globals::version::set_global_version;
pub use self::library::LuneStandardLibrary;
/**
Injects all standard globals into the given Lua state / VM.
This includes all enabled standard libraries, which can
be used from Lua with `require("@lune/library-name")`.
# Errors
Errors when out of memory, or if *default* Lua globals are missing.
*/
pub fn inject_globals(lua: &Lua) -> LuaResult<()> {
for global in LuneStandardGlobal::ALL {
lua.globals().set(global.name(), global.create(lua)?)?;
}
Ok(())
}

View file

@ -1,127 +0,0 @@
use std::str::FromStr;
use mlua::prelude::*;
/**
A standard library provided by Lune.
*/
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[rustfmt::skip]
pub enum LuneStandardLibrary {
#[cfg(feature = "datetime")] DateTime,
#[cfg(feature = "fs")] Fs,
#[cfg(feature = "luau")] Luau,
#[cfg(feature = "net")] Net,
#[cfg(feature = "task")] Task,
#[cfg(feature = "process")] Process,
#[cfg(feature = "regex")] Regex,
#[cfg(feature = "serde")] Serde,
#[cfg(feature = "stdio")] Stdio,
#[cfg(feature = "roblox")] Roblox,
}
impl LuneStandardLibrary {
/**
All available standard libraries.
*/
#[rustfmt::skip]
pub const ALL: &'static [Self] = &[
#[cfg(feature = "datetime")] Self::DateTime,
#[cfg(feature = "fs")] Self::Fs,
#[cfg(feature = "luau")] Self::Luau,
#[cfg(feature = "net")] Self::Net,
#[cfg(feature = "task")] Self::Task,
#[cfg(feature = "process")] Self::Process,
#[cfg(feature = "regex")] Self::Regex,
#[cfg(feature = "serde")] Self::Serde,
#[cfg(feature = "stdio")] Self::Stdio,
#[cfg(feature = "roblox")] Self::Roblox,
];
/**
Gets the name of the library, such as `datetime` or `fs`.
*/
#[must_use]
#[rustfmt::skip]
#[allow(unreachable_patterns)]
pub fn name(&self) -> &'static str {
match self {
#[cfg(feature = "datetime")] Self::DateTime => "datetime",
#[cfg(feature = "fs")] Self::Fs => "fs",
#[cfg(feature = "luau")] Self::Luau => "luau",
#[cfg(feature = "net")] Self::Net => "net",
#[cfg(feature = "task")] Self::Task => "task",
#[cfg(feature = "process")] Self::Process => "process",
#[cfg(feature = "regex")] Self::Regex => "regex",
#[cfg(feature = "serde")] Self::Serde => "serde",
#[cfg(feature = "stdio")] Self::Stdio => "stdio",
#[cfg(feature = "roblox")] Self::Roblox => "roblox",
_ => unreachable!("no standard library enabled"),
}
}
/**
Creates the Lua module for the library.
# Errors
If the library could not be created.
*/
#[rustfmt::skip]
#[allow(unreachable_patterns)]
pub fn module<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaMultiValue<'lua>> {
let res: LuaResult<LuaTable> = match self {
#[cfg(feature = "datetime")] Self::DateTime => lune_std_datetime::module(lua),
#[cfg(feature = "fs")] Self::Fs => lune_std_fs::module(lua),
#[cfg(feature = "luau")] Self::Luau => lune_std_luau::module(lua),
#[cfg(feature = "net")] Self::Net => lune_std_net::module(lua),
#[cfg(feature = "task")] Self::Task => lune_std_task::module(lua),
#[cfg(feature = "process")] Self::Process => lune_std_process::module(lua),
#[cfg(feature = "regex")] Self::Regex => lune_std_regex::module(lua),
#[cfg(feature = "serde")] Self::Serde => lune_std_serde::module(lua),
#[cfg(feature = "stdio")] Self::Stdio => lune_std_stdio::module(lua),
#[cfg(feature = "roblox")] Self::Roblox => lune_std_roblox::module(lua),
_ => unreachable!("no standard library enabled"),
};
match res {
Ok(v) => v.into_lua_multi(lua),
Err(e) => Err(e.context(format!(
"Failed to create standard library '{}'",
self.name()
))),
}
}
}
impl FromStr for LuneStandardLibrary {
type Err = String;
#[rustfmt::skip]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let low = s.trim().to_ascii_lowercase();
Ok(match low.as_str() {
#[cfg(feature = "datetime")] "datetime" => Self::DateTime,
#[cfg(feature = "fs")] "fs" => Self::Fs,
#[cfg(feature = "luau")] "luau" => Self::Luau,
#[cfg(feature = "net")] "net" => Self::Net,
#[cfg(feature = "task")] "task" => Self::Task,
#[cfg(feature = "process")] "process" => Self::Process,
#[cfg(feature = "regex")] "regex" => Self::Regex,
#[cfg(feature = "serde")] "serde" => Self::Serde,
#[cfg(feature = "stdio")] "stdio" => Self::Stdio,
#[cfg(feature = "roblox")] "roblox" => Self::Roblox,
_ => {
return Err(format!(
"Unknown standard library '{low}'\nValid libraries are: {}",
Self::ALL
.iter()
.map(Self::name)
.collect::<Vec<_>>()
.join(", ")
))
}
})
}
}

View file

@ -1,164 +0,0 @@
use std::{
collections::HashMap,
path::{Path, PathBuf, MAIN_SEPARATOR},
sync::Arc,
};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use tokio::fs::read;
use lune_utils::path::{clean_path, clean_path_and_make_absolute};
const LUAURC_FILE: &str = ".luaurc";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum LuauLanguageMode {
NoCheck,
NonStrict,
Strict,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LuauRcConfig {
#[serde(skip_serializing_if = "Option::is_none")]
language_mode: Option<LuauLanguageMode>,
#[serde(skip_serializing_if = "Option::is_none")]
lint: Option<HashMap<String, JsonValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
lint_errors: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
type_errors: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
globals: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
paths: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
aliases: Option<HashMap<String, String>>,
}
/**
A deserialized `.luaurc` file.
Contains utility methods for validating and searching for aliases.
*/
#[derive(Debug, Clone)]
pub struct LuauRc {
dir: Arc<Path>,
config: LuauRcConfig,
}
impl LuauRc {
/**
Reads a `.luaurc` file from the given directory.
If the file does not exist, or if it is invalid, this function returns `None`.
*/
pub async fn read(dir: impl AsRef<Path>) -> Option<Self> {
let dir = clean_path_and_make_absolute(dir);
let path = dir.join(LUAURC_FILE);
let bytes = read(&path).await.ok()?;
let config = serde_json::from_slice(&bytes).ok()?;
Some(Self {
dir: dir.into(),
config,
})
}
/**
Reads a `.luaurc` file from the given directory, and then recursively searches
for a `.luaurc` file in the parent directories if a predicate is not satisfied.
If no `.luaurc` file exists, or if they are invalid, this function returns `None`.
*/
pub async fn read_recursive(
dir: impl AsRef<Path>,
mut predicate: impl FnMut(&Self) -> bool,
) -> Option<Self> {
let mut current = clean_path_and_make_absolute(dir);
loop {
if let Some(rc) = Self::read(&current).await {
if predicate(&rc) {
return Some(rc);
}
}
if let Some(parent) = current.parent() {
current = parent.to_path_buf();
} else {
return None;
}
}
}
/**
Validates that the `.luaurc` file is correct.
This primarily validates aliases since they are not
validated during creation of the [`LuauRc`] struct.
# Errors
If an alias key is invalid.
*/
pub fn validate(&self) -> Result<(), String> {
if let Some(aliases) = &self.config.aliases {
for alias in aliases.keys() {
if !is_valid_alias_key(alias) {
return Err(format!("invalid alias key: {alias}"));
}
}
}
Ok(())
}
/**
Gets a copy of all aliases in the `.luaurc` file.
Will return an empty map if there are no aliases.
*/
#[must_use]
pub fn aliases(&self) -> HashMap<String, String> {
self.config.aliases.clone().unwrap_or_default()
}
/**
Finds an alias in the `.luaurc` file by name.
If the alias does not exist, this function returns `None`.
*/
#[must_use]
pub fn find_alias(&self, name: &str) -> Option<PathBuf> {
self.config.aliases.as_ref().and_then(|aliases| {
aliases.iter().find_map(|(alias, path)| {
if alias
.trim_end_matches(MAIN_SEPARATOR)
.eq_ignore_ascii_case(name)
&& is_valid_alias_key(alias)
{
Some(clean_path(self.dir.join(path)))
} else {
None
}
})
})
}
}
fn is_valid_alias_key(alias: impl AsRef<str>) -> bool {
let alias = alias.as_ref();
if alias.is_empty()
|| alias.starts_with('.')
|| alias.starts_with("..")
|| alias.chars().any(|c| c == MAIN_SEPARATOR)
{
false // Paths are not valid alias keys
} else {
alias.chars().all(is_valid_alias_char)
}
}
fn is_valid_alias_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.'
}

View file

@ -1,26 +0,0 @@
[package]
name = "lune-utils"
version = "0.1.3"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
description = "Utilities library for Lune"
[lib]
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mlua = { version = "0.9.9", features = ["luau", "async"] }
tokio = { version = "1", default-features = false, features = ["fs"] }
console = "0.15"
dunce = "1.0"
once_cell = "1.17"
path-clean = "1.0"
pathdiff = "0.2"
parking_lot = "0.12.3"
semver = "1.0"

View file

@ -1,196 +0,0 @@
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use console::style;
use mlua::prelude::*;
use once_cell::sync::Lazy;
use super::StackTrace;
static STYLED_STACK_BEGIN: Lazy<String> = Lazy::new(|| {
format!(
"{}{}{}",
style("[").dim(),
style("Stack Begin").blue(),
style("]").dim()
)
});
static STYLED_STACK_END: Lazy<String> = Lazy::new(|| {
format!(
"{}{}{}",
style("[").dim(),
style("Stack End").blue(),
style("]").dim()
)
});
// NOTE: We indent using 4 spaces instead of tabs since
// these errors are most likely to be displayed in a terminal
// or some kind of live output - and tabs don't work well there
const STACK_TRACE_INDENT: &str = " ";
/**
Error components parsed from a [`LuaError`].
Can be used to display a human-friendly error message
and stack trace, in the following Roblox-inspired format:
```plaintext
Error message
[Stack Begin]
Stack trace line
Stack trace line
Stack trace line
[Stack End]
```
*/
#[derive(Debug, Default, Clone)]
pub struct ErrorComponents {
messages: Vec<String>,
trace: Option<StackTrace>,
}
impl ErrorComponents {
/**
Returns the error messages.
*/
#[must_use]
pub fn messages(&self) -> &[String] {
&self.messages
}
/**
Returns the stack trace, if it exists.
*/
#[must_use]
pub fn trace(&self) -> Option<&StackTrace> {
self.trace.as_ref()
}
/**
Returns `true` if the error has a non-empty stack trace.
Note that a trace may still *exist*, but it may be empty.
*/
#[must_use]
pub fn has_trace(&self) -> bool {
self.trace
.as_ref()
.is_some_and(|trace| !trace.lines().is_empty())
}
}
impl fmt::Display for ErrorComponents {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for message in self.messages() {
writeln!(f, "{message}")?;
}
if self.has_trace() {
let trace = self.trace.as_ref().unwrap();
writeln!(f, "{}", *STYLED_STACK_BEGIN)?;
for line in trace.lines() {
writeln!(f, "{STACK_TRACE_INDENT}{line}")?;
}
writeln!(f, "{}", *STYLED_STACK_END)?;
}
Ok(())
}
}
impl From<LuaError> for ErrorComponents {
fn from(error: LuaError) -> Self {
fn lua_error_message(e: &LuaError) -> String {
if let LuaError::RuntimeError(s) = e {
s.to_string()
} else {
e.to_string()
}
}
fn lua_stack_trace(source: &str) -> Option<StackTrace> {
// FUTURE: Preserve a parsing error here somehow?
// Maybe we can emit parsing errors using tracing?
StackTrace::from_str(source).ok()
}
// Extract any additional "context" messages before the actual error(s)
// The Arc is necessary here because mlua wraps all inner errors in an Arc
let mut error = Arc::new(error);
let mut messages = Vec::new();
while let LuaError::WithContext {
ref context,
ref cause,
} = *error
{
messages.push(context.to_string());
error = cause.clone();
}
// We will then try to extract any stack trace
let mut trace = if let LuaError::CallbackError {
ref traceback,
ref cause,
} = *error
{
messages.push(lua_error_message(cause));
lua_stack_trace(traceback)
} else if let LuaError::RuntimeError(ref s) = *error {
// NOTE: Runtime errors may include tracebacks, but they're
// joined with error messages, so we need to split them out
if let Some(pos) = s.find("stack traceback:") {
let (message, traceback) = s.split_at(pos);
messages.push(message.trim().to_string());
lua_stack_trace(traceback)
} else {
messages.push(s.to_string());
None
}
} else {
messages.push(lua_error_message(&error));
None
};
// Sometimes, we can get duplicate stack trace lines that only
// mention "[C]", without a function name or path, and these can
// be safely ignored / removed if the following line has more info
if let Some(trace) = &mut trace {
let lines = trace.lines_mut();
loop {
let first_is_c_and_empty = lines
.first()
.is_some_and(|line| line.source().is_c() && line.is_empty());
let second_is_c_and_nonempty = lines
.get(1)
.is_some_and(|line| line.source().is_c() && !line.is_empty());
if first_is_c_and_empty && second_is_c_and_nonempty {
lines.remove(0);
} else {
break;
}
}
}
// Finally, we do some light postprocessing to remove duplicate
// information, such as the location prefix in the error message
if let Some(message) = messages.last_mut() {
if let Some(line) = trace
.iter()
.flat_map(StackTrace::lines)
.find(|line| line.source().is_lua())
{
let location_prefix = format!(
"[string \"{}\"]:{}:",
line.path().unwrap(),
line.line_number().unwrap()
);
if message.starts_with(&location_prefix) {
*message = message[location_prefix.len()..].trim().to_string();
}
}
}
ErrorComponents { messages, trace }
}
}

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