[
  {
    "path": ".cargo/config.toml",
    "content": "# Statically link the vcruntime\n# https://users.rust-lang.org/t/static-vcruntime-distribute-windows-msvc-binaries-without-needing-to-deploy-vcruntime-dll/57599\n[target.'cfg(all(windows, target_env = \"msvc\"))']\nrustflags = [\n\t\"-C\",\n\t\"link-args=/DEFAULTLIB:ucrt.lib /DEFAULTLIB:libvcruntime.lib libcmt.lib\",\n\t\"-C\",\n\t\"link-args=/NODEFAULTLIB:libvcruntimed.lib /NODEFAULTLIB:vcruntime.lib /NODEFAULTLIB:vcruntimed.lib\",\n\t\"-C\",\n\t\"link-args=/NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:msvcrtd.lib\",\n\t\"-C\",\n\t\"link-args=/NODEFAULTLIB:libucrt.lib /NODEFAULTLIB:libucrtd.lib /NODEFAULTLIB:ucrtd.lib\",\n]\n\n[target.aarch64-unknown-linux-gnu]\nlinker = \"aarch64-linux-gnu-gcc\"\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{json,jsonc,json5}]\nindent_style = space\nindent_size = 4\n\n[*.{yml,yaml,md}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n\n# Ensure all lua files use LF\n*.lua  eol=lf\n*.luau eol=lf\n\n# Ensure all txt files within tests use LF\ntests/**/*.txt eol=lf\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\ndefaults:\n  run:\n    shell: bash\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  fmt:\n    name: Check formatting\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          components: rustfmt\n\n      - name: Install Tooling\n        uses: CompeyDev/setup-rokit@v0.1.2\n\n      - name: Check Formatting\n        run: ./scripts/format-check.sh\n\n  analyze:\n    needs: [\"fmt\"]\n    name: Analyze and lint Luau files\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install Tooling\n        uses: CompeyDev/setup-rokit@v0.1.2\n\n      - name: Analyze\n        run: ./scripts/analyze.sh\n\n  ci:\n    needs: [\"fmt\"]\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: Windows x86_64\n            runner-os: windows-2022\n            cargo-target: x86_64-pc-windows-msvc\n\n          - name: Windows aarch64\n            runner-os: windows-11-arm\n            cargo-target: aarch64-pc-windows-msvc\n\n          - name: Linux x86_64\n            runner-os: ubuntu-22.04\n            cargo-target: x86_64-unknown-linux-gnu\n\n          - name: Linux aarch64\n            runner-os: ubuntu-22.04-arm\n            cargo-target: aarch64-unknown-linux-gnu\n\n          - name: macOS x86_64\n            runner-os: macos-15\n            cargo-target: x86_64-apple-darwin\n\n          - name: macOS aarch64\n            runner-os: macos-15\n            cargo-target: aarch64-apple-darwin\n\n    name: CI - ${{ matrix.name }}\n    runs-on: ${{ matrix.runner-os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          submodules: true\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n          targets: ${{ matrix.cargo-target }}\n\n      - name: Install binstall\n        uses: cargo-bins/cargo-binstall@main\n\n      - name: Install nextest\n        run: cargo binstall cargo-nextest\n\n      - name: Build\n        run: |\n          cargo build --workspace \\\n          --locked --all-features \\\n          --target ${{ matrix.cargo-target }}\n\n      - name: Lint\n        run: |\n          cargo clippy --workspace \\\n          --locked --all-features \\\n          --target ${{ matrix.cargo-target }}\n\n      - name: Test\n        run: |\n          cargo nextest run --no-fail-fast \\\n          --locked --all-features \\\n          --target ${{ matrix.cargo-target }}\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\n\non:\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  init:\n    name: Init\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.get_version.outputs.value }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Get version from manifest\n        id: get_version\n        run: |\n          PACKAGE_VERSION=$(./scripts/get-version.sh)\n          echo \"value=${PACKAGE_VERSION}\" >> $GITHUB_OUTPUT\n\n  dry-run:\n    name: Dry-run\n    needs: [\"init\"]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Publish (dry-run)\n        uses: katyo/publish-crates@v2\n        with:\n          dry-run: true\n          check-repo: true\n          registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n\n  build:\n    needs: [\"init\", \"dry-run\"]\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: Windows x86_64\n            runner-os: windows-2022\n            artifact-name: lune-${{ needs.init.outputs.version }}-windows-x86_64\n            cargo-target: x86_64-pc-windows-msvc\n\n          - name: Windows aarch64\n            runner-os: windows-11-arm\n            artifact-name: lune-${{ needs.init.outputs.version }}-windows-aarch64\n            cargo-target: aarch64-pc-windows-msvc\n\n          - name: Linux x86_64\n            runner-os: ubuntu-22.04\n            artifact-name: lune-${{ needs.init.outputs.version }}-linux-x86_64\n            cargo-target: x86_64-unknown-linux-gnu\n\n          - name: Linux aarch64\n            runner-os: ubuntu-22.04-arm\n            artifact-name: lune-${{ needs.init.outputs.version }}-linux-aarch64\n            cargo-target: aarch64-unknown-linux-gnu\n\n          - name: macOS x86_64\n            runner-os: macos-15\n            artifact-name: lune-${{ needs.init.outputs.version }}-macos-x86_64\n            cargo-target: x86_64-apple-darwin\n\n          - name: macOS aarch64\n            runner-os: macos-15\n            artifact-name: lune-${{ needs.init.outputs.version }}-macos-aarch64\n            cargo-target: aarch64-apple-darwin\n\n    name: Build - ${{ matrix.name }}\n    runs-on: ${{ matrix.runner-os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.cargo-target }}\n\n      - name: Build binary\n        run: |\n          cargo build \\\n          --locked --all-features \\\n          --release --target ${{ matrix.cargo-target }}\n\n      - name: Create release archive\n        run: ./scripts/zip-release.sh ${{ matrix.cargo-target }}\n\n      - name: Upload release artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.artifact-name }}\n          path: release.zip\n\n  release-github:\n    name: Release (GitHub)\n    runs-on: ubuntu-latest\n    needs: [\"init\", \"dry-run\", \"build\"]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Download releases\n        uses: actions/download-artifact@v4\n        with:\n          path: ./releases\n\n      - name: Unpack releases\n        run: ./scripts/unpack-releases.sh \"./releases\"\n\n      - name: Create release\n        uses: softprops/action-gh-release@v2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          name: ${{ needs.init.outputs.version }}\n          tag_name: v${{ needs.init.outputs.version }}\n          fail_on_unmatched_files: true\n          files: ./releases/*.zip\n          draft: true\n\n  release-crates:\n    name: Release (crates.io)\n    runs-on: ubuntu-latest\n    needs: [\"init\", \"dry-run\", \"build\"]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Publish crates\n        uses: katyo/publish-crates@v2\n        with:\n          dry-run: false\n          check-repo: true\n          registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Annoying macOS finder stuff\n\n/.DS_Store\n/**/.DS_Store\n\n# Autogenerated dirs\n\n/bin\n/out\n/target\n/staging\n\n/**/bin\n/**/out\n/**/target\n/**/staging\n\n# Autogenerated files\n\nlune.yml\nluneDocs.json\nluneTypes.d.luau\n\n# Dirs generated by runtime or build scripts\n\n/types\n\n# Files generated by runtime or build scripts\n\nscripts/brick_color.rs\nscripts/font_enum_map.rs\nscripts/physical_properties_enum_map.rs\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"tests/roblox/rbx-test-files\"]\n\tpath = tests/roblox/rbx-test-files\n\turl = https://github.com/rojo-rbx/rbx-test-files\n"
  },
  {
    "path": ".luaurc",
    "content": "{\n  \"languageMode\": \"strict\",\n  \"lint\": {\n    \"*\": true\n  },\n  \"lintErrors\": false,\n  \"typeErrors\": true,\n  \"globals\": [\n    \"warn\"\n  ],\n  \"aliases\": {\n    \"lune\": \"./types/\",\n    \"tests\": \"./tests\",\n    \"require-tests\": \"./tests/require/tests\"\n  }\n}\n"
  },
  {
    "path": ".lune/csv_printer.luau",
    "content": "--> A utility script that prints out a CSV\n--> file in a prettified format to stdout\n\nlocal LINE_SEPARATOR = \"\\n\"\nlocal COMMA_SEPARATOR = \",\"\n\nlocal fs = require(\"@lune/fs\")\nlocal process = require(\"@lune/process\")\n\nlocal path = process.args[1] or \".lune/data/test.csv\"\n\nassert(path ~= nil and #path > 0, \"No input file path was given\")\nassert(not fs.isDir(path), \"Input file path was a dir, not a file\")\nassert(fs.isFile(path), \"Input file path does not exist\")\n\n-- Read all the lines of the wanted file, and then split\n-- out the raw lines containing comma-separated values\n\nlocal csvTable = {}\nfor index, rawLine in string.split(fs.readFile(path), LINE_SEPARATOR) do\n\tif #rawLine > 0 then\n\t\tcsvTable[index] = string.split(rawLine, COMMA_SEPARATOR)\n\tend\nend\n\n-- Gather the maximum widths of strings\n-- for alignment & spacing in advance\n\nlocal maxWidths = {}\nfor _, row in csvTable do\n\tfor index, value in row do\n\t\tmaxWidths[index] = math.max(maxWidths[index] or 0, #value)\n\tend\nend\n\nlocal totalWidth = 0\nlocal totalColumns = 0\nfor _, width in maxWidths do\n\ttotalWidth += width\n\ttotalColumns += 1\nend\n\n-- We have everything we need, print it out with\n-- the help of some unicode box drawing characters\n\nlocal thiccLine = string.rep(\"━\", totalWidth + totalColumns * 3 - 1)\n\nprint(string.format(\"┏%s┓\", thiccLine))\n\nfor rowIndex, row in csvTable do\n\tlocal paddedValues = {}\n\tfor valueIndex, value in row do\n\t\tlocal spacing = string.rep(\" \", maxWidths[valueIndex] - #value)\n\t\ttable.insert(paddedValues, value .. spacing)\n\tend\n\tprint(string.format(\"┃ %s ┃\", table.concat(paddedValues, \" ┃ \")))\n\t-- The first line is the header, we should\n\t-- print out an extra separator below it\n\tif rowIndex == 1 then\n\t\tprint(string.format(\"┣%s┫\", thiccLine))\n\tend\nend\nprint(string.format(\"┗%s┛\", thiccLine))\n"
  },
  {
    "path": ".lune/data/test.csv",
    "content": "Header1,Header2,Header3\nHello,World,!\n1,2,3\nFoo,Bar,Baz\n"
  },
  {
    "path": ".lune/hello_lune.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal serde = require(\"@lune/serde\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\n--[[\n\tEXAMPLE #1\n\n\tUsing arguments given to the program\n]]\n\nif #process.args > 0 then\n\tprint(\"Got arguments:\")\n\tprint(process.args)\n\tif #process.args > 3 then\n\t\terror(\"Too many arguments!\")\n\tend\nelse\n\tprint(\"Got no arguments ☹️\")\nend\n\n--[[\n\tEXAMPLE #2\n\n\tUsing the stdio library to prompt for terminal input\n]]\n\nlocal text = stdio.prompt(\"text\", \"Please write some text\")\n\nprint(\"You wrote '\" .. text .. \"'!\")\n\nlocal confirmed = stdio.prompt(\"confirm\", \"Please confirm that you wrote some text\")\nif confirmed == false then\n\terror(\"You didn't confirm!\")\nelse\n\tprint(\"Confirmed!\")\nend\n\n--[[\n\tEXAMPLE #3\n\n\tGet & set environment variables\n\n\tChecks if environment variables are empty or not,\n\tprints out ❌ if empty and ✅ if they have a value\n]]\n\nprint(\"Reading current environment 🔎\")\n\n-- Environment variables can be read directly\nassert(process.env.PATH ~= nil, \"Missing PATH\")\nassert(process.env.PWD ~= nil, \"Missing PWD\")\n\n-- And they can also be accessed using Luau's generalized iteration (but not pairs())\nfor key, value in process.env do\n\tlocal box = if value and value ~= \"\" then \"✅\" else \"❌\"\n\tprint(string.format(\"[%s] %s\", box, key))\nend\n\n--[[\n\tEXAMPLE #4\n\n\tSpawning concurrent tasks\n\n\tThese tasks will run at the same time as other Lua code which lets you do primitive multitasking\n]]\n\ntask.spawn(function()\n\tprint(\"Spawned a task that will run instantly but not block\")\n\ttask.wait(5)\nend)\n\nprint(\"Spawning a delayed task that will run in 5 seconds\")\ntask.delay(5, function()\n\tprint(\"...\")\n\ttask.wait(1)\n\tprint(\"Hello again!\")\n\ttask.wait(1)\n\tprint(\"Goodbye again! 🌙\")\nend)\n\n--[[\n\tEXAMPLE #5\n\n\tRead files in the current directory\n\n\tThis prints out directory & file names with some fancy icons\n]]\n\nprint(\"Reading current dir 🗂️\")\nlocal entries = fs.readDir(\".\")\n\n-- NOTE: We have to do this outside of the sort function\n-- to avoid yielding across the metamethod boundary, all\n-- of the filesystem APIs are asynchronous and yielding\nlocal entryIsDir = {}\nfor _, entry in entries do\n\tentryIsDir[entry] = fs.isDir(entry)\nend\n\n-- Sort prioritizing directories first, then alphabetically\ntable.sort(entries, function(entry0, entry1)\n\tif entryIsDir[entry0] ~= entryIsDir[entry1] then\n\t\treturn entryIsDir[entry0]\n\tend\n\treturn entry0 < entry1\nend)\n\n-- Make sure we got some known files that should always exist\nassert(table.find(entries, \"Cargo.toml\") ~= nil, \"Missing Cargo.toml\")\nassert(table.find(entries, \"Cargo.lock\") ~= nil, \"Missing Cargo.lock\")\n\n-- Print the pretty stuff\nfor _, entry in entries do\n\tif fs.isDir(entry) then\n\t\tprint(\"📁 \" .. entry)\n\telse\n\t\tprint(\"📄 \" .. entry)\n\tend\nend\n\n--[[\n\tEXAMPLE #6\n\n\tCall out to another program / executable\n\n\tYou can also get creative and combine this with example #6 to spawn several programs at the same time!\n]]\n\nprint(\"Sending 4 pings to google 🌏\")\nlocal result = process.exec(\"ping\", {\n\t\"google.com\",\n\t\"-c 4\",\n})\n\n--[[\n\tEXAMPLE #7\n\n\tUsing the result of a spawned process, exiting the process\n\n\tThis looks scary with lots of weird symbols, but, it's just some Lua-style pattern matching\n\tto parse the lines of \"min/avg/max/stddev = W/X/Y/Z ms\" that the ping program outputs to us\n]]\n\nif result.ok then\n\tassert(#result.stdout > 0, \"Result output was empty\")\n\tlocal min, avg, max, stddev =\n\t\tstring.match(result.stdout, \"min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms\")\n\tprint(string.format(\"Minimum ping time: %.3fms\", assert(tonumber(min))))\n\tprint(string.format(\"Maximum ping time: %.3fms\", assert(tonumber(max))))\n\tprint(string.format(\"Average ping time: %.3fms\", assert(tonumber(avg))))\n\tprint(string.format(\"Standard deviation: %.3fms\", assert(tonumber(stddev))))\nelse\n\tprint(\"Failed to send ping to google!\")\n\tprint(result.stderr)\n\tprocess.exit(result.code)\nend\n\n--[[\n\tEXAMPLE #8\n\n\tUsing the built-in networking library, encoding & decoding json\n]]\n\nprint(\"Sending PATCH request to web API 📤\")\nlocal apiResult = net.request({\n\turl = \"https://jsonplaceholder.typicode.com/posts/1\",\n\tmethod = \"PATCH\",\n\theaders = {\n\t\t[\"Content-Type\"] = \"application/json\",\n\t} :: { [string]: string },\n\tbody = serde.encode(\"json\", {\n\t\ttitle = \"foo\",\n\t\tbody = \"bar\",\n\t}),\n})\n\nif not apiResult.ok then\n\tprint(\"Failed to send network request!\")\n\tprint(string.format(\"%d (%s)\", apiResult.statusCode, apiResult.statusMessage))\n\tprint(apiResult.body)\n\tprocess.exit(1)\nend\n\ntype ApiResponse = {\n\tid: number,\n\ttitle: string,\n\tbody: string,\n\tuserId: number,\n}\n\nlocal apiResponse: ApiResponse = serde.decode(\"json\", apiResult.body)\nassert(apiResponse.title == \"foo\", \"Invalid json response\")\nassert(apiResponse.body == \"bar\", \"Invalid json response\")\nprint(\"Got valid JSON response with changes applied\")\n\n--[[\n\tEXAMPLE #9\n\n\tUsing the stdio library to print pretty\n]]\n\nprint(\"Printing with pretty colors and auto-formatting 🎨\")\n\nprint(stdio.color(\"blue\") .. string.rep(\"—\", 22) .. stdio.color(\"reset\"))\n\nprint(\"API response:\", apiResponse)\nwarn({\n\tOh = {\n\t\tNo = {\n\t\t\tTooMuch = {\n\t\t\t\tNesting = {\n\t\t\t\t\t\"Will not print\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n})\n\nprint(stdio.color(\"blue\") .. string.rep(\"—\", 22) .. stdio.color(\"reset\"))\n\n--[[\n\tEXAMPLE #10\n\n\tSaying goodbye 😔\n]]\n\nprint(\"Goodbye, lune! 🌙\")\n"
  },
  {
    "path": ".lune/http_server.luau",
    "content": "--> A basic http server that echoes the given request\n--> body at /ping and otherwise responds 404 \"Not Found\"\n\nlocal net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\n\nlocal PORT = if process.env.PORT ~= nil and #process.env.PORT > 0\n\tthen assert(tonumber(process.env.PORT), \"Failed to parse port from env\")\n\telse 8080\n\n-- Create our responder functions\n\nlocal function root(_request: net.ServeRequest): string\n\treturn `Hello from Lune server!`\nend\n\nlocal function pong(request: net.ServeRequest): string\n\treturn `Pong!\\n{request.path}\\n{request.body}`\nend\n\nlocal function teapot(_request: net.ServeRequest): net.ServeResponse\n\treturn {\n\t\tstatus = 418,\n\t\tbody = \"🫖\",\n\t}\nend\n\nlocal function notFound(_request: net.ServeRequest): net.ServeResponse\n\treturn {\n\t\tstatus = 404,\n\t\tbody = \"Not Found\",\n\t}\nend\n\n-- Run the server on the port forever\n\nnet.serve(PORT, function(request)\n\tif request.path == \"/\" then\n\t\treturn root(request)\n\telseif string.sub(request.path, 1, 5) == \"/ping\" then\n\t\treturn pong(request)\n\telseif string.sub(request.path, 1, 7) == \"/teapot\" then\n\t\treturn teapot(request)\n\telse\n\t\treturn notFound(request)\n\tend\nend)\n\nprint(`Listening on port {PORT} 🚀`)\nprint(\"Press Ctrl+C to stop\")\n"
  },
  {
    "path": ".lune/websocket_client.luau",
    "content": "--> A basic web socket client that communicates with an echo server\n\nlocal net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = if process.env.PORT ~= nil and #process.env.PORT > 0\n\tthen assert(tonumber(process.env.PORT), \"Failed to parse port from env\")\n\telse 8080\n\nlocal URL = `ws://127.0.0.1:{PORT}`\n\n-- Connect to our web socket server\n\nlocal socket = net.socket(URL)\n\nprint(\"Connected to echo web socket server at '\" .. URL .. \"'\")\nprint(\"Sending a message every second for 5 seconds...\")\n\n-- Force exit after 10 seconds in case the server is not responding well\n\nlocal forceExit = task.delay(10, function()\n\twarn(\"Example did not complete in time, exiting...\")\n\tprocess.exit(1)\nend)\n\n-- Send one message per second and time it\n\nfor _ = 1, 5 do\n\tlocal start = os.clock()\n\tsocket:send(tostring(1))\n\tlocal response = socket:next()\n\tlocal elapsed = os.clock() - start\n\tprint(`Got response '{response}' in {elapsed * 1_000} milliseconds`)\n\ttask.wait(1 - elapsed)\nend\n\n-- Everything went well, and we are done with the socket, so we can close it\n\nprint(\"Closing web socket...\")\nsocket:close()\n\ntask.cancel(forceExit)\nprint(\"Done! 🌙\")\n"
  },
  {
    "path": ".lune/websocket_server.luau",
    "content": "--> A basic web socket server that echoes given messages\n\nlocal net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = if process.env.PORT ~= nil and #process.env.PORT > 0\n\tthen assert(tonumber(process.env.PORT), \"Failed to parse port from env\")\n\telse 8080\n\n-- Run the server on port 8080, if we get a normal http request on\n-- the port this will respond with 426 Upgrade Required by default\n\nlocal handle = net.serve(PORT, {\n\thandleWebSocket = function(socket)\n\t\tprint(\"Got new web socket connection!\")\n\t\trepeat\n\t\t\tlocal message = socket:next()\n\t\t\tif message ~= nil then\n\t\t\t\tsocket:send(\"Echo - \" .. message)\n\t\t\tend\n\t\tuntil message == nil\n\t\tprint(\"Web socket disconnected.\")\n\tend,\n})\n\nprint(`Listening on port {PORT} 🚀`)\n\n-- Exit our example after a small delay, if you copy this\n-- example just remove this part to keep the server running\n\ntask.delay(10, function()\n\tprint(\"Shutting down...\")\n\ttask.wait(1)\n\thandle.stop()\n\ttask.wait(1)\nend)\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    \"recommendations\": [\n        \"rust-lang.rust-analyzer\",\n        \"esbenp.prettier-vscode\",\n        \"JohnnyMorganz.stylua\",\n        \"DavidAnson.vscode-markdownlint\"\n    ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"luau-lsp.sourcemap.enabled\": false,\n    \"luau-lsp.types.roblox\": false,\n    \"luau-lsp.ignoreGlobs\": [\n        \"tests/roblox/rbx-test-files/**/*.lua\",\n        \"tests/roblox/rbx-test-files/**/*.luau\"\n    ],\n    \"rust-analyzer.check.command\": \"clippy\",\n    \"editor.formatOnSave\": true,\n    \"stylua.searchParentDirectories\": true,\n    \"prettier.tabWidth\": 2,\n    \"[luau][lua]\": {\n        \"editor.defaultFormatter\": \"JohnnyMorganz.stylua\"\n    },\n    \"[json][jsonc][markdown][yaml]\": {\n        \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n    },\n    \"[rust]\": {\n        \"editor.defaultFormatter\": \"rust-lang.rust-analyzer\"\n    }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<!-- markdownlint-disable MD023 -->\n<!-- markdownlint-disable MD033 -->\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## `0.10.4` - October 14th, 2025\n\n### Added\n\n- Added support for the `jsonc` serialization format in the `serde` standard library\n- Added an `mlua` feature flag to `lune-roblox`, so that it may be used from Rust without bundling the entirety of Luau\n\n### Changed\n\n- Updated to rbx-dom database version `0.694`\n- Updated to Luau version `0.694`\n\n### Fixed\n\n- Fixed attribute name validation being too strict in the `roblox` standard library, not allowing the characters `/.-`\n- Fixed various issues in the `roblox` standard library caused by an outdated reflection database and version of `rbx-dom` ([#358])\n\n[#358]: https://github.com/lune-org/lune/pull/358\n\n## `0.10.3` - October 6th, 2025\n\n### Changed\n\n- Updated to Luau version `0.693`\n\n### Fixed\n\n- Fixed `readToEnd` being slow for child processes with large output ([#354])\n- Fixed `process.exec` not reading stdio until child exits ([#353])\n\n[#354]: https://github.com/lune-org/lune/pull/354\n[#353]: https://github.com/lune-org/lune/pull/353\n\n## `0.10.2` - August 31st, 2025\n\n### Added\n\n- Added support for the ZSTD compression format in the `serde` standard library ([#339])\n- Added support for `UniqueId` properties to the `roblox` standard library ([#343])\n- Added prebuilt Lune binaries for Windows on ARM to GitHub releases\n\n### Changed\n\n- Updated to Luau version `0.688`\n- Lune no longer depends on `liblzma`, making it easier to install on macOS\n- Prebuilt binaries for Ubuntu now use an older version (22.04) for better GLIBC compatibility\n\n### Fixed\n\n- Fixed Lune crashing while emitting an error and parsing its source\n\n[#339]: https://github.com/lune-org/lune/pull/339\n[#343]: https://github.com/lune-org/lune/pull/343\n\n## `0.10.1` - July 16th, 2025\n\n### Fixed\n\n- Fixed a regression where it was not possible to run directories with `init.luau` files in them directly using `lune run directory-name`.\n- Fixed a panic when calling `process.exit` inside a file that `require` was called on. ([#333])\n- Fixed a panic when calling `process.exit` inside a request handler for `net.serve`. ([#333])\n\n[#333]: https://github.com/lune-org/lune/pull/333\n\n## `0.10.0` - July 15th, 2025\n\nThis version of Lune contains a major internal refactoring of the `require` function, now using the proper require-by-string APIs exposed by Luau.\nIf you relied on any (incorrect) behavior of relative, non-`@self` requires, from within `init.luau` files in Lune `0.9.0`, you may need to update your code.\nNo other usages of `require` will be affected - but if you previously encountered any internal bugs or panics during `require`, these will have been fixed!\n\n### Added\n\n- Added support for TCP client in the `net` standard library.\n  It may be used either with TLS or not, and basic usage looks as such:\n\n  ```luau\n  -- Plain TCP connections\n  local stream = net.tcp.connect(\"example.com\", 80)\n\n  -- TLS connections (shorthand)\n  local tlsStream = net.tcp.connect(\"example.com\", 443, true)\n\n  -- Connections with custom TLS setting & TTL\n  local customStream = net.tcp.connect(\"192.168.1.100\", 8080, {\n  \t  tls = false,\n  \t  ttl = 128\n  })\n\n  -- Interacting with the stream\n  tlsStream:write(\"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")\n\n  while true do\n      local data = tlsStream:read()\n      if data ~= nil then\n          print(data)\n      else\n          break -- Connection was closed\n      end\n  end\n  ```\n\n- Added submodules to the `net` standard library: `http`, `tcp`, and `ws`.\n\n  These will be the preferred way of interacting with the `net` standard library going forward, but none of the old functions have been removed or deprecated yet to allow users time to migrate.\n\n  In a future major version of Lune, direct functions such as `net.socket` will be removed in favor of their equivalent functions in submodules, such as `net.ws.connect`. Here is the full new list of functions:\n  - `net.http.request`\n  - `net.http.serve`\n  - `net.tcp.connect`\n  - `net.ws.connect`\n\n- Added a method `with_lib` to the `Runtime` struct in the `lune` crate, to allow registering custom libraries.\n\n### Changed\n\n- Updated to Luau version `0.682`\n- Upgraded to `mlua` version `0.11` - if you use any of the Lune crates as a dependency, you may also need to upgrade\n\n### Fixed\n\n- Fixed errors being emitted twice when the error is thrown from the main (entrypoint) script\n- Fixed a panic when calling `net.request` and related functions in the main body of a module during `require`\n- Fixed various issues with not conforming to the new Luau require-by-string semantics\n\n## `0.9.4` - June 13th, 2025\n\n### Changed\n\n- `lune setup` now properly sets up a `.luaurc` file instead of using legacy VSCode-specific settings\n- `process.args` and `process.env` are now plain tables again - _not userdata_ - thank you to everyone who provided feedback on this and the usability issues!\n\n### Fixed\n\n- Fixed invalid handling of http redirects in `net.request`\n- Fixed not being able to download binaries for cross-compiling with `lune build`\n- Fixed binary output when running `lune build` not being deterministic and sometimes truncating\n- Fixed `cargo install lune` failing due to a yanked dependency ([#323])\n\n[#323]: https://github.com/lune-org/lune/pull/323\n\n## `0.9.3` - May 6th, 2025\n\n### Added\n\n- Added support for non-UTF8 strings in arguments to `process.exec` and `process.spawn`\n\n### Changed\n\n- Improved cross-platform compatibility and correctness for values in `process.args` and `process.env`, especially on Windows\n\n### Fixed\n\n- Fixed stdin not being properly closed when not providing the stdin option to `process.exec`\n- Fixed various crashes during require that had the error `cannot mutably borrow app data container`\n\n## `0.9.2` - April 30th, 2025\n\n### Changed\n\n- Improved performance of `net.request` and `net.serve` when handling large request bodies\n- Improved performance and memory usage of `task.spawn`, `task.defer`, and `task.delay`\n\n### Fixed\n\n- Fixed accidental breakage of `net.request` in version `0.9.1`\n\n## `0.9.1` - April 29th, 2025\n\n### Added\n\n- Added support for automatic decompression of HTTP requests in `net.serve` ([#310])\n\n### Fixed\n\n- Fixed `net.serve` no longer serving requests if the returned `ServeHandle` is discarded ([#310])\n- Fixed `net.serve` having various performance issues ([#310])\n- Fixed Lune still running after cancelling a task such as `task.delay(5, ...)` and all tasks having completed\n\n[#310]: https://github.com/lune-org/lune/pull/310\n\n## `0.9.0` - April 25th, 2025\n\nThe next major version of Lune has finally been released!\n\nThis release has been a long time coming, and many breaking changes have been made.\nIf you are an existing Lune user upgrading to this version, you will **most likely** be affected.\nThe full list of breaking changes can be found on below.\n\n### Breaking changes & additions\n\n- The behavior of `require` has changed, according to the latest Luau RFCs and specifications.\n\n  For the full details, feel free to read documentation [here](https://github.com/luau-lang/rfcs), otherwise, the most notable changes here are:\n  - Paths passed to require must start with either `./`, `../` or `@` - require statements such as `require(\"foo\")` **will now error** and must be changed to `require(\"./foo\")`.\n  - The behavior of require from within `init.luau` and `init.lua` files has changed - previously `require(\"./foo\")` would resolve\n    to the file or directory `foo` _as a **sibling** of the init file_, but will now resolve to the file or directory `foo` _which is a sibling of the **parent directory** of the init file_.\n    To require files inside of the same directory as the init file, the new `@self` alias must be used - like `require(\"@self/foo\")`.\n\n- The main `lune run` subcommand will no longer sink flags passed to it - `lune run --` will now _literally_ pass the string `--` as the first\n  value in `process.args`, and `--` is no longer necessary to be able to pass flag arguments such as `--foo` and `-b` properly to your Lune programs.\n\n- Two new process spawning functions - `process.create` and `process.exec` - replace the previous `process.spawn` API. ([#211])\n\n  To migrate from `process.spawn`, use the new `process.exec` API which retains the same behavior as the old function, with slight changes in how the `stdin` option is passed.\n\n  The new `process.create` function is a non-blocking process creation API and can be used to interactively\n  read and write to standard input and output streams of the child process.\n\n  ```lua\n  local child = process.create(\"program\", {\n    \"first-argument\",\n    \"second-argument\"\n  })\n\n  -- Writing to stdin\n  child.stdin:write(\"Hello from Lune!\")\n\n  -- Reading partial data from stdout\n  local data = child.stdout:read()\n  print(data)\n\n  -- Reading the full stdout\n  local full = child.stdout:readToEnd()\n  print(full)\n  ```\n\n- Removed `net.jsonEncode` and `net.jsonDecode` - please use the equivalent `serde.encode(\"json\", ...)` and `serde.decode(\"json\", ...)` instead\n\n- WebSocket methods in `net.socket` and `net.serve` now use standard Lua method calling convention and colon syntax.\n  This means `socket.send(...)` is now `socket:send(...)`, `socket.close(...)` is now `socket:close(...)`, and so on.\n\n- Various changes have been made to the Lune Rust crates:\n  - `Runtime::run` now returns a more useful value instead of an `ExitCode` ([#178])\n  - All Lune standard library crates now export a `typedefs` function that returns the source code for the respective standard library module type definitions\n  - All Lune crates now depend on `mlua` version `0.10` or above\n  - Most Lune crates have been migrated to the `smol` and `async-*` ecosystem instead of `tokio`, with a full migration expected soon (this will not break public types)\n  - The `roblox` crate re-export has been removed from the main `lune` crate - please depend on `lune-roblox` crate directly instead\n\n### Added\n\n- Added functions for getting Roblox Studio locations to the `roblox` standard library ([#284])\n- Added support for the `Content` datatype in the `roblox` standard library ([#305])\n- Added support for `EnumItem` instance attributes in the `roblox` standard library ([#306])\n- Added support for RFC 2822 dates in the `datetime` standard library using `fromRfc2822` ([#285]) - the `fromIsoDate`\n  function has also been deprecated (not removed yet) and `fromRfc3339` should instead be preferred for any new work.\n- Added a `readLine` function to the `stdio` standard library for reading line-by-line from stdin.\n- Added a way to disable JIT by setting the `LUNE_LUAU_JIT` environment variable to `false` before running Lune.\n- Added `process.endianness` constant ([#267])\n\n### Changed\n\n- Documentation comments for several standard library properties have been improved ([#248], [#250])\n- Error messages no longer contain redundant or duplicate stack trace information\n- Updated to Luau version `0.663`\n- Updated to rbx-dom database version `0.670`\n\n### Fixed\n\n- Fixed deadlock in `stdio.format` calls in `__tostring` metamethods ([#288])\n- Fixed `task.wait` and `task.delay` not being guaranteed to yield when duration is set to zero or very small values\n- Fixed `__tostring` metamethods sometimes not being respected in `print` and `stdio.format` calls\n\n[#178]: https://github.com/lune-org/lune/pull/178\n[#211]: https://github.com/lune-org/lune/pull/211\n[#248]: https://github.com/lune-org/lune/pull/248\n[#250]: https://github.com/lune-org/lune/pull/250\n[#265]: https://github.com/lune-org/lune/pull/265\n[#267]: https://github.com/lune-org/lune/pull/267\n[#284]: https://github.com/lune-org/lune/pull/284\n[#285]: https://github.com/lune-org/lune/pull/285\n[#288]: https://github.com/lune-org/lune/pull/288\n[#305]: https://github.com/lune-org/lune/pull/305\n[#306]: https://github.com/lune-org/lune/pull/306\n\n## `0.8.9` - October 7th, 2024\n\n### Changed\n\n- Updated to Luau version `0.640`\n\n## `0.8.8` - August 22nd, 2024\n\n### Fixed\n\n- Fixed errors when deserializing `Lighting.AttributesSerialize` by updating `rbx-dom` dependencies ([#245])\n\n[#245]: https://github.com/lune-org/lune/pull/245\n\n## `0.8.7` - August 10th, 2024\n\n### Added\n\n- Added a compression level option to `serde.compress` ([#224])\n- Added missing vector methods to the `roblox` library ([#228])\n\n### Changed\n\n- Updated to Luau version `0.635`\n- Updated to rbx-dom database version `0.634`\n\n### Fixed\n\n- Fixed `fs.readDir` with trailing forward-slash on Windows ([#220])\n- Fixed `__type` and `__tostring` metamethods not always being respected when formatting tables\n\n[#220]: https://github.com/lune-org/lune/pull/220\n[#224]: https://github.com/lune-org/lune/pull/224\n[#228]: https://github.com/lune-org/lune/pull/228\n\n## `0.8.6` - June 23rd, 2024\n\n### Added\n\n- Added a builtin API for hashing and calculating HMACs as part of the `serde` library ([#193])\n\n  Basic usage:\n\n  ```lua\n  local serde = require(\"@lune/serde\")\n  local hash = serde.hash(\"sha256\", \"a message to hash\")\n  local hmac = serde.hmac(\"sha256\", \"a message to hash\", \"a secret string\")\n\n  print(hash)\n  print(hmac)\n  ```\n\n  The returned hashes are sequences of lowercase hexadecimal digits. The following algorithms are supported:\n  `md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `blake3`\n\n- Added two new options to `luau.load`:\n  - `codegenEnabled` - whether or not codegen should be enabled for the loaded chunk.\n  - `injectGlobals` - whether or not to inject globals into a passed `environment`.\n\n  By default, globals are injected and codegen is disabled.\n  Check the documentation for the `luau` standard library for more information.\n\n- Implemented support for floor division operator / `__idiv` for the `Vector2` and `Vector3` types in the `roblox` standard library ([#196])\n- Fixed the `_VERSION` global containing an incorrect Lune version string.\n\n### Changed\n\n- Sandboxing and codegen in the Luau VM is now fully enabled, resulting in up to 2x or faster code execution.\n  This should not result in any behavior differences in Lune, but if it does, please open an issue.\n- Improved formatting of custom error objects (such as when `fs.readFile` returns an error) when printed or formatted using `stdio.format`.\n\n### Fixed\n\n- Fixed `__type` and `__tostring` metamethods on userdatas and tables not being respected when printed or formatted using `stdio.format`.\n\n[#193]: https://github.com/lune-org/lune/pull/193\n[#196]: https://github.com/lune-org/lune/pull/196\n\n## `0.8.5` - June 1st, 2024\n\n### Changed\n\n- Improved table pretty formatting when using `print`, `warn`, and `stdio.format`:\n  - Keys are sorted numerically / alphabetically when possible.\n  - Keys of different types are put in distinct sections for mixed tables.\n  - Tables that are arrays no longer display their keys.\n  - Empty tables are no longer spread across lines.\n\n## Fixed\n\n- Fixed formatted values in tables not being separated by newlines.\n- Fixed panicking (crashing) when using `process.spawn` with a program that does not exist.\n- Fixed `instance:SetAttribute(\"name\", nil)` throwing an error and not removing the attribute.\n\n## `0.8.4` - May 12th, 2024\n\n### Added\n\n- Added a builtin API for regular expressions.\n\n  Example basic usage:\n\n  ```lua\n  local Regex = require(\"@lune/regex\")\n\n  local re = Regex.new(\"hello\")\n\n  if re:isMatch(\"hello, world!\") then\n  \tprint(\"Matched!\")\n  end\n\n  local caps = re:captures(\"hello, world! hello, again!\")\n\n  print(#caps) -- 2\n  print(caps:get(1)) -- \"hello\"\n  print(caps:get(2)) -- \"hello\"\n  print(caps:get(3)) -- nil\n  ```\n\n  Check out the documentation for more details.\n\n- Added support for buffers as arguments in builtin APIs ([#148])\n\n  This includes APIs such as `fs.writeFile`, `serde.encode`, and more.\n\n- Added support for cross-compilation of standalone binaries ([#162])\n\n  You can now compile standalone binaries for other platforms by passing\n  an additional `target` argument to the `build` subcommand:\n\n  ```sh\n  lune build my-file.luau --output my-bin --target windows-x86_64\n  ```\n\n  Currently supported targets are the same as the ones included with each\n  release of Lune on GitHub. Check releases for a full list of targets.\n\n- Added `stdio.readToEnd()` for reading the entire stdin passed to Lune\n\n### Changed\n\n- Split the repository into modular crates instead of a monolith. ([#188])\n\n  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:\n  - `lune` (old)\n  - `lune-utils`\n  - `lune-roblox`\n  - `lune-std-*` for every builtin library\n\n  When depending on the main `lune` crate, each builtin library also has a feature flag that can be toggled in the format `std-*`.\n\n  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.\n\n- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])\n- Updated to Luau version `0.622`.\n\n### Fixed\n\n- Fixed not being able to decompress `lz4` format in high compression mode\n- Fixed stack overflow for tables with circular keys ([#183])\n- Fixed `net.serve` no longer accepting ipv6 addresses\n- Fixed headers in `net.serve` being raw bytes instead of strings\n\n[#148]: https://github.com/lune-org/lune/pull/148\n[#162]: https://github.com/lune-org/lune/pull/162\n[#183]: https://github.com/lune-org/lune/pull/183\n[#186]: https://github.com/lune-org/lune/pull/186\n[#188]: https://github.com/lune-org/lune/pull/188\n\n## `0.8.3` - April 15th, 2024\n\n### Fixed\n\n- Fixed `require` not throwing syntax errors ([#168])\n- Fixed `require` caching not working correctly ([#171])\n- Fixed case-sensitivity issue in `require` with aliases ([#173])\n- Fixed `itertools` dependency being marked optional even though it is mandatory ([#176])\n- Fixed test cases for the `net` built-in library on Windows ([#177])\n\n[#168]: https://github.com/lune-org/lune/pull/168\n[#171]: https://github.com/lune-org/lune/pull/171\n[#173]: https://github.com/lune-org/lune/pull/173\n[#176]: https://github.com/lune-org/lune/pull/176\n[#177]: https://github.com/lune-org/lune/pull/177\n\n## `0.8.2` - March 12th, 2024\n\n### Fixed\n\n- Fixed REPL panicking after the first evaluation / run.\n- Fixed globals reloading on each run in the REPL, causing unnecessary slowdowns.\n- Fixed `net.serve` requests no longer being plain tables in Lune `0.8.1`, breaking usage of things such as `table.clone`.\n\n## `0.8.1` - March 11th, 2024\n\n### Added\n\n- Added the ability to specify an address in `net.serve`. ([#142])\n\n### Changed\n\n- Update to Luau version `0.616`.\n- Major performance improvements when using a large amount of threads / asynchronous Lune APIs. ([#165])\n- Minor performance improvements and less overhead for `net.serve` and `net.socket`. ([#165])\n\n### Fixed\n\n- Fixed `fs.copy` not working with empty dirs. ([#155])\n- Fixed stack overflow when printing tables with cyclic references. ([#158])\n- Fixed not being able to yield in `net.serve` handlers without blocking other requests. ([#165])\n- Fixed various scheduler issues / panics. ([#165])\n\n[#142]: https://github.com/lune-org/lune/pull/142\n[#155]: https://github.com/lune-org/lune/pull/155\n[#158]: https://github.com/lune-org/lune/pull/158\n[#165]: https://github.com/lune-org/lune/pull/165\n\n## `0.8.0` - January 14th, 2024\n\n### Breaking Changes\n\n- The Lune CLI now uses subcommands instead of flag options: <br/>\n  - `lune script_name arg1 arg2 arg3` -> `lune run script_name arg1 arg2 arg3`\n  - `lune --list` -> `lune list`\n  - `lune --setup` -> `lune setup`\n\n  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.\n\n- The `createdAt`, `modifiedAt`, and `accessedAt` properties returned from `fs.metadata` are now `DateTime` values instead of numbers.\n\n- The `Lune` struct has been renamed to `Runtime` in the Lune rust crate.\n\n### Added\n\n- Added support for compiling single Lune scripts into standalone executables! ([#140])\n\n  Example usage:\n\n  ```lua\n  -- my_cool_script.luau\n  print(\"Hello, standalone!\")\n  ```\n\n  ```sh\n  > lune build my_cool_script.luau\n  # Creates `my_cool_script.exe` (Windows) or `my_cool_script` (macOS / Linux)\n  ```\n\n  ```sh\n  > ./my_cool_script.exe # Windows\n  > ./my_cool_script # macOS / Linux\n  > \"Hello, standalone!\"\n  ```\n\n  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.\n\n- Added support for path aliases using `.luaurc` config files!\n\n  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:\n\n  ```jsonc\n  // .luaurc\n  {\n    \"aliases\": {\n      \"modules\": \"./some/long/path/to/modules\",\n    },\n  }\n  ```\n\n  ```lua\n  -- ./some/long/path/to/modules/foo.luau\n  return { World = \"World!\" }\n\n  -- ./anywhere/you/want/my_script.luau\n  local mod = require(\"@modules/foo\")\n  print(\"Hello, \" .. mod.World)\n  ```\n\n- 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:\n  - Single values remain unchanged and will work exactly the same as before. <br/>\n\n    ```lua\n    -- https://example.com/?foo=bar&baz=qux\n    local net = require(\"@lune/net\")\n    net.request({\n        url = \"example.com\",\n        query = {\n            foo = \"bar\",\n            baz = \"qux\",\n        }\n    })\n    ```\n\n  - Multiple values _on a single query / header_ are represented as an ordered array of strings. <br/>\n\n    ```lua\n    -- https://example.com/?foo=first&foo=second&foo=third&bar=baz\n    local net = require(\"@lune/net\")\n    net.request({\n        url = \"example.com\",\n        query = {\n            foo = { \"first\", \"second\", \"third\" },\n            bar = \"baz\",\n        }\n    })\n    ```\n\n[#140]: https://github.com/lune-org/lune/pull/140\n\n### Changed\n\n- Update to Luau version `0.606`.\n\n### Fixed\n\n- Fixed the `print` and `warn` global functions yielding the thread, preventing them from being used in places such as the callback to `table.sort`.\n- Fixed the `overwrite` option for `fs.move` not correctly removing existing files / directories. ([#133])\n\n[#133]: https://github.com/lune-org/lune/pull/133\n\n## `0.7.11` - October 29th, 2023\n\n### Changed\n\n- Update to Luau version `0.601`.\n\n### Fixed\n\n- Fixed `roblox.getAuthCookie` not being compatible with the latest cookie format by upgrading rbx_cookie.\n\n## `0.7.10` - October 25th, 2023\n\n### Added\n\n- 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.\n\n### Fixed\n\n- Fixed issues with `SecurityCapabilities` on instances in the `roblox` built-in by upgrading rbx-dom.\n\n## `0.7.9` - October 21st, 2023\n\n### Added\n\n- Added `implementProperty` and `implementMethod` to the `roblox` built-in library to fill in missing functionality that Lune does not aim to implement itself.\n\n  Example usage:\n\n  ```lua\n  local roblox = require(\"@lune/roblox\")\n\n  local part = roblox.Instance.new(\"Part\")\n\n  roblox.implementMethod(\"BasePart\", \"TestMethod\", function(_, ...)\n      print(\"Tried to call TestMethod with\", ...)\n  end)\n\n  part:TestMethod(\"Hello\", \"world!\")\n  ```\n\n### Changed\n\n- Update to Luau version `0.599`.\n- 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.\n\n## `0.7.8` - October 5th, 2023\n\n### Added\n\n- Added a new `datetime` built-in library for handling date & time values, parsing, formatting, and more. ([#94])\n\n  Example usage:\n\n  ```lua\n  local DateTime = require(\"@lune/datetime\")\n\n  -- Creates a DateTime for the current exact moment in time\n  local now = DateTime.now()\n\n  -- Formats the current moment in time as an ISO 8601 string\n  print(now:toIsoDate())\n\n  -- Formats the current moment in time, using the local\n  -- time, the French locale, and the specified time string\n  print(now:formatLocalTime(\"%A, %d %B %Y\", \"fr\"))\n\n  -- Returns a specific moment in time as a DateTime instance\n  local someDayInTheFuture = DateTime.fromLocalTime({\n      year = 3033,\n      month = 8,\n      day = 26,\n      hour = 16,\n      minute = 56,\n      second = 28,\n      millisecond = 892,\n  })\n\n  -- Extracts the current local date & time as separate values (same values as above table)\n  print(now:toLocalTime())\n\n  -- Returns a DateTime instance from a given float, where the whole\n  -- denotes the seconds and the fraction denotes the milliseconds\n  -- Note that the fraction for millis here is completely optional\n  DateTime.fromUnixTimestamp(871978212313.321)\n\n  -- Extracts the current universal (UTC) date & time as separate values\n  print(now:toUniversalTime())\n  ```\n\n- Added support for passing `stdin` in `process.spawn` ([#106])\n- Added support for setting a custom environment in load options for `luau.load`, not subject to `getfenv` / `setfenv` deoptimizations\n- 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])\n- Added support for a variable number of arguments for CFrame methods ([#85])\n\n### Changed\n\n- Update to Luau version `0.596`.\n- Update to rbx-dom database version `0.596`.\n- `process.spawn` now uses `powershell` instead of `/bin/bash` as the shell on Windows, with `shell = true`.\n- 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.\n\n### Fixed\n\n- Fixed the `process` built-in library not loading correctly when using Lune in REPL mode.\n- Fixed list subcommand not listing global scripts without a local `.lune` / `lune` directory present.\n- Fixed `net.serve` stopping when the returned `ServeHandle` is garbage collected.\n- Fixed missing trailing newline when using the `warn` global.\n- Fixed constructor for `CFrame` in the `roblox` built-in library not parsing the 12-arg overload correctly. ([#102])\n- Fixed various functions for `CFrame` in the `roblox` built-in library being incorrect, specifically row-column ordering and some flipped signs. ([#103])\n- Fixed cross-service Instance references disappearing when using the `roblox` built-in library ([#117])\n\n[#85]: https://github.com/lune-org/lune/pull/85\n[#93]: https://github.com/lune-org/lune/pull/93\n[#94]: https://github.com/lune-org/lune/pull/94\n[#102]: https://github.com/lune-org/lune/pull/102\n[#103]: https://github.com/lune-org/lune/pull/103\n[#106]: https://github.com/lune-org/lune/pull/106\n[#117]: https://github.com/lune-org/lune/pull/117\n\n## `0.7.7` - August 23rd, 2023\n\n### Added\n\n- Added a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) to Lune. ([#83])\n\n  This allows you to run scripts within Lune without writing files!\n\n  Example usage, inside your favorite terminal:\n\n  ```bash\n  # 1. Run the Lune executable, without any arguments\n  lune\n\n  # 2. You will be shown the current Lune version and a blank prompt arrow:\n  Lune v0.7.7\n  >\n\n  # 3. Start typing, and hit enter when you want to run your script!\n  #    Your script will run until completion and output things along the way.\n  > print(2 + 3)\n  5\n  > print(\"Hello, lune changelog!\")\n  Hello, lune changelog!\n\n  # 4. You can also set variables that will get preserved between runs.\n  #    Note that local variables do not get preserved here.\n  > myVariable = 123\n  > print(myVariable)\n  123\n\n  # 5. Press either of these key combinations to exit the REPL:\n  #    - Ctrl + D\n  #    - Ctrl + C\n  ```\n\n- Added a new `luau` built-in library for manually compiling and loading Luau source code. ([#82])\n\n  Example usage:\n\n  ```lua\n  local luau = require(\"@lune/luau\")\n\n  local bytecode = luau.compile(\"print('Hello, World!')\")\n  local callableFn = luau.load(bytecode)\n\n  callableFn()\n\n  -- Additionally, we can skip the bytecode generation and\n  -- load a callable function directly from the code itself.\n  local callableFn2 = luau.load(\"print('Hello, World!')\")\n\n  callableFn2()\n  ```\n\n### Changed\n\n- Update to Luau version `0.591`.\n- Lune's internal task scheduler and `require` functionality has been completely rewritten. <br/>\n  The new scheduler is much more stable, conforms to a larger test suite, and has a few additional benefits:\n  - 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.\n  - Spawned processes using `process.spawn` now run on different thread(s), freeing up resources for the main thread where luau runs.\n  - 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.\n  - 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.\n\n- 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.\n\n### Fixed\n\n- Fixed not being able to pass arguments to the thread using `coroutine.resume`. ([#86])\n- Fixed a large number of long-standing issues, from the task scheduler rewrite:\n  - Fixed `require` hanging indefinitely when the module being require-d uses an async function in its main body.\n  - Fixed background tasks (such as `net.serve`) not keeping Lune alive even if there are no lua threads to run.\n  - Fixed spurious panics and error messages such as `Tried to resume next queued future but none are queued`.\n  - Fixed not being able to catch non-string errors properly, errors were accidentally being wrapped in an opaque `userdata` type.\n\n[#82]: https://github.com/lune-org/lune/pull/82\n[#83]: https://github.com/lune-org/lune/pull/83\n[#86]: https://github.com/lune-org/lune/pull/86\n\n## `0.7.6` - August 9th, 2023\n\n### Changed\n\n- Update to Luau version `0.588`\n- Enabled Luau JIT backend for potential performance improvements 🚀 <br/>\n  If you run into any strange behavior please open an issue!\n\n### Fixed\n\n- Fixed publishing of the Lune library to `crates.io`\n- Fixed `serde.decode` deserializing `null` values as `userdata` instead of `nil`.\n- Fixed not being able to require files with multiple extensions, eg. `module.spec.luau` was not require-able using `require(\"module.spec\")`.\n- Fixed instances and `roblox` built-in library APIs erroring when used asynchronously/concurrently.\n\n## `0.7.5` - July 22nd, 2023\n\n### Added\n\n- Lune now has a new documentation site! </br>\n  This addresses new APIs from version `0.7.0` not being available on the docs site, brings much improved searching functionality, and will help us keep documentation more up-to-date going forward with a more automated process. You can check out the new site at [lune-org.github.io](https://lune-org.github.io/docs).\n\n- Added `fs.copy` to recursively copy files and directories.\n\n  Example usage:\n\n  ```lua\n  local fs = require(\"@lune/fs\")\n\n  fs.writeDir(\"myCoolDir\")\n  fs.writeFile(\"myCoolDir/myAwesomeFile.json\", \"{}\")\n\n  fs.copy(\"myCoolDir\", \"myCoolDir2\")\n\n  assert(fs.isDir(\"myCoolDir2\"))\n  assert(fs.isFile(\"myCoolDir2/myAwesomeFile.json\"))\n  assert(fs.readFile(\"myCoolDir2/myAwesomeFile.json\") == \"{}\")\n  ```\n\n- Added `fs.metadata` to get metadata about files and directories.\n\n  Example usage:\n\n  ```lua\n  local fs = require(\"@lune/fs\")\n\n  fs.writeFile(\"myAwesomeFile.json\", \"{}\")\n\n  local meta = fs.metadata(\"myAwesomeFile.json\")\n\n  print(meta.exists) --> true\n  print(meta.kind) --> \"file\"\n  print(meta.createdAt) --> 1689848548.0577152 (unix timestamp)\n  print(meta.permissions) --> { readOnly: false }\n  ```\n\n- Added `roblox.getReflectionDatabase` to access the builtin database containing information about classes and enums.\n\n  Example usage:\n\n  ```lua\n  local roblox = require(\"@lune/roblox\")\n\n  local db = roblox.getReflectionDatabase()\n\n  print(\"There are\", #db:GetClassNames(), \"classes in the reflection database\")\n\n  print(\"All base instance properties:\")\n\n  local class = db:GetClass(\"Instance\")\n  for name, prop in class.Properties do\n  \tprint(string.format(\n  \t\t\"- %s with datatype %s and default value %s\",\n  \t\tprop.Name,\n  \t\tprop.Datatype,\n  \t\ttostring(class.DefaultProperties[prop.Name])\n  \t))\n  end\n  ```\n\n- Added support for running directories with an `init.luau` or `init.lua` file in them in the CLI.\n\n### Changed\n\n- Update to Luau version `0.583`\n\n### Fixed\n\n- Fixed publishing of Lune to crates.io by migrating away from a monorepo.\n- Fixed crashes when writing a very deeply nested `Instance` to a file. ([#62])\n- Fixed not being able to read & write to WebSocket objects at the same time. ([#68])\n- Fixed tab character at the start of a script causing it not to parse correctly. ([#72])\n\n[#62]: https://github.com/lune-org/lune/pull/62\n[#68]: https://github.com/lune-org/lune/pull/66\n[#72]: https://github.com/lune-org/lune/pull/72\n\n## `0.7.4` - July 7th, 2023\n\n### Added\n\n- Added support for `CFrame` and `Font` types in attributes when using the `roblox` builtin.\n\n### Fixed\n\n- Fixed `roblox.serializeModel` still keeping some unique ids.\n\n## `0.7.3` - July 5th, 2023\n\n### Changed\n\n- When using `roblox.serializeModel`, Lune will no longer keep internal unique ids. <br/>\n  This is consistent with what Roblox does and prevents Lune from always generating a new and unique file. <br/>\n  This previously caused unnecessary diffs when using git or other kinds of source control. ([Relevant issue](https://github.com/lune-org/lune/issues/61))\n\n## `0.7.2` - June 28th, 2023\n\n### Added\n\n- Added support for `init` files in directories, similar to Rojo, or `index.js` / `mod.rs` in JavaScript / Rust. <br/>\n  This means that placing a file named `init.luau` or `init.lua` in a directory will now let you `require` that directory.\n\n### Changed\n\n- The `lune --setup` command is now much more user-friendly.\n- Update to Luau version `0.581`\n\n## `0.7.1` - June 17th, 2023\n\n### Added\n\n- Added support for TLS in websockets, enabling usage of `wss://`-prefixed URLs. ([#57])\n\n### Fixed\n\n- Fixed `closeCode` erroring when being accessed on websockets. ([#57])\n- Fixed issues with `UniqueId` when using the `roblox` builtin by downgrading `rbx-dom`.\n\n[#57]: https://github.com/lune-org/lune/pull/57\n\n## `0.7.0` - June 12th, 2023\n\n### Breaking Changes\n\n- Globals for the `fs`, `net`, `process`, `stdio`, and `task` builtins have been removed, and the `require(\"@lune/...\")` syntax is now the only way to access builtin libraries. If you have previously been using a global such as `fs` directly, you will now need to put `local fs = require(\"@lune/fs\")` at the top of the file instead.\n\n- Migrated several functions in the `roblox` builtin to new, more flexible APIs:\n  - `readPlaceFile -> deserializePlace`\n  - `readModelFile -> deserializeModel`\n  - `writePlaceFile -> serializePlace`\n  - `writeModelFile -> serializeModel`\n\n  These new APIs **_no longer use file paths_**, meaning to use them with files you must first read them using the `fs` builtin.\n\n- Removed `CollectionService` and its methods from the `roblox` builtin library - new instance methods have been added as replacements.\n- Removed [`Instance:FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) which was a method that was never enabled in the official Roblox API and will soon be removed. <br/>\n  Use the second argument of the already existing find methods instead to find descendants.\n- Removed the global `printinfo` function - it was generally not used, and did not work as intended. Use the `stdio` builtin for formatting and logging instead.\n- Removed support for Windows on ARM - it's more trouble than its worth right now, we may revisit it later.\n\n### Added\n\n- Added `serde.compress` and `serde.decompress` for compressing and decompressing strings using one of several compression formats: `brotli`, `gzip`, `lz4`, or `zlib`.\n\n  Example usage:\n\n  ```lua\n  local INPUT = string.rep(\"Input string to compress\", 16) -- Repeated string 16 times for the purposes of this example\n\n  local serde = require(\"@lune/serde\")\n\n  local compressed = serde.compress(\"gzip\", INPUT)\n  local decompressed = serde.decompress(\"gzip\", compressed)\n\n  assert(decompressed == INPUT)\n  ```\n\n- Added automatic decompression for compressed responses when using `net.request`.\n  This behavior can be disabled by passing `options = { decompress = false }` in request params.\n\n- Added support for finding scripts in the current home directory.\n  This means that if you have a script called `script-name.luau`, you can place it in the following location:\n  - `C:\\Users\\YourName\\.lune\\script-name.luau` (Windows)\n  - `/Users/YourName/.lune/script-name.luau` (macOS)\n  - `/home/YourName/.lune/script-name.luau` (Linux)\n\n  And then run it using `lune script-name` from any directory you are currently in.\n\n- Added several new instance methods in the `roblox` builtin library:\n  - [`Instance:AddTag`](https://create.roblox.com/docs/reference/engine/classes/Instance#AddTag)\n  - [`Instance:GetTags`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetTags)\n  - [`Instance:HasTag`](https://create.roblox.com/docs/reference/engine/classes/Instance#HasTag)\n  - [`Instance:RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/Instance#RemoveTag)\n- Implemented the second argument of the `FindFirstChild` / `FindFirstChildOfClass` / `FindFirstChildWhichIsA` instance methods.\n\n### Changed\n\n- Update to Luau version `0.579`\n- Both `stdio.write` and `stdio.ewrite` now support writing arbitrary bytes, instead of only valid UTF-8.\n\n### Fixed\n\n- Fixed `stdio.write` and `stdio.ewrite` not being flushed and causing output to be interleaved. ([#47])\n- Fixed `typeof` returning `userdata` for roblox types such as `Instance`, `Vector3`, ...\n\n[#47]: https://github.com/lune-org/lune/pull/47\n\n## `0.6.7` - May 14th, 2023\n\n### Added\n\n- Replaced all of the separate typedef & documentation generation commands with a unified `lune --setup` command.\n\n  This command will generate type definition files for all of the builtins and will work with the new `require(\"@lune/...\")` syntax. Note that this also means that there is no longer any way to generate type definitions for globals - this is because they will be removed in the next major release in favor of the beforementioned syntax.\n\n- New releases now include prebuilt binaries for arm64 / aarch64! <br />\n  These new binaries will have names with the following format:\n  - `lune-windows-0.6.7-aarch64.exe`\n  - `lune-linux-0.6.7-aarch64`\n  - `lune-macos-0.6.7-aarch64`\n- Added global types to documentation site\n\n## `0.6.6` - April 30th, 2023\n\n### Added\n\n- Added tracing / logging for rare and hard to diagnose error cases, which can be configured using the env var `RUST_LOG`.\n\n### Changed\n\n- The `_VERSION` global now follows a consistent format `Lune x.y.z+luau` to allow libraries to check against it for version requirements.\n\n  Examples:\n  - `Lune 0.0.0+0`\n  - `Lune 1.0.0+500`\n  - `Lune 0.11.22+9999`\n\n- Updated to Luau version `0.573`\n- Updated `rbx-dom` to support reading and writing `Font` datatypes\n\n### Fixed\n\n- Fixed `_G` not being a readable & writable table\n- Fixed `_G` containing normal globals such as `print`, `math`, ...\n- Fixed using instances as keys in tables\n\n## `0.6.5` - March 27th, 2023\n\n### Changed\n\n- Functions such as `print`, `warn`, ... now respect `__tostring` metamethods.\n\n### Fixed\n\n- Fixed access of roblox instance properties such as `Workspace.Terrain`, `game.Workspace` that are actually links to child instances. <br />\n  These properties are always guaranteed to exist, and they are not always properly set, meaning they must be found through an internal lookup.\n- Fixed issues with the `CFrame.lookAt` and `CFrame.new(Vector3, Vector3)` constructors.\n- Fixed issues with CFrame math operations returning rotation angles in the wrong order.\n\n## `0.6.4` - March 26th, 2023\n\n### Fixed\n\n- Fixed instances with attributes not saving if they contain integer attributes.\n- Fixed attributes not being set properly if the instance has an empty attributes property.\n- Fixed error messages for reading & writing roblox files not containing the full error message.\n- Fixed crash when trying to access an instance reference property that points to a destroyed instance.\n- Fixed crash when trying to save instances that contain unsupported attribute types.\n\n## `0.6.3` - March 26th, 2023\n\n### Added\n\n- Added support for instance tags & `CollectionService` in the `roblox` built-in. <br />\n  Currently implemented methods are listed on the [docs site](https://lune-org.github.io/docs/roblox/4-api-status).\n\n### Fixed\n\n- Fixed accessing a destroyed instance printing an error message even if placed inside a pcall.\n- Fixed cloned instances not having correct instance reference properties set (`ObjectValue.Value`, `Motor6D.Part0`, ...)\n- Fixed `Instance::GetDescendants` returning the same thing as `Instance::GetChildren`.\n\n## `0.6.2` - March 25th, 2023\n\nThis release adds some new features and fixes for the `roblox` built-in.\n\n### Added\n\n- Added `GetAttribute`, `GetAttributes` and `SetAttribute` methods for instances.\n- Added support for getting & setting properties that are instance references.\n\n### Changed\n\n- Improved handling of optional property types such as optional cframes & default physical properties.\n\n### Fixed\n\n- Fixed handling of instance properties that are serialized as binary strings.\n\n## `0.6.1` - March 22nd, 2023\n\n### Fixed\n\n- Fixed `writePlaceFile` and `writeModelFile` in the new `roblox` built-in making mysterious \"ROOT\" instances.\n\n## `0.6.0` - March 22nd, 2023\n\n### Added\n\n- Added a `roblox` built-in\n\n  If you're familiar with [Remodel](https://github.com/rojo-rbx/remodel), this new built-in contains more or less the same APIs, integrated into Lune. <br />\n  There are just too many new APIs to list in this changelog, so head over to the [docs sit](https://lune-org.github.io/docs/roblox/1-introduction) to learn more!\n\n- Added a `serde` built-in\n\n  This built-in contains previously available functions `encode` and `decode` from the `net` global. <br />\n  The plan is for this built-in to contain more serialization and encoding functionality in the future.\n\n- `require` has been reimplemented and overhauled in several ways:\n  - New built-ins such as `roblox` and `serde` can **_only_** be imported using `require(\"@lune/roblox\")`, `require(\"@lune/serde\")`, ...\n  - Previous globals such as `fs`, `net` and others can now _also_ be imported using `require(\"@lune/fs\")`, `require(\"@lune/net\")`, ...\n  - Requiring a script is now completely asynchronous and will not block lua threads other than the caller.\n  - Requiring a script will no longer error when using async APIs in the main body of the required script.\n\n  All new built-ins will be added using this syntax and new built-ins will no longer be available in the global scope, and current globals will stay available as globals until proper editor and LSP support is available to ensure Lune users have a good development experience. This is the first step towards moving away from adding each library as a global, and allowing Lune to have more built-in libraries in general.\n\n  Behavior otherwise stays the same, and requires are still relative to file unless the special `@` prefix is used.\n\n- Added `net.urlEncode` and `net.urlDecode` for URL-encoding and decoding strings\n\n### Changed\n\n- Renamed the global `info` function to `printinfo` to make it less ambiguous\n\n### Removed\n\n- Removed experimental `net.encode` and `net.decode` functions, since they are now available using `require(\"@lune/serde\")`\n- Removed option to preserve default Luau require behavior\n\n## `0.5.6` - March 11th, 2023\n\n### Added\n\n- Added support for shebangs at the top of a script, meaning scripts such as this one will now run without throwing a syntax error:\n\n  ```lua\n  #!/usr/bin/env lune\n\n  print(\"Hello, world!\")\n  ```\n\n### Fixed\n\n- Fixed `fs.writeFile` and `fs.readFile` not working with strings / files that are invalid utf-8\n\n## `0.5.5` - March 8th, 2023\n\n### Added\n\n- Added support for running scripts by passing absolute file paths in the CLI\n  - This does not have the restriction of scripts having to use the `.luau` or `.lua` extension, since it is presumed that if you pass an absolute path you know exactly what you are doing\n\n### Changed\n\n- Improved error messages for passing invalid file names / file paths substantially - they now include helpful formatting to make file names distinct from file extensions, and give suggestions on how to solve the problem\n- Improved general formatting of error messages, both in the CLI and for Luau scripts being run\n\n### Fixed\n\n- Fixed the CLI being a bit too picky about file names when trying to run files in `lune` or `.lune` directories\n- Fixed documentation misses from large changes made in version `0.5.0`\n\n## `0.5.4` - March 7th, 2023\n\n### Added\n\n- Added support for reading scripts from stdin by passing `\"-\"` as the script name\n- Added support for close codes in the `net` WebSocket APIs:\n  - A close code can be sent by passing it to `socket.close`\n  - A received close code can be checked with the `socket.closeCode` value, which is populated after a socket has been closed - note that using `socket.close` will not set the close code value, it is only set when received and is guaranteed to exist after closure\n\n### Changed\n\n- Update to Luau version 0.566\n\n### Fixed\n\n- Fixed scripts having to be valid utf8, they may now use any kind of encoding that base Luau supports\n- The `net` WebSocket APIs will no longer return `nil` for partial messages being received in `socket.next`, and will instead wait for the full message to arrive\n\n## `0.5.3` - February 26th, 2023\n\n### Fixed\n\n- Fixed `lune --generate-selene-types` generating an invalid Selene definitions file\n- Fixed type definition parsing issues on Windows\n\n## `0.5.2` - February 26th, 2023\n\n### Fixed\n\n- Fixed crash when using `stdio.color()` or `stdio.style()` in a CI environment or non-interactive terminal\n\n## `0.5.1` - February 25th, 2023\n\n### Added\n\n- Added `net.encode` and `net.decode` which are equivalent to `net.jsonEncode` and `net.jsonDecode`, but with support for more formats.\n\n  **_WARNING: Unstable API_**\n\n  _This API is unstable and may change or be removed in the next major version of Lune. The purpose of making a new release with these functions is to gather feedback from the community, and potentially replace the JSON-specific encoding and decoding utilities._\n\n  Example usage:\n\n  ```lua\n  local toml = net.decode(\"toml\", [[\n  [package]\n  name = \"my-cool-toml-package\"\n  version = \"0.1.0\"\n\n  [values]\n  epic = true\n  ]])\n\n  assert(toml.package.name == \"my-cool-toml-package\")\n  assert(toml.package.version == \"0.1.0\")\n  assert(toml.values.epic == true)\n  ```\n\n### Fixed\n\n- Fixed indentation of closing curly bracket when printing tables\n\n## `0.5.0` - February 23rd, 2023\n\n### Added\n\n- Added auto-generated API reference pages and documentation using GitHub wiki pages\n- Added support for `query` in `net.request` parameters, which enables usage of query parameters in URLs without having to manually URL encode values.\n- Added a new function `fs.move` to move / rename a file or directory from one path to another.\n- Implemented a new task scheduler which resolves several long-standing issues:\n  - Issues with yielding across the C-call/metamethod boundary no longer occur when calling certain async APIs that Lune provides.\n  - Ordering of interleaved calls to `task.spawn/task.defer` is now completely deterministic, deferring is now guaranteed to run last even in these cases.\n  - The minimum wait time possible when using `task.wait` and minimum delay time using `task.delay` are now much smaller, and only limited by the underlying OS implementation. For most systems this means `task.wait` and `task.delay` are now accurate down to about 5 milliseconds or less.\n\n### Changed\n\n- Type definitions are now bundled as part of the Lune executable, meaning they no longer need to be downloaded.\n  - `lune --generate-selene-types` will generate the Selene type definitions file, replacing `lune --download-selene-types`\n  - `lune --generate-luau-types` will generate the Luau type definitions file, replacing `lune --download-luau-types`\n- Improved accuracy of Selene type definitions, strongly typed arrays are now used where possible\n- Improved error handling and messages for `net.serve`\n- Improved error handling and messages for `stdio.prompt`\n- File path representations on Windows now use legacy paths instead of UNC paths wherever possible, preventing some confusing cases where file paths don't work as expected\n\n### Fixed\n\n- Fixed `process.cwd` not having the correct ending path separator on Windows\n- Fixed remaining edge cases where the `task` and `coroutine` libraries weren't interoperable\n- Fixed `task.delay` keeping the script running even if it was cancelled using `task.cancel`\n- Fixed `stdio.prompt` blocking all other lua threads while prompting for input\n\n## `0.4.0` - February 11th, 2023\n\n### Added\n\n- ### Web Sockets\n\n  `net` now supports web sockets for both clients and servers! <br />\n  Note that the web socket object is identical on both client and\n  server, but how you retrieve a web socket object is different.\n\n  #### Server API\n\n  The server web socket API is an extension of the existing `net.serve` function. <br />\n  This allows for serving both normal HTTP requests and web socket requests on the same port.\n\n  Example usage:\n\n  ```lua\n  net.serve(8080, {\n      handleRequest = function(request)\n          return \"Hello, world!\"\n      end,\n      handleWebSocket = function(socket)\n          task.delay(10, function()\n              socket.send(\"Timed out!\")\n              socket.close()\n          end)\n          -- The message will be nil when the socket has closed\n          repeat\n              local messageFromClient = socket.next()\n              if messageFromClient == \"Ping\" then\n                  socket.send(\"Pong\")\n              end\n          until messageFromClient == nil\n      end,\n  })\n  ```\n\n  #### Client API\n\n  Example usage:\n\n  ```lua\n  local socket = net.socket(\"ws://localhost:8080\")\n\n  socket.send(\"Ping\")\n\n  task.delay(5, function()\n      socket.close()\n  end)\n\n  -- The message will be nil when the socket has closed\n  repeat\n      local messageFromServer = socket.next()\n      if messageFromServer == \"Ping\" then\n          socket.send(\"Pong\")\n      end\n  until messageFromServer == nil\n  ```\n\n### Changed\n\n- `net.serve` now returns a `NetServeHandle` which can be used to stop serving requests safely.\n\n  Example usage:\n\n  ```lua\n  local handle = net.serve(8080, function()\n      return \"Hello, world!\"\n  end)\n\n  print(\"Shutting down after 1 second...\")\n  task.wait(1)\n  handle.stop()\n  print(\"Shut down succesfully\")\n  ```\n\n- The third and optional argument of `process.spawn` is now a global type `ProcessSpawnOptions`.\n- Setting `cwd` in the options for `process.spawn` to a path starting with a tilde (`~`) will now use a path relative to the platform-specific home / user directory.\n- `NetRequest` query parameters value has been changed to be a table of key-value pairs similar to `process.env`.\n  If any query parameter is specified more than once in the request url, the value chosen will be the last one that was specified.\n- The internal http client for `net.request` now reuses headers and connections for more efficient requests.\n- Refactored the Lune rust crate to be much more user-friendly and documented all of the public functions.\n\n### Fixed\n\n- Fixed `process.spawn` blocking all lua threads if the spawned child process yields.\n\n## `0.3.0` - February 6th, 2023\n\n### Added\n\n- Added a new global `stdio` which replaces `console`\n- Added `stdio.write` which writes a string directly to stdout, without any newlines\n- Added `stdio.ewrite` which writes a string directly to stderr, without any newlines\n- Added `stdio.prompt` which will prompt the user for different kinds of input\n\n  Example usage:\n\n  ```lua\n  local text = stdio.prompt()\n\n  local text2 = stdio.prompt(\"text\", \"Please write some text\")\n\n  local didConfirm = stdio.prompt(\"confirm\", \"Please confirm this action\")\n\n  local optionIndex = stdio.prompt(\"select\", \"Please select an option\", { \"one\", \"two\", \"three\" })\n\n  local optionIndices = stdio.prompt(\n      \"multiselect\",\n      \"Please select one or more options\",\n      { \"one\", \"two\", \"three\", \"four\", \"five\" }\n  )\n  ```\n\n### Changed\n\n- Migrated `console.setColor/resetColor` and `console.setStyle/resetStyle` to `stdio.color` and `stdio.style` to allow for more flexibility in custom printing using ANSI color codes. Check the documentation for new usage and behavior.\n- Migrated the pretty-printing and formatting behavior of `console.log/info/warn/error` to the standard Luau printing functions.\n\n### Removed\n\n- Removed printing functions `console.log/info/warn/error` in favor of regular global functions for printing.\n\n### Fixed\n\n- Fixed scripts hanging indefinitely on error\n\n## `0.2.2` - February 5th, 2023\n\n### Added\n\n- Added global types for networking & child process APIs\n  - `net.request` gets `NetFetchParams` and `NetFetchResponse` for its argument and return value\n  - `net.serve` gets `NetRequest` and `NetResponse` for the handler function argument and return value\n  - `process.spawn` gets `ProcessSpawnOptions` for its third and optional parameter\n\n### Changed\n\n- Reorganize repository structure to take advantage of cargo workspaces, improves compile times\n\n## `0.2.1` - February 3rd, 2023\n\n### Added\n\n- Added support for string interpolation syntax (update to Luau 0.561)\n- Added network server functionality using `net.serve`\n\n  Example usage:\n\n  ```lua\n  net.serve(8080, function(request)\n      print(`Got a {request.method} request at {request.path}!`)\n\n      local data = net.jsonDecode(request.body)\n\n      -- For simple text responses with a 200 status\n      return \"OK\"\n\n      -- For anything else\n      return {\n          status = 203,\n          headers = { [\"Content-Type\"] = \"application/json\" },\n          body = net.jsonEncode({\n              message = \"echo\",\n              data = data,\n          })\n      }\n  end)\n  ```\n\n### Changed\n\n- Improved type definitions file for Selene, now including constants like `process.env` + tags such as `readonly` and `mustuse` wherever applicable\n\n### Fixed\n\n- Fixed type definitions file for Selene not including all API members and parameters\n- Fixed `process.exit` exiting at the first yield instead of exiting instantly as it should\n\n## `0.2.0` - January 28th, 2023\n\n### Added\n\n- Added full documentation for all global APIs provided by Lune! This includes over 200 lines of pure documentation about behavior & error cases for all of the current 35 constants & functions. Check the [README](/README.md) to find out how to enable documentation in your editor.\n\n- Added a third argument `options` for `process.spawn`:\n  - `cwd` - The current working directory for the process\n  - `env` - Extra environment variables to give to the process\n  - `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell\n  - `stdio` - How to treat output and error streams from the child process - set to `\"inherit\"` to pass output and error streams to the current process\n\n- Added `process.cwd`, the path to the current working directory in which the Lune script is running\n\n## `0.1.3` - January 25th, 2023\n\n### Added\n\n- Added a `--list` subcommand to list scripts found in the `lune` or `.lune` directory.\n\n## `0.1.2` - January 24th, 2023\n\n### Added\n\n- Added automatic publishing of the Lune library to [crates.io](https://crates.io/crates/lune)\n\n### Fixed\n\n- Fixed scripts that terminate instantly sometimes hanging\n\n## `0.1.1` - January 24th, 2023\n\n### Fixed\n\n- Fixed errors containing `./` and / or `../` in the middle of file paths\n- Potential fix for spawned processes that yield erroring with \"attempt to yield across metamethod/c-call boundary\"\n\n## `0.1.0` - January 24th, 2023\n\n### Added\n\n- `task` now supports passing arguments in `task.spawn` / `task.delay` / `task.defer`\n- `require` now uses paths relative to the file instead of being relative to the current directory, which is consistent with almost all other languages but not original Lua / Luau - this is a breaking change but will allow for proper packaging of third-party modules and more in the future.\n  - **_NOTE:_** _If you still want to use the default Lua behavior instead of relative paths, set the environment variable `LUAU_PWD_REQUIRE` to `true`_\n\n### Changed\n\n- Improved error message when an invalid file path is passed to `require`\n- Much improved error formatting and stack traces\n\n### Fixed\n\n- Fixed downloading of type definitions making json files instead of the proper format\n- Process termination will now always make sure all lua state is cleaned up before exiting, in all cases\n\n## `0.0.6` - January 23rd, 2023\n\n### Added\n\n- Initial implementation of [Roblox's task library](https://create.roblox.com/docs/reference/engine/libraries/task), with some caveats:\n  - Minimum wait / delay time is currently set to 10ms, subject to change\n  - It is not yet possible to pass arguments to tasks created using `task.spawn` / `task.delay` / `task.defer`\n  - Timings for `task.defer` are flaky and deferred tasks are not (yet) guaranteed to run after spawned tasks\n\n  With all that said, everything else should be stable!\n  - Mixing and matching the `coroutine` library with `task` works in all cases\n  - `process.exit()` will stop all spawned / delayed / deferred threads and exit the process\n  - Lune is guaranteed to keep running until there are no longer any waiting threads\n\n  If any of the abovementioned things do not work as expected, it is a bug, please file an issue!\n\n### Fixed\n\n- Potential fix for spawned processes that yield erroring with \"attempt to yield across metamethod/c-call boundary\"\n\n## `0.0.5` - January 22nd, 2023\n\n### Added\n\n- Added full test suites for all Lune globals to ensure correct behavior\n- Added library version of Lune that can be used from other Rust projects\n\n### Changed\n\n- Large internal changes to allow for implementing the `task` library.\n- Improved general formatting of errors to make them more readable & glanceable\n- Improved output formatting of non-primitive types\n- Improved output formatting of empty tables\n\n### Fixed\n\n- Fixed double stack trace for certain kinds of errors\n\n## `0.0.4` - January 21st, 2023\n\n### Added\n\n- Added `process.args` for inspecting values given to Lune when running (read only)\n- Added `process.env` which is a plain table where you can get & set environment variables\n\n### Changed\n\n- Improved error formatting & added proper file name to stack traces\n\n### Removed\n\n- Removed `...` for process arguments, use `process.args` instead\n- Removed individual functions for getting & setting environment variables, use `process.env` instead\n\n## `0.0.3` - January 20th, 2023\n\n### Added\n\n- Added networking functions under `net`\n\n  Example usage:\n\n  ```lua\n  local apiResult = net.request({\n  \turl = \"https://jsonplaceholder.typicode.com/posts/1\",\n  \tmethod = \"PATCH\",\n  \theaders = {\n  \t\t[\"Content-Type\"] = \"application/json\",\n  \t},\n  \tbody = net.jsonEncode({\n  \t\ttitle = \"foo\",\n  \t\tbody = \"bar\",\n  \t}),\n  })\n\n  local apiResponse = net.jsonDecode(apiResult.body)\n  assert(apiResponse.title == \"foo\", \"Invalid json response\")\n  assert(apiResponse.body == \"bar\", \"Invalid json response\")\n  ```\n\n- Added console logging & coloring functions under `console`\n\n  This piece of code:\n\n  ```lua\n  local tab = { Integer = 1234, Hello = { \"World\" } }\n  console.log(tab)\n  ```\n\n  Will print the following formatted text to the console, **_with syntax highlighting_**:\n\n  ```lua\n  {\n      Integer = 1234,\n      Hello = {\n          \"World\",\n      }\n  }\n  ```\n\n  Additional utility functions exist with the same behavior but that also print out a colored\n  tag together with any data given to them: `console.info`, `console.warn`, `console.error` -\n  These print out prefix tags `[INFO]`, `[WARN]`, `[ERROR]` in blue, orange, and red, respectively.\n\n### Changed\n\n- The `json` api is now part of `net`\n  - `json.encode` becomes `net.jsonEncode`\n  - `json.decode` become `net.jsonDecode`\n\n### Fixed\n\n- Fixed JSON decode not working properly\n\n## `0.0.2` - January 19th, 2023\n\n### Added\n\n- Added support for command-line parameters to scripts\n\n  These can be accessed as a vararg in the root of a script:\n\n  ```lua\n  local firstArg: string, secondArg: string = ...\n  print(firstArg, secondArg)\n  ```\n\n- Added CLI parameters for downloading type definitions:\n  - `lune --download-selene-types` to download Selene types to the current directory\n  - `lune --download-luau-types` to download Luau types to the current directory\n\n  These files will be downloaded as `lune.yml` and `luneTypes.d.luau`\n  respectively and are also available in each release on GitHub.\n\n## `0.0.1` - January 18th, 2023\n\nInitial Release\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "<!-- markdownlint-disable MD001 -->\n<!-- markdownlint-disable MD033 -->\n\n# Contributing\n\n---\n\n### Reporting a Bug\n\n- Make sure the bug has not already been reported by searching on GitHub under [Issues](https://github.com/lune-org/lune/issues).\n- 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.\n\n---\n\n### Contributing - Bug Fixes\n\n1. 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.\n2. Open a new GitHub pull request for it. A pull request for a bug fix must include:\n   - A clear and concise description of the bug it is fixing.\n   - A new test file ensuring there are no regressions after the bug has been fixed.\n   - A link to the relevant issue, or a `Fixes #issue` line, if an issue exists.\n\n### Contributing - Features\n\n1. 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.\n2. 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.\n3. Familiarize yourself with the codebase and the tools you will be using. Some important parts include:\n   - The [mlua](https://crates.io/crates/mlua) library, which we use to interface with Luau.\n   - 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.\n   - 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.\n4. Write some code!\n5. Open a new GitHub pull request. A pull request for a feature must include:\n   - A clear and concise description of the new feature or changes to the feature.\n   - Test files for any added or changed functionality.\n   - A link to the relevant issue, or a `Closes #issue` line.\n\n### Contributing - Formatting & Cosmetic Changes\n\nChanges 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.\n\n### Contributing - Documentation\n\n#### Documentation Site\n\nCheck out the [docs](https://github.com/lune-org/docs) repository and its contribution guidelines.\n\n#### Type Definitions\n\nIf type definitions for built-in libraries need improvements:\n\n1. Check out the [types](https://github.com/lune-org/lune/tree/main/types) directory at the root of the repository.\n2. Make the desired changes, and verify that they have the desired outcome.\n3. Open a new GitHub pull request for your changes.\n\n---\n\n### Publishing a Release\n\nThe Lune release process is semi-automated, and takes care of most things for you. Here's how to create a new release:\n\n1. Make sure the changelog is up to date and contains all of the changes since the last release.\n2. Add the release date in the changelog + set a new version number in `Cargo.toml`.\n3. 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.\n4. 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.\n5. Add in changes from the changelog for the new pending release into the description, hit \"accept\" on creating a new version tag, and publish 🚀\n\n---\n\nIf 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!\n\nThank you for contributing to Lune! 🌙\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\ndefault-members = [\"crates/lune\"]\nmembers = [\n    \"crates/lune\",\n    \"crates/lune-roblox\",\n    \"crates/lune-std\",\n    \"crates/lune-std-datetime\",\n    \"crates/lune-std-fs\",\n    \"crates/lune-std-luau\",\n    \"crates/lune-std-net\",\n    \"crates/lune-std-process\",\n    \"crates/lune-std-regex\",\n    \"crates/lune-std-roblox\",\n    \"crates/lune-std-serde\",\n    \"crates/lune-std-stdio\",\n    \"crates/lune-std-task\",\n    \"crates/lune-utils\",\n    \"crates/mlua-luau-scheduler\",\n]\n\n# Profile for building the release binary, with the following options set:\n#\n# 1. Optimize for size\n# 2. Automatically strip symbols from the binary\n# 3. Enable link-time optimization\n#\n# Note that we could abort instead of panicking to cut down on size\n# even more, but because we use the filesystem & some other APIs we\n# need the panic unwinding to properly handle usage of said APIs\n#\n[profile.release]\nopt-level = \"z\"\nstrip = true\nlto = true\n\n# Lints for all crates in the workspace\n#\n# 1. Error on all lints by default, then make cargo + clippy pedantic lints just warn\n# 2. Selectively allow some lints that are _too_ pedantic, such as:\n#    - Casts between number types\n#    - Module naming conventions\n#    - Imports and multiple dependency versions\n[workspace.lints.clippy]\nall = { level = \"deny\", priority = -3 }\ncargo = { level = \"warn\", priority = -2 }\npedantic = { level = \"warn\", priority = -1 }\n\ncast_lossless = { level = \"allow\", priority = 1 }\ncast_possible_truncation = { level = \"allow\", priority = 1 }\ncast_possible_wrap = { level = \"allow\", priority = 1 }\ncast_precision_loss = { level = \"allow\", priority = 1 }\ncast_sign_loss = { level = \"allow\", priority = 1 }\n\nsimilar_names = { level = \"allow\", priority = 1 }\nunnecessary_wraps = { level = \"allow\", priority = 1 }\nunnested_or_patterns = { level = \"allow\", priority = 1 }\nunreadable_literal = { level = \"allow\", priority = 1 }\n\nmultiple_crate_versions = { level = \"allow\", priority = 1 }\nmodule_inception = { level = \"allow\", priority = 1 }\nmodule_name_repetitions = { level = \"allow\", priority = 1 }\nneedless_pass_by_value = { level = \"allow\", priority = 1 }\nwildcard_imports = { level = \"allow\", priority = 1 }\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "<!-- markdownlint-disable MD033 -->\n<!-- markdownlint-disable MD041 -->\n\n<img align=\"right\" width=\"250\" src=\"assets/logo/tilt_svg.svg\" alt=\"Lune logo\" />\n\n<h1 align=\"center\">Lune</h1>\n\n<div align=\"center\">\n\t<div>\n\t\t<a href=\"https://crates.io/crates/lune\">\n\t\t\t<img src=\"https://img.shields.io/crates/v/lune.svg?label=Version\" alt=\"Current Lune library version\" />\n\t\t</a>\n\t\t<a href=\"https://github.com/lune-org/lune/actions\">\n\t\t\t<img src=\"https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/lune-org/lune/ci.yaml\" alt=\"CI status\" />\n\t\t</a>\n\t\t<a href=\"https://github.com/lune-org/lune/actions\">\n\t\t\t<img src=\"https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/lune-org/lune/release.yaml\" alt=\"Release status\" />\n\t\t</a>\n\t\t<a href=\"https://github.com/lune-org/lune/blob/main/LICENSE.txt\">\n\t\t\t<img src=\"https://img.shields.io/github/license/lune-org/lune.svg?label=License&color=informational\" alt=\"Lune license\" />\n\t\t</a>\n\t</div>\n</div>\n\n<br/>\n\nA standalone [Luau](https://luau-lang.org) runtime.\n\nWrite 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.\n\nLune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for speed, safety and correctness.\n\n## Features\n\n- 🌙 Strictly minimal but powerful interface that is easy to read and remember, just like Luau itself\n- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb zipped) executable\n- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary\n- 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port\n- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances\n\n## Non-goals\n\n- Making programs short and terse - proper autocomplete / intellisense make using Lune just as quick, and readability is important\n- Running full Roblox games outside of Roblox - there is some compatibility, but Lune is meant for different purposes\n\n## Where do I start?\n\nHead over to the [Installation](https://lune-org.github.io/docs/getting-started/1-installation) page to get started using Lune!\n"
  },
  {
    "path": "crates/lune/Cargo.toml",
    "content": "[package]\nname = \"lune\"\nversion = \"0.10.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"A standalone Luau runtime\"\nreadme = \"../../README.md\"\nkeywords = [\"cli\", \"lua\", \"luau\", \"runtime\"]\ncategories = [\"command-line-interface\"]\n\n[[bin]]\nname = \"lune\"\npath = \"src/main.rs\"\n\n[lib]\nname = \"lune\"\npath = \"src/lib.rs\"\n\n[features]\ndefault = [\"std\", \"cli\"]\n\nstd-datetime = [\"dep:lune-std\", \"lune-std/datetime\"]\nstd-fs = [\"dep:lune-std\", \"lune-std/fs\"]\nstd-luau = [\"dep:lune-std\", \"lune-std/luau\"]\nstd-net = [\"dep:lune-std\", \"lune-std/net\"]\nstd-process = [\"dep:lune-std\", \"lune-std/process\"]\nstd-regex = [\"dep:lune-std\", \"lune-std/regex\"]\nstd-roblox = [\"dep:lune-std\", \"lune-std/roblox\"]\nstd-serde = [\"dep:lune-std\", \"lune-std/serde\"]\nstd-stdio = [\"dep:lune-std\", \"lune-std/stdio\"]\nstd-task = [\"dep:lune-std\", \"lune-std/task\"]\n\nstd = [\n    \"std-datetime\",\n    \"std-fs\",\n    \"std-luau\",\n    \"std-net\",\n    \"std-process\",\n    \"std-regex\",\n    \"std-roblox\",\n    \"std-serde\",\n    \"std-stdio\",\n    \"std-task\",\n]\n\ncli = [\"dep:clap\", \"dep:rustyline\", \"dep:zip\", \"dep:lune-std-net\"]\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\nanyhow = \"1.0\"\nconsole = \"0.16\"\ndialoguer = \"0.12\"\ndirectories = \"6.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\nthiserror = \"2.0\"\n\nasync-io = \"2.4\"\nasync-fs = \"2.1\"\nblocking = \"1.6\"\nfutures-lite = \"2.6\"\n\ntracing = \"0.1\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\n\nlune-std = { optional = true, version = \"0.3.4\", path = \"../lune-std\" }\nlune-std-net = { optional = true, version = \"0.3.4\", path = \"../lune-std-net\" }\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n\n### CLI\n\nclap = { optional = true, version = \"4.1\", features = [\"derive\"] }\nrustyline = { optional = true, version = \"17.0\" }\nzip = { optional = true, version = \"5.1\", default-features = false, features = [\n\t\"bzip2\",\n\t\"deflate\",\n\t\"deflate64\",\n\t\"zstd\"\n] }\n"
  },
  {
    "path": "crates/lune/src/cli/build/base_exe.rs",
    "content": "use std::{\n    io::{Cursor, Read},\n    path::PathBuf,\n};\n\nuse async_fs as fs;\nuse blocking::unblock;\n\nuse crate::standalone::metadata::CURRENT_EXE;\n\nuse super::{\n    files::write_executable_file_to,\n    result::{BuildError, BuildResult},\n    target::{BuildTarget, CACHE_DIR},\n};\n\nconst RELEASE_REQUEST_HEADERS: &[(&str, &str)] = &[\n    (\n        \"User-Agent\",\n        concat!(\n            \"Lune/\",\n            env!(\"CARGO_PKG_VERSION\"),\n            \" (\",\n            env!(\"CARGO_PKG_REPOSITORY\"),\n            \")\"\n        ),\n    ),\n    (\"Accept\", \"application/octet-stream\"),\n    // (\"Accept-Encoding\", \"gzip\"),\n];\n\n/**\n    Discovers the path to the base executable to use for cross-compilation.\n\n    If the target is the same as the current system, the current executable is used.\n\n    If no binary exists at the target path, it will attempt to download it from the internet.\n*/\npub async fn get_or_download_base_executable(target: BuildTarget) -> BuildResult<PathBuf> {\n    if target.is_current_system() {\n        return Ok(CURRENT_EXE.to_path_buf());\n    }\n    if target.cache_path().exists() {\n        return Ok(target.cache_path());\n    }\n\n    // The target is not cached, we must download it\n    println!(\"Requested target '{target}' does not exist in cache\");\n    let version = env!(\"CARGO_PKG_VERSION\");\n    let target_triple = format!(\"lune-{version}-{target}\");\n\n    let release_url = format!(\n        \"{base_url}/v{version}/{target_triple}.zip\",\n        base_url = \"https://github.com/lune-org/lune/releases/download\",\n    );\n\n    // NOTE: This is not entirely accurate, but it is clearer for a user\n    println!(\"Downloading {target_triple}{}...\", target.exe_suffix());\n\n    // Try to request to download the zip file from the target url,\n    // making sure transient errors are handled gracefully and\n    // with a different error message than \"not found\"\n    let url = release_url.parse().expect(\"release url is valid\");\n    let headers = RELEASE_REQUEST_HEADERS\n        .iter()\n        .map(|(k, v)| ((*k).to_string(), (*v).to_string()))\n        .collect();\n    let res = lune_std_net::fetch(url, None, Some(headers), None)\n        .await\n        .map_err(BuildError::Download)?;\n    let (parts, body) = res.into_inner().into_parts();\n\n    if !parts.status.is_success() {\n        if parts.status.as_u16() == 404 {\n            return Err(BuildError::ReleaseTargetNotFound(target));\n        }\n        let body = body.into_bytes();\n        return Err(BuildError::Download(format!(\n            \"Request was not successful\\\n            \\nStatus: {}\\\n            \\nBody: {}\",\n            parts.status,\n            if body.len() > 128 {\n                String::from_utf8_lossy(&body[0..128])\n            } else {\n                String::from_utf8_lossy(&body)\n            }\n        )));\n    }\n\n    // Start reading the zip file\n    let zip_file = Cursor::new(body.into_bytes());\n\n    // Look for and extract the binary file from the zip file\n    // NOTE: We use spawn_blocking here since reading a zip\n    // archive is a somewhat slow / blocking operation\n    let binary_file_name = format!(\"lune{}\", target.exe_suffix());\n    let binary_file_handle = unblock(move || {\n        let mut archive = zip::ZipArchive::new(zip_file)?;\n\n        let mut binary = Vec::new();\n        archive\n            .by_name(&binary_file_name)\n            .or(Err(BuildError::ZippedBinaryNotFound(binary_file_name)))?\n            .read_to_end(&mut binary)?;\n\n        Ok::<_, BuildError>(binary)\n    });\n    let binary_file_contents = binary_file_handle.await?;\n\n    // Finally, write the extracted binary to the cache\n    if !CACHE_DIR.exists() {\n        fs::create_dir_all(CACHE_DIR.as_path()).await?;\n    }\n    write_executable_file_to(target.cache_path(), binary_file_contents).await?;\n    println!(\"Downloaded successfully and added to cache\");\n\n    Ok(target.cache_path())\n}\n"
  },
  {
    "path": "crates/lune/src/cli/build/files.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse anyhow::Result;\nuse async_fs as fs;\nuse futures_lite::prelude::*;\n\n/**\n    Removes the source file extension from the given path, if it has one.\n\n    A source file extension is an extension such as `.lua` or `.luau`.\n*/\npub fn remove_source_file_ext(path: &Path) -> PathBuf {\n    if path\n        .extension()\n        .is_some_and(|ext| matches!(ext.to_str(), Some(\"lua\" | \"luau\")))\n    {\n        path.with_extension(\"\")\n    } else {\n        path.to_path_buf()\n    }\n}\n\n/**\n    Writes the given bytes to a file at the specified path,\n    and makes sure it has permissions to be executed.\n*/\npub async fn write_executable_file_to(\n    path: impl AsRef<Path>,\n    bytes: impl AsRef<[u8]>,\n) -> Result<(), std::io::Error> {\n    let mut options = fs::OpenOptions::new();\n    options.write(true).create(true).truncate(true);\n\n    #[cfg(unix)]\n    {\n        use fs::unix::OpenOptionsExt;\n        options.mode(0o755); // Read & execute for all, write for owner\n    }\n\n    let mut file = options.open(path).await?;\n    file.write_all(bytes.as_ref()).await?;\n    file.flush().await?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune/src/cli/build/mod.rs",
    "content": "use std::{path::PathBuf, process::ExitCode};\n\nuse anyhow::{Context, Result, bail};\nuse async_fs as fs;\nuse clap::Parser;\nuse console::style;\n\nuse crate::standalone::metadata::Metadata;\n\nmod base_exe;\nmod files;\nmod result;\nmod target;\n\nuse self::base_exe::get_or_download_base_executable;\nuse self::files::{remove_source_file_ext, write_executable_file_to};\nuse self::target::BuildTarget;\n\n/// Build a standalone executable\n#[derive(Debug, Clone, Parser)]\npub struct BuildCommand {\n    /// The path to the input file\n    pub input: PathBuf,\n\n    /// The path to the output file - defaults to the\n    /// input file path with an executable extension\n    #[clap(short, long)]\n    pub output: Option<PathBuf>,\n\n    /// The target to compile for in the format `os-arch` -\n    /// defaults to the os and arch of the current system\n    #[clap(short, long)]\n    pub target: Option<BuildTarget>,\n}\n\nimpl BuildCommand {\n    pub async fn run(self) -> Result<ExitCode> {\n        // Derive target spec to use, or default to the current host system\n        let target = self.target.unwrap_or_else(BuildTarget::current_system);\n\n        // Derive paths to use, and make sure the output path is\n        // not the same as the input, so that we don't overwrite it\n        let output_path = self\n            .output\n            .clone()\n            .unwrap_or_else(|| remove_source_file_ext(&self.input));\n        let output_path = output_path.with_extension(target.exe_extension());\n        if output_path == self.input {\n            if self.output.is_some() {\n                bail!(\"output path cannot be the same as input path\");\n            }\n            bail!(\n                \"output path cannot be the same as input path, please specify a different output path\"\n            );\n        }\n\n        // Try to read the given input file\n        // FUTURE: We should try and resolve a full require file graph using the input\n        // path here instead, see the notes in the `standalone` module for more details\n        let source_code = fs::read(&self.input)\n            .await\n            .context(\"failed to read input file\")?;\n\n        // Derive the base executable path based on the arguments provided\n        let base_exe_path = get_or_download_base_executable(target).await?;\n\n        // Read the contents of the lune interpreter as our starting point\n        println!(\n            \"Compiling standalone binary from {}\",\n            style(self.input.display()).green()\n        );\n        let patched_bin = Metadata::create_env_patched_bin(base_exe_path, source_code)\n            .await\n            .context(\"failed to create patched binary\")?;\n\n        // And finally write the patched binary to the output file\n        println!(\n            \"Writing standalone binary to {}\",\n            style(output_path.display()).blue()\n        );\n        write_executable_file_to(output_path, patched_bin).await?; // Read & execute for all, write for owner\n\n        Ok(ExitCode::SUCCESS)\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/build/result.rs",
    "content": "use thiserror::Error;\n\nuse super::target::BuildTarget;\n\n/**\n    Errors that may occur when building a standalone binary\n*/\n#[derive(Debug, Error)]\npub enum BuildError {\n    #[error(\"failed to find lune target '{0}' in GitHub release\")]\n    ReleaseTargetNotFound(BuildTarget),\n    #[error(\"failed to find lune binary '{0}' in downloaded zip file\")]\n    ZippedBinaryNotFound(String),\n    #[error(\"failed to download lune binary: {0}\")]\n    Download(String),\n    #[error(\"failed to unzip lune binary: {0}\")]\n    Unzip(#[from] zip::result::ZipError),\n    #[error(\"io error: {0}\")]\n    IoError(#[from] std::io::Error),\n}\n\npub type BuildResult<T, E = BuildError> = std::result::Result<T, E>;\n"
  },
  {
    "path": "crates/lune/src/cli/build/target.rs",
    "content": "use std::{env::consts::ARCH, fmt, path::PathBuf, str::FromStr, sync::LazyLock};\n\nuse directories::BaseDirs;\n\nstatic HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| {\n    BaseDirs::new()\n        .expect(\"could not find home directory\")\n        .home_dir()\n        .to_path_buf()\n});\n\npub static CACHE_DIR: LazyLock<PathBuf> = LazyLock::new(|| HOME_DIR.join(\".lune\").join(\"target\"));\n\n/**\n    A target operating system supported by Lune\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum BuildTargetOS {\n    Windows,\n    Linux,\n    MacOS,\n}\n\nimpl BuildTargetOS {\n    fn current_system() -> Self {\n        match std::env::consts::OS {\n            \"windows\" => Self::Windows,\n            \"linux\" => Self::Linux,\n            \"macos\" => Self::MacOS,\n            _ => panic!(\"unsupported target OS\"),\n        }\n    }\n\n    fn exe_extension(self) -> &'static str {\n        // NOTE: We can't use the constants from std since\n        // they are only accessible for the current target\n        match self {\n            Self::Windows => \"exe\",\n            _ => \"\",\n        }\n    }\n\n    fn exe_suffix(self) -> &'static str {\n        match self {\n            Self::Windows => \".exe\",\n            _ => \"\",\n        }\n    }\n}\n\nimpl fmt::Display for BuildTargetOS {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Windows => write!(f, \"windows\"),\n            Self::Linux => write!(f, \"linux\"),\n            Self::MacOS => write!(f, \"macos\"),\n        }\n    }\n}\n\nimpl FromStr for BuildTargetOS {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.trim().to_ascii_lowercase().as_str() {\n            \"win\" | \"windows\" => Ok(Self::Windows),\n            \"linux\" => Ok(Self::Linux),\n            \"mac\" | \"macos\" | \"darwin\" => Ok(Self::MacOS),\n            _ => Err(\"invalid target OS\"),\n        }\n    }\n}\n\n/**\n    A target architecture supported by Lune\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum BuildTargetArch {\n    X86_64,\n    Aarch64,\n}\n\nimpl BuildTargetArch {\n    fn current_system() -> Self {\n        match ARCH {\n            \"x86_64\" => Self::X86_64,\n            \"aarch64\" => Self::Aarch64,\n            _ => panic!(\"unsupported target architecture\"),\n        }\n    }\n}\n\nimpl fmt::Display for BuildTargetArch {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::X86_64 => write!(f, \"x86_64\"),\n            Self::Aarch64 => write!(f, \"aarch64\"),\n        }\n    }\n}\n\nimpl FromStr for BuildTargetArch {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.trim().to_ascii_lowercase().as_str() {\n            \"x86_64\" | \"x64\" => Ok(Self::X86_64),\n            \"aarch64\" | \"arm64\" => Ok(Self::Aarch64),\n            _ => Err(\"invalid target architecture\"),\n        }\n    }\n}\n\n/**\n    A full target description that Lune supports (OS + Arch)\n\n    This is used to determine the target to build for standalone binaries,\n    and to download the correct base executable for cross-compilation.\n\n    The target may be parsed from and displayed in the form `os-arch`.\n    Examples of valid targets are:\n\n    - `linux-aarch64`\n    - `linux-x86_64`\n    - `macos-aarch64`\n    - `macos-x86_64`\n    - `windows-x86_64`\n*/\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct BuildTarget {\n    pub os: BuildTargetOS,\n    pub arch: BuildTargetArch,\n}\n\nimpl BuildTarget {\n    pub fn current_system() -> Self {\n        Self {\n            os: BuildTargetOS::current_system(),\n            arch: BuildTargetArch::current_system(),\n        }\n    }\n\n    pub fn is_current_system(&self) -> bool {\n        self.os == BuildTargetOS::current_system() && self.arch == BuildTargetArch::current_system()\n    }\n\n    pub fn exe_extension(&self) -> &'static str {\n        self.os.exe_extension()\n    }\n\n    pub fn exe_suffix(&self) -> &'static str {\n        self.os.exe_suffix()\n    }\n\n    pub fn cache_path(&self) -> PathBuf {\n        CACHE_DIR.join(format!(\"{self}{}\", self.os.exe_extension()))\n    }\n}\n\nimpl fmt::Display for BuildTarget {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}-{}\", self.os, self.arch)\n    }\n}\n\nimpl FromStr for BuildTarget {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let (left, right) = s\n            .split_once('-')\n            .ok_or(\"target must be in the form `os-arch`\")?;\n\n        let os = left.parse()?;\n        let arch = right.parse()?;\n\n        Ok(Self { os, arch })\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/list.rs",
    "content": "use std::{fmt::Write as _, process::ExitCode};\n\nuse anyhow::Result;\nuse clap::Parser;\n\nuse super::utils::listing::{find_lune_scripts, sort_lune_scripts, write_lune_scripts_list};\n\n/// List scripts available to run\n#[derive(Debug, Clone, Parser)]\npub struct ListCommand {}\n\nimpl ListCommand {\n    pub async fn run(self) -> Result<ExitCode> {\n        let sorted_relative = find_lune_scripts(false).await.map(sort_lune_scripts);\n\n        let sorted_home_dir = find_lune_scripts(true).await.map(sort_lune_scripts);\n        if sorted_relative.is_err() && sorted_home_dir.is_err() {\n            eprintln!(\"{}\", sorted_relative.unwrap_err());\n            return Ok(ExitCode::FAILURE);\n        }\n\n        let sorted_relative = sorted_relative.unwrap_or(Vec::new());\n        let sorted_home_dir = sorted_home_dir.unwrap_or(Vec::new());\n\n        let mut buffer = String::new();\n        if !sorted_relative.is_empty() {\n            if sorted_home_dir.is_empty() {\n                write!(&mut buffer, \"Available scripts:\")?;\n            } else {\n                write!(&mut buffer, \"Available scripts in current directory:\")?;\n            }\n            write_lune_scripts_list(&mut buffer, sorted_relative)?;\n        }\n        if !sorted_home_dir.is_empty() {\n            write!(&mut buffer, \"Available global scripts:\")?;\n            write_lune_scripts_list(&mut buffer, sorted_home_dir)?;\n        }\n\n        if buffer.is_empty() {\n            println!(\"No scripts found.\");\n        } else {\n            print!(\"{buffer}\");\n        }\n\n        Ok(ExitCode::SUCCESS)\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/mod.rs",
    "content": "use std::{env::args_os, process::ExitCode};\n\nuse anyhow::Result;\nuse clap::{Parser, Subcommand};\n\npub(crate) mod build;\npub(crate) mod list;\npub(crate) mod repl;\npub(crate) mod run;\npub(crate) mod setup;\npub(crate) mod utils;\n\npub use self::{\n    build::BuildCommand, list::ListCommand, repl::ReplCommand, run::RunCommand, setup::SetupCommand,\n};\n\n#[derive(Debug, Clone, Subcommand)]\npub enum CliSubcommand {\n    Run(RunCommand),\n    List(ListCommand),\n    Setup(SetupCommand),\n    Build(BuildCommand),\n    Repl(ReplCommand),\n}\n\nimpl Default for CliSubcommand {\n    fn default() -> Self {\n        Self::Repl(ReplCommand::default())\n    }\n}\n\n/// Lune, a standalone Luau runtime\n#[derive(Parser, Debug, Default, Clone)]\n#[command(version, about, long_about = None)]\npub struct Cli {\n    #[clap(subcommand)]\n    subcommand: Option<CliSubcommand>,\n}\n\nimpl Cli {\n    pub fn new() -> Self {\n        // TODO: Figure out if there is a better way to do this using clap ... ?\n        // https://github.com/lune-org/lune/issues/253\n        if args_os()\n            .nth(1)\n            .is_some_and(|arg| arg.eq_ignore_ascii_case(\"run\"))\n        {\n            let Some(script_path) = args_os()\n                .nth(2)\n                .and_then(|arg| arg.to_str().map(String::from))\n            else {\n                return Self::parse(); // Will fail and return the help message\n            };\n\n            let script_args = args_os()\n                .skip(3)\n                .filter_map(|arg| arg.to_str().map(String::from))\n                .collect::<Vec<_>>();\n\n            Self {\n                subcommand: Some(CliSubcommand::Run(RunCommand {\n                    script_path,\n                    script_args,\n                })),\n            }\n        } else {\n            Self::parse()\n        }\n    }\n\n    pub async fn run(self) -> Result<ExitCode> {\n        match self.subcommand.unwrap_or_default() {\n            CliSubcommand::Run(cmd) => cmd.run().await,\n            CliSubcommand::List(cmd) => cmd.run().await,\n            CliSubcommand::Setup(cmd) => cmd.run().await,\n            CliSubcommand::Build(cmd) => cmd.run().await,\n            CliSubcommand::Repl(cmd) => cmd.run().await,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/repl.rs",
    "content": "use std::{path::PathBuf, process::ExitCode};\n\nuse anyhow::{Context, Result};\nuse async_fs as fs;\nuse clap::Parser;\nuse directories::UserDirs;\nuse rustyline::{DefaultEditor, error::ReadlineError};\n\nuse lune::Runtime;\n\nconst MESSAGE_WELCOME: &str = concat!(\"Lune v\", env!(\"CARGO_PKG_VERSION\"));\nconst MESSAGE_INTERRUPT: &str = \"Interrupt: ^C again to exit\";\n\nenum PromptState {\n    Regular,\n    Continuation,\n}\n\n/// Launch an interactive REPL (default)\n#[derive(Debug, Clone, Default, Parser)]\npub struct ReplCommand {}\n\nimpl ReplCommand {\n    pub async fn run(self) -> Result<ExitCode> {\n        println!(\"{MESSAGE_WELCOME}\");\n\n        let history_file_path: &PathBuf = &UserDirs::new()\n            .context(\"Failed to find user home directory\")?\n            .home_dir()\n            .join(\".lune_history\");\n        if !history_file_path.exists() {\n            fs::write(history_file_path, &[]).await?;\n        }\n\n        let mut repl = DefaultEditor::new()?;\n        repl.load_history(history_file_path)?;\n\n        let mut interrupt_counter = 0;\n        let mut prompt_state = PromptState::Regular;\n        let mut source_code = String::new();\n\n        let mut lune_instance = Runtime::new()?;\n\n        loop {\n            let prompt = match prompt_state {\n                PromptState::Regular => \"> \",\n                PromptState::Continuation => \">> \",\n            };\n\n            match repl.readline(prompt) {\n                Ok(code) => {\n                    interrupt_counter = 0;\n\n                    // TODO: Should we add history entries for each separate line?\n                    // Or should we add and save history only when we have complete\n                    // lua input that may or may not be multiple lines long?\n                    repl.add_history_entry(&code)?;\n                    repl.save_history(history_file_path)?;\n\n                    match prompt_state {\n                        PromptState::Regular => source_code = code,\n                        PromptState::Continuation => source_code.push_str(&code),\n                    }\n                }\n\n                Err(ReadlineError::Eof) => break,\n                Err(ReadlineError::Interrupted) => {\n                    interrupt_counter += 1;\n\n                    // NOTE: We actually want the user to do ^C twice to exit,\n                    // and if we get an interrupt we should continue to the next\n                    // readline loop iteration so we don't run input code twice\n                    if interrupt_counter == 1 {\n                        println!(\"{MESSAGE_INTERRUPT}\");\n                        continue;\n                    }\n\n                    break;\n                }\n\n                Err(err) => {\n                    eprintln!(\"REPL ERROR: {err}\");\n                    return Ok(ExitCode::FAILURE);\n                }\n            }\n\n            match lune_instance.run_custom(\"REPL\", &source_code).await {\n                Ok(_) => prompt_state = PromptState::Regular,\n\n                Err(err) => {\n                    if err.is_incomplete_input() {\n                        prompt_state = PromptState::Continuation;\n                        source_code.push('\\n');\n                    } else {\n                        eprintln!(\"{err}\");\n                    }\n                }\n            }\n        }\n\n        repl.save_history(history_file_path)?;\n\n        Ok(ExitCode::SUCCESS)\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/run.rs",
    "content": "use std::{env, io::stdin, process::ExitCode};\n\nuse anyhow::{Context, Result};\nuse blocking::Unblock;\nuse clap::Parser;\nuse futures_lite::prelude::*;\n\nuse lune::Runtime;\n\nuse super::utils::files::discover_script_path_including_lune_dirs;\n\n/// Run a script\n#[derive(Debug, Clone, Parser)]\npub struct RunCommand {\n    /// Script name or full path to the file to run\n    pub(super) script_path: String,\n    /// Arguments to pass to the script, stored in process.args\n    pub(super) script_args: Vec<String>,\n}\n\nimpl RunCommand {\n    pub async fn run(self) -> Result<ExitCode> {\n        // Check if the user has explicitly disabled JIT (on by default)\n        let jit_disabled = env::var(\"LUNE_LUAU_JIT\")\n            .ok()\n            .is_some_and(|s| matches!(s.as_str(), \"0\" | \"false\" | \"off\"));\n\n        // Create a new lune runtime with all globals & run the script\n        let mut rt = Runtime::new()?\n            .with_args(self.script_args)\n            .with_jit(!jit_disabled);\n\n        // Figure out if we should run stdin or run a file,\n        // reading from stdin is marked by passing a single \"-\"\n        // (dash) as the script name to run to the cli\n        let result = if &self.script_path == \"-\" {\n            let mut stdin_contents = Vec::new();\n            Unblock::new(stdin())\n                .read_to_end(&mut stdin_contents)\n                .await\n                .context(\"Failed to read script contents from stdin\")?;\n            rt.run_custom(\"stdin\", stdin_contents).await\n        } else {\n            let file_path = discover_script_path_including_lune_dirs(&self.script_path)?;\n            rt.run_file(file_path).await\n        };\n\n        Ok(match result {\n            Err(err) => {\n                eprintln!(\"{err}\");\n                ExitCode::FAILURE\n            }\n            Ok(values) => ExitCode::from(values.status()),\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/setup.rs",
    "content": "use std::{io::ErrorKind, process::ExitCode};\n\nuse anyhow::{Context, Result};\nuse async_fs as fs;\nuse clap::Parser;\nuse directories::UserDirs;\nuse thiserror::Error;\n\nuse serde_json::Value as JsonValue;\n\nconst LUAURC_PATH: &str = \".luaurc\";\n\n/// Set up type definitions for your editor\n#[derive(Debug, Clone, Parser)]\npub struct SetupCommand {}\n\nimpl SetupCommand {\n    pub async fn run(self) -> Result<ExitCode> {\n        generate_typedef_files_from_definitions()\n            .await\n            .expect(\"Failed to generate typedef files\");\n\n        let mut luaurc = read_or_create_luaurc().await?;\n\n        add_values_to_luaurc(&mut luaurc);\n\n        write_luaurc(luaurc).await?;\n\n        println!(\n            \"Type definitions for Lune v{} have been set up successfully.\\\n            \\nYou may need to restart your editor for the changes to take effect.\",\n            lune_version()\n        );\n\n        Ok(ExitCode::SUCCESS)\n    }\n}\n\n#[derive(Debug, Clone, Copy, Error)]\nenum SetupError {\n    #[error(\"Failed to read settings\")]\n    Read,\n    #[error(\"Failed to write settings\")]\n    Write,\n    #[error(\"Failed to parse settings\")]\n    Deserialize,\n    #[error(\"Failed to create settings\")]\n    Serialize,\n}\n\nfn lune_version() -> &'static str {\n    env!(\"CARGO_PKG_VERSION\")\n}\n\nasync fn read_or_create_luaurc() -> Result<JsonValue, SetupError> {\n    match fs::read(LUAURC_PATH).await {\n        Err(e) if e.kind() == ErrorKind::NotFound => match fs::write(LUAURC_PATH, \"{}\").await {\n            Err(_) => Err(SetupError::Write),\n            Ok(()) => Ok(JsonValue::Object(serde_json::Map::new())),\n        },\n        Err(_) => Err(SetupError::Read),\n        Ok(contents) => match serde_json::from_slice(&contents) {\n            Err(_) => Err(SetupError::Deserialize),\n            Ok(json) => Ok(json),\n        },\n    }\n}\n\nasync fn write_luaurc(luaurc: JsonValue) -> Result<(), SetupError> {\n    match serde_json::to_vec_pretty(&luaurc) {\n        Err(_) => Err(SetupError::Serialize),\n        Ok(mut json) => {\n            json.push(b'\\n');\n            match fs::write(LUAURC_PATH, json).await {\n                Err(_) => Err(SetupError::Write),\n                Ok(()) => Ok(()),\n            }\n        }\n    }\n}\n\nfn add_values_to_luaurc(luaurc: &mut JsonValue) {\n    if let JsonValue::Object(luaurc) = luaurc {\n        let field = String::from(\"aliases\");\n        let alias = String::from(\"lune\");\n        let dir = JsonValue::String(format!(\"~/.lune/.typedefs/{}/\", lune_version()));\n\n        if let Some(JsonValue::Object(aliases)) = luaurc.get_mut(&field) {\n            if aliases.contains_key(&alias) {\n                if aliases.get(&alias).unwrap() != &dir {\n                    aliases.insert(alias, dir);\n                }\n            } else {\n                aliases.insert(alias, dir);\n            }\n        } else {\n            let mut map = serde_json::Map::new();\n            map.insert(alias, dir);\n            luaurc.insert(field, JsonValue::Object(map));\n        }\n    }\n}\n\nasync fn generate_typedef_files_from_definitions() -> Result<String> {\n    let version_string = env!(\"CARGO_PKG_VERSION\");\n    let mut dirs_to_write = Vec::new();\n    let mut files_to_write = Vec::new();\n\n    // Create the typedefs dir in the users cache dir\n    let cache_dir = UserDirs::new()\n        .context(\"Failed to find user home directory\")?\n        .home_dir()\n        .join(\".lune\")\n        .join(\".typedefs\")\n        .join(version_string);\n    dirs_to_write.push(cache_dir.clone());\n\n    // Make typedef files\n    for builtin in lune_std::LuneStandardLibrary::ALL {\n        let name = builtin.name().to_lowercase();\n        let path = cache_dir.join(&name).with_extension(\"luau\");\n        files_to_write.push((name, path, builtin.typedefs()));\n    }\n\n    // Write all dirs and files\n    for dir in dirs_to_write {\n        fs::create_dir_all(dir).await?;\n    }\n    for (_name, path, contents) in files_to_write {\n        fs::write(path, contents).await?;\n    }\n    Ok(version_string.to_string())\n}\n"
  },
  {
    "path": "crates/lune/src/cli/utils/files.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result, anyhow};\nuse console::style;\nuse directories::UserDirs;\n\nuse lune_utils::path::{LuauFilePath, LuauModulePath, get_current_dir};\n\nconst LUNE_COMMENT_PREFIX: &str = \"-->\";\n\n/**\n    Discovers a script file path based on a given module path *or* file path.\n\n    See the documentation for [`LuauModulePath`] for more information about\n    what a module path vs a script path is.\n*/\npub fn discover_script_path(path: impl Into<PathBuf>, in_home_dir: bool) -> Result<PathBuf> {\n    // First, for legacy compatibility, we will strip any lua/luau file extension,\n    // and if the entire file stem is simply \"init\", we will get rid of that too\n    // This lets users pass \"dir/init.luau\" and have it resolve to simply \"dir\",\n    // which is a valid luau module path, while \"dir/init.luau\" is not\n    let path = LuauModulePath::strip(path);\n\n    // If we got an absolute path, we should not modify it,\n    // otherwise we should either resolve against home or cwd\n    let path = if path.is_absolute() {\n        path\n    } else if in_home_dir {\n        UserDirs::new()\n            .context(\"Missing home directory\")?\n            .home_dir()\n            .join(path)\n    } else {\n        get_current_dir().join(path)\n    };\n\n    // The rest of the logic should follow Luau module path resolution rules\n    match LuauModulePath::resolve(&path) {\n        Err(e) => Err(anyhow!(\n            \"Failed to resolve script at path {} ({})\",\n            style(path.display()).yellow(),\n            style(format!(\"{e:?}\")).red()\n        )),\n        Ok(m) => match m.target() {\n            LuauFilePath::File(f) => Ok(f.clone()),\n            LuauFilePath::Directory(_) => Err(anyhow!(\n                \"Failed to resolve script at path {}\\\n                \\nThe path is a directory without an init file\",\n                style(path.display()).yellow()\n            )),\n        },\n    }\n}\n\n/**\n    Discovers a script file path based on a given script name, and tries to\n    find scripts in `lune` and `.lune` folders if one was not directly found.\n\n    Note that looking in `lune` and `.lune` folders is automatically\n    disabled if the given script name is an absolute path.\n\n    Behavior is otherwise exactly the same as for `discover_script_file_path`.\n*/\npub fn discover_script_path_including_lune_dirs(path: impl AsRef<Path>) -> Result<PathBuf> {\n    let path: &Path = path.as_ref();\n    match discover_script_path(path, false) {\n        Ok(path) => Ok(path),\n        Err(e) => {\n            // If we got any absolute path it means the user has also\n            // told us to not look in any special relative directories\n            // so we should error right away with the first err message\n            if path.is_absolute() {\n                return Err(e);\n            }\n\n            // Otherwise we take a look in relative lune and .lune\n            // directories + the home directory for the current user\n            let res = discover_script_path(Path::new(\"lune\").join(path), false)\n                .or_else(|_| discover_script_path(Path::new(\".lune\").join(path), false))\n                .or_else(|_| discover_script_path(Path::new(\"lune\").join(path), true))\n                .or_else(|_| discover_script_path(Path::new(\".lune\").join(path), true));\n\n            match res {\n                // NOTE: The first error message is generally more\n                // descriptive than the ones for the lune subfolders\n                Err(_) => Err(e),\n                Ok(path) => Ok(path),\n            }\n        }\n    }\n}\n\npub fn parse_lune_description_from_file(contents: &str) -> Option<String> {\n    let mut comment_lines = Vec::new();\n    for line in contents.lines() {\n        if let Some(stripped) = line.strip_prefix(LUNE_COMMENT_PREFIX) {\n            comment_lines.push(stripped);\n        } else {\n            break;\n        }\n    }\n    if comment_lines.is_empty() {\n        None\n    } else {\n        let shortest_indent = comment_lines.iter().fold(usize::MAX, |acc, line| {\n            let first_alphanumeric = line.find(char::is_alphanumeric).unwrap();\n            acc.min(first_alphanumeric)\n        });\n        let unindented_lines = comment_lines\n            .iter()\n            .map(|line| line[shortest_indent..].to_string())\n            .collect::<Vec<_>>()\n            .join(\" \");\n        Some(unindented_lines)\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/cli/utils/listing.rs",
    "content": "#![allow(clippy::match_same_arms)]\n\nuse std::{\n    cmp::Ordering, ffi::OsStr, fmt::Write as _, io::ErrorKind, path::PathBuf, sync::LazyLock,\n};\n\nuse anyhow::{Result, bail};\nuse async_fs as fs;\nuse console::Style;\nuse directories::UserDirs;\nuse futures_lite::prelude::*;\n\nuse super::files::{discover_script_path, parse_lune_description_from_file};\n\npub static COLOR_BLUE: LazyLock<Style> = LazyLock::new(|| Style::new().blue());\npub static STYLE_DIM: LazyLock<Style> = LazyLock::new(|| Style::new().dim());\n\npub async fn find_lune_scripts(in_home_dir: bool) -> Result<Vec<(String, String)>> {\n    let base_path = if in_home_dir {\n        UserDirs::new().unwrap().home_dir().to_path_buf()\n    } else {\n        PathBuf::new()\n    };\n    let mut lune_dir = fs::read_dir(base_path.join(\"lune\")).await;\n    if lune_dir.is_err() {\n        lune_dir = fs::read_dir(base_path.join(\".lune\")).await;\n    }\n    match lune_dir {\n        Ok(mut dir) => {\n            let mut files = Vec::new();\n            while let Some(entry) = dir.try_next().await? {\n                let meta = entry.metadata().await?;\n                if meta.is_file() {\n                    let contents = fs::read(entry.path()).await?;\n                    files.push((entry, meta, contents));\n                } else if meta.is_dir() {\n                    let entry_path_os = entry.path();\n\n                    let Some(dir_path) = entry_path_os.to_str() else {\n                        bail!(\"Failed to cast path to string slice.\")\n                    };\n\n                    let Ok(init_file_path) = discover_script_path(dir_path, in_home_dir) else {\n                        continue;\n                    };\n\n                    let contents = fs::read(init_file_path).await?;\n\n                    files.push((entry, meta, contents));\n                }\n            }\n            let parsed: Vec<_> = files\n                .iter()\n                .filter(|(entry, _, _)| {\n                    let mut is_match = matches!(\n                        entry.path().extension().and_then(OsStr::to_str),\n                        Some(\"lua\" | \"luau\")\n                    );\n\n                    // If the entry is not a lua or luau file, and is a directory,\n                    // then we check if it contains a init.lua(u), and if it does,\n                    // we include it in our Vec\n                    if !is_match\n                        && entry.path().is_dir()\n                        && (entry.path().join(\"init.lua\").exists()\n                            || entry.path().join(\"init.luau\").exists())\n                    {\n                        is_match = true;\n                    }\n\n                    is_match\n                })\n                .map(|(entry, _, contents)| {\n                    let contents_str = String::from_utf8_lossy(contents);\n                    let file_path = entry.path().with_extension(\"\");\n                    let file_name = file_path.file_name().unwrap().to_string_lossy();\n                    let description = parse_lune_description_from_file(&contents_str);\n                    (file_name.to_string(), description.unwrap_or_default())\n                })\n                .collect();\n            Ok(parsed)\n        }\n        Err(e) if matches!(e.kind(), ErrorKind::NotFound) => {\n            bail!(\"No lune directory was found.\")\n        }\n        Err(e) => {\n            bail!(\"Failed to read lune files!\\n{e}\")\n        }\n    }\n}\n\npub fn sort_lune_scripts(scripts: Vec<(String, String)>) -> Vec<(String, String)> {\n    let mut sorted = scripts;\n    sorted.sort_by(|left, right| {\n        // Prefer scripts that have a description\n        let left_has_desc = !left.1.is_empty();\n        let right_has_desc = !right.1.is_empty();\n        if left_has_desc == right_has_desc {\n            // If both have a description or both\n            // have no description, we sort by name\n            left.0.cmp(&right.0)\n        } else if left_has_desc {\n            Ordering::Less\n        } else {\n            Ordering::Greater\n        }\n    });\n    sorted\n}\n\npub fn write_lune_scripts_list(buffer: &mut String, scripts: Vec<(String, String)>) -> Result<()> {\n    let longest_file_name_len = scripts\n        .iter()\n        .fold(0, |acc, (file_name, _)| acc.max(file_name.len()));\n    let script_with_description_exists = scripts.iter().any(|(_, desc)| !desc.is_empty());\n    // Pre-calculate some strings that will be used often\n    let prefix = format!(\"{}  \", COLOR_BLUE.apply_to('>'));\n    let separator = format!(\"{}\", STYLE_DIM.apply_to('-'));\n    // Write the entire output to a buffer, doing this instead of using individual\n    // writeln! calls will ensure that no output get mixed up in between these lines\n    if script_with_description_exists {\n        for (file_name, description) in scripts {\n            if description.is_empty() {\n                write!(buffer, \"\\n{prefix}{file_name}\")?;\n            } else {\n                let mut lines = description.lines();\n                let first_line = lines.next().unwrap_or_default();\n                let file_spacing = \" \".repeat(file_name.len());\n                let line_spacing = \" \".repeat(longest_file_name_len - file_name.len());\n                write!(\n                    buffer,\n                    \"\\n{prefix}{file_name}{line_spacing}  {separator} {}\",\n                    COLOR_BLUE.apply_to(first_line)\n                )?;\n                for line in lines {\n                    write!(\n                        buffer,\n                        \"\\n{prefix}{file_spacing}{line_spacing}    {}\",\n                        COLOR_BLUE.apply_to(line)\n                    )?;\n                }\n            }\n        }\n    } else {\n        for (file_name, _) in scripts {\n            write!(buffer, \"\\n{prefix}{file_name}\")?;\n        }\n    }\n    // Finally, write an ending newline\n    writeln!(buffer)?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune/src/cli/utils/mod.rs",
    "content": "pub mod files;\npub mod listing;\n"
  },
  {
    "path": "crates/lune/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nmod rt;\n\n#[cfg(test)]\nmod tests;\n\npub use crate::rt::{Runtime, RuntimeError, RuntimeResult, RuntimeReturnValues};\n"
  },
  {
    "path": "crates/lune/src/main.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse std::{io::stderr, process::ExitCode};\n\n#[cfg(feature = \"cli\")]\npub(crate) mod cli;\n\npub(crate) mod standalone;\n\nuse lune_utils::fmt::Label;\n\nfn main() -> ExitCode {\n    tracing_subscriber::fmt()\n        .compact()\n        .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())\n        .with_target(true)\n        .with_timer(tracing_subscriber::fmt::time::uptime())\n        .with_level(true)\n        .with_writer(stderr)\n        .init();\n\n    async_io::block_on(async {\n        if let Some(bin) = standalone::check().await {\n            return standalone::run(bin).await.unwrap();\n        }\n\n        #[cfg(feature = \"cli\")]\n        {\n            match cli::Cli::new().run().await {\n                Ok(code) => code,\n                Err(err) => {\n                    eprintln!(\"{}\\n{err:?}\", Label::Error);\n                    ExitCode::FAILURE\n                }\n            }\n        }\n\n        #[cfg(not(feature = \"cli\"))]\n        {\n            eprintln!(\"{}\\nCLI feature is disabled\", Label::Error);\n            ExitCode::FAILURE\n        }\n    })\n}\n"
  },
  {
    "path": "crates/lune/src/rt/mod.rs",
    "content": "mod result;\nmod runtime;\n\npub use self::result::{RuntimeError, RuntimeResult};\npub use self::runtime::{Runtime, RuntimeReturnValues};\n"
  },
  {
    "path": "crates/lune/src/rt/result.rs",
    "content": "use std::{\n    error::Error,\n    fmt::{Debug, Display, Formatter, Result as FmtResult},\n};\n\nuse mlua::prelude::*;\n\nuse lune_utils::fmt::ErrorComponents;\n\npub type RuntimeResult<T, E = RuntimeError> = Result<T, E>;\n\n/**\n    An opaque error type for formatted lua errors.\n*/\n#[derive(Debug, Clone)]\npub struct RuntimeError {\n    error: LuaError,\n    disable_colors: bool,\n}\n\nimpl RuntimeError {\n    /**\n        Enables colorization of the error message when formatted using the [`Display`] trait.\n\n        Colorization is enabled by default.\n    */\n    #[must_use]\n    #[doc(hidden)]\n    pub fn enable_colors(mut self) -> Self {\n        self.disable_colors = false;\n        self\n    }\n\n    /**\n        Disables colorization of the error message when formatted using the [`Display`] trait.\n\n        Colorization is enabled by default.\n    */\n    #[must_use]\n    #[doc(hidden)]\n    pub fn disable_colors(mut self) -> Self {\n        self.disable_colors = true;\n        self\n    }\n\n    /**\n        Returns `true` if the error can likely be fixed by appending more input to the source code.\n\n        See [`mlua::Error::SyntaxError`] for more information.\n    */\n    #[must_use]\n    pub fn is_incomplete_input(&self) -> bool {\n        matches!(\n            self.error,\n            LuaError::SyntaxError {\n                incomplete_input: true,\n                ..\n            }\n        )\n    }\n}\n\nimpl From<LuaError> for RuntimeError {\n    fn from(value: LuaError) -> Self {\n        Self {\n            error: value,\n            disable_colors: false,\n        }\n    }\n}\n\nimpl From<&LuaError> for RuntimeError {\n    fn from(value: &LuaError) -> Self {\n        Self {\n            error: value.clone(),\n            disable_colors: false,\n        }\n    }\n}\n\nimpl Display for RuntimeError {\n    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {\n        write!(f, \"{}\", ErrorComponents::from(self.error.clone()))\n    }\n}\n\nimpl Error for RuntimeError {\n    fn cause(&self) -> Option<&dyn Error> {\n        Some(&self.error)\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/rt/runtime.rs",
    "content": "#![allow(clippy::missing_panics_doc)]\n\nuse std::{\n    ffi::OsString,\n    path::PathBuf,\n    sync::{\n        Arc,\n        atomic::{AtomicBool, Ordering},\n    },\n};\n\nuse async_fs as fs;\nuse lune_utils::{\n    path::{LuauModulePath, constants::FILE_CHUNK_PREFIX},\n    process::{ProcessArgs, ProcessEnv, ProcessJitEnablement},\n};\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{Functions, Scheduler};\n\nuse super::{RuntimeError, RuntimeResult};\n\n/**\n    Values returned by running a Lune runtime until completion.\n*/\n#[derive(Debug)]\n#[non_exhaustive]\npub struct RuntimeReturnValues {\n    /// The exit code manually returned from the runtime, if any.\n    pub code: Option<u8>,\n    /// Whether any errors were thrown from threads\n    /// that were not the main thread, or not.\n    pub errored: bool,\n    /// The final values returned by the main thread.\n    pub values: LuaMultiValue,\n}\n\nimpl RuntimeReturnValues {\n    /**\n        Returns the final, combined \"status\" of the runtime return values.\n\n        If no exit code was explicitly set by either the main thread,\n        or any threads it may have spawned, the status will be either:\n\n        - `0` if no threads errored\n        - `1` if any threads errored\n    */\n    #[must_use]\n    pub fn status(&self) -> u8 {\n        self.code.unwrap_or(u8::from(self.errored))\n    }\n\n    /**\n        Returns whether the run was considered successful, or not.\n\n        See [`RuntimeReturnValues::status`] for more information.\n    */\n    #[must_use]\n    pub fn success(&self) -> bool {\n        self.status() == 0\n    }\n}\n\n/**\n    A Lune runtime.\n*/\npub struct Runtime {\n    lua: Lua,\n    sched: Scheduler,\n    args: ProcessArgs,\n    env: ProcessEnv,\n    jit: ProcessJitEnablement,\n}\n\nimpl Runtime {\n    /**\n        Creates a new Lune runtime, with a new Luau VM.\n\n        Injects standard globals and libraries if any of the `std` features are enabled.\n\n        # Errors\n\n        - If out of memory or other memory-related errors occur\n        - If any of the standard globals and libraries fail to inject\n    */\n    pub fn new() -> LuaResult<Self> {\n        let lua = Lua::new();\n\n        let sched = Scheduler::new(lua.clone());\n        let fns = Functions::new(lua.clone()).expect(\"has scheduler\");\n\n        // Overwrite some globals that are not compatible with our scheduler\n        let co = lua.globals().get::<LuaTable>(\"coroutine\")?;\n        co.set(\"resume\", fns.resume.clone())?;\n        co.set(\"wrap\", fns.wrap.clone())?;\n\n        // Inject all the globals that are enabled\n        #[cfg(any(\n            feature = \"std-datetime\",\n            feature = \"std-fs\",\n            feature = \"std-luau\",\n            feature = \"std-net\",\n            feature = \"std-process\",\n            feature = \"std-regex\",\n            feature = \"std-roblox\",\n            feature = \"std-serde\",\n            feature = \"std-stdio\",\n            feature = \"std-task\",\n        ))]\n        {\n            lune_std::set_global_version(&lua, env!(\"CARGO_PKG_VERSION\"));\n            lune_std::inject_globals(lua.clone())?;\n        }\n\n        // Sandbox the Luau VM and make it go zooooooooom\n        lua.sandbox(true)?;\n\n        // _G table needs to be injected again after sandboxing,\n        // otherwise it will be read-only and completely unusable\n        #[cfg(any(\n            feature = \"std-datetime\",\n            feature = \"std-fs\",\n            feature = \"std-luau\",\n            feature = \"std-net\",\n            feature = \"std-process\",\n            feature = \"std-regex\",\n            feature = \"std-roblox\",\n            feature = \"std-serde\",\n            feature = \"std-stdio\",\n            feature = \"std-task\",\n        ))]\n        {\n            let g_table = lune_std::LuneStandardGlobal::GTable;\n            lua.globals()\n                .set(g_table.name(), g_table.create(lua.clone())?)?;\n        }\n\n        let args = ProcessArgs::current();\n        let env = ProcessEnv::current();\n        let jit = ProcessJitEnablement::default();\n\n        Ok(Self {\n            lua,\n            sched,\n            args,\n            env,\n            jit,\n        })\n    }\n\n    /**\n        Sets arguments to give in `process.args` for Lune scripts.\n\n        By default, `std::env::args_os()` is used.\n    */\n    #[must_use]\n    pub fn with_args<A, S>(mut self, args: A) -> Self\n    where\n        A: IntoIterator<Item = S>,\n        S: Into<OsString>,\n    {\n        self.args = args.into_iter().map(Into::into).collect();\n        self\n    }\n\n    /**\n        Sets environment values to give in `process.env` for Lune scripts.\n\n        By default, `std::env::vars_os()` is used.\n    */\n    #[must_use]\n    pub fn with_env<E, K, V>(mut self, env: E) -> Self\n    where\n        E: IntoIterator<Item = (K, V)>,\n        K: Into<OsString>,\n        V: Into<OsString>,\n    {\n        self.env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect();\n        self\n    }\n\n    /**\n        Enables or disables JIT compilation.\n    */\n    #[must_use]\n    pub fn with_jit<J>(mut self, jit_status: J) -> Self\n    where\n        J: Into<ProcessJitEnablement>,\n    {\n        self.jit = jit_status.into();\n        self\n    }\n\n    /**\n        Adds a custom library to the runtime, making it available through `require`.\n\n        # Example Usage\n\n        First, create a library as such:\n\n        ```rs\n        Runtime::new().with_lib(\"@myalias/mylib\", |lua| {\n            let t = lua.create_table()?;\n            let f = lua.create_function(|lua| {\n                println!(\"bar\");\n                Ok(())\n            })?;\n\n            t.set(\"foo\", f)?;\n\n            Ok(t)\n        });\n        ```\n\n        Then, use it in Lua:\n\n        ```luau\n        local lib = require(\"@myalias/mylib\")\n\n        lib.foo() --> \"bar\"\n        ```\n\n        # Errors\n\n        Returns an error if:\n\n        - The library name does not start with `@`\n        - The library uses the reserved `lune` alias\n        - The library uses the reserved `self` alias\n        - The provided `make_lib` function errors\n    */\n    pub fn with_lib<S, F>(self, name: S, make_lib: F) -> RuntimeResult<Self>\n    where\n        S: AsRef<str>,\n        F: FnOnce(&Lua) -> LuaResult<LuaValue>,\n    {\n        let name = name.as_ref().trim();\n\n        if !name.starts_with('@') {\n            return Err(RuntimeError::from(LuaError::external(\n                \"Library names must start with '@'\",\n            )));\n        }\n        if name.starts_with(\"@lune/\") {\n            return Err(RuntimeError::from(LuaError::external(\n                \"Library names must not start with '@lune/'\",\n            )));\n        }\n        if name.starts_with(\"@self/\") {\n            return Err(RuntimeError::from(LuaError::external(\n                \"Library names must not start with '@self/'\",\n            )));\n        }\n\n        let lib = make_lib(&self.lua)?;\n        self.lua.register_module(name, lib)?;\n\n        Ok(self)\n    }\n\n    /**\n        Runs some kind of custom input, inside of the current runtime.\n\n        For any input that is a real module or file path, [`run_file`] should\n        be used instead, since when using this method, any file requires will\n        fail. Requires for standard libraries and custom libraries will work.\n\n        # Errors\n\n        Returns an error if:\n\n        - The script fails to run (not if the script itself errors)\n    */\n    pub async fn run_custom(\n        &mut self,\n        chunk_name: impl AsRef<str>,\n        chunk_contents: impl AsRef<[u8]>,\n    ) -> RuntimeResult<RuntimeReturnValues> {\n        let chunk_name = format!(\"={}\", chunk_name.as_ref());\n        self.run_inner(chunk_name, chunk_contents).await\n    }\n\n    /**\n        Runs a file at the given file or module path, inside of the current runtime.\n\n        # Errors\n\n        Returns an error if:\n\n        - The file does not exist or can not be read\n        - The script fails to run (not if the script itself errors)\n    */\n    pub async fn run_file(\n        &mut self,\n        path: impl Into<PathBuf>,\n    ) -> RuntimeResult<RuntimeReturnValues> {\n        /*\n            For calls to `require` to resolve properly, we must:\n\n            1. Strip any lua/luau extensions, as well as \"init\" file\n               segments from the path.\n            2. Resolve any given file path to the respective \"module\"\n               path according to the require-by-string specification.\n\n            After doing this, we should end up with both:\n\n            - A source (module path)\n            - A target (file path)\n\n            If the given path was already a valid module path,\n            this should be a no-op.\n        */\n        let module_or_file_path = LuauModulePath::strip(path);\n        let module_path = LuauModulePath::resolve(&module_or_file_path)\n            .map_err(|e| LuaError::external(format!(\"{e:?}\")))\n            .with_context(|_| {\n                format!(\n                    \"Failed to read file at path \\\"{}\\\"\",\n                    module_or_file_path.display()\n                )\n            })?;\n\n        let contents = fs::read(module_path.target())\n            .await\n            .into_lua_err()\n            .with_context(|_| {\n                format!(\"Failed to read file at path \\\"{}\\\"\", module_path.target())\n            })?;\n\n        let module_name = format!(\"{FILE_CHUNK_PREFIX}{module_path}\");\n        let module_contents = strip_shebang(contents);\n\n        self.run_inner(module_name, module_contents).await\n    }\n\n    async fn run_inner(\n        &mut self,\n        chunk_name: impl AsRef<str>,\n        chunk_contents: impl AsRef<[u8]>,\n    ) -> RuntimeResult<RuntimeReturnValues> {\n        // Add error callback to format errors nicely + store status\n        let got_any_error = Arc::new(AtomicBool::new(false));\n        let got_any_inner = Arc::clone(&got_any_error);\n        self.sched.set_error_callback(move |e| {\n            got_any_inner.store(true, Ordering::SeqCst);\n            eprintln!(\"{}\", RuntimeError::from(e));\n        });\n\n        // Store the provided args, environment variables, and jit enablement as AppData\n        self.lua.set_app_data(self.args.clone());\n        self.lua.set_app_data(self.env.clone());\n        self.lua.set_app_data(self.jit);\n\n        // Inject all the standard libraries that are enabled - this needs to be done after\n        // storing the args/env, since some standard libraries use those during initialization\n        #[cfg(any(\n            feature = \"std-datetime\",\n            feature = \"std-fs\",\n            feature = \"std-luau\",\n            feature = \"std-net\",\n            feature = \"std-process\",\n            feature = \"std-regex\",\n            feature = \"std-roblox\",\n            feature = \"std-serde\",\n            feature = \"std-stdio\",\n            feature = \"std-task\",\n        ))]\n        {\n            lune_std::inject_std(self.lua.clone())?;\n        }\n\n        // Enable / disable the JIT as requested, before loading anything\n        self.lua.enable_jit(self.jit.enabled());\n\n        // Load our \"main\" thread\n        let main = self\n            .lua\n            .load(chunk_contents.as_ref())\n            .set_name(chunk_name.as_ref());\n\n        // Run it on our scheduler until it and any other spawned threads complete\n        let main_thread_id = self.sched.push_thread_back(main, ())?;\n        self.sched.run().await;\n\n        let main_thread_values = self\n            .sched\n            .get_thread_result(main_thread_id)\n            .unwrap_or_else(|| Ok(LuaMultiValue::new())) // Ignore missing result (interruption), we just want to extract values\n            .unwrap_or_default(); // Ignore any errors from the script, we just want to extract values\n\n        Ok(RuntimeReturnValues {\n            code: self.sched.get_exit_code(),\n            errored: got_any_error.load(Ordering::SeqCst),\n            values: main_thread_values,\n        })\n    }\n}\n\nfn strip_shebang(mut contents: Vec<u8>) -> Vec<u8> {\n    if contents.starts_with(b\"#!\")\n        && let Some(first_newline_idx) = contents\n            .iter()\n            .enumerate()\n            .find_map(|(idx, c)| if *c == b'\\n' { Some(idx) } else { None })\n    {\n        // NOTE: We keep the newline here on purpose to preserve\n        // correct line numbers in stack traces, the only reason\n        // we strip the shebang is to get the lua script to parse\n        // and the extra newline is not really a problem for that\n        contents.drain(..first_newline_idx);\n    }\n    contents\n}\n"
  },
  {
    "path": "crates/lune/src/standalone/metadata.rs",
    "content": "use std::{env, path::PathBuf, sync::LazyLock};\n\nuse anyhow::{Result, bail};\nuse async_fs as fs;\nuse mlua::Compiler as LuaCompiler;\n\npub static CURRENT_EXE: LazyLock<PathBuf> =\n    LazyLock::new(|| env::current_exe().expect(\"failed to get current exe\"));\nconst MAGIC: &[u8; 8] = b\"cr3sc3nt\";\n\n/*\n    TODO: Right now all we do is append the bytecode to the end\n    of the binary, but we will need a more flexible solution in\n    the future to store many files as well as their metadata.\n\n    The best solution here is most likely to use a well-supported\n    and rust-native binary serialization format with a stable\n    specification, one that also supports byte arrays well without\n    overhead, so the best solution seems to currently be Postcard:\n\n    https://github.com/jamesmunns/postcard\n    https://crates.io/crates/postcard\n*/\n\n/**\n    Metadata for a standalone Lune executable. Can be used to\n    discover and load the bytecode contained in a standalone binary.\n*/\n#[derive(Debug, Clone)]\npub struct Metadata {\n    pub bytecode: Vec<u8>,\n}\n\nimpl Metadata {\n    /**\n        Returns whether or not the currently executing Lune binary\n        is a standalone binary, and if so, the bytes of the binary.\n    */\n    pub async fn check_env() -> (bool, Vec<u8>) {\n        let contents = fs::read(CURRENT_EXE.to_path_buf())\n            .await\n            .unwrap_or_default();\n        let is_standalone = contents.ends_with(MAGIC);\n        (is_standalone, contents)\n    }\n\n    /**\n        Creates a patched standalone binary from the given script contents.\n    */\n    pub async fn create_env_patched_bin(\n        base_exe_path: PathBuf,\n        script_contents: impl Into<Vec<u8>>,\n    ) -> Result<Vec<u8>> {\n        let compiler = LuaCompiler::new()\n            .set_optimization_level(2)\n            .set_coverage_level(0)\n            .set_debug_level(1);\n\n        let mut patched_bin = fs::read(base_exe_path).await?;\n\n        // Compile luau input into bytecode\n        let bytecode = compiler.compile(script_contents.into())?;\n\n        // Append the bytecode / metadata to the end\n        let meta = Self { bytecode };\n        patched_bin.extend_from_slice(&meta.to_bytes());\n\n        Ok(patched_bin)\n    }\n\n    /**\n        Tries to read a standalone binary from the given bytes.\n    */\n    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {\n        let bytes = bytes.as_ref();\n        if bytes.len() < 16 || !bytes.ends_with(MAGIC) {\n            bail!(\"not a standalone binary\")\n        }\n\n        // Extract bytecode size\n        let bytecode_size_bytes = &bytes[bytes.len() - 16..bytes.len() - 8];\n        let bytecode_size =\n            usize::try_from(u64::from_be_bytes(bytecode_size_bytes.try_into().unwrap()))?;\n\n        // Extract bytecode\n        let bytecode = bytes[bytes.len() - 16 - bytecode_size..].to_vec();\n\n        Ok(Self { bytecode })\n    }\n\n    /**\n        Writes the metadata chunk to a byte vector, to later bet read using `from_bytes`.\n    */\n    pub fn to_bytes(&self) -> Vec<u8> {\n        let mut bytes = Vec::new();\n        bytes.extend_from_slice(&self.bytecode);\n        bytes.extend_from_slice(&(self.bytecode.len() as u64).to_be_bytes());\n        bytes.extend_from_slice(MAGIC);\n        bytes\n    }\n}\n"
  },
  {
    "path": "crates/lune/src/standalone/mod.rs",
    "content": "use std::{env, process::ExitCode};\n\nuse anyhow::Result;\nuse lune::Runtime;\n\npub(crate) mod metadata;\npub(crate) mod tracer;\n\nuse self::metadata::Metadata;\n\n/**\n    Returns whether or not the currently executing Lune binary\n    is a standalone binary, and if so, the bytes of the binary.\n*/\npub async fn check() -> Option<Vec<u8>> {\n    let (is_standalone, patched_bin) = Metadata::check_env().await;\n    if is_standalone {\n        Some(patched_bin)\n    } else {\n        None\n    }\n}\n\n/**\n    Discovers, loads and executes the bytecode contained in a standalone binary.\n*/\npub async fn run(patched_bin: impl AsRef<[u8]>) -> Result<ExitCode> {\n    // The first argument is the path to the current executable\n    let args = env::args().skip(1).collect::<Vec<_>>();\n    let meta = Metadata::from_bytes(patched_bin).expect(\"must be a standalone binary\");\n\n    let mut rt = Runtime::new()?.with_args(args);\n\n    let result = rt.run_custom(\"STANDALONE\", meta.bytecode).await;\n\n    Ok(match result {\n        Err(err) => {\n            eprintln!(\"{err}\");\n            ExitCode::FAILURE\n        }\n        Ok(values) => ExitCode::from(values.status()),\n    })\n}\n"
  },
  {
    "path": "crates/lune/src/standalone/tracer.rs",
    "content": "/*\n    TODO: Implement tracing of requires here\n\n    Rough steps / outline:\n\n    1. Create a new tracer struct using a main entrypoint script path\n    2. Some kind of discovery mechanism that goes through all require chains (failing on recursive ones)\n       2a. Conversion of script-relative paths to cwd-relative paths + normalization\n       2b. Cache all found files in a map of file path -> file contents\n       2c. Prepend some kind of symbol to paths that can tell our runtime `require` function that it\n           should look up a bundled/standalone script, a good symbol here is probably a dollar sign ($)\n    3. ???\n    4. Profit\n*/\n"
  },
  {
    "path": "crates/lune/src/tests.rs",
    "content": "use std::env::set_current_dir;\nuse std::path::PathBuf;\nuse std::process::ExitCode;\n\nuse anyhow::Result;\nuse console::set_colors_enabled;\nuse console::set_colors_enabled_stderr;\n\nuse lune_utils::path::clean_path;\n\nuse crate::Runtime;\n\nconst ARGS: &[&str] = &[\"Foo\", \"Bar\"];\n\nfn run_test(path: &str) -> Result<ExitCode> {\n    async_io::block_on(async {\n        // We need to change the current directory to the workspace root since\n        // we are in a sub-crate and tests would run relative to the sub-crate\n        let workspace_dir_str = concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/../../\");\n        let workspace_dir = clean_path(PathBuf::from(workspace_dir_str));\n        set_current_dir(&workspace_dir)?;\n\n        // Disable styling for stdout and stderr since\n        // some tests rely on output not being styled\n        set_colors_enabled(false);\n        set_colors_enabled_stderr(false);\n\n        // The rest of the test logic can continue as normal\n        let mut rt = Runtime::new()?.with_args(ARGS).with_jit(true);\n\n        let script_path = workspace_dir.join(\"tests\").join(format!(\"{path}.luau\"));\n        let script_values = rt.run_file(script_path).await?;\n\n        Ok(ExitCode::from(script_values.status()))\n    })\n}\n\nmacro_rules! create_tests {\n    ($($name:ident: $value:expr,)*) => { $(\n        #[test]\n        fn $name() -> Result<ExitCode> {\n        \trun_test($value)\n        }\n    )* }\n}\n\n#[cfg(any(\n    feature = \"std-datetime\",\n    feature = \"std-fs\",\n    feature = \"std-luau\",\n    feature = \"std-net\",\n    feature = \"std-process\",\n    feature = \"std-regex\",\n    feature = \"std-roblox\",\n    feature = \"std-serde\",\n    feature = \"std-stdio\",\n    feature = \"std-task\",\n))]\ncreate_tests! {\n    require_aliases: \"require/tests/aliases\",\n    require_async: \"require/tests/async\",\n    require_async_concurrent: \"require/tests/async_concurrent\",\n    require_async_sequential: \"require/tests/async_sequential\",\n    require_builtins: \"require/tests/builtins\",\n    require_children: \"require/tests/children\",\n    require_init: \"require/tests/init_files\",\n    require_invalid: \"require/tests/invalid\",\n    require_multi_ext: \"require/tests/multi_ext\",\n    require_nested: \"require/tests/nested\",\n    require_parents: \"require/tests/parents\",\n    require_siblings: \"require/tests/siblings\",\n    require_state: \"require/tests/state\",\n\n    global_g_table: \"globals/_G\",\n    global_version: \"globals/_VERSION\",\n    global_coroutine: \"globals/coroutine\",\n    global_error: \"globals/error\",\n    global_pcall: \"globals/pcall\",\n    global_type: \"globals/type\",\n    global_typeof: \"globals/typeof\",\n    global_warn: \"globals/warn\",\n}\n\n#[cfg(feature = \"std-datetime\")]\ncreate_tests! {\n    datetime_format_local_time: \"datetime/formatLocalTime\",\n    datetime_format_universal_time: \"datetime/formatUniversalTime\",\n    datetime_from_rfc_2822: \"datetime/fromRfc2822\",\n    datetime_from_rfc_3339: \"datetime/fromRfc3339\",\n    datetime_from_local_time: \"datetime/fromLocalTime\",\n    datetime_from_universal_time: \"datetime/fromUniversalTime\",\n    datetime_from_unix_timestamp: \"datetime/fromUnixTimestamp\",\n    datetime_now: \"datetime/now\",\n    datetime_to_rfc_2822: \"datetime/toRfc2822\",\n    datetime_to_rfc_3339: \"datetime/toRfc3339\",\n    datetime_to_local_time: \"datetime/toLocalTime\",\n    datetime_to_universal_time: \"datetime/toUniversalTime\",\n}\n\n#[cfg(feature = \"std-fs\")]\ncreate_tests! {\n    fs_files: \"fs/files\",\n    fs_copy: \"fs/copy\",\n    fs_dirs: \"fs/dirs\",\n    fs_metadata: \"fs/metadata\",\n    fs_move: \"fs/move\",\n}\n\n#[cfg(feature = \"std-luau\")]\ncreate_tests! {\n    luau_compile: \"luau/compile\",\n    luau_load: \"luau/load\",\n    luau_options: \"luau/options\",\n    luau_safeenv: \"luau/safeenv\",\n}\n\n#[cfg(feature = \"std-net\")]\ncreate_tests! {\n    net_request_codes: \"net/request/codes\",\n    net_request_compression: \"net/request/compression\",\n    net_request_https: \"net/request/https\",\n    net_request_methods: \"net/request/methods\",\n    net_request_query: \"net/request/query\",\n    net_request_redirect: \"net/request/redirect\",\n\n    net_serve_addresses: \"net/serve/addresses\",\n    net_serve_handles: \"net/serve/handles\",\n    net_serve_non_blocking: \"net/serve/non_blocking\",\n    net_serve_requests: \"net/serve/requests\",\n    net_serve_websockets: \"net/serve/websockets\",\n\n    net_socket_basic: \"net/socket/basic\",\n    net_socket_wss: \"net/socket/wss\",\n    net_socket_wss_rw: \"net/socket/wss_rw\",\n\n    net_tcp_basic: \"net/tcp/basic\",\n    net_tcp_info: \"net/tcp/info\",\n    net_tcp_tls: \"net/tcp/tls\",\n\n    net_url_encode: \"net/url/encode\",\n    net_url_decode: \"net/url/decode\",\n}\n\n#[cfg(feature = \"std-process\")]\ncreate_tests! {\n    process_args: \"process/args\",\n    process_cwd: \"process/cwd\",\n    process_env: \"process/env\",\n    process_exit: \"process/exit\",\n    process_exec_async: \"process/exec/async\",\n    process_exec_basic: \"process/exec/basic\",\n    process_exec_cwd: \"process/exec/cwd\",\n    process_exec_no_panic: \"process/exec/no_panic\",\n    process_exec_shell: \"process/exec/shell\",\n    process_exec_stdin: \"process/exec/stdin\",\n    process_exec_stdio: \"process/exec/stdio\",\n    process_spawn_non_blocking: \"process/create/non_blocking\",\n    process_spawn_status: \"process/create/status\",\n    process_spawn_stream: \"process/create/stream\",\n}\n\n#[cfg(feature = \"std-regex\")]\ncreate_tests! {\n    regex_general: \"regex/general\",\n    regex_metamethods: \"regex/metamethods\",\n    regex_replace: \"regex/replace\",\n}\n\n#[cfg(feature = \"std-roblox\")]\ncreate_tests! {\n    roblox_datatype_axes: \"roblox/datatypes/Axes\",\n    roblox_datatype_brick_color: \"roblox/datatypes/BrickColor\",\n    roblox_datatype_cframe: \"roblox/datatypes/CFrame\",\n    roblox_datatype_color3: \"roblox/datatypes/Color3\",\n    roblox_datatype_color_sequence: \"roblox/datatypes/ColorSequence\",\n    roblox_datatype_color_sequence_keypoint: \"roblox/datatypes/ColorSequenceKeypoint\",\n    roblox_datatype_content: \"roblox/datatypes/Content\",\n    roblox_datatype_enum: \"roblox/datatypes/Enum\",\n    roblox_datatype_faces: \"roblox/datatypes/Faces\",\n    roblox_datatype_font: \"roblox/datatypes/Font\",\n    roblox_datatype_number_range: \"roblox/datatypes/NumberRange\",\n    roblox_datatype_number_sequence: \"roblox/datatypes/NumberSequence\",\n    roblox_datatype_number_sequence_keypoint: \"roblox/datatypes/NumberSequenceKeypoint\",\n    roblox_datatype_physical_properties: \"roblox/datatypes/PhysicalProperties\",\n    roblox_datatype_ray: \"roblox/datatypes/Ray\",\n    roblox_datatype_rect: \"roblox/datatypes/Rect\",\n    roblox_datatype_udim: \"roblox/datatypes/UDim\",\n    roblox_datatype_udim2: \"roblox/datatypes/UDim2\",\n    roblox_datatype_uniqueid: \"roblox/datatypes/UniqueId\",\n    roblox_datatype_region3: \"roblox/datatypes/Region3\",\n    roblox_datatype_region3int16: \"roblox/datatypes/Region3int16\",\n    roblox_datatype_vector2: \"roblox/datatypes/Vector2\",\n    roblox_datatype_vector2int16: \"roblox/datatypes/Vector2int16\",\n    roblox_datatype_vector3: \"roblox/datatypes/Vector3\",\n    roblox_datatype_vector3int16: \"roblox/datatypes/Vector3int16\",\n\n    roblox_files_deserialize_model: \"roblox/files/deserializeModel\",\n    roblox_files_deserialize_place: \"roblox/files/deserializePlace\",\n    roblox_files_serialize_model: \"roblox/files/serializeModel\",\n    roblox_files_serialize_place: \"roblox/files/serializePlace\",\n\n    roblox_instance_attributes: \"roblox/instance/attributes\",\n    roblox_instance_new: \"roblox/instance/new\",\n    roblox_instance_properties: \"roblox/instance/properties\",\n    roblox_instance_tags: \"roblox/instance/tags\",\n\n    roblox_instance_classes_data_model: \"roblox/instance/classes/DataModel\",\n    roblox_instance_classes_workspace: \"roblox/instance/classes/Workspace\",\n    roblox_instance_classes_terrain: \"roblox/instance/classes/Terrain\",\n\n    roblox_instance_custom_async: \"roblox/instance/custom/async\",\n    roblox_instance_custom_methods: \"roblox/instance/custom/methods\",\n    roblox_instance_custom_properties: \"roblox/instance/custom/properties\",\n\n    roblox_instance_methods_clear_all_children: \"roblox/instance/methods/ClearAllChildren\",\n    roblox_instance_methods_clone: \"roblox/instance/methods/Clone\",\n    roblox_instance_methods_destroy: \"roblox/instance/methods/Destroy\",\n    roblox_instance_methods_find_first_ancestor: \"roblox/instance/methods/FindFirstAncestor\",\n    roblox_instance_methods_find_first_ancestor_of_class: \"roblox/instance/methods/FindFirstAncestorOfClass\",\n    roblox_instance_methods_find_first_ancestor_which_is_a: \"roblox/instance/methods/FindFirstAncestorWhichIsA\",\n    roblox_instance_methods_find_first_child: \"roblox/instance/methods/FindFirstChild\",\n    roblox_instance_methods_find_first_child_of_class: \"roblox/instance/methods/FindFirstChildOfClass\",\n    roblox_instance_methods_find_first_child_which_is_a: \"roblox/instance/methods/FindFirstChildWhichIsA\",\n    roblox_instance_methods_get_children: \"roblox/instance/methods/GetChildren\",\n    roblox_instance_methods_get_debug_id: \"roblox/instance/methods/GetDebugId\",\n    roblox_instance_methods_get_descendants: \"roblox/instance/methods/GetDescendants\",\n    roblox_instance_methods_get_full_name: \"roblox/instance/methods/GetFullName\",\n    roblox_instance_methods_is_a: \"roblox/instance/methods/IsA\",\n    roblox_instance_methods_is_ancestor_of: \"roblox/instance/methods/IsAncestorOf\",\n    roblox_instance_methods_is_descendant_of: \"roblox/instance/methods/IsDescendantOf\",\n\n    roblox_misc_typeof: \"roblox/misc/typeof\",\n\n    roblox_reflection_class: \"roblox/reflection/class\",\n    roblox_reflection_database: \"roblox/reflection/database\",\n    roblox_reflection_enums: \"roblox/reflection/enums\",\n    roblox_reflection_property: \"roblox/reflection/property\",\n}\n\n#[cfg(feature = \"std-serde\")]\ncreate_tests! {\n    serde_compression_files: \"serde/compression/files\",\n    serde_compression_roundtrip: \"serde/compression/roundtrip\",\n    serde_json_decode: \"serde/json/decode\",\n    serde_json_encode: \"serde/json/encode\",\n    serde_jsonc_decode: \"serde/jsonc/decode\",\n    serde_jsonc_encode: \"serde/jsonc/encode\",\n    serde_toml_decode: \"serde/toml/decode\",\n    serde_toml_encode: \"serde/toml/encode\",\n    serde_hashing_hash: \"serde/hashing/hash\",\n    serde_hashing_hmac: \"serde/hashing/hmac\",\n}\n\n#[cfg(feature = \"std-stdio\")]\ncreate_tests! {\n    stdio_format: \"stdio/format\",\n    stdio_color: \"stdio/color\",\n    stdio_style: \"stdio/style\",\n    stdio_write: \"stdio/write\",\n    stdio_ewrite: \"stdio/ewrite\",\n}\n\n#[cfg(feature = \"std-task\")]\ncreate_tests! {\n    task_cancel: \"task/cancel\",\n    task_defer: \"task/defer\",\n    task_delay: \"task/delay\",\n    task_spawn: \"task/spawn\",\n    task_wait: \"task/wait\",\n}\n"
  },
  {
    "path": "crates/lune-roblox/Cargo.toml",
    "content": "[package]\nname = \"lune-roblox\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Roblox library for Lune\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[features]\ndefault = [\"mlua\"]\nmlua = [\"dep:mlua\", \"dep:lune-utils\"]\n\n[dependencies]\nmlua = { version = \"0.11.4\", optional = true, features = [\"luau\"] }\n\nglam = \"0.30\"\nrand = \"0.9\"\nthiserror = \"2.0\"\n\nrbx_binary = \"2.0\"\nrbx_dom_weak = \"4.0\"\nrbx_reflection = \"6.0\"\nrbx_reflection_database = \"2.0\"\nrbx_xml = \"2.0\"\n\nlune-utils = { version = \"0.3.4\", optional = true, path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/attributes.rs",
    "content": "use mlua::prelude::*;\n\nuse rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};\n\nuse super::extension::DomValueExt;\n\n/**\n    Checks if the given name is a valid attribute name.\n\n    # Errors\n\n    - If the name starts with the prefix \"RBX\".\n    - If the name contains any characters other than alphanumeric characters, periods, hyphens, underscores, or forward slashes.\n    - If the name is longer than 100 characters.\n*/\npub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {\n    let name = name.as_ref();\n    if name.to_ascii_uppercase().starts_with(\"RBX\") {\n        Err(LuaError::RuntimeError(\n            \"Attribute names must not start with the prefix \\\"RBX\\\"\".to_string(),\n        ))\n    } else if !name\n        .chars()\n        .all(|c| c.is_alphanumeric() || matches!(c, '.' | '_' | '-' | '/'))\n    {\n        Err(LuaError::RuntimeError(\n            \"Attribute names must only use alphanumeric characters, periods, hyphens, underscores, or forward slashes.\".to_string(),\n        ))\n    } else if name.len() > 100 {\n        Err(LuaError::RuntimeError(\n            \"Attribute names must be 100 characters or less in length\".to_string(),\n        ))\n    } else {\n        Ok(())\n    }\n}\n\n/**\n    Checks if the given value is a valid attribute value.\n\n    # Errors\n\n    - If the value is not a valid attribute type.\n*/\npub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {\n    let is_valid = matches!(\n        value.ty(),\n        DomType::Bool\n            | DomType::BrickColor\n            | DomType::CFrame\n            | DomType::Color3\n            | DomType::ColorSequence\n            | DomType::EnumItem\n            | DomType::Float32\n            | DomType::Float64\n            | DomType::Font\n            | DomType::Int32\n            | DomType::Int64\n            | DomType::NumberRange\n            | DomType::NumberSequence\n            | DomType::Rect\n            | DomType::String\n            | DomType::UDim\n            | DomType::UDim2\n            | DomType::Vector2\n            | DomType::Vector3\n    );\n    if is_valid {\n        Ok(())\n    } else {\n        Err(LuaError::RuntimeError(format!(\n            \"'{}' is not a valid attribute type\",\n            value.ty().variant_name().unwrap_or(\"???\")\n        )))\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/conversion.rs",
    "content": "use mlua::prelude::*;\n\nuse rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};\n\nuse crate::{datatypes::extension::DomValueExt, instance::Instance};\n\nuse super::*;\n\npub(crate) trait LuaToDomValue {\n    /**\n        Converts a lua value into a weak dom value.\n\n        If a `variant_type` is given the conversion will be more strict\n        and also more accurate, it should be given whenever possible.\n    */\n    fn lua_to_dom_value(\n        &self,\n        lua: &Lua,\n        variant_type: Option<DomType>,\n    ) -> DomConversionResult<DomValue>;\n}\n\npub(crate) trait DomValueToLua: Sized {\n    /**\n        Converts a weak dom value into a lua value.\n    */\n    fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self>;\n}\n\n/*\n\n    Blanket trait implementations for converting between LuaValue and rbx_dom Variant values\n\n    These should be considered stable and done, already containing all of the known primitives\n\n    See bottom of module for implementations between our custom datatypes and lua userdata\n\n*/\n\nimpl DomValueToLua for LuaValue {\n    fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self> {\n        use rbx_dom_weak::types as dom;\n\n        match LuaAnyUserData::dom_value_to_lua(lua, variant) {\n            Ok(value) => Ok(LuaValue::UserData(value)),\n            Err(e) => match variant {\n                DomValue::Bool(b) => Ok(LuaValue::Boolean(*b)),\n                DomValue::Int64(i) => Ok(LuaValue::Number(*i as f64)),\n                DomValue::Int32(i) => Ok(LuaValue::Number(*i as f64)),\n                DomValue::Float64(n) => Ok(LuaValue::Number(*n)),\n                DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),\n                DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),\n                DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),\n                DomValue::ContentId(s) => Ok(LuaValue::String(\n                    lua.create_string(AsRef::<str>::as_ref(s))?,\n                )),\n\n                // NOTE: Dom references may point to instances that\n                // no longer exist, so we handle that here instead of\n                // in the userdata conversion to be able to return nils\n                DomValue::Ref(value) => match Instance::new_opt(*value) {\n                    Some(inst) => Ok(inst.into_lua(lua)?),\n                    None => Ok(LuaValue::Nil),\n                },\n\n                // NOTE: Some values are either optional or default and we should handle\n                // that properly here since the userdata conversion above will always fail\n                DomValue::OptionalCFrame(None)\n                | DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => {\n                    Ok(LuaValue::Nil)\n                }\n\n                _ => Err(e),\n            },\n        }\n    }\n}\n\nimpl LuaToDomValue for LuaValue {\n    fn lua_to_dom_value(\n        &self,\n        lua: &Lua,\n        variant_type: Option<DomType>,\n    ) -> DomConversionResult<DomValue> {\n        use rbx_dom_weak::types as dom;\n\n        if let Some(variant_type) = variant_type {\n            match (self, variant_type) {\n                (LuaValue::Boolean(b), DomType::Bool) => Ok(DomValue::Bool(*b)),\n\n                (LuaValue::Integer(i), DomType::Int64) => Ok(DomValue::Int64(*i)),\n                (LuaValue::Integer(i), DomType::Int32) => Ok(DomValue::Int32(*i as i32)),\n                (LuaValue::Integer(i), DomType::Float64) => Ok(DomValue::Float64(*i as f64)),\n                (LuaValue::Integer(i), DomType::Float32) => Ok(DomValue::Float32(*i as f32)),\n\n                (LuaValue::Number(n), DomType::Int64) => Ok(DomValue::Int64(*n as i64)),\n                (LuaValue::Number(n), DomType::Int32) => Ok(DomValue::Int32(*n as i32)),\n                (LuaValue::Number(n), DomType::Float64) => Ok(DomValue::Float64(*n)),\n                (LuaValue::Number(n), DomType::Float32) => Ok(DomValue::Float32(*n as f32)),\n\n                (LuaValue::String(s), DomType::String) => {\n                    Ok(DomValue::String(s.to_str()?.to_string()))\n                }\n                (LuaValue::String(s), DomType::BinaryString) => {\n                    Ok(DomValue::BinaryString(s.as_bytes().to_vec().into()))\n                }\n                (LuaValue::String(s), DomType::ContentId) => {\n                    Ok(DomValue::ContentId(s.to_str()?.to_string().into()))\n                }\n\n                // NOTE: Some values are either optional or default and we\n                // should handle that here before trying to convert as userdata\n                (LuaValue::Nil, DomType::OptionalCFrame) => Ok(DomValue::OptionalCFrame(None)),\n                (LuaValue::Nil, DomType::PhysicalProperties) => Ok(DomValue::PhysicalProperties(\n                    dom::PhysicalProperties::Default,\n                )),\n\n                (LuaValue::UserData(u), d) => u.lua_to_dom_value(lua, Some(d)),\n\n                (v, d) => Err(DomConversionError::ToDomValue {\n                    to: d.variant_name().unwrap_or(\"???\"),\n                    from: v.type_name(),\n                    detail: None,\n                }),\n            }\n        } else {\n            match self {\n                LuaValue::Boolean(b) => Ok(DomValue::Bool(*b)),\n                LuaValue::Integer(i) => Ok(DomValue::Int32(*i as i32)),\n                LuaValue::Number(n) => Ok(DomValue::Float64(*n)),\n                LuaValue::String(s) => Ok(DomValue::String(s.to_str()?.to_string())),\n                LuaValue::UserData(u) => u.lua_to_dom_value(lua, None),\n                v => Err(DomConversionError::ToDomValue {\n                    to: \"unknown\",\n                    from: v.type_name(),\n                    detail: None,\n                }),\n            }\n        }\n    }\n}\n\n/*\n\n    Trait implementations for converting between all of\n    our custom datatypes and generic Lua userdata values\n\n    NOTE: When adding a new datatype, make sure to add it below to _both_\n    of the traits and not just one to allow for bidirectional conversion\n\n*/\n\nmacro_rules! dom_to_userdata {\n    ($lua:expr, $value:ident => $to_type:ty) => {\n        Ok($lua.create_userdata(Into::<$to_type>::into($value.clone()))?)\n    };\n}\n\n/**\n    Converts a generic lua userdata to an rbx-dom type.\n\n    Since the type of the userdata needs to be specified\n    in an explicit manner, this macro syntax was chosen:\n\n    ```rs\n    userdata_to_dom!(value_identifier as UserdataType => DomType)\n    ```\n*/\nmacro_rules! userdata_to_dom {\n    ($userdata:ident as $from_type:ty => $to_type:ty) => {\n        match $userdata.borrow::<$from_type>() {\n            Ok(value) => Ok(From::<$to_type>::from(value.clone().into())),\n            Err(error) => match error {\n                LuaError::UserDataTypeMismatch => Err(DomConversionError::ToDomValue {\n                    to: stringify!($to_type),\n                    from: \"userdata\",\n                    detail: Some(\"Type mismatch\".to_string()),\n                }),\n                e => Err(DomConversionError::ToDomValue {\n                    to: stringify!($to_type),\n                    from: \"userdata\",\n                    detail: Some(format!(\"Internal error: {e}\")),\n                }),\n            },\n        }\n    };\n}\n\nimpl DomValueToLua for LuaAnyUserData {\n    #[allow(clippy::match_same_arms)]\n    #[rustfmt::skip]\n    fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self> {\n\t\tuse super::types::*;\n\n        use rbx_dom_weak::types as dom;\n\n        match variant {\n            DomValue::Axes(value)           => dom_to_userdata!(lua, value => Axes),\n            DomValue::BrickColor(value)     => dom_to_userdata!(lua, value => BrickColor),\n            DomValue::CFrame(value)         => dom_to_userdata!(lua, value => CFrame),\n            DomValue::Color3(value)         => dom_to_userdata!(lua, value => Color3),\n            DomValue::Color3uint8(value)    => dom_to_userdata!(lua, value => Color3),\n            DomValue::ColorSequence(value)  => dom_to_userdata!(lua, value => ColorSequence),\n            DomValue::Content(value)        => dom_to_userdata!(lua, value => Content),\n            DomValue::EnumItem(value)       => dom_to_userdata!(lua, value => EnumItem),\n            DomValue::Faces(value)          => dom_to_userdata!(lua, value => Faces),\n            DomValue::Font(value)           => dom_to_userdata!(lua, value => Font),\n            DomValue::NumberRange(value)    => dom_to_userdata!(lua, value => NumberRange),\n            DomValue::NumberSequence(value) => dom_to_userdata!(lua, value => NumberSequence),\n            DomValue::Ray(value)            => dom_to_userdata!(lua, value => Ray),\n            DomValue::Rect(value)           => dom_to_userdata!(lua, value => Rect),\n            DomValue::Region3(value)        => dom_to_userdata!(lua, value => Region3),\n            DomValue::Region3int16(value)   => dom_to_userdata!(lua, value => Region3int16),\n            DomValue::UDim(value)           => dom_to_userdata!(lua, value => UDim),\n            DomValue::UDim2(value)          => dom_to_userdata!(lua, value => UDim2),\n            DomValue::UniqueId(value)       => dom_to_userdata!(lua, value => UniqueId),\n            DomValue::Vector2(value)        => dom_to_userdata!(lua, value => Vector2),\n            DomValue::Vector2int16(value)   => dom_to_userdata!(lua, value => Vector2int16),\n            DomValue::Vector3(value)        => dom_to_userdata!(lua, value => Vector3),\n            DomValue::Vector3int16(value)   => dom_to_userdata!(lua, value => Vector3int16),\n\n            // NOTE: The none and default variants of these types are handled in\n\t\t\t// DomValueToLua for the LuaValue type instead, allowing for nil/default\n            DomValue::OptionalCFrame(Some(value)) => dom_to_userdata!(lua, value => CFrame),\n            DomValue::PhysicalProperties(dom::PhysicalProperties::Custom(value)) => {\n\t\t\t\tdom_to_userdata!(lua, value => PhysicalProperties)\n            },\n\n            v => {\n                Err(DomConversionError::FromDomValue {\n                    from: v.variant_name().unwrap_or(\"???\"),\n                    to: \"userdata\",\n                    detail: Some(\"Type not supported\".to_string()),\n                })\n            }\n        }\n    }\n}\n\nimpl LuaToDomValue for LuaAnyUserData {\n    #[rustfmt::skip]\n    fn lua_to_dom_value(&self, _: &Lua, variant_type: Option<DomType>) -> DomConversionResult<DomValue> {\n        use super::types::*;\n\n        use rbx_dom_weak::types as dom;\n\n        if let Some(variant_type) = variant_type {\n\t\t\t/*\n\t\t\t\tStrict target type, use it to skip checking the actual\n\t\t\t\ttype of the userdata and try to just do a pure conversion\n\t\t\t*/\n            match variant_type {\n                DomType::Axes           => userdata_to_dom!(self as Axes           => dom::Axes),\n                DomType::BrickColor     => userdata_to_dom!(self as BrickColor     => dom::BrickColor),\n                DomType::CFrame         => userdata_to_dom!(self as CFrame         => dom::CFrame),\n                DomType::Color3         => userdata_to_dom!(self as Color3         => dom::Color3),\n                DomType::Color3uint8    => userdata_to_dom!(self as Color3         => dom::Color3uint8),\n                DomType::ColorSequence  => userdata_to_dom!(self as ColorSequence  => dom::ColorSequence),\n                DomType::Content        => userdata_to_dom!(self as Content        => dom::Content),\n                DomType::EnumItem       => userdata_to_dom!(self as EnumItem       => dom::EnumItem),\n                DomType::Faces          => userdata_to_dom!(self as Faces          => dom::Faces),\n                DomType::Font           => userdata_to_dom!(self as Font           => dom::Font),\n                DomType::NumberRange    => userdata_to_dom!(self as NumberRange    => dom::NumberRange),\n                DomType::NumberSequence => userdata_to_dom!(self as NumberSequence => dom::NumberSequence),\n                DomType::Ray            => userdata_to_dom!(self as Ray            => dom::Ray),\n                DomType::Rect           => userdata_to_dom!(self as Rect           => dom::Rect),\n                DomType::Ref            => userdata_to_dom!(self as Instance       => dom::Ref),\n                DomType::Region3        => userdata_to_dom!(self as Region3        => dom::Region3),\n                DomType::Region3int16   => userdata_to_dom!(self as Region3int16   => dom::Region3int16),\n                DomType::UDim           => userdata_to_dom!(self as UDim           => dom::UDim),\n                DomType::UDim2          => userdata_to_dom!(self as UDim2          => dom::UDim2),\n                DomType::UniqueId       => userdata_to_dom!(self as UniqueId       => dom::UniqueId),\n                DomType::Vector2        => userdata_to_dom!(self as Vector2        => dom::Vector2),\n                DomType::Vector2int16   => userdata_to_dom!(self as Vector2int16   => dom::Vector2int16),\n                DomType::Vector3        => userdata_to_dom!(self as Vector3        => dom::Vector3),\n                DomType::Vector3int16   => userdata_to_dom!(self as Vector3int16   => dom::Vector3int16),\n\n\t            // NOTE: The none and default variants of these types are handled in\n\t\t\t\t// LuaToDomValue for the LuaValue type instead, allowing for nil/default\n                DomType::OptionalCFrame => {\n                    match self.borrow::<CFrame>() {\n                        Err(_) => unreachable!(\"Invalid use of conversion method, should be using LuaValue\"),\n                        Ok(value) => Ok(DomValue::OptionalCFrame(Some(dom::CFrame::from(*value)))),\n                    }\n                }\n                DomType::PhysicalProperties => {\n                    match self.borrow::<PhysicalProperties>() {\n                        Err(_) => unreachable!(\"Invalid use of conversion method, should be using LuaValue\"),\n                        Ok(value) => {\n                            let props = dom::CustomPhysicalProperties::from(*value);\n                            let custom = dom::PhysicalProperties::Custom(props);\n                            Ok(DomValue::PhysicalProperties(custom))\n                        }\n                    }\n                }\n\n                ty => {\n                    Err(DomConversionError::ToDomValue {\n                        to: ty.variant_name().unwrap_or(\"???\"),\n                        from: \"userdata\",\n                        detail: Some(\"Type not supported\".to_string()),\n                    })\n                }\n            }\n        } else {\n\t\t\t/*\n\t\t\t\tNon-strict target type, here we need to do manual typechecks\n\t\t\t\ton the userdata to see what we should be converting it into\n\n\t\t\t\tThis is used for example for attributes, where the wanted\n\t\t\t\ttype is not known by the dom and instead determined by the user\n\t\t\t*/\n            match self {\n                value if value.is::<Axes>()           => userdata_to_dom!(value as Axes           => dom::Axes),\n                value if value.is::<BrickColor>()     => userdata_to_dom!(value as BrickColor     => dom::BrickColor),\n                value if value.is::<CFrame>()         => userdata_to_dom!(value as CFrame         => dom::CFrame),\n                value if value.is::<Color3>()         => userdata_to_dom!(value as Color3         => dom::Color3),\n                value if value.is::<ColorSequence>()  => userdata_to_dom!(value as ColorSequence  => dom::ColorSequence),\n                value if value.is::<EnumItem>()       => userdata_to_dom!(value as EnumItem       => dom::EnumItem),\n                value if value.is::<Faces>()          => userdata_to_dom!(value as Faces          => dom::Faces),\n                value if value.is::<Font>()           => userdata_to_dom!(value as Font           => dom::Font),\n                value if value.is::<Instance>()       => userdata_to_dom!(value as Instance       => dom::Ref),\n                value if value.is::<NumberRange>()    => userdata_to_dom!(value as NumberRange    => dom::NumberRange),\n                value if value.is::<NumberSequence>() => userdata_to_dom!(value as NumberSequence => dom::NumberSequence),\n                value if value.is::<Ray>()            => userdata_to_dom!(value as Ray            => dom::Ray),\n                value if value.is::<Rect>()           => userdata_to_dom!(value as Rect           => dom::Rect),\n                value if value.is::<Region3>()        => userdata_to_dom!(value as Region3        => dom::Region3),\n                value if value.is::<Region3int16>()   => userdata_to_dom!(value as Region3int16   => dom::Region3int16),\n                value if value.is::<UDim>()           => userdata_to_dom!(value as UDim           => dom::UDim),\n                value if value.is::<UDim2>()          => userdata_to_dom!(value as UDim2          => dom::UDim2),\n                value if value.is::<UniqueId>()       => userdata_to_dom!(value as UniqueId       => dom::UniqueId),\n                value if value.is::<Vector2>()        => userdata_to_dom!(value as Vector2        => dom::Vector2),\n                value if value.is::<Vector2int16>()   => userdata_to_dom!(value as Vector2int16   => dom::Vector2int16),\n                value if value.is::<Vector3>()        => userdata_to_dom!(value as Vector3        => dom::Vector3),\n                value if value.is::<Vector3int16>()   => userdata_to_dom!(value as Vector3int16   => dom::Vector3int16),\n\n                _ => Err(DomConversionError::ToDomValue {\n                    to: \"unknown\",\n                    from: \"userdata\",\n                    detail: Some(\"Type not supported\".to_string()),\n                })\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/extension.rs",
    "content": "use super::*;\n\npub(crate) trait DomValueExt {\n    fn variant_name(&self) -> Option<&'static str>;\n}\n\nimpl DomValueExt for DomType {\n    fn variant_name(&self) -> Option<&'static str> {\n        #[allow(clippy::enum_glob_use)]\n        use DomType::*;\n        Some(match self {\n            Attributes => \"Attributes\",\n            Axes => \"Axes\",\n            BinaryString => \"BinaryString\",\n            Bool => \"Bool\",\n            BrickColor => \"BrickColor\",\n            CFrame => \"CFrame\",\n            Color3 => \"Color3\",\n            Color3uint8 => \"Color3uint8\",\n            ColorSequence => \"ColorSequence\",\n            Content => \"Content\",\n            ContentId => \"ContentId\",\n            Enum => \"Enum\",\n            EnumItem => \"EnumItem\",\n            Faces => \"Faces\",\n            Float32 => \"Float32\",\n            Float64 => \"Float64\",\n            Font => \"Font\",\n            Int32 => \"Int32\",\n            Int64 => \"Int64\",\n            MaterialColors => \"MaterialColors\",\n            NumberRange => \"NumberRange\",\n            NumberSequence => \"NumberSequence\",\n            PhysicalProperties => \"PhysicalProperties\",\n            Ray => \"Ray\",\n            Rect => \"Rect\",\n            Ref => \"Ref\",\n            Region3 => \"Region3\",\n            Region3int16 => \"Region3int16\",\n            SharedString => \"SharedString\",\n            String => \"String\",\n            Tags => \"Tags\",\n            UDim => \"UDim\",\n            UDim2 => \"UDim2\",\n            UniqueId => \"UniqueId\",\n            Vector2 => \"Vector2\",\n            Vector2int16 => \"Vector2int16\",\n            Vector3 => \"Vector3\",\n            Vector3int16 => \"Vector3int16\",\n            OptionalCFrame => \"OptionalCFrame\",\n            SecurityCapabilities => \"SecurityCapabilities\",\n            NetAssetRef => \"NetAssetRef\",\n            _ => return None,\n        })\n    }\n}\n\nimpl DomValueExt for DomValue {\n    fn variant_name(&self) -> Option<&'static str> {\n        self.ty().variant_name()\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/mod.rs",
    "content": "pub(crate) use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};\n\npub mod extension;\n\n#[cfg(feature = \"mlua\")]\npub mod attributes;\n#[cfg(feature = \"mlua\")]\npub mod conversion;\n#[cfg(feature = \"mlua\")]\npub mod result;\n#[cfg(feature = \"mlua\")]\npub mod types;\n\n#[cfg(feature = \"mlua\")]\nmod util;\n\n#[cfg(feature = \"mlua\")]\nuse result::*;\n\n#[cfg(feature = \"mlua\")]\npub use crate::shared::userdata::*;\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/result.rs",
    "content": "use core::fmt;\n\nuse std::error::Error;\nuse std::io::Error as IoError;\n\n#[cfg(feature = \"mlua\")]\nuse mlua::Error as LuaError;\n\n#[allow(dead_code)]\n#[derive(Clone, Debug)]\npub(crate) enum DomConversionError {\n    #[cfg(feature = \"mlua\")]\n    LuaError(LuaError),\n    External {\n        message: String,\n    },\n    FromDomValue {\n        from: &'static str,\n        to: &'static str,\n        detail: Option<String>,\n    },\n    ToDomValue {\n        to: &'static str,\n        from: &'static str,\n        detail: Option<String>,\n    },\n}\n\nimpl fmt::Display for DomConversionError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                #[cfg(feature = \"mlua\")]\n                Self::LuaError(error) => error.to_string(),\n                Self::External { message } => message.to_string(),\n                Self::FromDomValue { from, to, detail } | Self::ToDomValue { from, to, detail } => {\n                    match detail {\n                        Some(d) => format!(\"Failed to convert from '{from}' into '{to}' - {d}\"),\n                        None => format!(\"Failed to convert from '{from}' into '{to}'\",),\n                    }\n                }\n            }\n        )\n    }\n}\n\nimpl Error for DomConversionError {}\n\n#[cfg(feature = \"mlua\")]\nimpl From<DomConversionError> for LuaError {\n    fn from(value: DomConversionError) -> Self {\n        use DomConversionError as E;\n        match value {\n            E::LuaError(e) => e,\n            E::External { message } => LuaError::external(message),\n            E::FromDomValue { .. } | E::ToDomValue { .. } => {\n                LuaError::RuntimeError(value.to_string())\n            }\n        }\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nimpl From<LuaError> for DomConversionError {\n    fn from(value: LuaError) -> Self {\n        Self::LuaError(value)\n    }\n}\n\nimpl From<IoError> for DomConversionError {\n    fn from(value: IoError) -> Self {\n        DomConversionError::External {\n            message: value.to_string(),\n        }\n    }\n}\n\npub(crate) type DomConversionResult<T> = Result<T, DomConversionError>;\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/axes.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Axes as DomAxes;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [Axes](https://create.roblox.com/docs/reference/engine/datatypes/Axes) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Axes class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Axes {\n    pub(crate) x: bool,\n    pub(crate) y: bool,\n    pub(crate) z: bool,\n}\n\nimpl LuaExportsTable for Axes {\n    const EXPORT_NAME: &'static str = \"Axes\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let axes_new = |_: &Lua, args: LuaMultiValue| {\n            let mut x = false;\n            let mut y = false;\n            let mut z = false;\n\n            let mut check = |e: &EnumItem| {\n                if e.parent.desc.name == \"Axis\" {\n                    match &e.name {\n                        name if name == \"X\" => x = true,\n                        name if name == \"Y\" => y = true,\n                        name if name == \"Z\" => z = true,\n                        _ => {}\n                    }\n                } else if e.parent.desc.name == \"NormalId\" {\n                    match &e.name {\n                        name if name == \"Left\" || name == \"Right\" => x = true,\n                        name if name == \"Top\" || name == \"Bottom\" => y = true,\n                        name if name == \"Front\" || name == \"Back\" => z = true,\n                        _ => {}\n                    }\n                }\n            };\n\n            for (index, arg) in args.into_iter().enumerate() {\n                if let LuaValue::UserData(u) = arg {\n                    if let Ok(e) = u.borrow::<EnumItem>() {\n                        check(&e);\n                    } else {\n                        return Err(LuaError::RuntimeError(format!(\n                            \"Expected argument #{index} to be an EnumItem, got userdata\",\n                        )));\n                    }\n                } else {\n                    return Err(LuaError::RuntimeError(format!(\n                        \"Expected argument #{} to be an EnumItem, got {}\",\n                        index,\n                        arg.type_name()\n                    )));\n                }\n            }\n\n            Ok(Axes { x, y, z })\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", axes_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Axes {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.y));\n        fields.add_field_method_get(\"Z\", |_, this| Ok(this.z));\n        fields.add_field_method_get(\"Left\", |_, this| Ok(this.x));\n        fields.add_field_method_get(\"Right\", |_, this| Ok(this.x));\n        fields.add_field_method_get(\"Top\", |_, this| Ok(this.y));\n        fields.add_field_method_get(\"Bottom\", |_, this| Ok(this.y));\n        fields.add_field_method_get(\"Front\", |_, this| Ok(this.z));\n        fields.add_field_method_get(\"Back\", |_, this| Ok(this.z));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Axes {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let write = make_list_writer();\n        write(f, self.x, \"X\")?;\n        write(f, self.y, \"Y\")?;\n        write(f, self.z, \"Z\")?;\n        Ok(())\n    }\n}\n\nimpl From<DomAxes> for Axes {\n    fn from(v: DomAxes) -> Self {\n        let bits = v.bits();\n        Self {\n            x: (bits & 1) == 1,\n            y: ((bits >> 1) & 1) == 1,\n            z: ((bits >> 2) & 1) == 1,\n        }\n    }\n}\n\nimpl From<Axes> for DomAxes {\n    fn from(v: Axes) -> Self {\n        let mut bits = 0;\n        bits += v.x as u8;\n        bits += (v.y as u8) << 1;\n        bits += (v.z as u8) << 2;\n        DomAxes::from_bits(bits).expect(\"Invalid bits\")\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/brick_color.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rand::prelude::*;\nuse rbx_dom_weak::types::BrickColor as DomBrickColor;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Color3};\n\n/**\n    An implementation of the [BrickColor](https://create.roblox.com/docs/reference/engine/datatypes/BrickColor) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `BrickColor` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct BrickColor {\n    // Unfortunately we can't use DomBrickColor as the backing type here\n    // because it does not expose any way of getting the actual rgb colors :-(\n    pub(crate) number: u16,\n    pub(crate) name: &'static str,\n    pub(crate) rgb: (u8, u8, u8),\n}\n\nimpl LuaExportsTable for BrickColor {\n    const EXPORT_NAME: &'static str = \"BrickColor\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        type ArgsNumber = u16;\n        type ArgsName = String;\n        type ArgsRgb = (u8, u8, u8);\n        type ArgsColor3 = LuaUserDataRef<Color3>;\n\n        let brick_color_new = |lua: &Lua, args: LuaMultiValue| {\n            if let Ok(number) = ArgsNumber::from_lua_multi(args.clone(), lua) {\n                Ok(color_from_number(number))\n            } else if let Ok(name) = ArgsName::from_lua_multi(args.clone(), lua) {\n                Ok(color_from_name(name))\n            } else if let Ok((r, g, b)) = ArgsRgb::from_lua_multi(args.clone(), lua) {\n                Ok(color_from_rgb(r, g, b))\n            } else if let Ok(color) = ArgsColor3::from_lua_multi(args.clone(), lua) {\n                Ok(Self::from(*color))\n            } else {\n                // FUTURE: Better error message here using given arg types\n                Err(LuaError::RuntimeError(\n                    \"Invalid arguments to constructor\".to_string(),\n                ))\n            }\n        };\n\n        let brick_color_palette = |_: &Lua, index: u16| {\n            if index == 0 {\n                Err(LuaError::RuntimeError(\"Invalid index\".to_string()))\n            } else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) {\n                Ok(color_from_number(*number))\n            } else {\n                Err(LuaError::RuntimeError(\"Invalid index\".to_string()))\n            }\n        };\n\n        let brick_color_random = |_: &Lua, ()| {\n            let number = BRICK_COLOR_PALETTE.choose(&mut rand::rng());\n            Ok(color_from_number(*number.unwrap()))\n        };\n\n        let mut builder = TableBuilder::new(lua)?\n            .with_function(\"new\", brick_color_new)?\n            .with_function(\"palette\", brick_color_palette)?\n            .with_function(\"random\", brick_color_random)?;\n\n        for (name, number) in BRICK_COLOR_CONSTRUCTORS {\n            let f = |_: &Lua, ()| Ok(color_from_number(*number));\n            builder = builder.with_function(*name, f)?;\n        }\n\n        builder.build_readonly()\n    }\n}\n\nimpl LuaUserData for BrickColor {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Number\", |_, this| Ok(this.number));\n        fields.add_field_method_get(\"Name\", |_, this| Ok(this.name));\n        fields.add_field_method_get(\"R\", |_, this| Ok(this.rgb.0 as f32 / 255f32));\n        fields.add_field_method_get(\"G\", |_, this| Ok(this.rgb.1 as f32 / 255f32));\n        fields.add_field_method_get(\"B\", |_, this| Ok(this.rgb.2 as f32 / 255f32));\n        fields.add_field_method_get(\"r\", |_, this| Ok(this.rgb.0 as f32 / 255f32));\n        fields.add_field_method_get(\"g\", |_, this| Ok(this.rgb.1 as f32 / 255f32));\n        fields.add_field_method_get(\"b\", |_, this| Ok(this.rgb.2 as f32 / 255f32));\n        fields.add_field_method_get(\"Color\", |_, this| Ok(Color3::from(*this)));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl Default for BrickColor {\n    fn default() -> Self {\n        color_from_number(BRICK_COLOR_DEFAULT)\n    }\n}\n\nimpl fmt::Display for BrickColor {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.name)\n    }\n}\n\nimpl From<Color3> for BrickColor {\n    fn from(value: Color3) -> Self {\n        let r = value.r.clamp(u8::MIN as f32, u8::MAX as f32) as u8;\n        let g = value.g.clamp(u8::MIN as f32, u8::MAX as f32) as u8;\n        let b = value.b.clamp(u8::MIN as f32, u8::MAX as f32) as u8;\n        color_from_rgb(r, g, b)\n    }\n}\n\nimpl From<BrickColor> for Color3 {\n    fn from(value: BrickColor) -> Self {\n        Self {\n            r: (value.rgb.0 as f32) / 255.0,\n            g: (value.rgb.1 as f32) / 255.0,\n            b: (value.rgb.2 as f32) / 255.0,\n        }\n    }\n}\n\nimpl From<DomBrickColor> for BrickColor {\n    fn from(v: DomBrickColor) -> Self {\n        color_from_name(v.to_string())\n    }\n}\n\nimpl From<BrickColor> for DomBrickColor {\n    fn from(v: BrickColor) -> Self {\n        DomBrickColor::from_number(v.number).unwrap_or(DomBrickColor::MediumStoneGrey)\n    }\n}\n\n/*\n\n    NOTE: The brick color definitions below are generated using\n    the brick_color script in the scripts dir next to src, which can\n    be ran using `cargo run packages/lib-roblox/scripts/brick_color`\n\n*/\n\ntype BrickColorDef = &'static (u16, &'static str, (u8, u8, u8));\n\nimpl From<BrickColorDef> for BrickColor {\n    fn from(value: BrickColorDef) -> Self {\n        Self {\n            number: value.0,\n            name: value.1,\n            rgb: value.2,\n        }\n    }\n}\n\nconst BRICK_COLOR_DEFAULT_VALUE: BrickColorDef =\n    &BRICK_COLOR_VALUES[(BRICK_COLOR_DEFAULT - 1) as usize];\n\nfn color_from_number(index: u16) -> BrickColor {\n    BRICK_COLOR_VALUES\n        .iter()\n        .find(|color| color.0 == index)\n        .unwrap_or(BRICK_COLOR_DEFAULT_VALUE)\n        .into()\n}\n\nfn color_from_name(name: impl AsRef<str>) -> BrickColor {\n    let name = name.as_ref();\n    BRICK_COLOR_VALUES\n        .iter()\n        .find(|color| color.1 == name)\n        .unwrap_or(BRICK_COLOR_DEFAULT_VALUE)\n        .into()\n}\n\nfn color_from_rgb(r: u8, g: u8, b: u8) -> BrickColor {\n    let r = r as i16;\n    let g = g as i16;\n    let b = b as i16;\n    BRICK_COLOR_VALUES\n        .iter()\n        .fold(\n            (None, u16::MAX),\n            |(closest_color, closest_distance), color| {\n                let cr = color.2.0 as i16;\n                let cg = color.2.1 as i16;\n                let cb = color.2.2 as i16;\n                let distance = ((r - cr) + (g - cg) + (b - cb)).unsigned_abs();\n                if distance < closest_distance {\n                    (Some(color), distance)\n                } else {\n                    (closest_color, closest_distance)\n                }\n            },\n        )\n        .0\n        .unwrap_or(BRICK_COLOR_DEFAULT_VALUE)\n        .into()\n}\n\nconst BRICK_COLOR_DEFAULT: u16 = 194;\n\nconst BRICK_COLOR_VALUES: &[(u16, &str, (u8, u8, u8))] = &[\n    (1, \"White\", (242, 243, 243)),\n    (2, \"Grey\", (161, 165, 162)),\n    (3, \"Light yellow\", (249, 233, 153)),\n    (5, \"Brick yellow\", (215, 197, 154)),\n    (6, \"Light green (Mint)\", (194, 218, 184)),\n    (9, \"Light reddish violet\", (232, 186, 200)),\n    (11, \"Pastel Blue\", (128, 187, 219)),\n    (12, \"Light orange brown\", (203, 132, 66)),\n    (18, \"Nougat\", (204, 142, 105)),\n    (21, \"Bright red\", (196, 40, 28)),\n    (22, \"Med. reddish violet\", (196, 112, 160)),\n    (23, \"Bright blue\", (13, 105, 172)),\n    (24, \"Bright yellow\", (245, 205, 48)),\n    (25, \"Earth orange\", (98, 71, 50)),\n    (26, \"Black\", (27, 42, 53)),\n    (27, \"Dark grey\", (109, 110, 108)),\n    (28, \"Dark green\", (40, 127, 71)),\n    (29, \"Medium green\", (161, 196, 140)),\n    (36, \"Lig. Yellowich orange\", (243, 207, 155)),\n    (37, \"Bright green\", (75, 151, 75)),\n    (38, \"Dark orange\", (160, 95, 53)),\n    (39, \"Light bluish violet\", (193, 202, 222)),\n    (40, \"Transparent\", (236, 236, 236)),\n    (41, \"Tr. Red\", (205, 84, 75)),\n    (42, \"Tr. Lg blue\", (193, 223, 240)),\n    (43, \"Tr. Blue\", (123, 182, 232)),\n    (44, \"Tr. Yellow\", (247, 241, 141)),\n    (45, \"Light blue\", (180, 210, 228)),\n    (47, \"Tr. Flu. Reddish orange\", (217, 133, 108)),\n    (48, \"Tr. Green\", (132, 182, 141)),\n    (49, \"Tr. Flu. Green\", (248, 241, 132)),\n    (50, \"Phosph. White\", (236, 232, 222)),\n    (100, \"Light red\", (238, 196, 182)),\n    (101, \"Medium red\", (218, 134, 122)),\n    (102, \"Medium blue\", (110, 153, 202)),\n    (103, \"Light grey\", (199, 193, 183)),\n    (104, \"Bright violet\", (107, 50, 124)),\n    (105, \"Br. yellowish orange\", (226, 155, 64)),\n    (106, \"Bright orange\", (218, 133, 65)),\n    (107, \"Bright bluish green\", (0, 143, 156)),\n    (108, \"Earth yellow\", (104, 92, 67)),\n    (110, \"Bright bluish violet\", (67, 84, 147)),\n    (111, \"Tr. Brown\", (191, 183, 177)),\n    (112, \"Medium bluish violet\", (104, 116, 172)),\n    (113, \"Tr. Medi. reddish violet\", (229, 173, 200)),\n    (115, \"Med. yellowish green\", (199, 210, 60)),\n    (116, \"Med. bluish green\", (85, 165, 175)),\n    (118, \"Light bluish green\", (183, 215, 213)),\n    (119, \"Br. yellowish green\", (164, 189, 71)),\n    (120, \"Lig. yellowish green\", (217, 228, 167)),\n    (121, \"Med. yellowish orange\", (231, 172, 88)),\n    (123, \"Br. reddish orange\", (211, 111, 76)),\n    (124, \"Bright reddish violet\", (146, 57, 120)),\n    (125, \"Light orange\", (234, 184, 146)),\n    (126, \"Tr. Bright bluish violet\", (165, 165, 203)),\n    (127, \"Gold\", (220, 188, 129)),\n    (128, \"Dark nougat\", (174, 122, 89)),\n    (131, \"Silver\", (156, 163, 168)),\n    (133, \"Neon orange\", (213, 115, 61)),\n    (134, \"Neon green\", (216, 221, 86)),\n    (135, \"Sand blue\", (116, 134, 157)),\n    (136, \"Sand violet\", (135, 124, 144)),\n    (137, \"Medium orange\", (224, 152, 100)),\n    (138, \"Sand yellow\", (149, 138, 115)),\n    (140, \"Earth blue\", (32, 58, 86)),\n    (141, \"Earth green\", (39, 70, 45)),\n    (143, \"Tr. Flu. Blue\", (207, 226, 247)),\n    (145, \"Sand blue metallic\", (121, 136, 161)),\n    (146, \"Sand violet metallic\", (149, 142, 163)),\n    (147, \"Sand yellow metallic\", (147, 135, 103)),\n    (148, \"Dark grey metallic\", (87, 88, 87)),\n    (149, \"Black metallic\", (22, 29, 50)),\n    (150, \"Light grey metallic\", (171, 173, 172)),\n    (151, \"Sand green\", (120, 144, 130)),\n    (153, \"Sand red\", (149, 121, 119)),\n    (154, \"Dark red\", (123, 46, 47)),\n    (157, \"Tr. Flu. Yellow\", (255, 246, 123)),\n    (158, \"Tr. Flu. Red\", (225, 164, 194)),\n    (168, \"Gun metallic\", (117, 108, 98)),\n    (176, \"Red flip/flop\", (151, 105, 91)),\n    (178, \"Yellow flip/flop\", (180, 132, 85)),\n    (179, \"Silver flip/flop\", (137, 135, 136)),\n    (180, \"Curry\", (215, 169, 75)),\n    (190, \"Fire Yellow\", (249, 214, 46)),\n    (191, \"Flame yellowish orange\", (232, 171, 45)),\n    (192, \"Reddish brown\", (105, 64, 40)),\n    (193, \"Flame reddish orange\", (207, 96, 36)),\n    (194, \"Medium stone grey\", (163, 162, 165)),\n    (195, \"Royal blue\", (70, 103, 164)),\n    (196, \"Dark Royal blue\", (35, 71, 139)),\n    (198, \"Bright reddish lilac\", (142, 66, 133)),\n    (199, \"Dark stone grey\", (99, 95, 98)),\n    (200, \"Lemon metalic\", (130, 138, 93)),\n    (208, \"Light stone grey\", (229, 228, 223)),\n    (209, \"Dark Curry\", (176, 142, 68)),\n    (210, \"Faded green\", (112, 149, 120)),\n    (211, \"Turquoise\", (121, 181, 181)),\n    (212, \"Light Royal blue\", (159, 195, 233)),\n    (213, \"Medium Royal blue\", (108, 129, 183)),\n    (216, \"Rust\", (144, 76, 42)),\n    (217, \"Brown\", (124, 92, 70)),\n    (218, \"Reddish lilac\", (150, 112, 159)),\n    (219, \"Lilac\", (107, 98, 155)),\n    (220, \"Light lilac\", (167, 169, 206)),\n    (221, \"Bright purple\", (205, 98, 152)),\n    (222, \"Light purple\", (228, 173, 200)),\n    (223, \"Light pink\", (220, 144, 149)),\n    (224, \"Light brick yellow\", (240, 213, 160)),\n    (225, \"Warm yellowish orange\", (235, 184, 127)),\n    (226, \"Cool yellow\", (253, 234, 141)),\n    (232, \"Dove blue\", (125, 187, 221)),\n    (268, \"Medium lilac\", (52, 43, 117)),\n    (301, \"Slime green\", (80, 109, 84)),\n    (302, \"Smoky grey\", (91, 93, 105)),\n    (303, \"Dark blue\", (0, 16, 176)),\n    (304, \"Parsley green\", (44, 101, 29)),\n    (305, \"Steel blue\", (82, 124, 174)),\n    (306, \"Storm blue\", (51, 88, 130)),\n    (307, \"Lapis\", (16, 42, 220)),\n    (308, \"Dark indigo\", (61, 21, 133)),\n    (309, \"Sea green\", (52, 142, 64)),\n    (310, \"Shamrock\", (91, 154, 76)),\n    (311, \"Fossil\", (159, 161, 172)),\n    (312, \"Mulberry\", (89, 34, 89)),\n    (313, \"Forest green\", (31, 128, 29)),\n    (314, \"Cadet blue\", (159, 173, 192)),\n    (315, \"Electric blue\", (9, 137, 207)),\n    (316, \"Eggplant\", (123, 0, 123)),\n    (317, \"Moss\", (124, 156, 107)),\n    (318, \"Artichoke\", (138, 171, 133)),\n    (319, \"Sage green\", (185, 196, 177)),\n    (320, \"Ghost grey\", (202, 203, 209)),\n    (321, \"Lilac\", (167, 94, 155)),\n    (322, \"Plum\", (123, 47, 123)),\n    (323, \"Olivine\", (148, 190, 129)),\n    (324, \"Laurel green\", (168, 189, 153)),\n    (325, \"Quill grey\", (223, 223, 222)),\n    (327, \"Crimson\", (151, 0, 0)),\n    (328, \"Mint\", (177, 229, 166)),\n    (329, \"Baby blue\", (152, 194, 219)),\n    (330, \"Carnation pink\", (255, 152, 220)),\n    (331, \"Persimmon\", (255, 89, 89)),\n    (332, \"Maroon\", (117, 0, 0)),\n    (333, \"Gold\", (239, 184, 56)),\n    (334, \"Daisy orange\", (248, 217, 109)),\n    (335, \"Pearl\", (231, 231, 236)),\n    (336, \"Fog\", (199, 212, 228)),\n    (337, \"Salmon\", (255, 148, 148)),\n    (338, \"Terra Cotta\", (190, 104, 98)),\n    (339, \"Cocoa\", (86, 36, 36)),\n    (340, \"Wheat\", (241, 231, 199)),\n    (341, \"Buttermilk\", (254, 243, 187)),\n    (342, \"Mauve\", (224, 178, 208)),\n    (343, \"Sunrise\", (212, 144, 189)),\n    (344, \"Tawny\", (150, 85, 85)),\n    (345, \"Rust\", (143, 76, 42)),\n    (346, \"Cashmere\", (211, 190, 150)),\n    (347, \"Khaki\", (226, 220, 188)),\n    (348, \"Lily white\", (237, 234, 234)),\n    (349, \"Seashell\", (233, 218, 218)),\n    (350, \"Burgundy\", (136, 62, 62)),\n    (351, \"Cork\", (188, 155, 93)),\n    (352, \"Burlap\", (199, 172, 120)),\n    (353, \"Beige\", (202, 191, 163)),\n    (354, \"Oyster\", (187, 179, 178)),\n    (355, \"Pine Cone\", (108, 88, 75)),\n    (356, \"Fawn brown\", (160, 132, 79)),\n    (357, \"Hurricane grey\", (149, 137, 136)),\n    (358, \"Cloudy grey\", (171, 168, 158)),\n    (359, \"Linen\", (175, 148, 131)),\n    (360, \"Copper\", (150, 103, 102)),\n    (361, \"Dirt brown\", (86, 66, 54)),\n    (362, \"Bronze\", (126, 104, 63)),\n    (363, \"Flint\", (105, 102, 92)),\n    (364, \"Dark taupe\", (90, 76, 66)),\n    (365, \"Burnt Sienna\", (106, 57, 9)),\n    (1001, \"Institutional white\", (248, 248, 248)),\n    (1002, \"Mid gray\", (205, 205, 205)),\n    (1003, \"Really black\", (17, 17, 17)),\n    (1004, \"Really red\", (255, 0, 0)),\n    (1005, \"Deep orange\", (255, 176, 0)),\n    (1006, \"Alder\", (180, 128, 255)),\n    (1007, \"Dusty Rose\", (163, 75, 75)),\n    (1008, \"Olive\", (193, 190, 66)),\n    (1009, \"New Yeller\", (255, 255, 0)),\n    (1010, \"Really blue\", (0, 0, 255)),\n    (1011, \"Navy blue\", (0, 32, 96)),\n    (1012, \"Deep blue\", (33, 84, 185)),\n    (1013, \"Cyan\", (4, 175, 236)),\n    (1014, \"CGA brown\", (170, 85, 0)),\n    (1015, \"Magenta\", (170, 0, 170)),\n    (1016, \"Pink\", (255, 102, 204)),\n    (1017, \"Deep orange\", (255, 175, 0)),\n    (1018, \"Teal\", (18, 238, 212)),\n    (1019, \"Toothpaste\", (0, 255, 255)),\n    (1020, \"Lime green\", (0, 255, 0)),\n    (1021, \"Camo\", (58, 125, 21)),\n    (1022, \"Grime\", (127, 142, 100)),\n    (1023, \"Lavender\", (140, 91, 159)),\n    (1024, \"Pastel light blue\", (175, 221, 255)),\n    (1025, \"Pastel orange\", (255, 201, 201)),\n    (1026, \"Pastel violet\", (177, 167, 255)),\n    (1027, \"Pastel blue-green\", (159, 243, 233)),\n    (1028, \"Pastel green\", (204, 255, 204)),\n    (1029, \"Pastel yellow\", (255, 255, 204)),\n    (1030, \"Pastel brown\", (255, 204, 153)),\n    (1031, \"Royal purple\", (98, 37, 209)),\n    (1032, \"Hot pink\", (255, 0, 191)),\n];\n\nconst BRICK_COLOR_PALETTE: &[u16] = &[\n    141, 301, 107, 26, 1012, 303, 1011, 304, 28, 1018, 302, 305, 306, 307, 308, 1021, 309, 310,\n    1019, 135, 102, 23, 1010, 312, 313, 37, 1022, 1020, 1027, 311, 315, 1023, 1031, 316, 151, 317,\n    318, 319, 1024, 314, 1013, 1006, 321, 322, 104, 1008, 119, 323, 324, 325, 320, 11, 1026, 1016,\n    1032, 1015, 327, 1005, 1009, 29, 328, 1028, 208, 45, 329, 330, 331, 1004, 21, 332, 333, 24,\n    334, 226, 1029, 335, 336, 342, 343, 338, 1007, 339, 133, 106, 340, 341, 1001, 1, 9, 1025, 337,\n    344, 345, 1014, 105, 346, 347, 348, 349, 1030, 125, 101, 350, 192, 351, 352, 353, 354, 1002, 5,\n    18, 217, 355, 356, 153, 357, 358, 359, 360, 38, 361, 362, 199, 194, 363, 364, 365, 1003,\n];\n\nconst BRICK_COLOR_CONSTRUCTORS: &[(&str, u16)] = &[\n    (\"Yellow\", 24),\n    (\"White\", 1),\n    (\"Black\", 26),\n    (\"Green\", 28),\n    (\"Red\", 21),\n    (\"DarkGray\", 199),\n    (\"Blue\", 23),\n    (\"Gray\", 194),\n];\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/cframe.rs",
    "content": "#![allow(clippy::items_after_statements)]\n\nuse core::fmt;\nuse std::ops;\n\nuse glam::{EulerRot, Mat3, Mat4, Quat, Vec3};\nuse mlua::{Variadic, prelude::*};\nuse rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Vector3};\n\n/**\n    An implementation of the [CFrame](https://create.roblox.com/docs/reference/engine/datatypes/CFrame)\n    Roblox datatype, backed by [`glam::Mat4`].\n\n    This implements most documented properties, methods & constructors of the `CFrame` class as of October 2025,\n    notably missing `CFrame.fromRotationBetweenVectors`, `CFrame:FuzzyEq()`, and `CFrame:AngleBetween()`.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct CFrame(pub Mat4);\n\nimpl CFrame {\n    pub const IDENTITY: Self = Self(Mat4::IDENTITY);\n\n    fn position(&self) -> Vec3 {\n        self.0.w_axis.truncate()\n    }\n\n    fn orientation(&self) -> Mat3 {\n        Mat3::from_cols(\n            self.0.x_axis.truncate(),\n            self.0.y_axis.truncate(),\n            self.0.z_axis.truncate(),\n        )\n    }\n\n    fn inverse(&self) -> Self {\n        Self(self.0.inverse())\n    }\n}\n\nimpl LuaExportsTable for CFrame {\n    const EXPORT_NAME: &'static str = \"CFrame\";\n\n    #[allow(clippy::too_many_lines)]\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let cframe_angles = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {\n            Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))\n        };\n\n        let cframe_from_axis_angle = |_: &Lua, (v, r): (LuaUserDataRef<Vector3>, f32)| {\n            Ok(CFrame(Mat4::from_axis_angle(v.0, r)))\n        };\n\n        let cframe_from_euler_angles_xyz = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {\n            Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))\n        };\n\n        let cframe_from_euler_angles_yxz = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {\n            Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))\n        };\n\n        let cframe_from_matrix = |_: &Lua,\n                                  (pos, rx, ry, rz): (\n            LuaUserDataRef<Vector3>,\n            LuaUserDataRef<Vector3>,\n            LuaUserDataRef<Vector3>,\n            Option<LuaUserDataRef<Vector3>>,\n        )| {\n            Ok(CFrame(Mat4::from_cols(\n                rx.0.extend(0.0),\n                ry.0.extend(0.0),\n                rz.map_or_else(|| rx.0.cross(ry.0).normalize(), |r| r.0)\n                    .extend(0.0),\n                pos.0.extend(1.0),\n            )))\n        };\n\n        let cframe_from_orientation = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {\n            Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))\n        };\n\n        let cframe_look_at = |_: &Lua,\n                              (from, to, up): (\n            LuaUserDataRef<Vector3>,\n            LuaUserDataRef<Vector3>,\n            Option<LuaUserDataRef<Vector3>>,\n        )| {\n            Ok(CFrame(look_at(\n                from.0,\n                to.0,\n                up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,\n            )))\n        };\n\n        // Dynamic args constructor\n        type ArgsPos = LuaUserDataRef<Vector3>;\n        type ArgsLook = (\n            LuaUserDataRef<Vector3>,\n            LuaUserDataRef<Vector3>,\n            Option<LuaUserDataRef<Vector3>>,\n        );\n\n        type ArgsPosXYZ = (f32, f32, f32);\n        type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32);\n        type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32);\n\n        let cframe_new = |lua: &Lua, args: LuaMultiValue| match args.len() {\n            0 => Ok(CFrame(Mat4::IDENTITY)),\n\n            1 => match ArgsPos::from_lua_multi(args, lua) {\n                Ok(pos) => Ok(CFrame(Mat4::from_translation(pos.0))),\n                Err(err) => Err(err),\n            },\n\n            3 => {\n                if let Ok((from, to, up)) = ArgsLook::from_lua_multi(args.clone(), lua) {\n                    Ok(CFrame(look_at(\n                        from.0,\n                        to.0,\n                        up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,\n                    )))\n                } else if let Ok((x, y, z)) = ArgsPosXYZ::from_lua_multi(args, lua) {\n                    Ok(CFrame(Mat4::from_translation(Vec3::new(x, y, z))))\n                } else {\n                    // TODO: Make this error message better\n                    Err(LuaError::RuntimeError(\n                        \"Invalid arguments to constructor\".to_string(),\n                    ))\n                }\n            }\n\n            7 => match ArgsPosXYZQuat::from_lua_multi(args, lua) {\n                Ok((x, y, z, qx, qy, qz, qw)) => Ok(CFrame(Mat4::from_rotation_translation(\n                    Quat::from_array([qx, qy, qz, qw]),\n                    Vec3::new(x, y, z),\n                ))),\n                Err(err) => Err(err),\n            },\n\n            12 => match ArgsMatrix::from_lua_multi(args, lua) {\n                Ok((x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)) => {\n                    Ok(CFrame(Mat4::from_cols_array_2d(&[\n                        [r00, r10, r20, 0.0],\n                        [r01, r11, r21, 0.0],\n                        [r02, r12, r22, 0.0],\n                        [x, y, z, 1.0],\n                    ])))\n                }\n                Err(err) => Err(err),\n            },\n\n            _ => Err(LuaError::RuntimeError(format!(\n                \"Invalid number of arguments: expected 0, 1, 3, 7, or 12, got {}\",\n                args.len()\n            ))),\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"Angles\", cframe_angles)?\n            .with_value(\"identity\", CFrame(Mat4::IDENTITY))?\n            .with_function(\"fromAxisAngle\", cframe_from_axis_angle)?\n            .with_function(\"fromEulerAnglesXYZ\", cframe_from_euler_angles_xyz)?\n            .with_function(\"fromEulerAnglesYXZ\", cframe_from_euler_angles_yxz)?\n            .with_function(\"fromMatrix\", cframe_from_matrix)?\n            .with_function(\"fromOrientation\", cframe_from_orientation)?\n            .with_function(\"lookAt\", cframe_look_at)?\n            .with_function(\"new\", cframe_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for CFrame {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Position\", |_, this| Ok(Vector3(this.position())));\n        fields.add_field_method_get(\"Rotation\", |_, this| {\n            Ok(CFrame(Mat4::from_cols(\n                this.0.x_axis,\n                this.0.y_axis,\n                this.0.z_axis,\n                Vec3::ZERO.extend(1.0),\n            )))\n        });\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.position().x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.position().y));\n        fields.add_field_method_get(\"Z\", |_, this| Ok(this.position().z));\n        fields.add_field_method_get(\"XVector\", |_, this| Ok(Vector3(this.orientation().x_axis)));\n        fields.add_field_method_get(\"YVector\", |_, this| Ok(Vector3(this.orientation().y_axis)));\n        fields.add_field_method_get(\"ZVector\", |_, this| Ok(Vector3(this.orientation().z_axis)));\n        fields.add_field_method_get(\"RightVector\", |_, this| {\n            Ok(Vector3(this.orientation().x_axis))\n        });\n        fields.add_field_method_get(\"UpVector\", |_, this| Ok(Vector3(this.orientation().y_axis)));\n        fields.add_field_method_get(\"LookVector\", |_, this| {\n            Ok(Vector3(-this.orientation().z_axis))\n        });\n    }\n\n    #[allow(clippy::too_many_lines)]\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"Inverse\", |_, this, ()| Ok(this.inverse()));\n        methods.add_method(\n            \"Lerp\",\n            |_, this, (goal, alpha): (LuaUserDataRef<CFrame>, f32)| {\n                let quat_this = Quat::from_mat4(&this.0);\n                let quat_goal = Quat::from_mat4(&goal.0);\n                let translation = this\n                    .0\n                    .w_axis\n                    .truncate()\n                    .lerp(goal.0.w_axis.truncate(), alpha);\n                let rotation = quat_this.slerp(quat_goal, alpha);\n                Ok(CFrame(Mat4::from_rotation_translation(\n                    rotation,\n                    translation,\n                )))\n            },\n        );\n        methods.add_method(\"Orthonormalize\", |_, this, ()| {\n            let rotation = Quat::from_mat4(&this.0);\n            let translation = this.0.w_axis.truncate();\n            Ok(CFrame(Mat4::from_rotation_translation(\n                rotation.normalize(),\n                translation,\n            )))\n        });\n        methods.add_method(\n            \"ToWorldSpace\",\n            |_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {\n                Ok(rhs\n                    .into_iter()\n                    .map(|cf| *this * *cf)\n                    .collect::<Variadic<_>>())\n            },\n        );\n        methods.add_method(\n            \"ToObjectSpace\",\n            |_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {\n                let inverse = this.inverse();\n                Ok(rhs\n                    .into_iter()\n                    .map(|cf| inverse * *cf)\n                    .collect::<Variadic<_>>())\n            },\n        );\n        methods.add_method(\n            \"PointToWorldSpace\",\n            |_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {\n                Ok(rhs\n                    .into_iter()\n                    .map(|v3| *this * *v3)\n                    .collect::<Variadic<_>>())\n            },\n        );\n        methods.add_method(\n            \"PointToObjectSpace\",\n            |_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {\n                let inverse = this.inverse();\n                Ok(rhs\n                    .into_iter()\n                    .map(|v3| inverse * *v3)\n                    .collect::<Variadic<_>>())\n            },\n        );\n        methods.add_method(\n            \"VectorToWorldSpace\",\n            |_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {\n                let result = *this - Vector3(this.position());\n                Ok(rhs\n                    .into_iter()\n                    .map(|v3| result * *v3)\n                    .collect::<Variadic<_>>())\n            },\n        );\n        methods.add_method(\n            \"VectorToObjectSpace\",\n            |_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {\n                let inverse = this.inverse();\n                let result = inverse - Vector3(inverse.position());\n                Ok(rhs\n                    .into_iter()\n                    .map(|v3| result * *v3)\n                    .collect::<Variadic<_>>())\n            },\n        );\n        #[rustfmt::skip]\n        methods.add_method(\"GetComponents\", |_, this, ()| {\n            let pos = this.position();\n            let transposed = this.orientation().transpose();\n            Ok((\n                pos.x,               pos.y,                 pos.z,\n                transposed.x_axis.x, transposed.x_axis.y,   transposed.x_axis.z,\n                transposed.y_axis.x, transposed.y_axis.y,   transposed.y_axis.z,\n                transposed.z_axis.x, transposed.z_axis.y,   transposed.z_axis.z,\n            ))\n        });\n        methods.add_method(\"ToEulerAnglesXYZ\", |_, this, ()| {\n            Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::XYZ))\n        });\n        methods.add_method(\"ToEulerAnglesYXZ\", |_, this, ()| {\n            let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);\n            Ok((rx, ry, rz))\n        });\n        methods.add_method(\"ToOrientation\", |_, this, ()| {\n            let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);\n            Ok((rx, ry, rz))\n        });\n        methods.add_method(\"ToAxisAngle\", |_, this, ()| {\n            let (axis, angle) = Quat::from_mat4(&this.0).to_axis_angle();\n            Ok((Vector3(axis), angle))\n        });\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Mul, |lua, this, rhs: LuaValue| {\n            if let LuaValue::UserData(ud) = &rhs {\n                if let Ok(cf) = ud.borrow::<CFrame>() {\n                    return lua.create_userdata(*this * *cf);\n                } else if let Ok(vec) = ud.borrow::<Vector3>() {\n                    return lua.create_userdata(*this * *vec);\n                }\n            }\n            Err(LuaError::FromLuaConversionError {\n                from: rhs.type_name(),\n                to: \"userdata\".to_string(),\n                message: Some(format!(\n                    \"Expected CFrame or Vector3, got {}\",\n                    rhs.type_name()\n                )),\n            })\n        });\n        methods.add_meta_method(\n            LuaMetaMethod::Add,\n            |_, this, vec: LuaUserDataRef<Vector3>| Ok(*this + *vec),\n        );\n        methods.add_meta_method(\n            LuaMetaMethod::Sub,\n            |_, this, vec: LuaUserDataRef<Vector3>| Ok(*this - *vec),\n        );\n    }\n}\n\nimpl fmt::Display for CFrame {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let pos = self.position();\n        let transposed = self.orientation().transpose();\n        write!(\n            f,\n            \"{}, {}, {}, {}\",\n            Vector3(pos),\n            Vector3(transposed.x_axis),\n            Vector3(transposed.y_axis),\n            Vector3(transposed.z_axis)\n        )\n    }\n}\n\nimpl ops::Mul for CFrame {\n    type Output = Self;\n    fn mul(self, rhs: Self) -> Self::Output {\n        CFrame(self.0 * rhs.0)\n    }\n}\n\nimpl ops::Mul<Vector3> for CFrame {\n    type Output = Vector3;\n    fn mul(self, rhs: Vector3) -> Self::Output {\n        Vector3(self.0.project_point3(rhs.0))\n    }\n}\n\nimpl ops::Add<Vector3> for CFrame {\n    type Output = Self;\n    fn add(self, rhs: Vector3) -> Self::Output {\n        CFrame(Mat4::from_cols(\n            self.0.x_axis,\n            self.0.y_axis,\n            self.0.z_axis,\n            self.0.w_axis + rhs.0.extend(0.0),\n        ))\n    }\n}\n\nimpl ops::Sub<Vector3> for CFrame {\n    type Output = Self;\n    fn sub(self, rhs: Vector3) -> Self::Output {\n        CFrame(Mat4::from_cols(\n            self.0.x_axis,\n            self.0.y_axis,\n            self.0.z_axis,\n            self.0.w_axis - rhs.0.extend(0.0),\n        ))\n    }\n}\n\nimpl From<DomCFrame> for CFrame {\n    fn from(v: DomCFrame) -> Self {\n        let transposed = v.orientation.transpose();\n        CFrame(Mat4::from_cols(\n            Vector3::from(transposed.x).0.extend(0.0),\n            Vector3::from(transposed.y).0.extend(0.0),\n            Vector3::from(transposed.z).0.extend(0.0),\n            Vector3::from(v.position).0.extend(1.0),\n        ))\n    }\n}\n\nimpl From<CFrame> for DomCFrame {\n    fn from(v: CFrame) -> Self {\n        let transposed = v.orientation().transpose();\n        DomCFrame {\n            position: DomVector3::from(Vector3(v.position())),\n            orientation: DomMatrix3::new(\n                DomVector3::from(Vector3(transposed.x_axis)),\n                DomVector3::from(Vector3(transposed.y_axis)),\n                DomVector3::from(Vector3(transposed.z_axis)),\n            ),\n        }\n    }\n}\n\n/**\n    Creates a matrix at the position `from`, looking towards `to`.\n\n    [`glam`] does provide functions such as [`look_at_lh`], [`look_at_rh`] and more but\n    they all create view matrices for camera transforms which is not what we want here.\n*/\nfn look_at(from: Vec3, to: Vec3, up: Vec3) -> Mat4 {\n    let dir = (to - from).normalize();\n    let xaxis = up.cross(dir).normalize();\n    let yaxis = dir.cross(xaxis).normalize();\n\n    Mat4::from_cols(\n        Vec3::new(xaxis.x, yaxis.x, dir.x).extend(0.0),\n        Vec3::new(xaxis.y, yaxis.y, dir.y).extend(0.0),\n        Vec3::new(xaxis.z, yaxis.z, dir.z).extend(0.0),\n        from.extend(1.0),\n    )\n}\n\n#[cfg(test)]\nmod cframe_test {\n    use glam::{Mat4, Vec3};\n    use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};\n\n    use super::CFrame;\n\n    #[test]\n    fn dom_cframe_from_cframe() {\n        let dom_cframe = DomCFrame::new(\n            DomVector3::new(1.0, 2.0, 3.0),\n            DomMatrix3::new(\n                DomVector3::new(1.0, 2.0, 3.0),\n                DomVector3::new(1.0, 2.0, 3.0),\n                DomVector3::new(1.0, 2.0, 3.0),\n            ),\n        );\n\n        let cframe = CFrame(Mat4::from_cols(\n            Vec3::new(1.0, 1.0, 1.0).extend(0.0),\n            Vec3::new(2.0, 2.0, 2.0).extend(0.0),\n            Vec3::new(3.0, 3.0, 3.0).extend(0.0),\n            Vec3::new(1.0, 2.0, 3.0).extend(1.0),\n        ));\n\n        assert_eq!(CFrame::from(dom_cframe), cframe);\n    }\n\n    #[test]\n    fn cframe_from_dom_cframe() {\n        let cframe = CFrame(Mat4::from_cols(\n            Vec3::new(1.0, 2.0, 3.0).extend(0.0),\n            Vec3::new(1.0, 2.0, 3.0).extend(0.0),\n            Vec3::new(1.0, 2.0, 3.0).extend(0.0),\n            Vec3::new(1.0, 2.0, 3.0).extend(1.0),\n        ));\n\n        let dom_cframe = DomCFrame::new(\n            DomVector3::new(1.0, 2.0, 3.0),\n            DomMatrix3::new(\n                DomVector3::new(1.0, 1.0, 1.0),\n                DomVector3::new(2.0, 2.0, 2.0),\n                DomVector3::new(3.0, 3.0, 3.0),\n            ),\n        );\n\n        assert_eq!(DomCFrame::from(cframe), dom_cframe);\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/color3.rs",
    "content": "#![allow(clippy::many_single_char_names)]\n\nuse core::fmt;\nuse std::ops;\n\nuse glam::Vec3;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::{Color3 as DomColor3, Color3uint8 as DomColor3uint8};\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [Color3](https://create.roblox.com/docs/reference/engine/datatypes/Color3) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Color3 class as of October 2025.\n\n    It also implements math operations for addition, subtraction, multiplication, and division,\n    all of which are suspiciously missing from the Roblox implementation of the Color3 datatype.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Color3 {\n    pub(crate) r: f32,\n    pub(crate) g: f32,\n    pub(crate) b: f32,\n}\n\nimpl LuaExportsTable for Color3 {\n    const EXPORT_NAME: &'static str = \"Color3\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let color3_from_rgb = |_: &Lua, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {\n            Ok(Color3 {\n                r: (r.unwrap_or_default() as f32) / 255f32,\n                g: (g.unwrap_or_default() as f32) / 255f32,\n                b: (b.unwrap_or_default() as f32) / 255f32,\n            })\n        };\n\n        let color3_from_hsv = |_: &Lua, (h, s, v): (f32, f32, f32)| {\n            // https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c\n            let i = (h * 6.0).floor();\n            let f = h * 6.0 - i;\n            let p = v * (1.0 - s);\n            let q = v * (1.0 - f * s);\n            let t = v * (1.0 - (1.0 - f) * s);\n\n            let (r, g, b) = match (i % 6.0) as u8 {\n                0 => (v, t, p),\n                1 => (q, v, p),\n                2 => (p, v, t),\n                3 => (p, q, v),\n                4 => (t, p, v),\n                5 => (v, p, q),\n                _ => unreachable!(),\n            };\n\n            Ok(Color3 { r, g, b })\n        };\n\n        let color3_from_hex = |_: &Lua, hex: String| {\n            let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();\n            let chars = if trimmed.len() == 3 {\n                (\n                    u8::from_str_radix(&trimmed[..1].repeat(2), 16),\n                    u8::from_str_radix(&trimmed[1..2].repeat(2), 16),\n                    u8::from_str_radix(&trimmed[2..3].repeat(2), 16),\n                )\n            } else if trimmed.len() == 6 {\n                (\n                    u8::from_str_radix(&trimmed[..2], 16),\n                    u8::from_str_radix(&trimmed[2..4], 16),\n                    u8::from_str_radix(&trimmed[4..6], 16),\n                )\n            } else {\n                return Err(LuaError::RuntimeError(format!(\n                    \"Hex color string must be 3 or 6 characters long, got {} character{}\",\n                    trimmed.len(),\n                    if trimmed.len() == 1 { \"\" } else { \"s\" }\n                )));\n            };\n            match chars {\n                (Ok(r), Ok(g), Ok(b)) => Ok(Color3 {\n                    r: (r as f32) / 255f32,\n                    g: (g as f32) / 255f32,\n                    b: (b as f32) / 255f32,\n                }),\n                _ => Err(LuaError::RuntimeError(format!(\n                    \"Hex color string '{trimmed}' contains invalid character\",\n                ))),\n            }\n        };\n\n        let color3_new = |_: &Lua, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {\n            Ok(Color3 {\n                r: r.unwrap_or_default(),\n                g: g.unwrap_or_default(),\n                b: b.unwrap_or_default(),\n            })\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"fromRGB\", color3_from_rgb)?\n            .with_function(\"fromHSV\", color3_from_hsv)?\n            .with_function(\"fromHex\", color3_from_hex)?\n            .with_function(\"new\", color3_new)?\n            .build_readonly()\n    }\n}\n\nimpl FromLua for Color3 {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::UserData(ud) = value {\n            Ok(*ud.borrow::<Color3>()?)\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"Color3\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\nimpl LuaUserData for Color3 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"R\", |_, this| Ok(this.r));\n        fields.add_field_method_get(\"G\", |_, this| Ok(this.g));\n        fields.add_field_method_get(\"B\", |_, this| Ok(this.b));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\n            \"Lerp\",\n            |_, this, (rhs, alpha): (LuaUserDataRef<Color3>, f32)| {\n                let v3_this = Vec3::new(this.r, this.g, this.b);\n                let v3_rhs = Vec3::new(rhs.r, rhs.g, rhs.b);\n                let v3 = v3_this.lerp(v3_rhs, alpha);\n                Ok(Color3 {\n                    r: v3.x,\n                    g: v3.y,\n                    b: v3.z,\n                })\n            },\n        );\n        methods.add_method(\"ToHSV\", |_, this, ()| {\n            // https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c\n            let (r, g, b) = (this.r, this.g, this.b);\n            let min = r.min(g).min(b);\n            let max = r.max(g).max(b);\n            let diff = max - min;\n\n            #[allow(clippy::float_cmp)]\n            let hue = (match max {\n                max if max == min => 0.0,\n                max if max == r => (g - b) / diff + (if g < b { 6.0 } else { 0.0 }),\n                max if max == g => (b - r) / diff + 2.0,\n                max if max == b => (r - g) / diff + 4.0,\n                _ => unreachable!(),\n            }) / 6.0;\n\n            let sat = if max == 0.0 {\n                0.0\n            } else {\n                (diff / max).clamp(0.0, 1.0)\n            };\n\n            Ok((hue, sat, max))\n        });\n        methods.add_method(\"ToHex\", |_, this, ()| {\n            Ok(format!(\n                \"{:02X}{:02X}{:02X}\",\n                (this.r * 255.0).clamp(u8::MIN as f32, u8::MAX as f32) as u8,\n                (this.g * 255.0).clamp(u8::MIN as f32, u8::MAX as f32) as u8,\n                (this.b * 255.0).clamp(u8::MIN as f32, u8::MAX as f32) as u8,\n            ))\n        });\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n        methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);\n        methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);\n    }\n}\n\nimpl Default for Color3 {\n    fn default() -> Self {\n        Self {\n            r: 0f32,\n            g: 0f32,\n            b: 0f32,\n        }\n    }\n}\n\nimpl fmt::Display for Color3 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}, {}\", self.r, self.g, self.b)\n    }\n}\n\nimpl ops::Neg for Color3 {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        Color3 {\n            r: -self.r,\n            g: -self.g,\n            b: -self.b,\n        }\n    }\n}\n\nimpl ops::Add for Color3 {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        Color3 {\n            r: self.r + rhs.r,\n            g: self.g + rhs.g,\n            b: self.b + rhs.b,\n        }\n    }\n}\n\nimpl ops::Sub for Color3 {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Color3 {\n            r: self.r - rhs.r,\n            g: self.g - rhs.g,\n            b: self.b - rhs.b,\n        }\n    }\n}\n\nimpl ops::Mul for Color3 {\n    type Output = Color3;\n    fn mul(self, rhs: Self) -> Self::Output {\n        Color3 {\n            r: self.r * rhs.r,\n            g: self.g * rhs.g,\n            b: self.b * rhs.b,\n        }\n    }\n}\n\nimpl ops::Mul<f32> for Color3 {\n    type Output = Color3;\n    fn mul(self, rhs: f32) -> Self::Output {\n        Color3 {\n            r: self.r * rhs,\n            g: self.g * rhs,\n            b: self.b * rhs,\n        }\n    }\n}\n\nimpl ops::Div for Color3 {\n    type Output = Color3;\n    fn div(self, rhs: Self) -> Self::Output {\n        Color3 {\n            r: self.r / rhs.r,\n            g: self.g / rhs.g,\n            b: self.b / rhs.b,\n        }\n    }\n}\n\nimpl ops::Div<f32> for Color3 {\n    type Output = Color3;\n    fn div(self, rhs: f32) -> Self::Output {\n        Color3 {\n            r: self.r / rhs,\n            g: self.g / rhs,\n            b: self.b / rhs,\n        }\n    }\n}\n\nimpl From<DomColor3> for Color3 {\n    fn from(v: DomColor3) -> Self {\n        Self {\n            r: v.r,\n            g: v.g,\n            b: v.b,\n        }\n    }\n}\n\nimpl From<Color3> for DomColor3 {\n    fn from(v: Color3) -> Self {\n        Self {\n            r: v.r,\n            g: v.g,\n            b: v.b,\n        }\n    }\n}\n\nimpl From<DomColor3uint8> for Color3 {\n    fn from(v: DomColor3uint8) -> Self {\n        Color3::from(DomColor3::from(v))\n    }\n}\n\nimpl From<Color3> for DomColor3uint8 {\n    fn from(v: Color3) -> Self {\n        DomColor3uint8::from(DomColor3::from(v))\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/color_sequence.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::{\n    ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint,\n};\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Color3, ColorSequenceKeypoint};\n\n/**\n    An implementation of the [ColorSequence](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `ColorSequence` class as of October 2025.\n*/\n#[derive(Debug, Clone, PartialEq)]\npub struct ColorSequence {\n    pub(crate) keypoints: Vec<ColorSequenceKeypoint>,\n}\n\nimpl LuaExportsTable for ColorSequence {\n    const EXPORT_NAME: &'static str = \"ColorSequence\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        type ArgsColor = LuaUserDataRef<Color3>;\n        type ArgsColors = (LuaUserDataRef<Color3>, LuaUserDataRef<Color3>);\n        type ArgsKeypoints = Vec<LuaUserDataRef<ColorSequenceKeypoint>>;\n\n        let color_sequence_new = |lua: &Lua, args: LuaMultiValue| {\n            if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {\n                Ok(ColorSequence {\n                    keypoints: vec![\n                        ColorSequenceKeypoint {\n                            time: 0.0,\n                            color: *color,\n                        },\n                        ColorSequenceKeypoint {\n                            time: 1.0,\n                            color: *color,\n                        },\n                    ],\n                })\n            } else if let Ok((c0, c1)) = ArgsColors::from_lua_multi(args.clone(), lua) {\n                Ok(ColorSequence {\n                    keypoints: vec![\n                        ColorSequenceKeypoint {\n                            time: 0.0,\n                            color: *c0,\n                        },\n                        ColorSequenceKeypoint {\n                            time: 1.0,\n                            color: *c1,\n                        },\n                    ],\n                })\n            } else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {\n                Ok(ColorSequence {\n                    keypoints: keypoints.iter().map(|k| **k).collect(),\n                })\n            } else {\n                // FUTURE: Better error message here using given arg types\n                Err(LuaError::RuntimeError(\n                    \"Invalid arguments to constructor\".to_string(),\n                ))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", color_sequence_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for ColorSequence {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Keypoints\", |_, this| Ok(this.keypoints.clone()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for ColorSequence {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        for (index, keypoint) in self.keypoints.iter().enumerate() {\n            if index < self.keypoints.len() - 1 {\n                write!(f, \"{keypoint}, \")?;\n            } else {\n                write!(f, \"{keypoint}\")?;\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl From<DomColorSequence> for ColorSequence {\n    fn from(v: DomColorSequence) -> Self {\n        Self {\n            keypoints: v\n                .keypoints\n                .iter()\n                .copied()\n                .map(ColorSequenceKeypoint::from)\n                .collect(),\n        }\n    }\n}\n\nimpl From<ColorSequence> for DomColorSequence {\n    fn from(v: ColorSequence) -> Self {\n        Self {\n            keypoints: v\n                .keypoints\n                .iter()\n                .copied()\n                .map(DomColorSequenceKeypoint::from)\n                .collect(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/color_sequence_keypoint.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::ColorSequenceKeypoint as DomColorSequenceKeypoint;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Color3};\n\n/**\n    An implementation of the [ColorSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `ColorSequenceKeypoint` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct ColorSequenceKeypoint {\n    pub(crate) time: f32,\n    pub(crate) color: Color3,\n}\n\nimpl LuaExportsTable for ColorSequenceKeypoint {\n    const EXPORT_NAME: &'static str = \"ColorSequenceKeypoint\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let color_sequence_keypoint_new =\n            |_: &Lua, (time, color): (f32, LuaUserDataRef<Color3>)| {\n                Ok(ColorSequenceKeypoint {\n                    time,\n                    color: *color,\n                })\n            };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", color_sequence_keypoint_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for ColorSequenceKeypoint {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Time\", |_, this| Ok(this.time));\n        fields.add_field_method_get(\"Value\", |_, this| Ok(this.color));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for ColorSequenceKeypoint {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{} > {}\", self.time, self.color)\n    }\n}\n\nimpl From<DomColorSequenceKeypoint> for ColorSequenceKeypoint {\n    fn from(v: DomColorSequenceKeypoint) -> Self {\n        Self {\n            time: v.time,\n            color: v.color.into(),\n        }\n    }\n}\n\nimpl From<ColorSequenceKeypoint> for DomColorSequenceKeypoint {\n    fn from(v: ColorSequenceKeypoint) -> Self {\n        Self {\n            time: v.time,\n            color: v.color.into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/content.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::{Content as DomContent, ContentType};\n\nuse lune_utils::TableBuilder;\n\nuse crate::{exports::LuaExportsTable, instance::Instance};\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [Content](https://create.roblox.com/docs/reference/engine/datatypes/Content) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Content type as of October 2025.\n*/\n#[derive(Debug, Clone, PartialEq)]\npub struct Content(ContentType);\n\nimpl LuaExportsTable for Content {\n    const EXPORT_NAME: &'static str = \"Content\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let from_uri = |_: &Lua, uri: String| Ok(Self(ContentType::Uri(uri)));\n\n        let from_object = |_: &Lua, obj: LuaUserDataRef<Instance>| {\n            let database = rbx_reflection_database::get().unwrap();\n            let instance_descriptor = database\n                .classes\n                .get(\"Instance\")\n                .expect(\"the reflection database should always have Instance in it\");\n            let param_descriptor = database.classes.get(obj.get_class_name()).expect(\n                \"you should not be able to construct an Instance that is not known to Lune\",\n            );\n            if database.has_superclass(param_descriptor, instance_descriptor) {\n                Err(LuaError::runtime(\n                    \"the provided object is a descendant class of 'Instance', expected one that was only an 'Object'\",\n                ))\n            } else {\n                Ok(Content(ContentType::Object(obj.dom_ref)))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_value(\"none\", Content(ContentType::None))?\n            .with_function(\"fromUri\", from_uri)?\n            .with_function(\"fromObject\", from_object)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Content {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"SourceType\", |_, this| {\n            let variant_name = match &this.0 {\n                ContentType::None => \"None\",\n                ContentType::Uri(_) => \"Uri\",\n                ContentType::Object(_) => \"Object\",\n                other => {\n                    return Err(LuaError::runtime(format!(\n                        \"cannot get SourceType: unknown ContentType variant '{other:?}'\"\n                    )));\n                }\n            };\n            Ok(EnumItem::from_enum_name_and_name(\n                \"ContentSourceType\",\n                variant_name,\n            ))\n        });\n        fields.add_field_method_get(\"Uri\", |_, this| {\n            if let ContentType::Uri(uri) = &this.0 {\n                Ok(Some(uri.to_owned()))\n            } else {\n                Ok(None)\n            }\n        });\n        fields.add_field_method_get(\"Object\", |_, this| {\n            if let ContentType::Object(referent) = &this.0 {\n                Ok(Instance::new_opt(*referent))\n            } else {\n                Ok(None)\n            }\n        });\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Content {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        // Regardless of the actual content of the Content, Roblox just emits\n        // `Content` when casting it to a string. We do not do that.\n        write!(f, \"Content(\")?;\n        match &self.0 {\n            ContentType::None => write!(f, \"None\")?,\n            ContentType::Uri(uri) => write!(f, \"Uri={uri}\")?,\n            ContentType::Object(_) => write!(f, \"Object\")?,\n            other => write!(f, \"UnknownType({other:?})\")?,\n        }\n        write!(f, \")\")\n    }\n}\n\nimpl From<DomContent> for Content {\n    fn from(value: DomContent) -> Self {\n        Self(value.value().clone())\n    }\n}\n\nimpl From<Content> for DomContent {\n    fn from(value: Content) -> Self {\n        match value.0 {\n            ContentType::None => Self::none(),\n            ContentType::Uri(uri) => Self::from_uri(uri),\n            ContentType::Object(referent) => Self::from_referent(referent),\n            other => unimplemented!(\"unknown variant of ContentType: {other:?}\"),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/enum.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_reflection::EnumDescriptor;\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [Enum](https://create.roblox.com/docs/reference/engine/datatypes/Enum) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Enum class as of October 2025.\n*/\n#[derive(Debug, Clone)]\npub struct Enum {\n    pub(crate) desc: &'static EnumDescriptor<'static>,\n}\n\nimpl Enum {\n    pub(crate) fn from_name(name: impl AsRef<str>) -> Option<Self> {\n        let db = rbx_reflection_database::get().unwrap();\n        db.enums.get(name.as_ref()).map(Enum::from)\n    }\n}\n\nimpl LuaUserData for Enum {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"GetEnumItems\", |_, this, ()| {\n            Ok(this\n                .desc\n                .items\n                .iter()\n                .map(|(name, value)| EnumItem {\n                    parent: this.clone(),\n                    name: name.to_string(),\n                    value: *value,\n                })\n                .collect::<Vec<_>>())\n        });\n        methods.add_meta_method(LuaMetaMethod::Index, |_, this, name: String| {\n            match EnumItem::from_enum_and_name(this, &name) {\n                Some(item) => Ok(item),\n                None => Err(LuaError::RuntimeError(format!(\n                    \"The enum item '{}' does not exist for enum '{}'\",\n                    name, this.desc.name\n                ))),\n            }\n        });\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Enum {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"Enum.{}\", self.desc.name)\n    }\n}\n\nimpl PartialEq for Enum {\n    fn eq(&self, other: &Self) -> bool {\n        self.desc.name == other.desc.name\n    }\n}\n\nimpl From<&'static EnumDescriptor<'static>> for Enum {\n    fn from(value: &'static EnumDescriptor<'static>) -> Self {\n        Self { desc: value }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/enum_item.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::EnumItem as DomEnumItem;\n\nuse super::{super::*, Enum};\n\n/**\n    An implementation of the [EnumItem](https://create.roblox.com/docs/reference/engine/datatypes/EnumItem) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `EnumItem` class as of October 2025.\n*/\n#[derive(Debug, Clone)]\npub struct EnumItem {\n    pub(crate) parent: Enum,\n    pub(crate) name: String,\n    pub(crate) value: u32,\n}\n\nimpl EnumItem {\n    pub(crate) fn from_enum_and_name(parent: &Enum, name: impl AsRef<str>) -> Option<Self> {\n        let enum_name = name.as_ref();\n        parent.desc.items.iter().find_map(|(name, v)| {\n            if *name == enum_name {\n                Some(Self {\n                    parent: parent.clone(),\n                    name: enum_name.to_string(),\n                    value: *v,\n                })\n            } else {\n                None\n            }\n        })\n    }\n\n    pub(crate) fn from_enum_and_value(parent: &Enum, value: u32) -> Option<Self> {\n        parent.desc.items.iter().find_map(|(name, v)| {\n            if *v == value {\n                Some(Self {\n                    parent: parent.clone(),\n                    name: name.to_string(),\n                    value,\n                })\n            } else {\n                None\n            }\n        })\n    }\n\n    pub(crate) fn from_enum_name_and_name(\n        enum_name: impl AsRef<str>,\n        name: impl AsRef<str>,\n    ) -> Option<Self> {\n        let parent = Enum::from_name(enum_name)?;\n        Self::from_enum_and_name(&parent, name)\n    }\n\n    pub(crate) fn from_enum_name_and_value(enum_name: impl AsRef<str>, value: u32) -> Option<Self> {\n        let parent = Enum::from_name(enum_name)?;\n        Self::from_enum_and_value(&parent, value)\n    }\n}\n\nimpl LuaUserData for EnumItem {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Name\", |_, this| Ok(this.name.clone()));\n        fields.add_field_method_get(\"Value\", |_, this| Ok(this.value));\n        fields.add_field_method_get(\"EnumType\", |_, this| Ok(this.parent.clone()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl FromLua for EnumItem {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::UserData(ud) = value {\n            Ok(ud.borrow::<EnumItem>()?.to_owned())\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"EnumItem\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\nimpl fmt::Display for EnumItem {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}.{}\", self.parent, self.name)\n    }\n}\n\nimpl PartialEq for EnumItem {\n    fn eq(&self, other: &Self) -> bool {\n        self.parent == other.parent && self.value == other.value\n    }\n}\n\nimpl From<EnumItem> for DomEnumItem {\n    fn from(v: EnumItem) -> Self {\n        DomEnumItem {\n            ty: v.parent.desc.name.to_string(),\n            value: v.value,\n        }\n    }\n}\n\nimpl From<DomEnumItem> for EnumItem {\n    fn from(value: DomEnumItem) -> Self {\n        EnumItem::from_enum_name_and_value(value.ty, value.value)\n            .expect(\"cannot convert rbx_type::EnumItem with unknown type into EnumItem\")\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/enums.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\n\nuse super::{super::*, Enum};\n\n/**\n    An implementation of the [Enums](https://create.roblox.com/docs/reference/engine/datatypes/Enums) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Enums class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Enums;\n\nimpl LuaUserData for Enums {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"GetEnums\", |_, _, ()| {\n            let db = rbx_reflection_database::get().unwrap();\n            Ok(db.enums.values().map(Enum::from).collect::<Vec<_>>())\n        });\n        methods.add_meta_method(\n            LuaMetaMethod::Index,\n            |_, _, name: String| match Enum::from_name(&name) {\n                Some(e) => Ok(e),\n                None => Err(LuaError::RuntimeError(format!(\n                    \"The enum '{name}' does not exist\",\n                ))),\n            },\n        );\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Enums {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"Enum\")\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/faces.rs",
    "content": "#![allow(clippy::struct_excessive_bools)]\n\nuse core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Faces as DomFaces;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [Faces](https://create.roblox.com/docs/reference/engine/datatypes/Faces) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Faces class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Faces {\n    pub(crate) right: bool,\n    pub(crate) top: bool,\n    pub(crate) back: bool,\n    pub(crate) left: bool,\n    pub(crate) bottom: bool,\n    pub(crate) front: bool,\n}\n\nimpl LuaExportsTable for Faces {\n    const EXPORT_NAME: &'static str = \"Faces\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let faces_new = |_: &Lua, args: LuaMultiValue| {\n            let mut right = false;\n            let mut top = false;\n            let mut back = false;\n            let mut left = false;\n            let mut bottom = false;\n            let mut front = false;\n\n            let mut check = |e: &EnumItem| {\n                if e.parent.desc.name == \"NormalId\" {\n                    match &e.name {\n                        name if name == \"Right\" => right = true,\n                        name if name == \"Top\" => top = true,\n                        name if name == \"Back\" => back = true,\n                        name if name == \"Left\" => left = true,\n                        name if name == \"Bottom\" => bottom = true,\n                        name if name == \"Front\" => front = true,\n                        _ => {}\n                    }\n                }\n            };\n\n            for (index, arg) in args.into_iter().enumerate() {\n                if let LuaValue::UserData(u) = arg {\n                    if let Ok(e) = u.borrow::<EnumItem>() {\n                        check(&e);\n                    } else {\n                        return Err(LuaError::RuntimeError(format!(\n                            \"Expected argument #{index} to be an EnumItem, got userdata\",\n                        )));\n                    }\n                } else {\n                    return Err(LuaError::RuntimeError(format!(\n                        \"Expected argument #{} to be an EnumItem, got {}\",\n                        index,\n                        arg.type_name()\n                    )));\n                }\n            }\n\n            Ok(Faces {\n                right,\n                top,\n                back,\n                left,\n                bottom,\n                front,\n            })\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", faces_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Faces {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Right\", |_, this| Ok(this.right));\n        fields.add_field_method_get(\"Top\", |_, this| Ok(this.top));\n        fields.add_field_method_get(\"Back\", |_, this| Ok(this.back));\n        fields.add_field_method_get(\"Left\", |_, this| Ok(this.left));\n        fields.add_field_method_get(\"Bottom\", |_, this| Ok(this.bottom));\n        fields.add_field_method_get(\"Front\", |_, this| Ok(this.front));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Faces {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let write = make_list_writer();\n        write(f, self.right, \"Right\")?;\n        write(f, self.top, \"Top\")?;\n        write(f, self.back, \"Back\")?;\n        write(f, self.left, \"Left\")?;\n        write(f, self.bottom, \"Bottom\")?;\n        write(f, self.front, \"Front\")?;\n        Ok(())\n    }\n}\n\nimpl From<DomFaces> for Faces {\n    fn from(v: DomFaces) -> Self {\n        let bits = v.bits();\n        Self {\n            right: (bits & 1) == 1,\n            top: ((bits >> 1) & 1) == 1,\n            back: ((bits >> 2) & 1) == 1,\n            left: ((bits >> 3) & 1) == 1,\n            bottom: ((bits >> 4) & 1) == 1,\n            front: ((bits >> 5) & 1) == 1,\n        }\n    }\n}\n\nimpl From<Faces> for DomFaces {\n    fn from(v: Faces) -> Self {\n        let mut bits = 0;\n        bits += v.right as u8;\n        bits += (v.top as u8) << 1;\n        bits += (v.back as u8) << 2;\n        bits += (v.left as u8) << 3;\n        bits += (v.bottom as u8) << 4;\n        bits += (v.front as u8) << 5;\n        DomFaces::from_bits(bits).expect(\"Invalid bits\")\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/font.rs",
    "content": "use core::fmt;\nuse std::str::FromStr;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::{\n    Font as DomFont, FontStyle as DomFontStyle, FontWeight as DomFontWeight,\n};\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [Font](https://create.roblox.com/docs/reference/engine/datatypes/Font) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the Font class as of October 2025.\n*/\n#[derive(Debug, Clone, PartialEq)]\npub struct Font {\n    pub(crate) family: String,\n    pub(crate) weight: FontWeight,\n    pub(crate) style: FontStyle,\n    pub(crate) cached_id: Option<String>,\n}\n\nimpl Font {\n    pub(crate) fn from_enum_item(material_enum_item: &EnumItem) -> Option<Font> {\n        FONT_ENUM_MAP\n            .iter()\n            .find(|props| props.0 == material_enum_item.name && props.1.is_some())\n            .map(|props| props.1.as_ref().unwrap())\n            .map(|props| Font {\n                family: props.0.to_string(),\n                weight: props.1,\n                style: props.2,\n                cached_id: None,\n            })\n    }\n}\n\nimpl LuaExportsTable for Font {\n    const EXPORT_NAME: &'static str = \"Font\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let font_from_enum = |_: &Lua, value: LuaUserDataRef<EnumItem>| {\n            if value.parent.desc.name == \"Font\" {\n                match Font::from_enum_item(&value) {\n                    Some(props) => Ok(props),\n                    None => Err(LuaError::RuntimeError(format!(\n                        \"Found unknown Font '{}'\",\n                        value.name\n                    ))),\n                }\n            } else {\n                Err(LuaError::RuntimeError(format!(\n                    \"Expected argument #1 to be a Font, got {}\",\n                    value.parent.desc.name\n                )))\n            }\n        };\n\n        let font_from_name =\n            |_: &Lua, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {\n                Ok(Font {\n                    family: format!(\"rbxasset://fonts/families/{file}.json\"),\n                    weight: weight.unwrap_or_default(),\n                    style: style.unwrap_or_default(),\n                    cached_id: None,\n                })\n            };\n\n        let font_from_id =\n            |_: &Lua, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {\n                Ok(Font {\n                    family: format!(\"rbxassetid://{id}\"),\n                    weight: weight.unwrap_or_default(),\n                    style: style.unwrap_or_default(),\n                    cached_id: None,\n                })\n            };\n\n        let font_new =\n            |_: &Lua, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {\n                Ok(Font {\n                    family,\n                    weight: weight.unwrap_or_default(),\n                    style: style.unwrap_or_default(),\n                    cached_id: None,\n                })\n            };\n\n        TableBuilder::new(lua)?\n            .with_function(\"fromEnum\", font_from_enum)?\n            .with_function(\"fromName\", font_from_name)?\n            .with_function(\"fromId\", font_from_id)?\n            .with_function(\"new\", font_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Font {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        // Getters\n        fields.add_field_method_get(\"Family\", |_, this| Ok(this.family.clone()));\n        fields.add_field_method_get(\"Weight\", |_, this| Ok(this.weight));\n        fields.add_field_method_get(\"Style\", |_, this| Ok(this.style));\n        fields.add_field_method_get(\"Bold\", |_, this| Ok(this.weight.as_u16() >= 600));\n        // Setters\n        fields.add_field_method_set(\"Weight\", |_, this, value: FontWeight| {\n            this.weight = value;\n            Ok(())\n        });\n        fields.add_field_method_set(\"Style\", |_, this, value: FontStyle| {\n            this.style = value;\n            Ok(())\n        });\n        fields.add_field_method_set(\"Bold\", |_, this, value: bool| {\n            if value {\n                this.weight = FontWeight::Bold;\n            } else {\n                this.weight = FontWeight::Regular;\n            }\n            Ok(())\n        });\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Font {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}, {}\", self.family, self.weight, self.style)\n    }\n}\n\nimpl From<DomFont> for Font {\n    fn from(v: DomFont) -> Self {\n        Self {\n            family: v.family,\n            weight: v.weight.into(),\n            style: v.style.into(),\n            cached_id: v.cached_face_id,\n        }\n    }\n}\n\nimpl From<Font> for DomFont {\n    fn from(v: Font) -> Self {\n        DomFont {\n            family: v.family,\n            weight: v.weight.into(),\n            style: v.style.into(),\n            cached_face_id: v.cached_id,\n        }\n    }\n}\n\nimpl From<DomFontWeight> for FontWeight {\n    fn from(v: DomFontWeight) -> Self {\n        FontWeight::from_u16(v.as_u16()).expect(\"Missing font weight\")\n    }\n}\n\nimpl From<FontWeight> for DomFontWeight {\n    fn from(v: FontWeight) -> Self {\n        DomFontWeight::from_u16(v.as_u16()).expect(\"Missing rbx font weight\")\n    }\n}\n\nimpl From<DomFontStyle> for FontStyle {\n    fn from(v: DomFontStyle) -> Self {\n        FontStyle::from_u8(v.as_u8()).expect(\"Missing font weight\")\n    }\n}\n\nimpl From<FontStyle> for DomFontStyle {\n    fn from(v: FontStyle) -> Self {\n        DomFontStyle::from_u8(v.as_u8()).expect(\"Missing rbx font weight\")\n    }\n}\n\n/*\n\n    NOTE: The font code below is all generated using the\n    font_enum_map script in the scripts dir next to src,\n    which can be ran in the Roblox Studio command bar\n\n*/\n\ntype FontData = (&'static str, FontWeight, FontStyle);\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub(crate) enum FontWeight {\n    Thin,\n    ExtraLight,\n    Light,\n    Regular,\n    Medium,\n    SemiBold,\n    Bold,\n    ExtraBold,\n    Heavy,\n}\n\nimpl FontWeight {\n    pub(crate) fn as_u16(self) -> u16 {\n        match self {\n            Self::Thin => 100,\n            Self::ExtraLight => 200,\n            Self::Light => 300,\n            Self::Regular => 400,\n            Self::Medium => 500,\n            Self::SemiBold => 600,\n            Self::Bold => 700,\n            Self::ExtraBold => 800,\n            Self::Heavy => 900,\n        }\n    }\n\n    pub(crate) fn from_u16(n: u16) -> Option<Self> {\n        match n {\n            100 => Some(Self::Thin),\n            200 => Some(Self::ExtraLight),\n            300 => Some(Self::Light),\n            400 => Some(Self::Regular),\n            500 => Some(Self::Medium),\n            600 => Some(Self::SemiBold),\n            700 => Some(Self::Bold),\n            800 => Some(Self::ExtraBold),\n            900 => Some(Self::Heavy),\n            _ => None,\n        }\n    }\n}\n\nimpl Default for FontWeight {\n    fn default() -> Self {\n        Self::Regular\n    }\n}\n\nimpl std::str::FromStr for FontWeight {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"Thin\" => Ok(Self::Thin),\n            \"ExtraLight\" => Ok(Self::ExtraLight),\n            \"Light\" => Ok(Self::Light),\n            \"Regular\" => Ok(Self::Regular),\n            \"Medium\" => Ok(Self::Medium),\n            \"SemiBold\" => Ok(Self::SemiBold),\n            \"Bold\" => Ok(Self::Bold),\n            \"ExtraBold\" => Ok(Self::ExtraBold),\n            \"Heavy\" => Ok(Self::Heavy),\n            _ => Err(\"Unknown FontWeight\"),\n        }\n    }\n}\n\nimpl std::fmt::Display for FontWeight {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Self::Thin => \"Thin\",\n                Self::ExtraLight => \"ExtraLight\",\n                Self::Light => \"Light\",\n                Self::Regular => \"Regular\",\n                Self::Medium => \"Medium\",\n                Self::SemiBold => \"SemiBold\",\n                Self::Bold => \"Bold\",\n                Self::ExtraBold => \"ExtraBold\",\n                Self::Heavy => \"Heavy\",\n            }\n        )\n    }\n}\n\nimpl FromLua for FontWeight {\n    fn from_lua(lua_value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        let mut message = None;\n        if let LuaValue::UserData(ud) = &lua_value {\n            let value = ud.borrow::<EnumItem>()?;\n            if value.parent.desc.name == \"FontWeight\" {\n                if let Ok(value) = FontWeight::from_str(&value.name) {\n                    return Ok(value);\n                }\n                message = Some(format!(\n                    \"Found unknown Enum.FontWeight value '{}'\",\n                    value.name\n                ));\n            } else {\n                message = Some(format!(\n                    \"Expected Enum.FontWeight, got Enum.{}\",\n                    value.parent.desc.name\n                ));\n            }\n        }\n        Err(LuaError::FromLuaConversionError {\n            from: lua_value.type_name(),\n            to: \"Enum.FontWeight\".to_string(),\n            message,\n        })\n    }\n}\n\nimpl IntoLua for FontWeight {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        match EnumItem::from_enum_name_and_name(\"FontWeight\", self.to_string()) {\n            Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),\n            None => Err(LuaError::ToLuaConversionError {\n                from: \"FontWeight\".to_string(),\n                to: \"EnumItem\",\n                message: Some(format!(\"Found unknown Enum.FontWeight value '{self}'\")),\n            }),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub(crate) enum FontStyle {\n    Normal,\n    Italic,\n}\n\nimpl FontStyle {\n    pub(crate) fn as_u8(self) -> u8 {\n        match self {\n            Self::Normal => 0,\n            Self::Italic => 1,\n        }\n    }\n\n    pub(crate) fn from_u8(n: u8) -> Option<Self> {\n        match n {\n            0 => Some(Self::Normal),\n            1 => Some(Self::Italic),\n            _ => None,\n        }\n    }\n}\n\nimpl Default for FontStyle {\n    fn default() -> Self {\n        Self::Normal\n    }\n}\n\nimpl std::str::FromStr for FontStyle {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"Normal\" => Ok(Self::Normal),\n            \"Italic\" => Ok(Self::Italic),\n            _ => Err(\"Unknown FontStyle\"),\n        }\n    }\n}\n\nimpl std::fmt::Display for FontStyle {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Self::Normal => \"Normal\",\n                Self::Italic => \"Italic\",\n            }\n        )\n    }\n}\n\nimpl FromLua for FontStyle {\n    fn from_lua(lua_value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        let mut message = None;\n        if let LuaValue::UserData(ud) = &lua_value {\n            let value = ud.borrow::<EnumItem>()?;\n            if value.parent.desc.name == \"FontStyle\" {\n                if let Ok(value) = FontStyle::from_str(&value.name) {\n                    return Ok(value);\n                }\n                message = Some(format!(\n                    \"Found unknown Enum.FontStyle value '{}'\",\n                    value.name\n                ));\n            } else {\n                message = Some(format!(\n                    \"Expected Enum.FontStyle, got Enum.{}\",\n                    value.parent.desc.name\n                ));\n            }\n        }\n        Err(LuaError::FromLuaConversionError {\n            from: lua_value.type_name(),\n            to: \"Enum.FontStyle\".to_string(),\n            message,\n        })\n    }\n}\n\nimpl IntoLua for FontStyle {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        match EnumItem::from_enum_name_and_name(\"FontStyle\", self.to_string()) {\n            Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),\n            None => Err(LuaError::ToLuaConversionError {\n                from: \"FontStyle\".to_string(),\n                to: \"EnumItem\",\n                message: Some(format!(\"Found unknown Enum.FontStyle value '{self}'\")),\n            }),\n        }\n    }\n}\n\n#[rustfmt::skip]\nconst FONT_ENUM_MAP: &[(&str, Option<FontData>)] = &[\n    (\"Legacy\",             Some((\"rbxasset://fonts/families/LegacyArial.json\",      FontWeight::Regular,  FontStyle::Normal))),\n    (\"Arial\",              Some((\"rbxasset://fonts/families/Arial.json\",            FontWeight::Regular,  FontStyle::Normal))),\n    (\"ArialBold\",          Some((\"rbxasset://fonts/families/Arial.json\",            FontWeight::Bold,     FontStyle::Normal))),\n    (\"SourceSans\",         Some((\"rbxasset://fonts/families/SourceSansPro.json\",    FontWeight::Regular,  FontStyle::Normal))),\n    (\"SourceSansBold\",     Some((\"rbxasset://fonts/families/SourceSansPro.json\",    FontWeight::Bold,     FontStyle::Normal))),\n    (\"SourceSansSemibold\", Some((\"rbxasset://fonts/families/SourceSansPro.json\",    FontWeight::SemiBold, FontStyle::Normal))),\n    (\"SourceSansLight\",    Some((\"rbxasset://fonts/families/SourceSansPro.json\",    FontWeight::Light,    FontStyle::Normal))),\n    (\"SourceSansItalic\",   Some((\"rbxasset://fonts/families/SourceSansPro.json\",    FontWeight::Regular,  FontStyle::Italic))),\n    (\"Bodoni\",             Some((\"rbxasset://fonts/families/AccanthisADFStd.json\",  FontWeight::Regular,  FontStyle::Normal))),\n    (\"Garamond\",           Some((\"rbxasset://fonts/families/Guru.json\",             FontWeight::Regular,  FontStyle::Normal))),\n    (\"Cartoon\",            Some((\"rbxasset://fonts/families/ComicNeueAngular.json\", FontWeight::Regular,  FontStyle::Normal))),\n    (\"Code\",               Some((\"rbxasset://fonts/families/Inconsolata.json\",      FontWeight::Regular,  FontStyle::Normal))),\n    (\"Highway\",            Some((\"rbxasset://fonts/families/HighwayGothic.json\",    FontWeight::Regular,  FontStyle::Normal))),\n    (\"SciFi\",              Some((\"rbxasset://fonts/families/Zekton.json\",           FontWeight::Regular,  FontStyle::Normal))),\n    (\"Arcade\",             Some((\"rbxasset://fonts/families/PressStart2P.json\",     FontWeight::Regular,  FontStyle::Normal))),\n    (\"Fantasy\",            Some((\"rbxasset://fonts/families/Balthazar.json\",        FontWeight::Regular,  FontStyle::Normal))),\n    (\"Antique\",            Some((\"rbxasset://fonts/families/RomanAntique.json\",     FontWeight::Regular,  FontStyle::Normal))),\n    (\"Gotham\",             Some((\"rbxasset://fonts/families/GothamSSm.json\",        FontWeight::Regular,  FontStyle::Normal))),\n    (\"GothamMedium\",       Some((\"rbxasset://fonts/families/GothamSSm.json\",        FontWeight::Medium,   FontStyle::Normal))),\n    (\"GothamBold\",         Some((\"rbxasset://fonts/families/GothamSSm.json\",        FontWeight::Bold,     FontStyle::Normal))),\n    (\"GothamBlack\",        Some((\"rbxasset://fonts/families/GothamSSm.json\",        FontWeight::Heavy,    FontStyle::Normal))),\n    (\"AmaticSC\",           Some((\"rbxasset://fonts/families/AmaticSC.json\",         FontWeight::Regular,  FontStyle::Normal))),\n    (\"Bangers\",            Some((\"rbxasset://fonts/families/Bangers.json\",          FontWeight::Regular,  FontStyle::Normal))),\n    (\"Creepster\",          Some((\"rbxasset://fonts/families/Creepster.json\",        FontWeight::Regular,  FontStyle::Normal))),\n    (\"DenkOne\",            Some((\"rbxasset://fonts/families/DenkOne.json\",          FontWeight::Regular,  FontStyle::Normal))),\n    (\"Fondamento\",         Some((\"rbxasset://fonts/families/Fondamento.json\",       FontWeight::Regular,  FontStyle::Normal))),\n    (\"FredokaOne\",         Some((\"rbxasset://fonts/families/FredokaOne.json\",       FontWeight::Regular,  FontStyle::Normal))),\n    (\"GrenzeGotisch\",      Some((\"rbxasset://fonts/families/GrenzeGotisch.json\",    FontWeight::Regular,  FontStyle::Normal))),\n    (\"IndieFlower\",        Some((\"rbxasset://fonts/families/IndieFlower.json\",      FontWeight::Regular,  FontStyle::Normal))),\n    (\"JosefinSans\",        Some((\"rbxasset://fonts/families/JosefinSans.json\",      FontWeight::Regular,  FontStyle::Normal))),\n    (\"Jura\",               Some((\"rbxasset://fonts/families/Jura.json\",             FontWeight::Regular,  FontStyle::Normal))),\n    (\"Kalam\",              Some((\"rbxasset://fonts/families/Kalam.json\",            FontWeight::Regular,  FontStyle::Normal))),\n    (\"LuckiestGuy\",        Some((\"rbxasset://fonts/families/LuckiestGuy.json\",      FontWeight::Regular,  FontStyle::Normal))),\n    (\"Merriweather\",       Some((\"rbxasset://fonts/families/Merriweather.json\",     FontWeight::Regular,  FontStyle::Normal))),\n    (\"Michroma\",           Some((\"rbxasset://fonts/families/Michroma.json\",         FontWeight::Regular,  FontStyle::Normal))),\n    (\"Nunito\",             Some((\"rbxasset://fonts/families/Nunito.json\",           FontWeight::Regular,  FontStyle::Normal))),\n    (\"Oswald\",             Some((\"rbxasset://fonts/families/Oswald.json\",           FontWeight::Regular,  FontStyle::Normal))),\n    (\"PatrickHand\",        Some((\"rbxasset://fonts/families/PatrickHand.json\",      FontWeight::Regular,  FontStyle::Normal))),\n    (\"PermanentMarker\",    Some((\"rbxasset://fonts/families/PermanentMarker.json\",  FontWeight::Regular,  FontStyle::Normal))),\n    (\"Roboto\",             Some((\"rbxasset://fonts/families/Roboto.json\",           FontWeight::Regular,  FontStyle::Normal))),\n    (\"RobotoCondensed\",    Some((\"rbxasset://fonts/families/RobotoCondensed.json\",  FontWeight::Regular,  FontStyle::Normal))),\n    (\"RobotoMono\",         Some((\"rbxasset://fonts/families/RobotoMono.json\",       FontWeight::Regular,  FontStyle::Normal))),\n    (\"Sarpanch\",           Some((\"rbxasset://fonts/families/Sarpanch.json\",         FontWeight::Regular,  FontStyle::Normal))),\n    (\"SpecialElite\",       Some((\"rbxasset://fonts/families/SpecialElite.json\",     FontWeight::Regular,  FontStyle::Normal))),\n    (\"TitilliumWeb\",       Some((\"rbxasset://fonts/families/TitilliumWeb.json\",     FontWeight::Regular,  FontStyle::Normal))),\n    (\"Ubuntu\",             Some((\"rbxasset://fonts/families/Ubuntu.json\",           FontWeight::Regular,  FontStyle::Normal))),\n    (\"Unknown\",            None),\n];\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/mod.rs",
    "content": "mod axes;\nmod brick_color;\nmod cframe;\nmod color3;\nmod color_sequence;\nmod color_sequence_keypoint;\nmod content;\nmod r#enum;\nmod r#enum_item;\nmod r#enums;\nmod faces;\nmod font;\nmod number_range;\nmod number_sequence;\nmod number_sequence_keypoint;\nmod physical_properties;\nmod ray;\nmod rect;\nmod region3;\nmod region3int16;\nmod udim;\nmod udim2;\nmod unique_id;\nmod vector2;\nmod vector2int16;\nmod vector3;\nmod vector3int16;\n\npub use axes::Axes;\npub use brick_color::BrickColor;\npub use cframe::CFrame;\npub use color_sequence::ColorSequence;\npub use color_sequence_keypoint::ColorSequenceKeypoint;\npub use color3::Color3;\npub use content::Content;\npub use r#enum::Enum;\npub use r#enum_item::EnumItem;\npub use r#enums::Enums;\npub use faces::Faces;\npub use font::Font;\npub use number_range::NumberRange;\npub use number_sequence::NumberSequence;\npub use number_sequence_keypoint::NumberSequenceKeypoint;\npub use physical_properties::PhysicalProperties;\npub use ray::Ray;\npub use rect::Rect;\npub use region3::Region3;\npub use region3int16::Region3int16;\npub use udim::UDim;\npub use udim2::UDim2;\npub use unique_id::UniqueId;\npub use vector2::Vector2;\npub use vector2int16::Vector2int16;\npub use vector3::Vector3;\npub use vector3int16::Vector3int16;\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/number_range.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::NumberRange as DomNumberRange;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [NumberRange](https://create.roblox.com/docs/reference/engine/datatypes/NumberRange) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `NumberRange` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct NumberRange {\n    pub(crate) min: f32,\n    pub(crate) max: f32,\n}\n\nimpl LuaExportsTable for NumberRange {\n    const EXPORT_NAME: &'static str = \"NumberRange\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let number_range_new = |_: &Lua, (min, max): (f32, Option<f32>)| {\n            Ok(match max {\n                Some(max) => NumberRange {\n                    min: min.min(max),\n                    max: min.max(max),\n                },\n                None => NumberRange { min, max: min },\n            })\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", number_range_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for NumberRange {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Min\", |_, this| Ok(this.min));\n        fields.add_field_method_get(\"Max\", |_, this| Ok(this.max));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for NumberRange {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.min, self.max)\n    }\n}\n\nimpl From<DomNumberRange> for NumberRange {\n    fn from(v: DomNumberRange) -> Self {\n        Self {\n            min: v.min,\n            max: v.max,\n        }\n    }\n}\n\nimpl From<NumberRange> for DomNumberRange {\n    fn from(v: NumberRange) -> Self {\n        Self {\n            min: v.min,\n            max: v.max,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/number_sequence.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::{\n    NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint,\n};\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, NumberSequenceKeypoint};\n\n/**\n    An implementation of the [NumberSequence](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequence) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `NumberSequence` class as of October 2025.\n*/\n#[derive(Debug, Clone, PartialEq)]\npub struct NumberSequence {\n    pub(crate) keypoints: Vec<NumberSequenceKeypoint>,\n}\n\nimpl LuaExportsTable for NumberSequence {\n    const EXPORT_NAME: &'static str = \"NumberSequence\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        type ArgsColor = f32;\n        type ArgsColors = (f32, f32);\n        type ArgsKeypoints = Vec<LuaUserDataRef<NumberSequenceKeypoint>>;\n\n        let number_sequence_new = |lua: &Lua, args: LuaMultiValue| {\n            if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {\n                Ok(NumberSequence {\n                    keypoints: vec![\n                        NumberSequenceKeypoint {\n                            time: 0.0,\n                            value,\n                            envelope: 0.0,\n                        },\n                        NumberSequenceKeypoint {\n                            time: 1.0,\n                            value,\n                            envelope: 0.0,\n                        },\n                    ],\n                })\n            } else if let Ok((v0, v1)) = ArgsColors::from_lua_multi(args.clone(), lua) {\n                Ok(NumberSequence {\n                    keypoints: vec![\n                        NumberSequenceKeypoint {\n                            time: 0.0,\n                            value: v0,\n                            envelope: 0.0,\n                        },\n                        NumberSequenceKeypoint {\n                            time: 1.0,\n                            value: v1,\n                            envelope: 0.0,\n                        },\n                    ],\n                })\n            } else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {\n                Ok(NumberSequence {\n                    keypoints: keypoints.iter().map(|k| **k).collect(),\n                })\n            } else {\n                // FUTURE: Better error message here using given arg types\n                Err(LuaError::RuntimeError(\n                    \"Invalid arguments to constructor\".to_string(),\n                ))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", number_sequence_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for NumberSequence {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Keypoints\", |_, this| Ok(this.keypoints.clone()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for NumberSequence {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        for (index, keypoint) in self.keypoints.iter().enumerate() {\n            if index < self.keypoints.len() - 1 {\n                write!(f, \"{keypoint}, \")?;\n            } else {\n                write!(f, \"{keypoint}\")?;\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl From<DomNumberSequence> for NumberSequence {\n    fn from(v: DomNumberSequence) -> Self {\n        Self {\n            keypoints: v\n                .keypoints\n                .iter()\n                .copied()\n                .map(NumberSequenceKeypoint::from)\n                .collect(),\n        }\n    }\n}\n\nimpl From<NumberSequence> for DomNumberSequence {\n    fn from(v: NumberSequence) -> Self {\n        Self {\n            keypoints: v\n                .keypoints\n                .iter()\n                .copied()\n                .map(DomNumberSequenceKeypoint::from)\n                .collect(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/number_sequence_keypoint.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::NumberSequenceKeypoint as DomNumberSequenceKeypoint;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [NumberSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequenceKeypoint) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `NumberSequenceKeypoint` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct NumberSequenceKeypoint {\n    pub(crate) time: f32,\n    pub(crate) value: f32,\n    pub(crate) envelope: f32,\n}\n\nimpl LuaExportsTable for NumberSequenceKeypoint {\n    const EXPORT_NAME: &'static str = \"NumberSequenceKeypoint\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let number_sequence_keypoint_new =\n            |_: &Lua, (time, value, envelope): (f32, f32, Option<f32>)| {\n                Ok(NumberSequenceKeypoint {\n                    time,\n                    value,\n                    envelope: envelope.unwrap_or_default(),\n                })\n            };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", number_sequence_keypoint_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for NumberSequenceKeypoint {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Time\", |_, this| Ok(this.time));\n        fields.add_field_method_get(\"Value\", |_, this| Ok(this.value));\n        fields.add_field_method_get(\"Envelope\", |_, this| Ok(this.envelope));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for NumberSequenceKeypoint {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{} > {}\", self.time, self.value)\n    }\n}\n\nimpl From<DomNumberSequenceKeypoint> for NumberSequenceKeypoint {\n    fn from(v: DomNumberSequenceKeypoint) -> Self {\n        Self {\n            time: v.time,\n            value: v.value,\n            envelope: v.envelope,\n        }\n    }\n}\n\nimpl From<NumberSequenceKeypoint> for DomNumberSequenceKeypoint {\n    fn from(v: NumberSequenceKeypoint) -> Self {\n        Self {\n            time: v.time,\n            value: v.value,\n            envelope: v.envelope,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/physical_properties.rs",
    "content": "use core::fmt;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::CustomPhysicalProperties as DomCustomPhysicalProperties;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [PhysicalProperties](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `PhysicalProperties` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct PhysicalProperties {\n    pub(crate) density: f32,\n    pub(crate) friction: f32,\n    pub(crate) friction_weight: f32,\n    pub(crate) elasticity: f32,\n    pub(crate) elasticity_weight: f32,\n    pub(crate) acoustic_absorption: f32,\n}\n\nimpl PhysicalProperties {\n    pub(crate) fn from_material(material_enum_item: &EnumItem) -> Option<PhysicalProperties> {\n        MATERIAL_ENUM_MAP\n            .iter()\n            .find(|props| props.0 == material_enum_item.name)\n            .map(|props| PhysicalProperties {\n                density: props.1,\n                friction: props.2,\n                elasticity: props.3,\n                friction_weight: props.4,\n                elasticity_weight: props.5,\n                acoustic_absorption: props.6,\n            })\n    }\n}\n\nimpl LuaExportsTable for PhysicalProperties {\n    const EXPORT_NAME: &'static str = \"PhysicalProperties\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        type ArgsMaterial = LuaUserDataRef<EnumItem>;\n        type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>, Option<f32>);\n\n        let physical_properties_new = |lua: &Lua, args: LuaMultiValue| {\n            if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) {\n                if value.parent.desc.name == \"Material\" {\n                    match PhysicalProperties::from_material(&value) {\n                        Some(props) => Ok(props),\n                        None => Err(LuaError::RuntimeError(format!(\n                            \"Found unknown Material '{}'\",\n                            value.name\n                        ))),\n                    }\n                } else {\n                    Err(LuaError::RuntimeError(format!(\n                        \"Expected argument #1 to be a Material, got {}\",\n                        value.parent.desc.name\n                    )))\n                }\n            } else if let Ok((\n                density,\n                friction,\n                elasticity,\n                friction_weight,\n                elasticity_weight,\n                acoustic_absorption,\n            )) = ArgsNumbers::from_lua_multi(args, lua)\n            {\n                Ok(PhysicalProperties {\n                    density,\n                    friction,\n                    friction_weight: friction_weight.unwrap_or(1.0),\n                    elasticity,\n                    elasticity_weight: elasticity_weight.unwrap_or(1.0),\n                    acoustic_absorption: acoustic_absorption.unwrap_or(1.0),\n                })\n            } else {\n                // FUTURE: Better error message here using given arg types\n                Err(LuaError::RuntimeError(\n                    \"Invalid arguments to constructor\".to_string(),\n                ))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", physical_properties_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for PhysicalProperties {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Density\", |_, this| Ok(this.density));\n        fields.add_field_method_get(\"Friction\", |_, this| Ok(this.friction));\n        fields.add_field_method_get(\"FrictionWeight\", |_, this| Ok(this.friction_weight));\n        fields.add_field_method_get(\"Elasticity\", |_, this| Ok(this.elasticity));\n        fields.add_field_method_get(\"ElasticityWeight\", |_, this| Ok(this.elasticity_weight));\n        fields.add_field_method_get(\"AcousticAbsorption\", |_, this| Ok(this.acoustic_absorption));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for PhysicalProperties {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}, {}, {}, {}, {}\",\n            self.density,\n            self.friction,\n            self.elasticity,\n            self.friction_weight,\n            self.elasticity_weight\n        )\n    }\n}\n\nimpl From<DomCustomPhysicalProperties> for PhysicalProperties {\n    fn from(v: DomCustomPhysicalProperties) -> Self {\n        Self {\n            density: v.density(),\n            friction: v.friction(),\n            friction_weight: v.friction_weight(),\n            elasticity: v.elasticity(),\n            elasticity_weight: v.elasticity_weight(),\n            acoustic_absorption: v.acoustic_absorption(),\n        }\n    }\n}\n\nimpl From<PhysicalProperties> for DomCustomPhysicalProperties {\n    fn from(v: PhysicalProperties) -> Self {\n        DomCustomPhysicalProperties::new(\n            v.density,\n            v.friction,\n            v.elasticity,\n            v.friction_weight,\n            v.elasticity_weight,\n            v.acoustic_absorption,\n        )\n    }\n}\n\n/*\n\n    NOTE: The material definitions below are generated using the\n    physical_properties_enum_map script in the scripts dir next\n    to src, which can be ran in the Roblox Studio command bar\n\n*/\n#[rustfmt::skip]\nconst MATERIAL_ENUM_MAP: &[(&str, f32, f32, f32, f32, f32, f32)] = &[\n    (\"Plastic\",       0.70, 0.30, 0.50, 1.00, 1.00, 0.30),\n    (\"SmoothPlastic\", 0.70, 0.20, 0.50, 1.00, 1.00, 0.20),\n    (\"Neon\",          0.70, 0.30, 0.20, 1.00, 1.00, 0.30),\n    (\"Wood\",          0.35, 0.48, 0.20, 1.00, 1.00, 0.25),\n    (\"WoodPlanks\",    0.35, 0.48, 0.20, 1.00, 1.00, 0.25),\n    (\"Marble\",        2.56, 0.20, 0.17, 1.00, 1.00, 0.01),\n    (\"Slate\",         2.69, 0.40, 0.20, 1.00, 1.00, 0.03),\n    (\"Concrete\",      2.40, 0.70, 0.20, 0.30, 1.00, 0.05),\n    (\"Granite\",       2.69, 0.40, 0.20, 1.00, 1.00, 0.20),\n    (\"Brick\",         1.92, 0.80, 0.15, 0.30, 1.00, 0.35),\n    (\"Pebble\",        2.40, 0.40, 0.17, 1.00, 1.50, 0.40),\n    (\"Cobblestone\",   2.69, 0.50, 0.17, 1.00, 1.00, 0.45),\n    (\"Rock\",          2.69, 0.50, 0.17, 1.00, 1.00, 0.15),\n    (\"Sandstone\",     2.69, 0.50, 0.15, 5.00, 1.00, 0.20),\n    (\"Basalt\",        2.69, 0.70, 0.15, 0.30, 1.00, 0.20),\n    (\"CrackedLava\",   2.69, 0.65, 0.15, 1.00, 1.00, 0.35),\n    (\"Limestone\",     2.69, 0.50, 0.15, 1.00, 1.00, 0.15),\n    (\"Pavement\",      2.69, 0.50, 0.17, 0.30, 1.00, 0.60),\n    (\"CorrodedMetal\", 7.85, 0.70, 0.20, 1.00, 1.00, 0.15),\n    (\"DiamondPlate\",  7.85, 0.35, 0.25, 1.00, 1.00, 0.05),\n    (\"Foil\",          2.70, 0.40, 0.25, 1.00, 1.00, 0.10),\n    (\"Metal\",         7.85, 0.40, 0.25, 1.00, 1.00, 0.10),\n    (\"Grass\",         0.90, 0.40, 0.10, 1.00, 1.50, 0.70),\n    (\"LeafyGrass\",    0.90, 0.40, 0.10, 2.00, 2.00, 0.75),\n    (\"Sand\",          1.60, 0.50, 0.05, 5.00, 2.50, 0.55),\n    (\"Fabric\",        0.70, 0.35, 0.05, 1.00, 1.00, 0.65),\n    (\"Snow\",          0.90, 0.30, 0.03, 3.00, 4.00, 0.80),\n    (\"Mud\",           0.90, 0.30, 0.07, 3.00, 4.00, 0.65),\n    (\"Ground\",        0.90, 0.45, 0.10, 1.00, 1.00, 0.60),\n    (\"Asphalt\",       2.36, 0.80, 0.20, 0.30, 1.00, 0.40),\n    (\"Salt\",          2.16, 0.50, 0.05, 1.00, 1.00, 0.15),\n    (\"Ice\",           0.92, 0.02, 0.15, 3.00, 1.00, 0.20),\n    (\"Glacier\",       0.92, 0.05, 0.15, 2.00, 1.00, 0.70),\n    (\"Glass\",         2.40, 0.25, 0.20, 1.00, 1.00, 0.10),\n    (\"ForceField\",    2.40, 0.25, 0.20, 1.00, 1.00, 0.00),\n    (\"Air\",           0.01, 0.01, 0.01, 1.00, 1.00, 0.01),\n    (\"Water\",         1.00, 0.00, 0.01, 1.00, 1.00, 0.01),\n    (\"Cardboard\",     0.70, 0.50, 0.05, 1.00, 2.00, 0.55),\n    (\"Carpet\",        1.10, 0.40, 0.25, 1.00, 2.00, 0.65),\n    (\"CeramicTiles\",  2.40, 0.51, 0.20, 1.00, 1.00, 0.04),\n    (\"ClayRoofTiles\", 2.00, 0.51, 0.20, 1.00, 1.00, 0.30),\n    (\"RoofShingles\",  2.36, 0.80, 0.20, 0.30, 1.00, 0.30),\n    (\"Leather\",       0.86, 0.35, 0.25, 1.00, 1.00, 0.65),\n    (\"Plaster\",       0.75, 0.60, 0.20, 0.30, 1.00, 0.30),\n    (\"Rubber\",        1.30, 1.50, 0.95, 3.00, 2.00, 0.50),\n];\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/ray.rs",
    "content": "use core::fmt;\n\nuse glam::Vec3;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Ray as DomRay;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Vector3};\n\n/**\n    An implementation of the [Ray](https://create.roblox.com/docs/reference/engine/datatypes/Ray)\n    Roblox datatype, backed by [`glam::Vec3`].\n\n    This implements all documented properties, methods & constructors of the Ray class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Ray {\n    pub(crate) origin: Vec3,\n    pub(crate) direction: Vec3,\n}\n\nimpl Ray {\n    fn closest_point(&self, point: Vec3) -> Vec3 {\n        let norm = self.direction.normalize();\n        let lhs = point - self.origin;\n\n        let dot_product = lhs.dot(norm).max(0.0);\n        self.origin + norm * dot_product\n    }\n}\n\nimpl LuaExportsTable for Ray {\n    const EXPORT_NAME: &'static str = \"Ray\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let ray_new =\n            |_: &Lua, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {\n                Ok(Ray {\n                    origin: origin.0,\n                    direction: direction.0,\n                })\n            };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", ray_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Ray {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Origin\", |_, this| Ok(Vector3(this.origin)));\n        fields.add_field_method_get(\"Direction\", |_, this| Ok(Vector3(this.direction)));\n        fields.add_field_method_get(\"Unit\", |_, this| {\n            Ok(Ray {\n                origin: this.origin,\n                direction: this.direction.normalize(),\n            })\n        });\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"ClosestPoint\", |_, this, to: LuaUserDataRef<Vector3>| {\n            Ok(Vector3(this.closest_point(to.0)))\n        });\n        methods.add_method(\"Distance\", |_, this, to: LuaUserDataRef<Vector3>| {\n            let closest = this.closest_point(to.0);\n            Ok((closest - to.0).length())\n        });\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Ray {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", Vector3(self.origin), Vector3(self.direction))\n    }\n}\n\nimpl From<DomRay> for Ray {\n    fn from(v: DomRay) -> Self {\n        Ray {\n            origin: Vector3::from(v.origin).0,\n            direction: Vector3::from(v.direction).0,\n        }\n    }\n}\n\nimpl From<Ray> for DomRay {\n    fn from(v: Ray) -> Self {\n        DomRay {\n            origin: Vector3(v.origin).into(),\n            direction: Vector3(v.direction).into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/rect.rs",
    "content": "use core::fmt;\nuse std::ops;\n\nuse glam::Vec2;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Rect as DomRect;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Vector2};\n\n/**\n    An implementation of the [Rect](https://create.roblox.com/docs/reference/engine/datatypes/Rect)\n    Roblox datatype, backed by [`glam::Vec2`].\n\n    This implements all documented properties, methods & constructors of the Rect class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Rect {\n    pub(crate) min: Vec2,\n    pub(crate) max: Vec2,\n}\n\nimpl Rect {\n    fn new(lhs: Vec2, rhs: Vec2) -> Self {\n        Self {\n            min: lhs.min(rhs),\n            max: lhs.max(rhs),\n        }\n    }\n}\n\nimpl LuaExportsTable for Rect {\n    const EXPORT_NAME: &'static str = \"Rect\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        type ArgsVector2s = (\n            Option<LuaUserDataRef<Vector2>>,\n            Option<LuaUserDataRef<Vector2>>,\n        );\n        type ArgsNums = (Option<f32>, Option<f32>, Option<f32>, Option<f32>);\n\n        let rect_new = |lua: &Lua, args: LuaMultiValue| {\n            if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) {\n                Ok(Rect::new(\n                    min.map(|m| *m).unwrap_or_default().0,\n                    max.map(|m| *m).unwrap_or_default().0,\n                ))\n            } else if let Ok((x0, y0, x1, y1)) = ArgsNums::from_lua_multi(args, lua) {\n                let min = Vec2::new(x0.unwrap_or_default(), y0.unwrap_or_default());\n                let max = Vec2::new(x1.unwrap_or_default(), y1.unwrap_or_default());\n                Ok(Rect::new(min, max))\n            } else {\n                // FUTURE: Better error message here using given arg types\n                Err(LuaError::RuntimeError(\n                    \"Invalid arguments to constructor\".to_string(),\n                ))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", rect_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Rect {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Min\", |_, this| Ok(Vector2(this.min)));\n        fields.add_field_method_get(\"Max\", |_, this| Ok(Vector2(this.max)));\n        fields.add_field_method_get(\"Width\", |_, this| Ok(this.max.x - this.min.x));\n        fields.add_field_method_get(\"Height\", |_, this| Ok(this.max.y - this.min.y));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n    }\n}\n\nimpl fmt::Display for Rect {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.min, self.max)\n    }\n}\n\nimpl ops::Neg for Rect {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        Rect::new(-self.min, -self.max)\n    }\n}\n\nimpl ops::Add for Rect {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        Rect::new(self.min + rhs.min, self.max + rhs.max)\n    }\n}\n\nimpl ops::Sub for Rect {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Rect::new(self.min - rhs.min, self.max - rhs.max)\n    }\n}\n\nimpl From<DomRect> for Rect {\n    fn from(v: DomRect) -> Self {\n        Rect {\n            min: Vec2::new(v.min.x, v.min.y),\n            max: Vec2::new(v.max.x, v.max.y),\n        }\n    }\n}\n\nimpl From<Rect> for DomRect {\n    fn from(v: Rect) -> Self {\n        DomRect {\n            min: Vector2(v.min).into(),\n            max: Vector2(v.max).into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/region3.rs",
    "content": "use core::fmt;\n\nuse glam::{Mat4, Vec3};\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Region3 as DomRegion3;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, CFrame, Vector3};\n\n/**\n    An implementation of the [Region3](https://create.roblox.com/docs/reference/engine/datatypes/Region3)\n    Roblox datatype, backed by [`glam::Vec3`].\n\n    This implements all documented properties, methods & constructors of the Region3 class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Region3 {\n    pub(crate) min: Vec3,\n    pub(crate) max: Vec3,\n}\n\nimpl LuaExportsTable for Region3 {\n    const EXPORT_NAME: &'static str = \"Region3\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let region3_new =\n            |_: &Lua, (min, max): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {\n                Ok(Region3 {\n                    min: min.0,\n                    max: max.0,\n                })\n            };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", region3_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Region3 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"CFrame\", |_, this| {\n            Ok(CFrame(Mat4::from_translation(this.min.lerp(this.max, 0.5))))\n        });\n        fields.add_field_method_get(\"Size\", |_, this| Ok(Vector3(this.max - this.min)));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"ExpandToGrid\", |_, this, resolution: f32| {\n            Ok(Region3 {\n                min: (this.min / resolution).floor() * resolution,\n                max: (this.max / resolution).ceil() * resolution,\n            })\n        });\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Region3 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", Vector3(self.min), Vector3(self.max))\n    }\n}\n\nimpl From<DomRegion3> for Region3 {\n    fn from(v: DomRegion3) -> Self {\n        Region3 {\n            min: Vector3::from(v.min).0,\n            max: Vector3::from(v.max).0,\n        }\n    }\n}\n\nimpl From<Region3> for DomRegion3 {\n    fn from(v: Region3) -> Self {\n        DomRegion3 {\n            min: Vector3(v.min).into(),\n            max: Vector3(v.max).into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/region3int16.rs",
    "content": "use core::fmt;\n\nuse glam::IVec3;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Region3int16 as DomRegion3int16;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, Vector3int16};\n\n/**\n    An implementation of the [Region3int16](https://create.roblox.com/docs/reference/engine/datatypes/Region3int16)\n    Roblox datatype, backed by [`glam::IVec3`].\n\n    This implements all documented properties, methods & constructors of the Region3int16 class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Region3int16 {\n    pub(crate) min: IVec3,\n    pub(crate) max: IVec3,\n}\n\nimpl LuaExportsTable for Region3int16 {\n    const EXPORT_NAME: &'static str = \"Region3int16\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let region3int16_new =\n            |_: &Lua, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {\n                Ok(Region3int16 {\n                    min: min.0,\n                    max: max.0,\n                })\n            };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", region3int16_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Region3int16 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Min\", |_, this| Ok(Vector3int16(this.min)));\n        fields.add_field_method_get(\"Max\", |_, this| Ok(Vector3int16(this.max)));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for Region3int16 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", Vector3int16(self.min), Vector3int16(self.max))\n    }\n}\n\nimpl From<DomRegion3int16> for Region3int16 {\n    fn from(v: DomRegion3int16) -> Self {\n        Region3int16 {\n            min: Vector3int16::from(v.min).0,\n            max: Vector3int16::from(v.max).0,\n        }\n    }\n}\n\nimpl From<Region3int16> for DomRegion3int16 {\n    fn from(v: Region3int16) -> Self {\n        DomRegion3int16 {\n            min: Vector3int16(v.min).into(),\n            max: Vector3int16(v.max).into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/udim.rs",
    "content": "use core::fmt;\nuse std::ops;\n\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::UDim as DomUDim;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [UDim](https://create.roblox.com/docs/reference/engine/datatypes/UDim) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `UDim` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct UDim {\n    pub(crate) scale: f32,\n    pub(crate) offset: i32,\n}\n\nimpl UDim {\n    pub(super) fn new(scale: f32, offset: i32) -> Self {\n        Self { scale, offset }\n    }\n}\n\nimpl LuaExportsTable for UDim {\n    const EXPORT_NAME: &'static str = \"UDim\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let udim_new = |_: &Lua, (scale, offset): (Option<f32>, Option<i32>)| {\n            Ok(UDim {\n                scale: scale.unwrap_or_default(),\n                offset: offset.unwrap_or_default(),\n            })\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", udim_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for UDim {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Scale\", |_, this| Ok(this.scale));\n        fields.add_field_method_get(\"Offset\", |_, this| Ok(this.offset));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n    }\n}\n\nimpl Default for UDim {\n    fn default() -> Self {\n        Self {\n            scale: 0f32,\n            offset: 0,\n        }\n    }\n}\n\nimpl fmt::Display for UDim {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.scale, self.offset)\n    }\n}\n\nimpl ops::Neg for UDim {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        UDim {\n            scale: -self.scale,\n            offset: -self.offset,\n        }\n    }\n}\n\nimpl ops::Add for UDim {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        UDim {\n            scale: self.scale + rhs.scale,\n            offset: self.offset + rhs.offset,\n        }\n    }\n}\n\nimpl ops::Sub for UDim {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        UDim {\n            scale: self.scale - rhs.scale,\n            offset: self.offset - rhs.offset,\n        }\n    }\n}\n\nimpl From<DomUDim> for UDim {\n    fn from(v: DomUDim) -> Self {\n        UDim {\n            scale: v.scale,\n            offset: v.offset,\n        }\n    }\n}\n\nimpl From<UDim> for DomUDim {\n    fn from(v: UDim) -> Self {\n        DomUDim {\n            scale: v.scale,\n            offset: v.offset,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/udim2.rs",
    "content": "#![allow(clippy::items_after_statements)]\n\nuse core::fmt;\nuse std::ops;\n\nuse glam::Vec2;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::UDim2 as DomUDim2;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::{super::*, UDim};\n\n/**\n    An implementation of the [UDim2](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) Roblox datatype.\n\n    This implements all documented properties, methods & constructors of the `UDim2` class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct UDim2 {\n    pub(crate) x: UDim,\n    pub(crate) y: UDim,\n}\n\nimpl LuaExportsTable for UDim2 {\n    const EXPORT_NAME: &'static str = \"UDim2\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let udim2_from_offset = |_: &Lua, (x, y): (Option<i32>, Option<i32>)| {\n            Ok(UDim2 {\n                x: UDim::new(0f32, x.unwrap_or_default()),\n                y: UDim::new(0f32, y.unwrap_or_default()),\n            })\n        };\n\n        let udim2_from_scale = |_: &Lua, (x, y): (Option<f32>, Option<f32>)| {\n            Ok(UDim2 {\n                x: UDim::new(x.unwrap_or_default(), 0),\n                y: UDim::new(y.unwrap_or_default(), 0),\n            })\n        };\n\n        type ArgsUDims = (Option<LuaUserDataRef<UDim>>, Option<LuaUserDataRef<UDim>>);\n        type ArgsNums = (Option<f32>, Option<i32>, Option<f32>, Option<i32>);\n        let udim2_new = |lua: &Lua, args: LuaMultiValue| {\n            if let Ok((x, y)) = ArgsUDims::from_lua_multi(args.clone(), lua) {\n                Ok(UDim2 {\n                    x: x.map(|x| *x).unwrap_or_default(),\n                    y: y.map(|y| *y).unwrap_or_default(),\n                })\n            } else if let Ok((sx, ox, sy, oy)) = ArgsNums::from_lua_multi(args, lua) {\n                Ok(UDim2 {\n                    x: UDim::new(sx.unwrap_or_default(), ox.unwrap_or_default()),\n                    y: UDim::new(sy.unwrap_or_default(), oy.unwrap_or_default()),\n                })\n            } else {\n                // FUTURE: Better error message here using given arg types\n                Err(LuaError::RuntimeError(\n                    \"Invalid arguments to constructor\".to_string(),\n                ))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"fromOffset\", udim2_from_offset)?\n            .with_function(\"fromScale\", udim2_from_scale)?\n            .with_function(\"new\", udim2_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for UDim2 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.y));\n        fields.add_field_method_get(\"Width\", |_, this| Ok(this.x));\n        fields.add_field_method_get(\"Height\", |_, this| Ok(this.y));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\n            \"Lerp\",\n            |_, this, (goal, alpha): (LuaUserDataRef<UDim2>, f32)| {\n                let this_x = Vec2::new(this.x.scale, this.x.offset as f32);\n                let goal_x = Vec2::new(goal.x.scale, goal.x.offset as f32);\n\n                let this_y = Vec2::new(this.y.scale, this.y.offset as f32);\n                let goal_y = Vec2::new(goal.y.scale, goal.y.offset as f32);\n\n                let x = this_x.lerp(goal_x, alpha);\n                let y = this_y.lerp(goal_y, alpha);\n\n                Ok(UDim2 {\n                    x: UDim {\n                        scale: x.x,\n                        offset: x.y.clamp(i32::MIN as f32, i32::MAX as f32).round() as i32,\n                    },\n                    y: UDim {\n                        scale: y.x,\n                        offset: y.y.clamp(i32::MIN as f32, i32::MAX as f32).round() as i32,\n                    },\n                })\n            },\n        );\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n    }\n}\n\nimpl fmt::Display for UDim2 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.x, self.y)\n    }\n}\n\nimpl ops::Neg for UDim2 {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        UDim2 {\n            x: -self.x,\n            y: -self.y,\n        }\n    }\n}\n\nimpl ops::Add for UDim2 {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        UDim2 {\n            x: self.x + rhs.x,\n            y: self.y + rhs.y,\n        }\n    }\n}\n\nimpl ops::Sub for UDim2 {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        UDim2 {\n            x: self.x - rhs.x,\n            y: self.y - rhs.y,\n        }\n    }\n}\n\nimpl From<DomUDim2> for UDim2 {\n    fn from(v: DomUDim2) -> Self {\n        UDim2 {\n            x: v.x.into(),\n            y: v.y.into(),\n        }\n    }\n}\n\nimpl From<UDim2> for DomUDim2 {\n    fn from(v: UDim2) -> Self {\n        DomUDim2 {\n            x: v.x.into(),\n            y: v.y.into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/unique_id.rs",
    "content": "use std::fmt;\n\nuse lune_utils::TableBuilder;\nuse mlua::BString;\nuse mlua::prelude::*;\n\nuse super::super::*;\nuse crate::exports::LuaExportsTable;\n\nuse rbx_dom_weak::types::UniqueId as DomUniqueId;\n\n/**\n    An implementation of the `UniqueId` Roblox datatype.\n\n    This type is not exposed to users in engine by Roblox itself,\n    but is used as an identifier for Instances, and is occasionally\n    useful when manipulating place and model files in Lune.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Default)]\npub struct UniqueId {\n    id: u128,\n}\n\nimpl LuaExportsTable for UniqueId {\n    const EXPORT_NAME: &'static str = \"UniqueId\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let from_string = |_: &Lua, input: BString| {\n            if input.len() == 16 {\n                let mut bytes = [0; 16];\n                bytes.copy_from_slice(&input);\n                Ok(UniqueId {\n                    id: u128::from_be_bytes(bytes),\n                })\n            } else {\n                Err(LuaError::RuntimeError(format!(\n                    \"UniqueId.fromString expects 16 bytes but {} bytes were provided\",\n                    input.len()\n                )))\n            }\n        };\n\n        let new = |_: &Lua, ()| match DomUniqueId::now() {\n            Ok(uid) => Ok(UniqueId::from(uid)),\n            Err(err) => Err(LuaError::RuntimeError(format!(\n                \"UniqueId.new encountered an error: {err}\"\n            ))),\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"fromString\", from_string)?\n            .with_function(\"new\", new)?\n            .with_value(\"null\", UniqueId { id: 0 })?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for UniqueId {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl fmt::Display for UniqueId {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        for byte in self.id.to_be_bytes() {\n            write!(f, \"{byte:02x}\")?;\n        }\n        Ok(())\n    }\n}\n\nimpl From<DomUniqueId> for UniqueId {\n    fn from(value: DomUniqueId) -> Self {\n        let mut bytes = [0; 16];\n        bytes[0..8].copy_from_slice(&value.random().to_be_bytes());\n        bytes[8..12].copy_from_slice(&value.time().to_be_bytes());\n        bytes[12..16].copy_from_slice(&value.index().to_be_bytes());\n        Self {\n            id: u128::from_be_bytes(bytes),\n        }\n    }\n}\n\nimpl From<UniqueId> for DomUniqueId {\n    fn from(value: UniqueId) -> Self {\n        let bytes = value.id.to_be_bytes();\n        DomUniqueId::new(\n            u32::from_be_bytes(bytes[12..16].try_into().unwrap()),\n            u32::from_be_bytes(bytes[8..12].try_into().unwrap()),\n            i64::from_be_bytes(bytes[0..8].try_into().unwrap()),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/vector2.rs",
    "content": "use core::fmt;\nuse std::ops;\n\nuse glam::{Vec2, Vec3};\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Vector2 as DomVector2;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [Vector2](https://create.roblox.com/docs/reference/engine/datatypes/Vector2)\n    Roblox datatype, backed by [`glam::Vec2`].\n\n    This implements all documented properties, methods &\n    constructors of the Vector2 class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Default)]\npub struct Vector2(pub Vec2);\n\nimpl LuaExportsTable for Vector2 {\n    const EXPORT_NAME: &'static str = \"Vector2\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let vector2_new = |_: &Lua, (x, y): (Option<f32>, Option<f32>)| {\n            Ok(Vector2(Vec2 {\n                x: x.unwrap_or_default(),\n                y: y.unwrap_or_default(),\n            }))\n        };\n\n        TableBuilder::new(lua)?\n            .with_value(\"xAxis\", Vector2(Vec2::X))?\n            .with_value(\"yAxis\", Vector2(Vec2::Y))?\n            .with_value(\"zero\", Vector2(Vec2::ZERO))?\n            .with_value(\"one\", Vector2(Vec2::ONE))?\n            .with_function(\"new\", vector2_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Vector2 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Magnitude\", |_, this| Ok(this.0.length()));\n        fields.add_field_method_get(\"Unit\", |_, this| Ok(Vector2(this.0.normalize())));\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.0.x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.0.y));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"Angle\", |_, this, rhs: LuaUserDataRef<Vector2>| {\n            Ok(this.0.angle_to(rhs.0))\n        });\n        methods.add_method(\"Cross\", |_, this, rhs: LuaUserDataRef<Vector2>| {\n            let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);\n            let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);\n            Ok(this_v3.cross(rhs_v3).z)\n        });\n        methods.add_method(\"Dot\", |_, this, rhs: LuaUserDataRef<Vector2>| {\n            Ok(this.0.dot(rhs.0))\n        });\n        methods.add_method(\n            \"FuzzyEq\",\n            |_, this, (rhs, epsilon): (LuaUserDataRef<Vector2>, f32)| {\n                let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;\n                let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;\n                Ok(eq_x && eq_y)\n            },\n        );\n        methods.add_method(\n            \"Lerp\",\n            |_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {\n                Ok(Vector2(this.0.lerp(rhs.0, alpha)))\n            },\n        );\n        methods.add_method(\"Max\", |_, this, rhs: LuaUserDataRef<Vector2>| {\n            Ok(Vector2(this.0.max(rhs.0)))\n        });\n        methods.add_method(\"Min\", |_, this, rhs: LuaUserDataRef<Vector2>| {\n            Ok(Vector2(this.0.min(rhs.0)))\n        });\n        methods.add_method(\"Abs\", |_, this, ()| Ok(Vector2(this.0.abs())));\n        methods.add_method(\"Ceil\", |_, this, ()| Ok(Vector2(this.0.ceil())));\n        methods.add_method(\"Floor\", |_, this, ()| Ok(Vector2(this.0.floor())));\n        methods.add_method(\"Sign\", |_, this, ()| Ok(Vector2(this.0.signum())));\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n        methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);\n        methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);\n        methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);\n    }\n}\n\nimpl fmt::Display for Vector2 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.0.x, self.0.y)\n    }\n}\n\nimpl ops::Neg for Vector2 {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        Vector2(-self.0)\n    }\n}\n\nimpl ops::Add for Vector2 {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        Vector2(self.0 + rhs.0)\n    }\n}\n\nimpl ops::Sub for Vector2 {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Vector2(self.0 - rhs.0)\n    }\n}\n\nimpl ops::Mul for Vector2 {\n    type Output = Vector2;\n    fn mul(self, rhs: Self) -> Self::Output {\n        Self(self.0 * rhs.0)\n    }\n}\n\nimpl ops::Mul<f32> for Vector2 {\n    type Output = Vector2;\n    fn mul(self, rhs: f32) -> Self::Output {\n        Self(self.0 * rhs)\n    }\n}\n\nimpl ops::Div for Vector2 {\n    type Output = Vector2;\n    fn div(self, rhs: Self) -> Self::Output {\n        Self(self.0 / rhs.0)\n    }\n}\n\nimpl ops::Div<f32> for Vector2 {\n    type Output = Vector2;\n    fn div(self, rhs: f32) -> Self::Output {\n        Self(self.0 / rhs)\n    }\n}\n\nimpl IDiv for Vector2 {\n    type Output = Vector2;\n    fn idiv(self, rhs: Self) -> Self::Output {\n        Self((self.0 / rhs.0).floor())\n    }\n}\n\nimpl IDiv<f32> for Vector2 {\n    type Output = Vector2;\n    fn idiv(self, rhs: f32) -> Self::Output {\n        Self((self.0 / rhs).floor())\n    }\n}\n\nimpl From<DomVector2> for Vector2 {\n    fn from(v: DomVector2) -> Self {\n        Vector2(Vec2 { x: v.x, y: v.y })\n    }\n}\n\nimpl From<Vector2> for DomVector2 {\n    fn from(v: Vector2) -> Self {\n        DomVector2 { x: v.0.x, y: v.0.y }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/vector2int16.rs",
    "content": "use core::fmt;\nuse std::ops;\n\nuse glam::IVec2;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Vector2int16 as DomVector2int16;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [Vector2int16](https://create.roblox.com/docs/reference/engine/datatypes/Vector2int16)\n    Roblox datatype, backed by [`glam::IVec2`].\n\n    This implements all documented properties, methods &\n    constructors of the Vector2int16 class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Vector2int16(pub IVec2);\n\nimpl LuaExportsTable for Vector2int16 {\n    const EXPORT_NAME: &'static str = \"Vector2int16\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let vector2int16_new = |_: &Lua, (x, y): (Option<i16>, Option<i16>)| {\n            Ok(Vector2int16(IVec2 {\n                x: x.unwrap_or_default() as i32,\n                y: y.unwrap_or_default() as i32,\n            }))\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", vector2int16_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Vector2int16 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.0.x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.0.y));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n        methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_i32);\n        methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_i32);\n    }\n}\n\nimpl fmt::Display for Vector2int16 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.0.x, self.0.y)\n    }\n}\n\nimpl ops::Neg for Vector2int16 {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        Vector2int16(-self.0)\n    }\n}\n\nimpl ops::Add for Vector2int16 {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        Vector2int16(self.0 + rhs.0)\n    }\n}\n\nimpl ops::Sub for Vector2int16 {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Vector2int16(self.0 - rhs.0)\n    }\n}\n\nimpl ops::Mul for Vector2int16 {\n    type Output = Vector2int16;\n    fn mul(self, rhs: Self) -> Self::Output {\n        Self(self.0 * rhs.0)\n    }\n}\n\nimpl ops::Mul<i32> for Vector2int16 {\n    type Output = Vector2int16;\n    fn mul(self, rhs: i32) -> Self::Output {\n        Self(self.0 * rhs)\n    }\n}\n\nimpl ops::Div for Vector2int16 {\n    type Output = Vector2int16;\n    fn div(self, rhs: Self) -> Self::Output {\n        Self(self.0 / rhs.0)\n    }\n}\n\nimpl ops::Div<i32> for Vector2int16 {\n    type Output = Vector2int16;\n    fn div(self, rhs: i32) -> Self::Output {\n        Self(self.0 / rhs)\n    }\n}\n\nimpl From<DomVector2int16> for Vector2int16 {\n    fn from(v: DomVector2int16) -> Self {\n        Vector2int16(IVec2 {\n            x: v.x.clamp(i16::MIN, i16::MAX) as i32,\n            y: v.y.clamp(i16::MIN, i16::MAX) as i32,\n        })\n    }\n}\n\nimpl From<Vector2int16> for DomVector2int16 {\n    fn from(v: Vector2int16) -> Self {\n        DomVector2int16 {\n            x: v.0.x.clamp(i16::MIN as i32, i16::MAX as i32) as i16,\n            y: v.0.y.clamp(i16::MIN as i32, i16::MAX as i32) as i16,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/vector3.rs",
    "content": "use core::fmt;\nuse std::ops;\n\nuse glam::Vec3;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Vector3 as DomVector3;\n\nuse lune_utils::TableBuilder;\n\nuse crate::{datatypes::util::round_float_decimal, exports::LuaExportsTable};\n\nuse super::{super::*, EnumItem};\n\n/**\n    An implementation of the [Vector3](https://create.roblox.com/docs/reference/engine/datatypes/Vector3)\n    Roblox datatype, backed by [`glam::Vec3`].\n\n    This implements all documented properties, methods &\n    constructors of the Vector3 class as of October 2025.\n\n    Note that this does not use native Luau vectors to simplify implementation\n    and instead allow us to implement all abovementioned APIs accurately.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Vector3(pub Vec3);\n\nimpl LuaExportsTable for Vector3 {\n    const EXPORT_NAME: &'static str = \"Vector3\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let vector3_from_axis = |_: &Lua, normal_id: LuaUserDataRef<EnumItem>| {\n            if normal_id.parent.desc.name == \"Axis\" {\n                Ok(match normal_id.name.as_str() {\n                    \"X\" => Vector3(Vec3::X),\n                    \"Y\" => Vector3(Vec3::Y),\n                    \"Z\" => Vector3(Vec3::Z),\n                    name => {\n                        return Err(LuaError::RuntimeError(format!(\n                            \"Axis '{name}' is not known\",\n                        )));\n                    }\n                })\n            } else {\n                Err(LuaError::RuntimeError(format!(\n                    \"EnumItem must be a Axis, got {}\",\n                    normal_id.parent.desc.name\n                )))\n            }\n        };\n\n        let vector3_from_normal_id = |_: &Lua, normal_id: LuaUserDataRef<EnumItem>| {\n            if normal_id.parent.desc.name == \"NormalId\" {\n                Ok(match normal_id.name.as_str() {\n                    \"Left\" => Vector3(Vec3::X),\n                    \"Top\" => Vector3(Vec3::Y),\n                    \"Front\" => Vector3(-Vec3::Z),\n                    \"Right\" => Vector3(-Vec3::X),\n                    \"Bottom\" => Vector3(-Vec3::Y),\n                    \"Back\" => Vector3(Vec3::Z),\n                    name => {\n                        return Err(LuaError::RuntimeError(format!(\n                            \"NormalId '{name}' is not known\",\n                        )));\n                    }\n                })\n            } else {\n                Err(LuaError::RuntimeError(format!(\n                    \"EnumItem must be a NormalId, got {}\",\n                    normal_id.parent.desc.name\n                )))\n            }\n        };\n\n        let vector3_new = |_: &Lua, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {\n            Ok(Vector3(Vec3 {\n                x: x.unwrap_or_default(),\n                y: y.unwrap_or_default(),\n                z: z.unwrap_or_default(),\n            }))\n        };\n\n        TableBuilder::new(lua)?\n            .with_value(\"xAxis\", Vector3(Vec3::X))?\n            .with_value(\"yAxis\", Vector3(Vec3::Y))?\n            .with_value(\"zAxis\", Vector3(Vec3::Z))?\n            .with_value(\"zero\", Vector3(Vec3::ZERO))?\n            .with_value(\"one\", Vector3(Vec3::ONE))?\n            .with_function(\"fromAxis\", vector3_from_axis)?\n            .with_function(\"fromNormalId\", vector3_from_normal_id)?\n            .with_function(\"new\", vector3_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Vector3 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Magnitude\", |_, this| Ok(this.0.length()));\n        fields.add_field_method_get(\"Unit\", |_, this| Ok(Vector3(this.0.normalize())));\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.0.x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.0.y));\n        fields.add_field_method_get(\"Z\", |_, this| Ok(this.0.z));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Methods\n        methods.add_method(\"Angle\", |_, this, rhs: LuaUserDataRef<Vector3>| {\n            Ok(this.0.angle_between(rhs.0))\n        });\n        methods.add_method(\"Cross\", |_, this, rhs: LuaUserDataRef<Vector3>| {\n            Ok(Vector3(this.0.cross(rhs.0)))\n        });\n        methods.add_method(\"Dot\", |_, this, rhs: LuaUserDataRef<Vector3>| {\n            Ok(this.0.dot(rhs.0))\n        });\n        methods.add_method(\n            \"FuzzyEq\",\n            |_, this, (rhs, epsilon): (LuaUserDataRef<Vector3>, f32)| {\n                let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;\n                let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;\n                let eq_z = (rhs.0.z - this.0.z).abs() <= epsilon;\n                Ok(eq_x && eq_y && eq_z)\n            },\n        );\n        methods.add_method(\n            \"Lerp\",\n            |_, this, (rhs, alpha): (LuaUserDataRef<Vector3>, f32)| {\n                Ok(Vector3(this.0.lerp(rhs.0, alpha)))\n            },\n        );\n        methods.add_method(\"Max\", |_, this, rhs: LuaUserDataRef<Vector3>| {\n            Ok(Vector3(this.0.max(rhs.0)))\n        });\n        methods.add_method(\"Min\", |_, this, rhs: LuaUserDataRef<Vector3>| {\n            Ok(Vector3(this.0.min(rhs.0)))\n        });\n        methods.add_method(\"Abs\", |_, this, ()| Ok(Vector3(this.0.abs())));\n        methods.add_method(\"Ceil\", |_, this, ()| Ok(Vector3(this.0.ceil())));\n        methods.add_method(\"Floor\", |_, this, ()| Ok(Vector3(this.0.floor())));\n        methods.add_method(\"Sign\", |_, this, ()| Ok(Vector3(this.0.signum())));\n        // Metamethods\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n        methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);\n        methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);\n        methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);\n    }\n}\n\nimpl fmt::Display for Vector3 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}, {}\", self.0.x, self.0.y, self.0.z)\n    }\n}\n\nimpl ops::Neg for Vector3 {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        Vector3(-self.0)\n    }\n}\n\nimpl ops::Add for Vector3 {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        Vector3(self.0 + rhs.0)\n    }\n}\n\nimpl ops::Sub for Vector3 {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Vector3(self.0 - rhs.0)\n    }\n}\n\nimpl ops::Mul for Vector3 {\n    type Output = Vector3;\n    fn mul(self, rhs: Self) -> Self::Output {\n        Self(self.0 * rhs.0)\n    }\n}\n\nimpl ops::Mul<f32> for Vector3 {\n    type Output = Vector3;\n    fn mul(self, rhs: f32) -> Self::Output {\n        Self(self.0 * rhs)\n    }\n}\n\nimpl ops::Div for Vector3 {\n    type Output = Vector3;\n    fn div(self, rhs: Self) -> Self::Output {\n        Self(self.0 / rhs.0)\n    }\n}\n\nimpl ops::Div<f32> for Vector3 {\n    type Output = Vector3;\n    fn div(self, rhs: f32) -> Self::Output {\n        Self(self.0 / rhs)\n    }\n}\n\nimpl IDiv for Vector3 {\n    type Output = Vector3;\n    fn idiv(self, rhs: Self) -> Self::Output {\n        Self((self.0 / rhs.0).floor())\n    }\n}\n\nimpl IDiv<f32> for Vector3 {\n    type Output = Vector3;\n    fn idiv(self, rhs: f32) -> Self::Output {\n        Self((self.0 / rhs).floor())\n    }\n}\n\nimpl From<DomVector3> for Vector3 {\n    fn from(v: DomVector3) -> Self {\n        Vector3(Vec3 {\n            x: v.x,\n            y: v.y,\n            z: v.z,\n        })\n    }\n}\n\nimpl From<Vector3> for DomVector3 {\n    fn from(v: Vector3) -> Self {\n        DomVector3 {\n            x: round_float_decimal(v.0.x),\n            y: round_float_decimal(v.0.y),\n            z: round_float_decimal(v.0.z),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/types/vector3int16.rs",
    "content": "use core::fmt;\nuse std::ops;\n\nuse glam::IVec3;\nuse mlua::prelude::*;\nuse rbx_dom_weak::types::Vector3int16 as DomVector3int16;\n\nuse lune_utils::TableBuilder;\n\nuse crate::exports::LuaExportsTable;\n\nuse super::super::*;\n\n/**\n    An implementation of the [Vector3int16](https://create.roblox.com/docs/reference/engine/datatypes/Vector3int16)\n    Roblox datatype, backed by [`glam::IVec3`].\n\n    This implements all documented properties, methods &\n    constructors of the Vector3int16 class as of October 2025.\n*/\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Vector3int16(pub IVec3);\n\nimpl LuaExportsTable for Vector3int16 {\n    const EXPORT_NAME: &'static str = \"Vector3int16\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let vector3int16_new = |_: &Lua, (x, y, z): (Option<i16>, Option<i16>, Option<i16>)| {\n            Ok(Vector3int16(IVec3 {\n                x: x.unwrap_or_default() as i32,\n                y: y.unwrap_or_default() as i32,\n                z: z.unwrap_or_default() as i32,\n            }))\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", vector3int16_new)?\n            .build_readonly()\n    }\n}\n\nimpl LuaUserData for Vector3int16 {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"X\", |_, this| Ok(this.0.x));\n        fields.add_field_method_get(\"Y\", |_, this| Ok(this.0.y));\n        fields.add_field_method_get(\"Z\", |_, this| Ok(this.0.z));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);\n        methods.add_meta_method(LuaMetaMethod::Add, userdata_impl_add);\n        methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);\n        methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_i32);\n        methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_i32);\n    }\n}\n\nimpl fmt::Display for Vector3int16 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}, {}\", self.0.x, self.0.y)\n    }\n}\n\nimpl ops::Neg for Vector3int16 {\n    type Output = Self;\n    fn neg(self) -> Self::Output {\n        Vector3int16(-self.0)\n    }\n}\n\nimpl ops::Add for Vector3int16 {\n    type Output = Self;\n    fn add(self, rhs: Self) -> Self::Output {\n        Vector3int16(self.0 + rhs.0)\n    }\n}\n\nimpl ops::Sub for Vector3int16 {\n    type Output = Self;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Vector3int16(self.0 - rhs.0)\n    }\n}\n\nimpl ops::Mul for Vector3int16 {\n    type Output = Vector3int16;\n    fn mul(self, rhs: Self) -> Self::Output {\n        Self(self.0 * rhs.0)\n    }\n}\n\nimpl ops::Mul<i32> for Vector3int16 {\n    type Output = Vector3int16;\n    fn mul(self, rhs: i32) -> Self::Output {\n        Self(self.0 * rhs)\n    }\n}\n\nimpl ops::Div for Vector3int16 {\n    type Output = Vector3int16;\n    fn div(self, rhs: Self) -> Self::Output {\n        Self(self.0 / rhs.0)\n    }\n}\n\nimpl ops::Div<i32> for Vector3int16 {\n    type Output = Vector3int16;\n    fn div(self, rhs: i32) -> Self::Output {\n        Self(self.0 / rhs)\n    }\n}\n\nimpl From<DomVector3int16> for Vector3int16 {\n    fn from(v: DomVector3int16) -> Self {\n        Vector3int16(IVec3 {\n            x: v.x.clamp(i16::MIN, i16::MAX) as i32,\n            y: v.y.clamp(i16::MIN, i16::MAX) as i32,\n            z: v.z.clamp(i16::MIN, i16::MAX) as i32,\n        })\n    }\n}\n\nimpl From<Vector3int16> for DomVector3int16 {\n    fn from(v: Vector3int16) -> Self {\n        DomVector3int16 {\n            x: v.0.x.clamp(i16::MIN as i32, i16::MAX as i32) as i16,\n            y: v.0.y.clamp(i16::MIN as i32, i16::MAX as i32) as i16,\n            z: v.0.z.clamp(i16::MIN as i32, i16::MAX as i32) as i16,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/datatypes/util.rs",
    "content": "// HACK: We round to the nearest Very Small Decimal\n// to reduce writing out floating point accumulation\n// errors to files (mostly relevant for xml formats)\nconst ROUNDING: usize = 65_536; // 2 ^ 16\n\npub fn round_float_decimal(value: f32) -> f32 {\n    let place = ROUNDING as f32;\n\n    // Round only the fractional part, we do not want to\n    // lose any float precision in case a user for some\n    // reason has very very large float numbers in files\n    let whole = value.trunc();\n    let fract = (value.fract() * place).round() / place;\n\n    whole + fract\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/document/error.rs",
    "content": "use thiserror::Error;\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\n#[derive(Debug, Clone, Error)]\npub enum DocumentError {\n    #[error(\"Unknown document kind\")]\n    UnknownKind,\n    #[error(\"Unknown document format\")]\n    UnknownFormat,\n    #[error(\"Failed to read document from buffer - {0}\")]\n    ReadError(String),\n    #[error(\"Failed to write document to buffer - {0}\")]\n    WriteError(String),\n    #[error(\"Failed to convert into a DataModel - the given document is not a place\")]\n    IntoDataModelInvalidArgs,\n    #[error(\"Failed to convert into array of Instances - the given document is a model\")]\n    IntoInstanceArrayInvalidArgs,\n    #[error(\"Failed to convert into a place - the given instance is not a DataModel\")]\n    FromDataModelInvalidArgs,\n    #[error(\"Failed to convert into a model - a given instance is a DataModel\")]\n    FromInstanceArrayInvalidArgs,\n}\n\n#[cfg(feature = \"mlua\")]\nimpl From<DocumentError> for LuaError {\n    fn from(value: DocumentError) -> Self {\n        Self::RuntimeError(value.to_string())\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/document/format.rs",
    "content": "// Original implementation from Remodel:\n// https://github.com/rojo-rbx/remodel/blob/master/src/sniff_type.rs\n\nuse std::path::Path;\n\n/**\n    A document format specifier.\n\n    Valid variants are the following:\n\n    - `Binary`\n    - `Xml`\n\n    Other variants are only to be used for logic internal to this crate.\n*/\n#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]\npub enum DocumentFormat {\n    Binary,\n    Xml,\n}\n\nimpl DocumentFormat {\n    /**\n        Try to convert a file extension into a valid document format specifier.\n\n        Returns `None` if the file extension is not a canonical roblox file format extension.\n    */\n    pub fn from_extension(extension: impl AsRef<str>) -> Option<Self> {\n        match extension.as_ref() {\n            \"rbxl\" | \"rbxm\" => Some(Self::Binary),\n            \"rbxlx\" | \"rbxmx\" => Some(Self::Xml),\n            _ => None,\n        }\n    }\n\n    /**\n        Try to convert a file path into a valid document format specifier.\n\n        Returns `None` if the file extension of the path\n        is not a canonical roblox file format extension.\n    */\n    pub fn from_path(path: impl AsRef<Path>) -> Option<Self> {\n        match path\n            .as_ref()\n            .extension()\n            .map(|ext| ext.to_string_lossy())\n            .as_deref()\n        {\n            Some(\"rbxl\") | Some(\"rbxm\") => Some(Self::Binary),\n            Some(\"rbxlx\") | Some(\"rbxmx\") => Some(Self::Xml),\n            _ => None,\n        }\n    }\n\n    /**\n        Try to detect a document format specifier from file contents.\n\n        Returns `None` if the file contents do not seem to be from a valid roblox file.\n    */\n    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Option<Self> {\n        let header = bytes.as_ref().get(0..8)?;\n\n        if header.starts_with(b\"<roblox\") {\n            match header[7] {\n                b'!' => Some(Self::Binary),\n                b' ' | b'>' => Some(Self::Xml),\n                _ => None,\n            }\n        } else {\n            None\n        }\n    }\n}\n\nimpl Default for DocumentFormat {\n    fn default() -> Self {\n        Self::Binary\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::PathBuf;\n\n    use super::*;\n\n    #[test]\n    fn from_extension_binary() {\n        assert_eq!(\n            DocumentFormat::from_extension(\"rbxl\"),\n            Some(DocumentFormat::Binary)\n        );\n\n        assert_eq!(\n            DocumentFormat::from_extension(\"rbxm\"),\n            Some(DocumentFormat::Binary)\n        );\n    }\n\n    #[test]\n    fn from_extension_xml() {\n        assert_eq!(\n            DocumentFormat::from_extension(\"rbxlx\"),\n            Some(DocumentFormat::Xml)\n        );\n\n        assert_eq!(\n            DocumentFormat::from_extension(\"rbxmx\"),\n            Some(DocumentFormat::Xml)\n        );\n    }\n\n    #[test]\n    fn from_extension_invalid() {\n        assert_eq!(DocumentFormat::from_extension(\"csv\"), None);\n        assert_eq!(DocumentFormat::from_extension(\"json\"), None);\n        assert_eq!(DocumentFormat::from_extension(\"rbx\"), None);\n        assert_eq!(DocumentFormat::from_extension(\"rbxn\"), None);\n        assert_eq!(DocumentFormat::from_extension(\"xlx\"), None);\n        assert_eq!(DocumentFormat::from_extension(\"xmx\"), None);\n    }\n\n    #[test]\n    fn from_path_binary() {\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"model.rbxl\")),\n            Some(DocumentFormat::Binary)\n        );\n\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"model.rbxm\")),\n            Some(DocumentFormat::Binary)\n        );\n    }\n\n    #[test]\n    fn from_path_xml() {\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"place.rbxlx\")),\n            Some(DocumentFormat::Xml)\n        );\n\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"place.rbxmx\")),\n            Some(DocumentFormat::Xml)\n        );\n    }\n\n    #[test]\n    fn from_path_invalid() {\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"data-file.csv\")),\n            None\n        );\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"nested/path/file.json\")),\n            None\n        );\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\".no-name-strange-rbx\")),\n            None\n        );\n        assert_eq!(\n            DocumentFormat::from_path(PathBuf::from(\"file_without_extension\")),\n            None\n        );\n    }\n\n    #[test]\n    fn from_bytes_binary() {\n        assert_eq!(\n            DocumentFormat::from_bytes(b\"<roblox!hello\"),\n            Some(DocumentFormat::Binary)\n        );\n\n        assert_eq!(\n            DocumentFormat::from_bytes(b\"<roblox!\"),\n            Some(DocumentFormat::Binary)\n        );\n    }\n\n    #[test]\n    fn from_bytes_xml() {\n        assert_eq!(\n            DocumentFormat::from_bytes(b\"<roblox xml:someschemajunk>\"),\n            Some(DocumentFormat::Xml)\n        );\n\n        assert_eq!(\n            DocumentFormat::from_bytes(b\"<roblox>\"),\n            Some(DocumentFormat::Xml)\n        );\n    }\n\n    #[test]\n    fn from_bytes_invalid() {\n        assert_eq!(DocumentFormat::from_bytes(b\"\"), None);\n        assert_eq!(DocumentFormat::from_bytes(b\" roblox\"), None);\n        assert_eq!(DocumentFormat::from_bytes(b\"<roblox\"), None);\n        assert_eq!(DocumentFormat::from_bytes(b\"<roblox-\"), None);\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/document/kind.rs",
    "content": "use std::path::Path;\n\nuse rbx_dom_weak::WeakDom;\n\nuse crate::shared::instance::class_is_a_service;\n\n/**\n    A document kind specifier.\n\n    Valid variants are the following:\n\n    - `Model`\n    - `Place`\n\n    Other variants are only to be used for logic internal to this crate.\n*/\n#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]\npub enum DocumentKind {\n    Place,\n    Model,\n}\n\nimpl DocumentKind {\n    /**\n        Try to convert a file extension into a valid document kind specifier.\n\n        Returns `None` if the file extension is not a canonical roblox file format extension.\n    */\n    pub fn from_extension(extension: impl AsRef<str>) -> Option<Self> {\n        match extension.as_ref() {\n            \"rbxl\" | \"rbxlx\" => Some(Self::Place),\n            \"rbxm\" | \"rbxmx\" => Some(Self::Model),\n            _ => None,\n        }\n    }\n\n    /**\n        Try to convert a file path into a valid document kind specifier.\n\n        Returns `None` if the file extension of the path\n        is not a canonical roblox file format extension.\n    */\n    pub fn from_path(path: impl AsRef<Path>) -> Option<Self> {\n        match path\n            .as_ref()\n            .extension()\n            .map(|ext| ext.to_string_lossy())\n            .as_deref()\n        {\n            Some(\"rbxl\") | Some(\"rbxlx\") => Some(Self::Place),\n            Some(\"rbxm\") | Some(\"rbxmx\") => Some(Self::Model),\n            _ => None,\n        }\n    }\n\n    /**\n        Try to detect a document kind specifier from a weak dom.\n\n        Returns `None` if the given dom is empty and as such can not have its kind inferred.\n    */\n    #[must_use]\n    pub fn from_weak_dom(dom: &WeakDom) -> Option<Self> {\n        let mut has_top_level_child = false;\n        let mut has_top_level_service = false;\n        for child_ref in dom.root().children() {\n            if let Some(child_inst) = dom.get_by_ref(*child_ref) {\n                has_top_level_child = true;\n                if class_is_a_service(child_inst.class).unwrap_or(false) {\n                    has_top_level_service = true;\n                    break;\n                }\n            }\n        }\n        if has_top_level_service {\n            Some(Self::Place)\n        } else if has_top_level_child {\n            Some(Self::Model)\n        } else {\n            None\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::PathBuf;\n\n    use rbx_dom_weak::InstanceBuilder;\n\n    use super::*;\n\n    #[test]\n    fn from_extension_place() {\n        assert_eq!(\n            DocumentKind::from_extension(\"rbxl\"),\n            Some(DocumentKind::Place)\n        );\n\n        assert_eq!(\n            DocumentKind::from_extension(\"rbxlx\"),\n            Some(DocumentKind::Place)\n        );\n    }\n\n    #[test]\n    fn from_extension_model() {\n        assert_eq!(\n            DocumentKind::from_extension(\"rbxm\"),\n            Some(DocumentKind::Model)\n        );\n\n        assert_eq!(\n            DocumentKind::from_extension(\"rbxmx\"),\n            Some(DocumentKind::Model)\n        );\n    }\n\n    #[test]\n    fn from_extension_invalid() {\n        assert_eq!(DocumentKind::from_extension(\"csv\"), None);\n        assert_eq!(DocumentKind::from_extension(\"json\"), None);\n        assert_eq!(DocumentKind::from_extension(\"rbx\"), None);\n        assert_eq!(DocumentKind::from_extension(\"rbxn\"), None);\n        assert_eq!(DocumentKind::from_extension(\"xlx\"), None);\n        assert_eq!(DocumentKind::from_extension(\"xmx\"), None);\n    }\n\n    #[test]\n    fn from_path_place() {\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"place.rbxl\")),\n            Some(DocumentKind::Place)\n        );\n\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"place.rbxlx\")),\n            Some(DocumentKind::Place)\n        );\n    }\n\n    #[test]\n    fn from_path_model() {\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"model.rbxm\")),\n            Some(DocumentKind::Model)\n        );\n\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"model.rbxmx\")),\n            Some(DocumentKind::Model)\n        );\n    }\n\n    #[test]\n    fn from_path_invalid() {\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"data-file.csv\")),\n            None\n        );\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"nested/path/file.json\")),\n            None\n        );\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\".no-name-strange-rbx\")),\n            None\n        );\n        assert_eq!(\n            DocumentKind::from_path(PathBuf::from(\"file_without_extension\")),\n            None\n        );\n    }\n\n    #[test]\n    fn from_weak_dom() {\n        let empty = WeakDom::new(InstanceBuilder::new(\"Instance\"));\n        assert_eq!(DocumentKind::from_weak_dom(&empty), None);\n\n        let with_services = WeakDom::new(\n            InstanceBuilder::new(\"Instance\")\n                .with_child(InstanceBuilder::new(\"Workspace\"))\n                .with_child(InstanceBuilder::new(\"ReplicatedStorage\")),\n        );\n        assert_eq!(\n            DocumentKind::from_weak_dom(&with_services),\n            Some(DocumentKind::Place)\n        );\n\n        let with_children = WeakDom::new(\n            InstanceBuilder::new(\"Instance\")\n                .with_child(InstanceBuilder::new(\"Model\"))\n                .with_child(InstanceBuilder::new(\"Part\")),\n        );\n        assert_eq!(\n            DocumentKind::from_weak_dom(&with_children),\n            Some(DocumentKind::Model)\n        );\n\n        let with_mixed = WeakDom::new(\n            InstanceBuilder::new(\"Instance\")\n                .with_child(InstanceBuilder::new(\"Workspace\"))\n                .with_child(InstanceBuilder::new(\"Part\")),\n        );\n        assert_eq!(\n            DocumentKind::from_weak_dom(&with_mixed),\n            Some(DocumentKind::Place)\n        );\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/document/mod.rs",
    "content": "use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom, types::Ref as DomRef};\nuse rbx_xml::{\n    DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior,\n    EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior,\n};\n\nmod error;\nmod format;\nmod kind;\nmod postprocessing;\n\npub use error::*;\npub use format::*;\npub use kind::*;\n\nuse postprocessing::*;\n\nuse crate::instance::Instance;\n\npub type DocumentResult<T> = Result<T, DocumentError>;\n\n/**\n    A container for [`rbx_dom_weak::WeakDom`] that also takes care of\n    reading and writing different kinds and formats of roblox files.\n\n    ---\n\n    ### Code Sample #1\n\n    ```rust ignore\n    // Reading a document from a file\n\n    let file_path = PathBuf::from(\"place-file.rbxl\");\n    let file_contents = std::fs::read(&file_path)?;\n\n    let document = Document::from_bytes_auto(file_contents)?;\n\n    // Writing a document to a file\n\n    let file_path = PathBuf::from(\"place-file\")\n        .with_extension(document.extension()?);\n\n    std::fs::write(&file_path, document.to_bytes()?)?;\n    ```\n\n    ---\n\n    ### Code Sample #2\n\n    ```rust ignore\n    // Converting a Document to a DataModel or model child instances\n    let data_model = document.into_data_model_instance()?;\n\n    let model_children = document.into_instance_array()?;\n\n    // Converting a DataModel or model child instances into a Document\n    let place_doc = Document::from_data_model_instance(data_model)?;\n\n    let model_doc = Document::from_instance_array(model_children)?;\n    ```\n*/\n#[derive(Debug)]\npub struct Document {\n    kind: DocumentKind,\n    format: DocumentFormat,\n    dom: WeakDom,\n}\n\nimpl Document {\n    /**\n        Gets the canonical file extension for a given kind and\n        format of document, which will follow this chart:\n\n        | Kind  | Format | Extension |\n        |:------|:-------|:----------|\n        | Place | Binary | `rbxl`    |\n        | Place | Xml    | `rbxlx`   |\n        | Model | Binary | `rbxm`    |\n        | Model | Xml    | `rbxmx`   |\n    */\n    #[must_use]\n\t#[rustfmt::skip]\n    pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> &'static str {\n        match (kind, format) {\n            (DocumentKind::Place, DocumentFormat::Binary) => \"rbxl\",\n            (DocumentKind::Place, DocumentFormat::Xml)    => \"rbxlx\",\n            (DocumentKind::Model, DocumentFormat::Binary) => \"rbxm\",\n            (DocumentKind::Model, DocumentFormat::Xml)    => \"rbxmx\",\n        }\n    }\n\n    fn from_bytes_inner(bytes: impl AsRef<[u8]>) -> DocumentResult<(DocumentFormat, WeakDom)> {\n        let bytes = bytes.as_ref();\n        let format = DocumentFormat::from_bytes(bytes).ok_or(DocumentError::UnknownFormat)?;\n        let dom = match format {\n            DocumentFormat::Binary => rbx_binary::from_reader(bytes)\n                .map_err(|err| DocumentError::ReadError(err.to_string())),\n            DocumentFormat::Xml => {\n                let xml_options = XmlDecodeOptions::new()\n                    .property_behavior(XmlDecodePropertyBehavior::ReadUnknown);\n                rbx_xml::from_reader(bytes, xml_options)\n                    .map_err(|err| DocumentError::ReadError(err.to_string()))\n            }\n        }?;\n        Ok((format, dom))\n    }\n\n    /**\n        Decodes and creates a new document from a byte buffer.\n\n        This will automatically handle and detect if the document should be decoded\n        using a roblox binary or roblox xml format, and if it is a model or place file.\n\n        Note that detection of model vs place file is heavily dependent on the structure\n        of the file, and a model file with services in it will detect as a place file, so\n        if possible using [`Document::from_bytes`] with an explicit kind should be preferred.\n\n        # Errors\n\n        Errors if the given bytes are not a valid roblox file.\n    */\n    pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> {\n        let (format, dom) = Self::from_bytes_inner(bytes)?;\n        let kind = DocumentKind::from_weak_dom(&dom).ok_or(DocumentError::UnknownKind)?;\n        Ok(Self { kind, format, dom })\n    }\n\n    /**\n        Decodes and creates a new document from a byte buffer.\n\n        This will automatically handle and detect if the document\n        should be decoded using a roblox binary or roblox xml format.\n\n        # Errors\n\n        Errors if the given bytes are not a valid roblox file or not of the given kind.\n    */\n    pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> DocumentResult<Self> {\n        let (format, dom) = Self::from_bytes_inner(bytes)?;\n        Ok(Self { kind, format, dom })\n    }\n\n    /**\n        Encodes the document as a vector of bytes, to\n        be written to a file or sent over the network.\n\n        This will use the same format that the document was created\n        with, meaning if the document is a binary document the output\n        will be binary, and vice versa for xml and other future formats.\n\n        # Errors\n\n        Errors if the document can not be encoded.\n    */\n    pub fn to_bytes(&self) -> DocumentResult<Vec<u8>> {\n        self.to_bytes_with_format(self.format)\n    }\n\n    /**\n        Encodes the document as a vector of bytes, to\n        be written to a file or sent over the network.\n\n        # Errors\n\n        Errors if the document can not be encoded.\n    */\n    pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult<Vec<u8>> {\n        let mut bytes = Vec::new();\n        match format {\n            DocumentFormat::Binary => {\n                rbx_binary::to_writer(&mut bytes, &self.dom, self.dom.root().children())\n                    .map_err(|err| DocumentError::WriteError(err.to_string()))\n            }\n            DocumentFormat::Xml => {\n                let xml_options = XmlEncodeOptions::new()\n                    .property_behavior(XmlEncodePropertyBehavior::WriteUnknown);\n                rbx_xml::to_writer(\n                    &mut bytes,\n                    &self.dom,\n                    self.dom.root().children(),\n                    xml_options,\n                )\n                .map_err(|err| DocumentError::WriteError(err.to_string()))\n            }\n        }?;\n        Ok(bytes)\n    }\n\n    /**\n        Gets the kind this document was created with.\n    */\n    #[must_use]\n    pub fn kind(&self) -> DocumentKind {\n        self.kind\n    }\n\n    /**\n        Gets the format this document was created with.\n    */\n    #[must_use]\n    pub fn format(&self) -> DocumentFormat {\n        self.format\n    }\n\n    /**\n        Gets the file extension for this document.\n    */\n    #[must_use]\n    pub fn extension(&self) -> &'static str {\n        Self::canonical_extension(self.kind, self.format)\n    }\n\n    /**\n        Creates a `DataModel` instance out of this place document.\n\n        # Errors\n\n        Errors if the document is not a place.\n    */\n    pub fn into_data_model_instance(mut self) -> DocumentResult<Instance> {\n        if self.kind != DocumentKind::Place {\n            return Err(DocumentError::IntoDataModelInvalidArgs);\n        }\n\n        let dom_root = self.dom.root_ref();\n\n        let data_model_ref = self\n            .dom\n            .insert(dom_root, DomInstanceBuilder::new(\"DataModel\"));\n        let data_model_child_refs = self.dom.root().children().to_vec();\n\n        for child_ref in data_model_child_refs {\n            if child_ref != data_model_ref {\n                self.dom.transfer_within(child_ref, data_model_ref);\n            }\n        }\n\n        Ok(Instance::from_external_dom(&mut self.dom, data_model_ref))\n    }\n\n    /**\n        Creates an array of instances out of this model document.\n\n        # Errors\n\n        Errors if the document is not a model.\n    */\n    pub fn into_instance_array(mut self) -> DocumentResult<Vec<Instance>> {\n        if self.kind != DocumentKind::Model {\n            return Err(DocumentError::IntoInstanceArrayInvalidArgs);\n        }\n\n        let dom_child_refs = self.dom.root().children().to_vec();\n\n        let root_child_instances = dom_child_refs\n            .into_iter()\n            .map(|child_ref| Instance::from_external_dom(&mut self.dom, child_ref))\n            .collect();\n\n        Ok(root_child_instances)\n    }\n\n    /**\n        Creates a place document out of a `DataModel` instance.\n\n        # Errors\n\n        Errors if the instance is not a `DataModel`.\n    */\n    pub fn from_data_model_instance(i: Instance) -> DocumentResult<Self> {\n        if i.get_class_name() != \"DataModel\" {\n            return Err(DocumentError::FromDataModelInvalidArgs);\n        }\n\n        let mut dom = WeakDom::new(DomInstanceBuilder::new(\"ROOT\"));\n        let children: Vec<DomRef> = i\n            .get_children()\n            .iter()\n            .map(|instance| instance.dom_ref)\n            .collect();\n\n        Instance::clone_multiple_into_external_dom(&children, &mut dom);\n        postprocess_dom_for_place(&mut dom);\n\n        Ok(Self {\n            kind: DocumentKind::Place,\n            format: DocumentFormat::default(),\n            dom,\n        })\n    }\n\n    /**\n        Creates a model document out of an array of instances.\n\n        # Errors\n\n        Errors if any of the instances is a `DataModel`.\n    */\n    pub fn from_instance_array(v: Vec<Instance>) -> DocumentResult<Self> {\n        for i in &v {\n            if i.get_class_name() == \"DataModel\" {\n                return Err(DocumentError::FromInstanceArrayInvalidArgs);\n            }\n        }\n\n        let mut dom = WeakDom::new(DomInstanceBuilder::new(\"ROOT\"));\n        let instances: Vec<DomRef> = v.iter().map(|instance| instance.dom_ref).collect();\n\n        Instance::clone_multiple_into_external_dom(&instances, &mut dom);\n        postprocess_dom_for_model(&mut dom);\n\n        Ok(Self {\n            kind: DocumentKind::Model,\n            format: DocumentFormat::default(),\n            dom,\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/document/postprocessing.rs",
    "content": "use rbx_dom_weak::{\n    Instance as DomInstance, WeakDom,\n    types::{Ref as DomRef, VariantType as DomType},\n    ustr,\n};\n\nuse crate::shared::instance::class_is_a;\n\npub fn postprocess_dom_for_place(_dom: &mut WeakDom) {\n    // Nothing here yet\n}\n\npub fn postprocess_dom_for_model(dom: &mut WeakDom) {\n    let root_ref = dom.root_ref();\n    recurse_instances(dom, root_ref, &|inst| {\n        // Get rid of some unique ids - roblox does not\n        // save these in model files, and we shouldn't either\n        remove_matching_prop(inst, DomType::UniqueId, \"UniqueId\");\n        remove_matching_prop(inst, DomType::UniqueId, \"HistoryId\");\n        // Similar story with ScriptGuid - this is used\n        // in the studio-only cloud script drafts feature\n        if class_is_a(inst.class, \"LuaSourceContainer\").unwrap_or(false) {\n            inst.properties.remove(&ustr(\"ScriptGuid\"));\n        }\n    });\n}\n\nfn recurse_instances<F>(dom: &mut WeakDom, dom_ref: DomRef, f: &F)\nwhere\n    F: Fn(&mut DomInstance) + 'static,\n{\n    let child_refs = match dom.get_by_ref_mut(dom_ref) {\n        Some(inst) => {\n            f(inst);\n            inst.children().to_vec()\n        }\n        None => Vec::new(),\n    };\n    for child_ref in child_refs {\n        recurse_instances(dom, child_ref, f);\n    }\n}\n\nfn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {\n    let name = &ustr(name);\n    if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {\n        inst.properties.remove(name);\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/exports.rs",
    "content": "use mlua::prelude::*;\n\n/**\n    Trait for any item that should be exported as part of the `roblox` built-in library.\n\n    This may be an enum or a struct that should export constants and/or constructs.\n\n    ### Example usage\n\n    ```rs\n    use mlua::prelude::*;\n\n    struct MyType(usize);\n\n    impl MyType {\n        pub fn new(n: usize) -> Self {\n            Self(n)\n        }\n    }\n\n    impl LuaExportsTable for MyType {\n        const EXPORT_NAME: &'static str = \"MyType\";\n\n        fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n            let my_type_new = |lua, n: Option<usize>| {\n                Self::new(n.unwrap_or_default())\n            };\n\n            TableBuilder::new(lua)?\n                .with_function(\"new\", my_type_new)?\n                .build_readonly()\n        }\n    }\n\n    impl LuaUserData for MyType {\n        // ...\n    }\n    ```\n*/\npub trait LuaExportsTable {\n    const EXPORT_NAME: &'static str;\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable>;\n}\n\n/**\n    Exports a single item that implements the [`LuaExportsTable`] trait.\n\n    Returns the name of the export, as well as the export table.\n\n    ### Example usage\n\n    ```rs\n    let lua: mlua::Lua::new();\n\n    let (name1, table1) = export::<Type1>(lua)?;\n    let (name2, table2) = export::<Type2>(lua)?;\n    ```\n*/\npub fn export<T>(lua: Lua) -> LuaResult<(&'static str, LuaValue)>\nwhere\n    T: LuaExportsTable,\n{\n    Ok((\n        T::EXPORT_NAME,\n        <T as LuaExportsTable>::create_exports_table(lua.clone())?.into_lua(&lua)?,\n    ))\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/instance/base.rs",
    "content": "#![allow(clippy::items_after_statements)]\n\nuse mlua::prelude::*;\n\nuse rbx_dom_weak::{\n    Instance as DomInstance,\n    types::{Variant as DomValue, VariantType as DomType},\n};\n\nuse crate::{\n    datatypes::{\n        attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value},\n        conversion::{DomValueToLua, LuaToDomValue},\n        types::EnumItem,\n        userdata_impl_eq, userdata_impl_to_string,\n    },\n    shared::instance::{class_is_a, find_property_info},\n};\n\nuse super::{Instance, data_model, registry::InstanceRegistry};\n\n#[allow(clippy::too_many_lines)]\npub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {\n    m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {\n        ensure_not_destroyed(this)?;\n        userdata_impl_to_string(lua, this, ())\n    });\n    m.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n    m.add_meta_method(LuaMetaMethod::Index, instance_property_get);\n    m.add_meta_method_mut(LuaMetaMethod::NewIndex, instance_property_set);\n    m.add_method(\"Clone\", |lua, this, ()| {\n        ensure_not_destroyed(this)?;\n        this.clone_instance().into_lua(lua)\n    });\n    m.add_method_mut(\"Destroy\", |_, this, ()| {\n        this.destroy();\n        Ok(())\n    });\n    m.add_method_mut(\"ClearAllChildren\", |_, this, ()| {\n        this.clear_all_children();\n        Ok(())\n    });\n    m.add_method(\"GetChildren\", |lua, this, ()| {\n        ensure_not_destroyed(this)?;\n        this.get_children().into_lua(lua)\n    });\n    m.add_method(\"GetDescendants\", |lua, this, ()| {\n        ensure_not_destroyed(this)?;\n        this.get_descendants().into_lua(lua)\n    });\n    m.add_method(\"GetFullName\", |lua, this, ()| {\n        ensure_not_destroyed(this)?;\n        this.get_full_name().into_lua(lua)\n    });\n    m.add_method(\"GetDebugId\", |lua, this, ()| {\n        this.dom_ref.to_string().into_lua(lua)\n    });\n    m.add_method(\"FindFirstAncestor\", |lua, this, name: String| {\n        ensure_not_destroyed(this)?;\n        this.find_ancestor(|child| child.name == name).into_lua(lua)\n    });\n    m.add_method(\n        \"FindFirstAncestorOfClass\",\n        |lua, this, class_name: String| {\n            ensure_not_destroyed(this)?;\n            this.find_ancestor(|child| child.class == class_name)\n                .into_lua(lua)\n        },\n    );\n    m.add_method(\n        \"FindFirstAncestorWhichIsA\",\n        |lua, this, class_name: String| {\n            ensure_not_destroyed(this)?;\n            this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false))\n                .into_lua(lua)\n        },\n    );\n    m.add_method(\n        \"FindFirstChild\",\n        |lua, this, (name, recursive): (String, Option<bool>)| {\n            ensure_not_destroyed(this)?;\n            let predicate = |child: &DomInstance| child.name == name;\n            if matches!(recursive, Some(true)) {\n                this.find_descendant(predicate).into_lua(lua)\n            } else {\n                this.find_child(predicate).into_lua(lua)\n            }\n        },\n    );\n    m.add_method(\n        \"FindFirstChildOfClass\",\n        |lua, this, (class_name, recursive): (String, Option<bool>)| {\n            ensure_not_destroyed(this)?;\n            let predicate = |child: &DomInstance| child.class == class_name;\n            if matches!(recursive, Some(true)) {\n                this.find_descendant(predicate).into_lua(lua)\n            } else {\n                this.find_child(predicate).into_lua(lua)\n            }\n        },\n    );\n    m.add_method(\n        \"FindFirstChildWhichIsA\",\n        |lua, this, (class_name, recursive): (String, Option<bool>)| {\n            ensure_not_destroyed(this)?;\n            let predicate =\n                |child: &DomInstance| class_is_a(child.class, &class_name).unwrap_or(false);\n            if matches!(recursive, Some(true)) {\n                this.find_descendant(predicate).into_lua(lua)\n            } else {\n                this.find_child(predicate).into_lua(lua)\n            }\n        },\n    );\n    m.add_method(\"IsA\", |_, this, class_name: String| {\n        Ok(class_is_a(this.class_name, class_name).unwrap_or(false))\n    });\n    m.add_method(\n        \"IsAncestorOf\",\n        |_, this, instance: LuaUserDataRef<Instance>| {\n            ensure_not_destroyed(this)?;\n            Ok(instance\n                .find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)\n                .is_some())\n        },\n    );\n    m.add_method(\n        \"IsDescendantOf\",\n        |_, this, instance: LuaUserDataRef<Instance>| {\n            ensure_not_destroyed(this)?;\n            Ok(this\n                .find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)\n                .is_some())\n        },\n    );\n    m.add_method(\"GetAttribute\", |lua, this, name: String| {\n        ensure_not_destroyed(this)?;\n        match this.get_attribute(name) {\n            Some(attribute) => Ok(LuaValue::dom_value_to_lua(lua, &attribute)?),\n            None => Ok(LuaValue::Nil),\n        }\n    });\n    m.add_method(\"GetAttributes\", |lua, this, ()| {\n        ensure_not_destroyed(this)?;\n        let attributes = this.get_attributes();\n        let tab = lua.create_table_with_capacity(0, attributes.len())?;\n        for (key, value) in attributes {\n            tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;\n        }\n        Ok(tab)\n    });\n    m.add_method(\n        \"SetAttribute\",\n        |lua, this, (attribute_name, lua_value): (String, LuaValue)| {\n            ensure_not_destroyed(this)?;\n            ensure_valid_attribute_name(&attribute_name)?;\n            if lua_value.is_nil() || lua_value.is_null() {\n                this.remove_attribute(attribute_name);\n                Ok(())\n            } else {\n                match lua_value.lua_to_dom_value(lua, None) {\n                    Ok(dom_value) => {\n                        ensure_valid_attribute_value(&dom_value)?;\n                        this.set_attribute(attribute_name, dom_value);\n                        Ok(())\n                    }\n                    Err(e) => Err(e.into()),\n                }\n            }\n        },\n    );\n    m.add_method(\"GetTags\", |_, this, ()| {\n        ensure_not_destroyed(this)?;\n        Ok(this.get_tags())\n    });\n    m.add_method(\"HasTag\", |_, this, tag: String| {\n        ensure_not_destroyed(this)?;\n        Ok(this.has_tag(tag))\n    });\n    m.add_method(\"AddTag\", |_, this, tag: String| {\n        ensure_not_destroyed(this)?;\n        this.add_tag(tag);\n        Ok(())\n    });\n    m.add_method(\"RemoveTag\", |_, this, tag: String| {\n        ensure_not_destroyed(this)?;\n        this.remove_tag(tag);\n        Ok(())\n    });\n}\n\nfn ensure_not_destroyed(inst: &Instance) -> LuaResult<()> {\n    if inst.is_destroyed() {\n        Err(LuaError::RuntimeError(\n            \"Instance has been destroyed\".to_string(),\n        ))\n    } else {\n        Ok(())\n    }\n}\n\n/*\n    Gets a property value for an instance.\n\n    Getting a value does the following:\n\n    1. Check if it is a special property like \"ClassName\", \"Name\" or \"Parent\"\n    2. Check if a property exists for the wanted name\n        2a. Get an existing instance property OR\n        2b. Get a property from a known default value\n    3. Get a current child of the instance\n    4. No valid property or instance found, throw error\n*/\nfn instance_property_get(lua: &Lua, this: &Instance, prop_name: String) -> LuaResult<LuaValue> {\n    match prop_name.as_str() {\n        \"ClassName\" => return this.get_class_name().into_lua(lua),\n        \"Parent\" => {\n            return this.get_parent().into_lua(lua);\n        }\n        _ => {}\n    }\n\n    ensure_not_destroyed(this)?;\n\n    if prop_name.as_str() == \"Name\" {\n        return this.get_name().into_lua(lua);\n    }\n\n    if let Some(info) = find_property_info(this.class_name, &prop_name) {\n        if let Some(prop) = this.get_property(&prop_name) {\n            if let DomValue::Enum(enum_value) = prop {\n                let enum_name = info.enum_name.ok_or_else(|| {\n                    LuaError::RuntimeError(format!(\n                        \"Failed to get property '{prop_name}' - encountered unknown enum\",\n                    ))\n                })?;\n                EnumItem::from_enum_name_and_value(&enum_name, enum_value.to_u32())\n                    .ok_or_else(|| {\n                        LuaError::RuntimeError(format!(\n                            \"Failed to get property '{}' - Enum.{} does not contain numeric value {}\",\n                            prop_name, enum_name, enum_value.to_u32()\n                        ))\n                    })?\n                    .into_lua(lua)\n            } else {\n                Ok(LuaValue::dom_value_to_lua(lua, &prop)?)\n            }\n        } else if let (Some(enum_name), Some(enum_value)) = (info.enum_name, info.enum_default) {\n            EnumItem::from_enum_name_and_value(&enum_name, enum_value)\n                .ok_or_else(|| {\n                    LuaError::RuntimeError(format!(\n                        \"Failed to get property '{prop_name}' - Enum.{enum_name} does not contain numeric value {enum_value}\",\n                    ))\n                })?\n                .into_lua(lua)\n        } else if let Some(prop_default) = info.value_default {\n            Ok(LuaValue::dom_value_to_lua(lua, prop_default)?)\n        } else if info.value_type.is_some() {\n            if info.value_type == Some(DomType::Ref) {\n                Ok(LuaValue::Nil)\n            } else {\n                Err(LuaError::RuntimeError(format!(\n                    \"Failed to get property '{prop_name}' - missing default value\",\n                )))\n            }\n        } else {\n            Err(LuaError::RuntimeError(format!(\n                \"Failed to get property '{prop_name}' - malformed property info\",\n            )))\n        }\n    } else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {\n        Ok(LuaValue::UserData(lua.create_userdata(inst)?))\n    } else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {\n        getter.call(*this)\n    } else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {\n        Ok(LuaValue::Function(method))\n    } else {\n        Err(LuaError::RuntimeError(format!(\n            \"{prop_name} is not a valid member of {this}\",\n        )))\n    }\n}\n\n/*\n    Sets a property value for an instance.\n\n    Setting a value does the following:\n\n    1. Check if it is a special property like \"ClassName\", \"Name\" or \"Parent\"\n    2. Check if a property exists for the wanted name\n        2a. Set a strict enum from a given EnumItem OR\n        2b. Set a normal property from a given value\n*/\nfn instance_property_set(\n    lua: &Lua,\n    this: &mut Instance,\n    (prop_name, prop_value): (String, LuaValue),\n) -> LuaResult<()> {\n    ensure_not_destroyed(this)?;\n\n    match prop_name.as_str() {\n        \"ClassName\" => {\n            return Err(LuaError::RuntimeError(\n                \"Failed to set ClassName - property is read-only\".to_string(),\n            ));\n        }\n        \"Name\" => {\n            let name = String::from_lua(prop_value, lua)?;\n            this.set_name(name);\n            return Ok(());\n        }\n        \"Parent\" => {\n            if this.get_class_name() == data_model::CLASS_NAME {\n                return Err(LuaError::RuntimeError(\n                    \"Failed to set Parent - DataModel can not be reparented\".to_string(),\n                ));\n            }\n            type Parent = Option<LuaUserDataRef<Instance>>;\n            let parent = Parent::from_lua(prop_value, lua)?;\n            this.set_parent(parent.map(|p| *p));\n            return Ok(());\n        }\n        _ => {}\n    }\n\n    if let Some(info) = find_property_info(this.class_name, &prop_name) {\n        if let Some(enum_name) = info.enum_name {\n            match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {\n                Ok(given_enum) if given_enum.parent.desc.name == enum_name => {\n                    this.set_property(prop_name, DomValue::EnumItem((*given_enum).clone().into()));\n                    Ok(())\n                }\n                Ok(given_enum) => Err(LuaError::RuntimeError(format!(\n                    \"Failed to set property '{}' - expected Enum.{}, got Enum.{}\",\n                    prop_name, enum_name, given_enum.parent.desc.name\n                ))),\n                Err(e) => Err(e),\n            }\n        } else if let Some(dom_type) = info.value_type {\n            match prop_value.lua_to_dom_value(lua, Some(dom_type)) {\n                Ok(dom_value) => {\n                    this.set_property(prop_name, dom_value);\n                    Ok(())\n                }\n                Err(e) => Err(e.into()),\n            }\n        } else {\n            Err(LuaError::RuntimeError(format!(\n                \"Failed to set property '{prop_name}' - malformed property info\",\n            )))\n        }\n    } else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {\n        setter.call((*this, prop_value))\n    } else {\n        Err(LuaError::RuntimeError(format!(\n            \"{prop_name} is not a valid member of {this}\",\n        )))\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/instance/data_model.rs",
    "content": "use mlua::prelude::*;\n\nuse crate::shared::{\n    classes::{\n        add_class_restricted_getter, add_class_restricted_method,\n        get_or_create_property_ref_instance,\n    },\n    instance::class_is_a_service,\n};\n\nuse super::Instance;\n\npub const CLASS_NAME: &str = \"DataModel\";\n\npub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {\n    add_class_restricted_getter(f, CLASS_NAME, \"Workspace\", data_model_get_workspace);\n}\n\npub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {\n    add_class_restricted_method(m, CLASS_NAME, \"GetService\", data_model_get_service);\n    add_class_restricted_method(m, CLASS_NAME, \"FindService\", data_model_find_service);\n}\n\n/**\n    Get the workspace parented under this datamodel, or create it if it doesn't exist.\n\n    ### See Also\n    * [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)\n      on the Roblox Developer Hub\n*/\nfn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {\n    get_or_create_property_ref_instance(this, \"Workspace\", \"Workspace\")\n}\n\n/**\n    Gets or creates a service for this `DataModel`.\n\n    ### See Also\n    * [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)\n      on the Roblox Developer Hub\n*/\nfn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> LuaResult<Instance> {\n    if matches!(class_is_a_service(&service_name), None | Some(false)) {\n        Err(LuaError::RuntimeError(format!(\n            \"'{service_name}' is not a valid service name\",\n        )))\n    } else if let Some(service) = this.find_child(|child| child.class == service_name) {\n        Ok(service)\n    } else {\n        let service = Instance::new_orphaned(service_name);\n        service.set_parent(Some(*this));\n        Ok(service)\n    }\n}\n\n/**\n    Gets a service for this `DataModel`, if it exists.\n\n    ### See Also\n    * [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)\n      on the Roblox Developer Hub\n*/\nfn data_model_find_service(\n    _: &Lua,\n    this: &Instance,\n    service_name: String,\n) -> LuaResult<Option<Instance>> {\n    if matches!(class_is_a_service(&service_name), None | Some(false)) {\n        Err(LuaError::RuntimeError(format!(\n            \"'{service_name}' is not a valid service name\",\n        )))\n    } else if let Some(service) = this.find_child(|child| child.class == service_name) {\n        Ok(Some(service))\n    } else {\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/instance/mod.rs",
    "content": "#![allow(clippy::missing_panics_doc)]\n\nuse std::{\n    collections::{BTreeMap, VecDeque},\n    fmt,\n    hash::{Hash, Hasher},\n    sync::{LazyLock, Mutex},\n};\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\nuse rbx_dom_weak::{\n    Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, Ustr, WeakDom,\n    types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},\n    ustr,\n};\n\n#[cfg(feature = \"mlua\")]\nuse lune_utils::TableBuilder;\n\nuse crate::shared::instance::class_is_a;\n\n#[cfg(feature = \"mlua\")]\nuse crate::{exports::LuaExportsTable, shared::instance::class_exists};\n\n#[cfg(feature = \"mlua\")]\npub(crate) mod base;\n#[cfg(feature = \"mlua\")]\npub(crate) mod data_model;\n#[cfg(feature = \"mlua\")]\npub(crate) mod terrain;\n#[cfg(feature = \"mlua\")]\npub(crate) mod workspace;\n\n#[cfg(feature = \"mlua\")]\npub mod registry;\n\nconst PROPERTY_NAME_ATTRIBUTES: &str = \"Attributes\";\nconst PROPERTY_NAME_TAGS: &str = \"Tags\";\n\nstatic INTERNAL_DOM: LazyLock<Mutex<WeakDom>> =\n    LazyLock::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new(\"ROOT\"))));\n\n#[derive(Debug, Clone, Copy)]\npub struct Instance {\n    pub(crate) dom_ref: DomRef,\n    pub(crate) class_name: Ustr,\n}\n\nimpl Instance {\n    /**\n        Creates a new `Instance` from an existing dom object ref.\n\n        Panics if the instance does not exist in the internal dom,\n        or if the given dom object ref points to the internal dom root.\n\n        **WARNING:** Creating a new instance requires locking the internal dom,\n        any existing lock must first be released to prevent any deadlocking.\n    */\n    #[must_use]\n    pub fn new(dom_ref: DomRef) -> Self {\n        Self::new_opt(dom_ref).expect(\"Failed to find instance in document\")\n    }\n\n    /**\n        Creates a new `Instance` from a dom object ref, if the instance exists.\n\n        Panics if the given dom object ref points to the internal dom root.\n\n        **WARNING:** Creating a new instance requires locking the internal dom,\n        any existing lock must first be released to prevent any deadlocking.\n    */\n    #[must_use]\n    pub fn new_opt(dom_ref: DomRef) -> Option<Self> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        if let Some(instance) = dom.get_by_ref(dom_ref) {\n            assert!(\n                !(instance.referent() == dom.root_ref()),\n                \"Instances can not be created from dom roots\"\n            );\n\n            Some(Self {\n                dom_ref,\n                class_name: instance.class,\n            })\n        } else {\n            None\n        }\n    }\n\n    /**\n        Creates a new orphaned `Instance` with a given class name.\n\n        An orphaned instance is an instance at the root of Lune's internal weak dom.\n\n        **WARNING:** Creating a new instance requires locking the internal dom,\n        any existing lock must first be released to prevent any deadlocking.\n    */\n    #[must_use]\n    pub fn new_orphaned(class_name: impl AsRef<str>) -> Self {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let class_name = class_name.as_ref();\n\n        let instance = DomInstanceBuilder::new(class_name);\n\n        let dom_root = dom.root_ref();\n        let dom_ref = dom.insert(dom_root, instance);\n\n        Self {\n            dom_ref,\n            class_name: ustr(class_name),\n        }\n    }\n\n    /**\n        Creates a new orphaned `Instance` by transferring\n        it from an external weak dom to the internal one.\n\n        An orphaned instance is an instance at the root of Lune's internal weak dom.\n\n        Panics if the given dom ref is the root dom ref of the external weak dom.\n    */\n    #[must_use]\n    pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let dom_root = dom.root_ref();\n\n        external_dom.transfer(external_dom_ref, &mut dom, dom_root);\n\n        drop(dom); // Self::new needs mutex handle, drop it first\n        Self::new(external_dom_ref)\n    }\n\n    /**\n        Clones an instance to an external weak dom.\n\n        This will place the instance as a child of the\n        root of the weak dom, and return its referent.\n    */\n    pub fn clone_into_external_dom(self, external_dom: &mut WeakDom) -> DomRef {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let cloned = dom.clone_into_external(self.dom_ref, external_dom);\n        external_dom.transfer_within(cloned, external_dom.root_ref());\n\n        cloned\n    }\n\n    /**\n        Clones multiple instances to an external weak dom.\n\n        This will place the instances as children of the\n        root of the weak dom, and return their referents.\n    */\n    pub fn clone_multiple_into_external_dom(\n        referents: &[DomRef],\n        external_dom: &mut WeakDom,\n    ) -> Vec<DomRef> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let cloned = dom.clone_multiple_into_external(referents, external_dom);\n\n        for referent in &cloned {\n            external_dom.transfer_within(*referent, external_dom.root_ref());\n        }\n\n        cloned\n    }\n\n    /**\n        Clones the instance and all of its descendants, and orphans it.\n\n        To then save the new instance it must be re-parented,\n        which matches the exact behavior of Roblox's instances.\n\n        ### See Also\n        * [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)\n          on the Roblox Developer Hub\n    */\n    #[must_use]\n    pub fn clone_instance(&self) -> Self {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let new_ref = dom.clone_within(self.dom_ref);\n        drop(dom); // Self::new needs mutex handle, drop it first\n\n        let new_inst = Self::new(new_ref);\n        new_inst.set_parent(None);\n        new_inst\n    }\n\n    /**\n        Destroys the instance, removing it completely\n        from the weak dom with no way of recovering it.\n\n        All member methods will throw errors when called from lua and panic\n        when called from rust after the instance has been destroyed.\n\n        Returns `true` if destroyed successfully, `false` if already destroyed.\n\n        ### See Also\n        * [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)\n          on the Roblox Developer Hub\n    */\n    pub fn destroy(&mut self) -> bool {\n        if self.is_destroyed() {\n            false\n        } else {\n            let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n            dom.destroy(self.dom_ref);\n            true\n        }\n    }\n\n    fn is_destroyed(&self) -> bool {\n        // NOTE: This property can not be cached since instance references\n        // other than this one may have destroyed this one, and we don't\n        // keep track of all current instance reference structs\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        dom.get_by_ref(self.dom_ref).is_none()\n    }\n\n    /**\n        Destroys all child instances.\n\n        ### See Also\n        * [`Instance::Destroy`] for more info about what happens when an instance gets destroyed\n        * [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren)\n          on the Roblox Developer Hub\n    */\n    pub fn clear_all_children(&mut self) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let instance = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n\n        let child_refs = instance.children().to_vec();\n        for child_ref in child_refs {\n            dom.destroy(child_ref);\n        }\n    }\n\n    /**\n        Checks if the instance matches or inherits a given class name.\n\n        ### See Also\n        * [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)\n          on the Roblox Developer Hub\n    */\n    pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {\n        class_is_a(self.class_name, class_name).unwrap_or(false)\n    }\n\n    /**\n        Gets the class name of the instance.\n\n        This will return the correct class name even if the instance has been destroyed.\n\n        ### See Also\n        * [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)\n          on the Roblox Developer Hub\n    */\n    #[must_use]\n    pub fn get_class_name(&self) -> &str {\n        self.class_name.as_str()\n    }\n\n    /**\n        Gets the name of the instance, if it exists.\n\n        ### See Also\n        * [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)\n          on the Roblox Developer Hub\n    */\n    pub fn get_name(&self) -> String {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        dom.get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .name\n            .clone()\n    }\n\n    /**\n        Sets the name of the instance, if it exists.\n\n        ### See Also\n        * [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)\n          on the Roblox Developer Hub\n    */\n    pub fn set_name(&self, name: impl Into<String>) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        dom.get_by_ref_mut(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .name = name.into();\n    }\n\n    /**\n        Gets the parent of the instance, if it exists.\n\n        ### See Also\n        * [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)\n          on the Roblox Developer Hub\n    */\n    pub fn get_parent(&self) -> Option<Instance> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let parent_ref = dom.get_by_ref(self.dom_ref)?.parent();\n\n        if parent_ref == dom.root_ref() {\n            None\n        } else {\n            drop(dom); // Self::new needs mutex handle, drop it first\n            Some(Self::new(parent_ref))\n        }\n    }\n\n    /**\n        Sets the parent of the instance, if it exists.\n\n        If the provided parent is [`None`] the instance will become orphaned.\n\n        An orphaned instance is an instance at the root of Lune's internal weak dom.\n\n        ### See Also\n        * [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)\n          on the Roblox Developer Hub\n    */\n    pub fn set_parent(&self, parent: Option<Instance>) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let parent_ref = parent.map_or_else(|| dom.root_ref(), |parent| parent.dom_ref);\n\n        dom.transfer_within(self.dom_ref, parent_ref);\n    }\n\n    /**\n        Gets a property for the instance, if it exists.\n    */\n    pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> {\n        INTERNAL_DOM\n            .lock()\n            .expect(\"Failed to lock document\")\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .properties\n            .get(&ustr(name.as_ref()))\n            .cloned()\n    }\n\n    /**\n        Sets a property for the instance.\n\n        Note that setting a property here will not fail even if the\n        property does not actually exist for the instance class.\n    */\n    pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) {\n        INTERNAL_DOM\n            .lock()\n            .expect(\"Failed to lock document\")\n            .get_by_ref_mut(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .properties\n            .insert(ustr(name.as_ref()), value);\n    }\n\n    /**\n        Gets an attribute for the instance, if it exists.\n\n        ### See Also\n        * [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)\n          on the Roblox Developer Hub\n    */\n    pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Attributes(attributes)) =\n            inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))\n        {\n            attributes.get(name.as_ref()).cloned()\n        } else {\n            None\n        }\n    }\n\n    /**\n        Gets all known attributes for the instance.\n\n        ### See Also\n        * [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)\n          on the Roblox Developer Hub\n    */\n    pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Attributes(attributes)) =\n            inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))\n        {\n            attributes.clone().into_iter().collect()\n        } else {\n            BTreeMap::new()\n        }\n    }\n\n    /**\n        Sets an attribute for the instance.\n\n        ### See Also\n        * [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)\n          on the Roblox Developer Hub\n    */\n    pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref_mut(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        // NOTE: Attributes do not support integers, only floats\n        let value = match value {\n            DomValue::Int32(i) => DomValue::Float32(i as f32),\n            DomValue::Int64(i) => DomValue::Float64(i as f64),\n            value => value,\n        };\n        if let Some(DomValue::Attributes(attributes)) =\n            inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))\n        {\n            attributes.insert(name.as_ref().to_string(), value);\n        } else {\n            let mut attributes = DomAttributes::new();\n            attributes.insert(name.as_ref().to_string(), value);\n            inst.properties.insert(\n                ustr(PROPERTY_NAME_ATTRIBUTES),\n                DomValue::Attributes(attributes),\n            );\n        }\n    }\n\n    /**\n        Removes an attribute from the instance.\n\n        Note that this does not have an equivalent in the Roblox engine API,\n        but separating this from `set_attribute` lets `set_attribute` be more\n        ergonomic and not require an `Option<DomValue>` for the value argument.\n        The equivalent in the Roblox engine API would be `instance:SetAttribute(name, nil)`.\n    */\n    pub fn remove_attribute(&self, name: impl AsRef<str>) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref_mut(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Attributes(attributes)) =\n            inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))\n        {\n            attributes.remove(name.as_ref());\n            if attributes.is_empty() {\n                inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES));\n            }\n        }\n    }\n\n    /**\n        Adds a tag to the instance.\n\n        ### See Also\n        * [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)\n          on the Roblox Developer Hub\n    */\n    pub fn add_tag(&self, name: impl AsRef<str>) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref_mut(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {\n            tags.push(name.as_ref());\n        } else {\n            inst.properties.insert(\n                ustr(PROPERTY_NAME_TAGS),\n                DomValue::Tags(vec![name.as_ref().to_string()].into()),\n            );\n        }\n    }\n\n    /**\n        Gets all current tags for the instance.\n\n        ### See Also\n        * [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)\n          on the Roblox Developer Hub\n    */\n    pub fn get_tags(&self) -> Vec<String> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {\n            tags.iter().map(ToString::to_string).collect()\n        } else {\n            Vec::new()\n        }\n    }\n\n    /**\n        Checks if the instance has a specific tag.\n\n        ### See Also\n        * [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)\n          on the Roblox Developer Hub\n    */\n    pub fn has_tag(&self, name: impl AsRef<str>) -> bool {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {\n            let name = name.as_ref();\n            tags.iter().any(|tag| tag == name)\n        } else {\n            false\n        }\n    }\n\n    /**\n        Removes a tag from the instance.\n\n        ### See Also\n        * [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)\n          on the Roblox Developer Hub\n    */\n    pub fn remove_tag(&self, name: impl AsRef<str>) {\n        let mut dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let inst = dom\n            .get_by_ref_mut(self.dom_ref)\n            .expect(\"Failed to find instance in document\");\n        if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {\n            let name = name.as_ref();\n            let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();\n            new_tags.retain(|tag| tag != name);\n            inst.properties\n                .insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into()));\n        }\n    }\n\n    /**\n        Gets all of the current children of this `Instance`.\n\n        Note that this is a somewhat expensive operation and that other\n        operations using weak dom referents should be preferred if possible.\n\n        ### See Also\n        * [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)\n          on the Roblox Developer Hub\n    */\n    pub fn get_children(&self) -> Vec<Instance> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let children = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .children()\n            .to_vec();\n\n        drop(dom); // Self::new needs mutex handle, drop it first\n        children.into_iter().map(Self::new).collect()\n    }\n\n    /**\n        Gets all of the current descendants of this `Instance` using a breadth-first search.\n\n        Note that this is a somewhat expensive operation and that other\n        operations using weak dom referents should be preferred if possible.\n\n        ### See Also\n        * [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)\n          on the Roblox Developer Hub\n    */\n    pub fn get_descendants(&self) -> Vec<Instance> {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let mut descendants = Vec::new();\n        let mut queue = VecDeque::from_iter(\n            dom.get_by_ref(self.dom_ref)\n                .expect(\"Failed to find instance in document\")\n                .children(),\n        );\n\n        while let Some(queue_ref) = queue.pop_front() {\n            descendants.push(*queue_ref);\n            let queue_inst = dom.get_by_ref(*queue_ref).unwrap();\n            for queue_ref_inner in queue_inst.children().iter().rev() {\n                queue.push_back(queue_ref_inner);\n            }\n        }\n\n        drop(dom); // Self::new needs mutex handle, drop it first\n        descendants.into_iter().map(Self::new).collect()\n    }\n\n    /**\n        Gets the \"full name\" of this instance.\n\n        This will be a path composed of instance names from the top-level\n        ancestor of this instance down to itself, in the following format:\n\n        `Ancestor.Child.Descendant.Instance`\n\n        ### See Also\n        * [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)\n          on the Roblox Developer Hub\n    */\n    pub fn get_full_name(&self) -> String {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n        let dom_root = dom.root_ref();\n\n        let mut parts = Vec::new();\n        let mut instance_ref = self.dom_ref;\n\n        while let Some(instance) = dom.get_by_ref(instance_ref) {\n            if instance_ref != dom_root && instance.class != \"DataModel\" {\n                instance_ref = instance.parent();\n                parts.push(instance.name.clone());\n            } else {\n                break;\n            }\n        }\n\n        parts.reverse();\n        parts.join(\".\")\n    }\n\n    /**\n        Finds a child of the instance using the given predicate callback.\n\n        ### See Also\n        * [`FindFirstChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChild) on the Roblox Developer Hub\n        * [`FindFirstChildOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildOfClass) on the Roblox Developer Hub\n        * [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA) on the Roblox Developer Hub\n    */\n    pub fn find_child<F>(&self, predicate: F) -> Option<Instance>\n    where\n        F: Fn(&DomInstance) -> bool,\n    {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let children = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .children()\n            .to_vec();\n\n        let found_ref = children.into_iter().find(|child_ref| {\n            if let Some(child_inst) = dom.get_by_ref(*child_ref) {\n                predicate(child_inst)\n            } else {\n                false\n            }\n        });\n\n        drop(dom); // Self::new needs mutex handle, drop it first\n        found_ref.map(Self::new)\n    }\n\n    /**\n        Finds an ancestor of the instance using the given predicate callback.\n\n        ### See Also\n        * [`FindFirstAncestor`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestor) on the Roblox Developer Hub\n        * [`FindFirstAncestorOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorOfClass) on the Roblox Developer Hub\n        * [`FindFirstAncestorWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorWhichIsA) on the Roblox Developer Hub\n    */\n    pub fn find_ancestor<F>(&self, predicate: F) -> Option<Instance>\n    where\n        F: Fn(&DomInstance) -> bool,\n    {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let mut ancestor_ref = dom\n            .get_by_ref(self.dom_ref)\n            .expect(\"Failed to find instance in document\")\n            .parent();\n\n        while let Some(ancestor) = dom.get_by_ref(ancestor_ref) {\n            if predicate(ancestor) {\n                drop(dom); // Self::new needs mutex handle, drop it first\n                return Some(Self::new(ancestor_ref));\n            }\n            ancestor_ref = ancestor.parent();\n        }\n\n        None\n    }\n\n    /**\n        Finds a descendant of the instance using the given\n        predicate callback and a breadth-first search.\n\n        ### See Also\n        * [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) on the Roblox Developer Hub\n    */\n    pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>\n    where\n        F: Fn(&DomInstance) -> bool,\n    {\n        let dom = INTERNAL_DOM.lock().expect(\"Failed to lock document\");\n\n        let mut queue = VecDeque::from_iter(\n            dom.get_by_ref(self.dom_ref)\n                .expect(\"Failed to find instance in document\")\n                .children(),\n        );\n\n        while let Some(queue_item) = queue\n            .pop_front()\n            .and_then(|queue_ref| dom.get_by_ref(*queue_ref))\n        {\n            if predicate(queue_item) {\n                let queue_ref = queue_item.referent();\n                drop(dom); // Self::new needs mutex handle, drop it first\n                return Some(Self::new(queue_ref));\n            }\n            queue.extend(queue_item.children());\n        }\n\n        None\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nimpl LuaExportsTable for Instance {\n    const EXPORT_NAME: &'static str = \"Instance\";\n\n    fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {\n        let instance_new = |lua: &Lua, class_name: String| {\n            if class_exists(&class_name) {\n                Instance::new_orphaned(class_name).into_lua(lua)\n            } else {\n                Err(LuaError::RuntimeError(format!(\n                    \"Failed to create Instance - '{class_name}' is not a valid class name\",\n                )))\n            }\n        };\n\n        TableBuilder::new(lua)?\n            .with_function(\"new\", instance_new)?\n            .build_readonly()\n    }\n}\n\n/*\n    Here we add inheritance-like behavior for instances by creating\n    fields that are restricted to specific classnames / base classes\n\n    Note that we should try to be conservative with how many classes\n    and methods we support here - we should only implement methods that\n    are necessary for modifying the dom and / or having ergonomic access\n    to the dom, not try to replicate Roblox engine behavior of instances\n\n    If a user wants to replicate Roblox engine behavior, they can use the\n    instance registry, and register properties + methods from the lua side\n*/\n#[cfg(feature = \"mlua\")]\nimpl LuaUserData for Instance {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        data_model::add_fields(fields);\n        workspace::add_fields(fields);\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        base::add_methods(methods);\n        data_model::add_methods(methods);\n        terrain::add_methods(methods);\n    }\n}\n\nimpl Hash for Instance {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        self.dom_ref.hash(state);\n    }\n}\n\nimpl fmt::Display for Instance {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            if self.is_destroyed() {\n                \"<<DESTROYED>>\".to_string()\n            } else {\n                self.get_name()\n            }\n        )\n    }\n}\n\nimpl PartialEq for Instance {\n    fn eq(&self, other: &Self) -> bool {\n        self.dom_ref == other.dom_ref\n    }\n}\n\nimpl From<Instance> for DomRef {\n    fn from(value: Instance) -> Self {\n        value.dom_ref\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/instance/registry.rs",
    "content": "use std::{\n    borrow::Borrow,\n    collections::HashMap,\n    sync::{Arc, Mutex},\n};\n\nuse mlua::{AppDataRef, prelude::*};\nuse thiserror::Error;\n\nuse super::Instance;\n\ntype InstanceRegistryMap = HashMap<String, HashMap<String, LuaRegistryKey>>;\n\n#[derive(Debug, Clone, Error)]\npub enum InstanceRegistryError {\n    #[error(\"class name '{0}' is not valid\")]\n    InvalidClassName(String),\n    #[error(\"class '{class_name}' already registered method '{method_name}'\")]\n    MethodAlreadyExists {\n        class_name: String,\n        method_name: String,\n    },\n    #[error(\"class '{class_name}' already registered property '{property_name}'\")]\n    PropertyAlreadyExists {\n        class_name: String,\n        property_name: String,\n    },\n}\n\n#[derive(Debug, Clone)]\npub struct InstanceRegistry {\n    getters: Arc<Mutex<InstanceRegistryMap>>,\n    setters: Arc<Mutex<InstanceRegistryMap>>,\n    methods: Arc<Mutex<InstanceRegistryMap>>,\n}\n\nimpl InstanceRegistry {\n    // NOTE: We lazily create the instance registry instead\n    // of always creating it together with the roblox builtin\n    // since it is less commonly used and it simplifies some app\n    // data borrowing relationship problems we'd otherwise have\n    fn get_or_create(lua: &Lua) -> AppDataRef<'_, Self> {\n        if lua.app_data_ref::<Self>().is_none() {\n            lua.set_app_data(Self {\n                getters: Arc::new(Mutex::new(HashMap::new())),\n                setters: Arc::new(Mutex::new(HashMap::new())),\n                methods: Arc::new(Mutex::new(HashMap::new())),\n            });\n        }\n        lua.app_data_ref::<Self>()\n            .expect(\"Missing InstanceRegistry in app data\")\n    }\n\n    /**\n        Inserts a method into the instance registry.\n\n        # Errors\n\n        - If the method already exists in the registry.\n    */\n    pub fn insert_method(\n        lua: &Lua,\n        class_name: &str,\n        method_name: &str,\n        method: LuaFunction,\n    ) -> Result<(), InstanceRegistryError> {\n        let registry = Self::get_or_create(lua);\n\n        let mut methods = registry\n            .methods\n            .lock()\n            .expect(\"Failed to lock instance registry methods\");\n\n        let class_methods = methods.entry(class_name.to_string()).or_default();\n        if class_methods.contains_key(method_name) {\n            return Err(InstanceRegistryError::MethodAlreadyExists {\n                class_name: class_name.to_string(),\n                method_name: method_name.to_string(),\n            });\n        }\n\n        let key = lua\n            .create_registry_value(method)\n            .expect(\"Failed to store method in lua registry\");\n        class_methods.insert(method_name.to_string(), key);\n\n        Ok(())\n    }\n\n    /**\n        Inserts a property getter into the instance registry.\n\n        # Errors\n\n        - If the property already exists in the registry.\n    */\n    pub fn insert_property_getter(\n        lua: &Lua,\n        class_name: &str,\n        property_name: &str,\n        property_getter: LuaFunction,\n    ) -> Result<(), InstanceRegistryError> {\n        let registry = Self::get_or_create(lua);\n\n        let mut getters = registry\n            .getters\n            .lock()\n            .expect(\"Failed to lock instance registry getters\");\n\n        let class_getters = getters.entry(class_name.to_string()).or_default();\n        if class_getters.contains_key(property_name) {\n            return Err(InstanceRegistryError::PropertyAlreadyExists {\n                class_name: class_name.to_string(),\n                property_name: property_name.to_string(),\n            });\n        }\n\n        let key = lua\n            .create_registry_value(property_getter)\n            .expect(\"Failed to store getter in lua registry\");\n        class_getters.insert(property_name.to_string(), key);\n\n        Ok(())\n    }\n\n    /**\n        Inserts a property setter into the instance registry.\n\n        # Errors\n\n        - If the property already exists in the registry.\n    */\n    pub fn insert_property_setter(\n        lua: &Lua,\n        class_name: &str,\n        property_name: &str,\n        property_setter: LuaFunction,\n    ) -> Result<(), InstanceRegistryError> {\n        let registry = Self::get_or_create(lua);\n\n        let mut setters = registry\n            .setters\n            .lock()\n            .expect(\"Failed to lock instance registry getters\");\n\n        let class_setters = setters.entry(class_name.to_string()).or_default();\n        if class_setters.contains_key(property_name) {\n            return Err(InstanceRegistryError::PropertyAlreadyExists {\n                class_name: class_name.to_string(),\n                property_name: property_name.to_string(),\n            });\n        }\n\n        let key = lua\n            .create_registry_value(property_setter)\n            .expect(\"Failed to store getter in lua registry\");\n        class_setters.insert(property_name.to_string(), key);\n\n        Ok(())\n    }\n\n    /**\n        Finds a method in the instance registry.\n\n        Returns `None` if the method is not found.\n    */\n    #[must_use]\n    pub fn find_method(lua: &Lua, instance: &Instance, method_name: &str) -> Option<LuaFunction> {\n        let registry = Self::get_or_create(lua);\n        let methods = registry\n            .methods\n            .lock()\n            .expect(\"Failed to lock instance registry methods\");\n\n        class_name_chain(&instance.class_name)\n            .iter()\n            .find_map(|&class_name| {\n                methods\n                    .get(class_name)\n                    .and_then(|class_methods| class_methods.get(method_name))\n                    .map(|key| lua.registry_value::<LuaFunction>(key).unwrap())\n            })\n    }\n\n    /**\n        Finds a property getter in the instance registry.\n\n        Returns `None` if the property getter is not found.\n    */\n    #[must_use]\n    pub fn find_property_getter(\n        lua: &Lua,\n        instance: &Instance,\n        property_name: &str,\n    ) -> Option<LuaFunction> {\n        let registry = Self::get_or_create(lua);\n        let getters = registry\n            .getters\n            .lock()\n            .expect(\"Failed to lock instance registry getters\");\n\n        class_name_chain(&instance.class_name)\n            .iter()\n            .find_map(|&class_name| {\n                getters\n                    .get(class_name)\n                    .and_then(|class_getters| class_getters.get(property_name))\n                    .map(|key| lua.registry_value::<LuaFunction>(key).unwrap())\n            })\n    }\n\n    /**\n        Finds a property setter in the instance registry.\n\n        Returns `None` if the property setter is not found.\n    */\n    #[must_use]\n    pub fn find_property_setter(\n        lua: &Lua,\n        instance: &Instance,\n        property_name: &str,\n    ) -> Option<LuaFunction> {\n        let registry = Self::get_or_create(lua);\n        let setters = registry\n            .setters\n            .lock()\n            .expect(\"Failed to lock instance registry setters\");\n\n        class_name_chain(&instance.class_name)\n            .iter()\n            .find_map(|&class_name| {\n                setters\n                    .get(class_name)\n                    .and_then(|class_setters| class_setters.get(property_name))\n                    .map(|key| lua.registry_value::<LuaFunction>(key).unwrap())\n            })\n    }\n}\n\n/**\n    Gets the class name chain for a given class name.\n\n    The chain starts with the given class name and ends with the root class.\n\n    # Panics\n\n    Panics if the class name is not valid.\n*/\n#[must_use]\npub fn class_name_chain(class_name: &str) -> Vec<&str> {\n    let db = rbx_reflection_database::get().unwrap();\n\n    let mut list = vec![class_name];\n    let mut current_name = class_name;\n\n    loop {\n        let class_descriptor = db\n            .classes\n            .get(current_name)\n            .expect(\"Got invalid class name\");\n        if let Some(sup) = &class_descriptor.superclass {\n            current_name = sup.borrow();\n            list.push(current_name);\n        } else {\n            break;\n        }\n    }\n\n    list\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/instance/terrain.rs",
    "content": "use mlua::prelude::*;\nuse rbx_dom_weak::types::{MaterialColors, TerrainMaterials, Variant};\n\nuse crate::{\n    datatypes::types::{Color3, EnumItem},\n    shared::classes::{add_class_restricted_method, add_class_restricted_method_mut},\n};\n\nuse super::Instance;\n\npub const CLASS_NAME: &str = \"Terrain\";\n\npub fn add_methods<M: LuaUserDataMethods<Instance>>(methods: &mut M) {\n    add_class_restricted_method(\n        methods,\n        CLASS_NAME,\n        \"GetMaterialColor\",\n        terrain_get_material_color,\n    );\n\n    add_class_restricted_method_mut(\n        methods,\n        CLASS_NAME,\n        \"SetMaterialColor\",\n        terrain_set_material_color,\n    );\n}\n\nfn get_or_create_material_colors(instance: &Instance) -> MaterialColors {\n    if let Some(Variant::MaterialColors(inner)) = instance.get_property(\"MaterialColors\") {\n        inner\n    } else {\n        MaterialColors::default()\n    }\n}\n\n/**\n    Returns the color of the given terrain material.\n\n    ### See Also\n    * [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)\n      on the Roblox Developer Hub\n*/\nfn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> LuaResult<Color3> {\n    let material_colors = get_or_create_material_colors(this);\n\n    if &material.parent.desc.name != \"Material\" {\n        return Err(LuaError::RuntimeError(format!(\n            \"Expected Enum.Material, got Enum.{}\",\n            &material.parent.desc.name\n        )));\n    }\n\n    let terrain_material = material\n        .name\n        .parse::<TerrainMaterials>()\n        .map_err(|err| LuaError::RuntimeError(err.to_string()))?;\n\n    Ok(material_colors.get_color(terrain_material).into())\n}\n\n/**\n    Sets the color of the given terrain material.\n\n    ### See Also\n    * [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)\n      on the Roblox Developer Hub\n*/\nfn terrain_set_material_color(\n    _: &Lua,\n    this: &mut Instance,\n    args: (EnumItem, Color3),\n) -> LuaResult<()> {\n    let mut material_colors = get_or_create_material_colors(this);\n    let material = args.0;\n    let color = args.1;\n\n    if &material.parent.desc.name != \"Material\" {\n        return Err(LuaError::RuntimeError(format!(\n            \"Expected Enum.Material, got Enum.{}\",\n            &material.parent.desc.name\n        )));\n    }\n\n    let terrain_material = material\n        .name\n        .parse::<TerrainMaterials>()\n        .map_err(|err| LuaError::RuntimeError(err.to_string()))?;\n\n    material_colors.set_color(terrain_material, color.into());\n    this.set_property(\"MaterialColors\", Variant::MaterialColors(material_colors));\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/instance/workspace.rs",
    "content": "use mlua::prelude::*;\n\nuse crate::shared::classes::{add_class_restricted_getter, get_or_create_property_ref_instance};\n\nuse super::Instance;\n\npub const CLASS_NAME: &str = \"Workspace\";\n\npub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {\n    add_class_restricted_getter(f, CLASS_NAME, \"Terrain\", workspace_get_terrain);\n    add_class_restricted_getter(f, CLASS_NAME, \"CurrentCamera\", workspace_get_camera);\n}\n\n/**\n    Get the terrain parented under this workspace, or create it if it doesn't exist.\n\n    ### See Also\n    * [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)\n      on the Roblox Developer Hub\n*/\nfn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {\n    get_or_create_property_ref_instance(this, \"Terrain\", \"Terrain\")\n}\n\n/**\n    Get the camera parented under this workspace, or create it if it doesn't exist.\n\n    ### See Also\n    * [`CurrentCamera`](https://create.roblox.com/docs/reference/engine/classes/Workspace#CurrentCamera)\n      on the Roblox Developer Hub\n*/\nfn workspace_get_camera(_: &Lua, this: &Instance) -> LuaResult<Instance> {\n    get_or_create_property_ref_instance(this, \"CurrentCamera\", \"Camera\")\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\n#[cfg(feature = \"mlua\")]\nuse lune_utils::TableBuilder;\n\npub mod datatypes;\npub mod document;\npub mod instance;\npub mod reflection;\n\n#[cfg(feature = \"mlua\")]\npub(crate) mod exports;\npub(crate) mod shared;\n\n#[cfg(feature = \"mlua\")]\nuse exports::export;\n\n#[cfg(feature = \"mlua\")]\nfn create_all_exports(lua: Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {\n    use datatypes::types::*;\n    use instance::Instance;\n    Ok(vec![\n        // Datatypes\n        export::<Axes>(lua.clone())?,\n        export::<BrickColor>(lua.clone())?,\n        export::<CFrame>(lua.clone())?,\n        export::<Color3>(lua.clone())?,\n        export::<ColorSequence>(lua.clone())?,\n        export::<ColorSequenceKeypoint>(lua.clone())?,\n        export::<Content>(lua.clone())?,\n        export::<Faces>(lua.clone())?,\n        export::<Font>(lua.clone())?,\n        export::<NumberRange>(lua.clone())?,\n        export::<NumberSequence>(lua.clone())?,\n        export::<NumberSequenceKeypoint>(lua.clone())?,\n        export::<PhysicalProperties>(lua.clone())?,\n        export::<Ray>(lua.clone())?,\n        export::<Rect>(lua.clone())?,\n        export::<UDim>(lua.clone())?,\n        export::<UDim2>(lua.clone())?,\n        export::<UniqueId>(lua.clone())?,\n        export::<Region3>(lua.clone())?,\n        export::<Region3int16>(lua.clone())?,\n        export::<Vector2>(lua.clone())?,\n        export::<Vector2int16>(lua.clone())?,\n        export::<Vector3>(lua.clone())?,\n        export::<Vector3int16>(lua.clone())?,\n        // Classes\n        export::<Instance>(lua.clone())?,\n        // Singletons\n        (\"Enum\", Enums.into_lua(&lua)?),\n    ])\n}\n\n/**\n    Creates a table containing all the Roblox datatypes, classes, and singletons.\n\n    Note that this is not guaranteed to contain any value unless indexed directly,\n    it may be optimized to use lazy initialization in the future.\n\n    # Errors\n\n    Errors when out of memory or when a value cannot be created.\n*/\n#[cfg(feature = \"mlua\")]\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    // FUTURE: We can probably create these lazily as users\n    // index the main exports (this return value) table and\n    // save some memory and startup time. The full exports\n    // table is quite big and probably won't get any smaller\n    // since we impl all roblox constructors for each datatype.\n    let exports = create_all_exports(lua.clone())?;\n    TableBuilder::new(lua)?\n        .with_values(exports)?\n        .build_readonly()\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/reflection/class.rs",
    "content": "use core::fmt;\nuse std::collections::HashMap;\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\nuse rbx_dom_weak::types::Variant as DomVariant;\nuse rbx_reflection::ClassDescriptor;\n\n#[cfg(feature = \"mlua\")]\nuse rbx_reflection::DataType;\n\nuse super::{property::DatabaseProperty, utils::*};\n\n#[cfg(feature = \"mlua\")]\nuse crate::datatypes::{\n    conversion::DomValueToLua, types::EnumItem, userdata_impl_eq, userdata_impl_to_string,\n};\n\ntype DbClass = &'static ClassDescriptor<'static>;\n\n/**\n    A wrapper for [`rbx_reflection::ClassDescriptor`] that\n    also provides access to the class descriptor from lua.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct DatabaseClass(DbClass);\n\nimpl DatabaseClass {\n    pub(crate) fn new(inner: DbClass) -> Self {\n        Self(inner)\n    }\n\n    /**\n        Get the name of this class.\n    */\n    #[must_use]\n    pub fn get_name(&self) -> String {\n        self.0.name.to_string()\n    }\n\n    /**\n        Get the superclass (parent class) of this class.\n\n        May be `None` if no parent class exists.\n    */\n    #[must_use]\n    pub fn get_superclass(&self) -> Option<String> {\n        let sup = self.0.superclass.as_ref()?;\n        Some(sup.to_string())\n    }\n\n    /**\n        Get all known properties for this class.\n    */\n    #[must_use]\n    pub fn get_properties(&self) -> HashMap<String, DatabaseProperty> {\n        self.0\n            .properties\n            .iter()\n            .map(|(name, prop)| (name.to_string(), DatabaseProperty::new(self.0, prop)))\n            .collect()\n    }\n\n    /**\n        Get all default values for properties of this class.\n    */\n    #[must_use]\n    pub fn get_defaults(&self) -> HashMap<String, DomVariant> {\n        self.0\n            .default_properties\n            .iter()\n            .map(|(name, prop)| (name.to_string(), prop.clone()))\n            .collect()\n    }\n\n    /**\n        Get all tags describing the class.\n\n        These include information such as if the class can be replicated\n        to players at runtime, and top-level class categories.\n    */\n    pub fn get_tags_str(&self) -> Vec<&'static str> {\n        self.0\n            .tags\n            .iter()\n            .copied()\n            .map(class_tag_to_str)\n            .collect::<Vec<_>>()\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nimpl LuaUserData for DatabaseClass {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Name\", |_, this| Ok(this.get_name()));\n        fields.add_field_method_get(\"Superclass\", |_, this| Ok(this.get_superclass()));\n        fields.add_field_method_get(\"Properties\", |_, this| Ok(this.get_properties()));\n        fields.add_field_method_get(\"DefaultProperties\", |lua, this| {\n            let defaults = this.get_defaults();\n            let mut map = HashMap::with_capacity(defaults.len());\n            for (name, prop) in defaults {\n                let value = if let DomVariant::Enum(enum_value) = prop {\n                    make_enum_value(this.0, &name, enum_value.to_u32())\n                        .and_then(|e| e.into_lua(lua))\n                } else {\n                    LuaValue::dom_value_to_lua(lua, &prop).into_lua_err()\n                };\n                if let Ok(value) = value {\n                    map.insert(name, value);\n                }\n            }\n            Ok(map)\n        });\n        fields.add_field_method_get(\"Tags\", |_, this| Ok(this.get_tags_str()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl PartialEq for DatabaseClass {\n    fn eq(&self, other: &Self) -> bool {\n        self.0.name == other.0.name\n    }\n}\n\nimpl fmt::Display for DatabaseClass {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"ReflectionDatabaseClass({})\", self.0.name)\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nfn find_enum_name(inner: DbClass, name: impl AsRef<str>) -> Option<String> {\n    inner.properties.iter().find_map(|(prop_name, prop_info)| {\n        if prop_name == name.as_ref() {\n            if let DataType::Enum(enum_name) = &prop_info.data_type {\n                Some(enum_name.to_string())\n            } else {\n                None\n            }\n        } else {\n            None\n        }\n    })\n}\n\n#[cfg(feature = \"mlua\")]\nfn make_enum_value(inner: DbClass, name: impl AsRef<str>, value: u32) -> LuaResult<EnumItem> {\n    let name = name.as_ref();\n    let enum_name = find_enum_name(inner, name).ok_or_else(|| {\n        LuaError::RuntimeError(format!(\n            \"Failed to get default property '{name}' - No enum descriptor was found\",\n        ))\n    })?;\n    EnumItem::from_enum_name_and_value(&enum_name, value).ok_or_else(|| {\n        LuaError::RuntimeError(format!(\n            \"Failed to get default property '{name}' - Enum.{enum_name} does not contain numeric value {value}\",\n        ))\n    })\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/reflection/enums.rs",
    "content": "use std::{collections::HashMap, fmt};\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\nuse rbx_reflection::EnumDescriptor;\n\n#[cfg(feature = \"mlua\")]\nuse crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};\n\ntype DbEnum = &'static EnumDescriptor<'static>;\n\n/**\n    A wrapper for [`rbx_reflection::EnumDescriptor`] that\n    also provides access to the class descriptor from lua.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct DatabaseEnum(DbEnum);\n\nimpl DatabaseEnum {\n    pub(crate) fn new(inner: DbEnum) -> Self {\n        Self(inner)\n    }\n\n    /**\n        Get the name of this enum.\n    */\n    #[must_use]\n    pub fn get_name(&self) -> String {\n        self.0.name.to_string()\n    }\n\n    /**\n        Get all known members of this enum.\n\n        Note that this is a direct map of name -> enum values,\n        and does not actually use the `EnumItem` datatype itself.\n    */\n    #[must_use]\n    pub fn get_items(&self) -> HashMap<String, u32> {\n        self.0\n            .items\n            .iter()\n            .map(|(k, v)| (k.to_string(), *v))\n            .collect()\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nimpl LuaUserData for DatabaseEnum {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Name\", |_, this| Ok(this.get_name()));\n        fields.add_field_method_get(\"Items\", |_, this| Ok(this.get_items()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl PartialEq for DatabaseEnum {\n    fn eq(&self, other: &Self) -> bool {\n        self.0.name == other.0.name\n    }\n}\n\nimpl fmt::Display for DatabaseEnum {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"ReflectionDatabaseEnum({})\", self.0.name)\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/reflection/mod.rs",
    "content": "use std::fmt;\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\nuse rbx_reflection::ReflectionDatabase;\n\nmod class;\nmod enums;\nmod property;\nmod utils;\n\npub use class::DatabaseClass;\npub use enums::DatabaseEnum;\npub use property::DatabaseProperty;\n\n#[cfg(feature = \"mlua\")]\nuse super::datatypes::{userdata_impl_eq, userdata_impl_to_string};\n\ntype Db = &'static ReflectionDatabase<'static>;\n\n/**\n    A wrapper for [`rbx_reflection::ReflectionDatabase`] that\n    also provides access to the reflection database from lua.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct Database(Db);\n\nimpl Database {\n    /**\n        Creates a new database struct, referencing the bundled reflection database.\n    */\n    #[must_use]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /**\n        Get the version string of the database.\n\n        This will follow the format `x.y.z.w`, which most\n        commonly looks something like `0.567.0.123456789`.\n    */\n    #[must_use]\n    pub fn get_version(&self) -> String {\n        let [x, y, z, w] = self.0.version;\n        format!(\"{x}.{y}.{z}.{w}\")\n    }\n\n    /**\n        Retrieves a list of all currently known enum names.\n    */\n    #[must_use]\n    pub fn get_enum_names(&self) -> Vec<String> {\n        self.0.enums.keys().map(ToString::to_string).collect()\n    }\n\n    /**\n        Retrieves a list of all currently known class names.\n    */\n    #[must_use]\n    pub fn get_class_names(&self) -> Vec<String> {\n        self.0.classes.keys().map(ToString::to_string).collect()\n    }\n\n    /**\n        Gets an enum with the exact given name, if one exists.\n    */\n    pub fn get_enum(&self, name: impl AsRef<str>) -> Option<DatabaseEnum> {\n        let e = self.0.enums.get(name.as_ref())?;\n        Some(DatabaseEnum::new(e))\n    }\n\n    /**\n        Gets a class with the exact given name, if one exists.\n    */\n    pub fn get_class(&self, name: impl AsRef<str>) -> Option<DatabaseClass> {\n        let c = self.0.classes.get(name.as_ref())?;\n        Some(DatabaseClass::new(c))\n    }\n\n    /**\n        Finds an enum with the given name.\n\n        This will use case-insensitive matching and ignore leading and trailing whitespace.\n    */\n    pub fn find_enum(&self, name: impl AsRef<str>) -> Option<DatabaseEnum> {\n        let name = name.as_ref().trim().to_lowercase();\n        let (ename, _) = self\n            .0\n            .enums\n            .iter()\n            .find(|(ename, _)| ename.trim().to_lowercase() == name)?;\n        self.get_enum(ename)\n    }\n\n    /**\n        Finds a class with the given name.\n\n        This will use case-insensitive matching and ignore leading and trailing whitespace.\n    */\n    pub fn find_class(&self, name: impl AsRef<str>) -> Option<DatabaseClass> {\n        let name = name.as_ref().trim().to_lowercase();\n        let (cname, _) = self\n            .0\n            .classes\n            .iter()\n            .find(|(cname, _)| cname.trim().to_lowercase() == name)?;\n        self.get_class(cname)\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nimpl LuaUserData for Database {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Version\", |_, this| Ok(this.get_version()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n        methods.add_method(\"GetEnumNames\", |_, this, (): ()| Ok(this.get_enum_names()));\n        methods.add_method(\n            \"GetClassNames\",\n            |_, this, (): ()| Ok(this.get_class_names()),\n        );\n        methods.add_method(\"GetEnum\", |_, this, name: String| Ok(this.get_enum(name)));\n        methods.add_method(\"GetClass\", |_, this, name: String| Ok(this.get_class(name)));\n        methods.add_method(\"FindEnum\", |_, this, name: String| Ok(this.find_enum(name)));\n        methods.add_method(\"FindClass\", |_, this, name: String| {\n            Ok(this.find_class(name))\n        });\n    }\n}\n\nimpl Default for Database {\n    fn default() -> Self {\n        Self(rbx_reflection_database::get().unwrap())\n    }\n}\n\nimpl PartialEq for Database {\n    fn eq(&self, _other: &Self) -> bool {\n        true // All database userdatas refer to the same underlying rbx-dom database\n    }\n}\n\nimpl fmt::Display for Database {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"ReflectionDatabase\")\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/reflection/property.rs",
    "content": "use std::fmt;\n\n#[cfg(feature = \"mlua\")]\nuse mlua::prelude::*;\n\nuse rbx_reflection::{ClassDescriptor, PropertyDescriptor};\n\n#[cfg(feature = \"mlua\")]\nuse crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};\n\nuse super::utils::*;\n\ntype DbClass = &'static ClassDescriptor<'static>;\ntype DbProp = &'static PropertyDescriptor<'static>;\n\n/**\n    A wrapper for [`rbx_reflection::PropertyDescriptor`] that\n    also provides access to the property descriptor from lua.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct DatabaseProperty(DbClass, DbProp);\n\nimpl DatabaseProperty {\n    pub(crate) fn new(inner: DbClass, inner_prop: DbProp) -> Self {\n        Self(inner, inner_prop)\n    }\n\n    /**\n        Get the name of this property.\n    */\n    #[must_use]\n    pub fn get_name(&self) -> String {\n        self.1.name.to_string()\n    }\n\n    /**\n        Get the datatype name of the property.\n\n        For normal datatypes this will be a string such as `string`, `Color3`, ...\n\n        For enums this will be a string formatted as `Enum.EnumName`.\n    */\n    #[must_use]\n    pub fn get_datatype_name(&self) -> String {\n        data_type_to_str(self.1.data_type.clone())\n    }\n\n    /**\n        Get the scriptability of this property, meaning if it can be written / read at runtime.\n\n        All properties are writable and readable in Lune even if scriptability is not.\n    */\n    #[must_use]\n    pub fn get_scriptability_str(&self) -> &'static str {\n        scriptability_to_str(self.1.scriptability)\n    }\n\n    /**\n        Get all known tags describing the property.\n\n        These include information such as if the property can be replicated to players\n        at runtime, if the property should be hidden in Roblox Studio, and more.\n    */\n    pub fn get_tags_str(&self) -> Vec<&'static str> {\n        self.1\n            .tags\n            .iter()\n            .copied()\n            .map(property_tag_to_str)\n            .collect::<Vec<_>>()\n    }\n}\n\n#[cfg(feature = \"mlua\")]\nimpl LuaUserData for DatabaseProperty {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"Name\", |_, this| Ok(this.get_name()));\n        fields.add_field_method_get(\"Datatype\", |_, this| Ok(this.get_datatype_name()));\n        fields.add_field_method_get(\"Scriptability\", |_, this| Ok(this.get_scriptability_str()));\n        fields.add_field_method_get(\"Tags\", |_, this| Ok(this.get_tags_str()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);\n        methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);\n    }\n}\n\nimpl PartialEq for DatabaseProperty {\n    fn eq(&self, other: &Self) -> bool {\n        self.0.name == other.0.name && self.1.name == other.1.name\n    }\n}\n\nimpl fmt::Display for DatabaseProperty {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"ReflectionDatabaseProperty({} > {})\",\n            self.0.name, self.1.name\n        )\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/reflection/utils.rs",
    "content": "use rbx_reflection::{ClassTag, DataType, PropertyTag, Scriptability};\n\nuse crate::datatypes::extension::DomValueExt;\n\npub fn data_type_to_str(data_type: DataType) -> String {\n    match data_type {\n        DataType::Enum(e) => format!(\"Enum.{e}\"),\n        DataType::Value(v) => v\n            .variant_name()\n            .expect(\"Encountered unknown data type variant\")\n            .to_string(),\n        _ => panic!(\"Encountered unknown data type\"),\n    }\n}\n\n/*\n     NOTE: Remember to add any new strings here to typedefs too!\n*/\n\npub fn scriptability_to_str(scriptability: Scriptability) -> &'static str {\n    match scriptability {\n        Scriptability::None => \"None\",\n        Scriptability::Custom => \"Custom\",\n        Scriptability::Read => \"Read\",\n        Scriptability::ReadWrite => \"ReadWrite\",\n        Scriptability::Write => \"Write\",\n        _ => panic!(\"Encountered unknown scriptability\"),\n    }\n}\n\npub fn property_tag_to_str(tag: PropertyTag) -> &'static str {\n    match tag {\n        PropertyTag::Deprecated => \"Deprecated\",\n        PropertyTag::Hidden => \"Hidden\",\n        PropertyTag::NotBrowsable => \"NotBrowsable\",\n        PropertyTag::NotReplicated => \"NotReplicated\",\n        PropertyTag::NotScriptable => \"NotScriptable\",\n        PropertyTag::ReadOnly => \"ReadOnly\",\n        PropertyTag::WriteOnly => \"WriteOnly\",\n        _ => panic!(\"Encountered unknown property tag\"),\n    }\n}\n\npub fn class_tag_to_str(tag: ClassTag) -> &'static str {\n    match tag {\n        ClassTag::Deprecated => \"Deprecated\",\n        ClassTag::NotBrowsable => \"NotBrowsable\",\n        ClassTag::NotCreatable => \"NotCreatable\",\n        ClassTag::NotReplicated => \"NotReplicated\",\n        ClassTag::PlayerReplicated => \"PlayerReplicated\",\n        ClassTag::Service => \"Service\",\n        ClassTag::Settings => \"Settings\",\n        ClassTag::UserSettings => \"UserSettings\",\n        _ => panic!(\"Encountered unknown class tag\"),\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/shared/classes.rs",
    "content": "use mlua::prelude::*;\n\nuse rbx_dom_weak::types::Variant as DomValue;\n\nuse crate::instance::Instance;\n\nuse super::instance::class_is_a;\n\npub(crate) fn add_class_restricted_getter<F: LuaUserDataFields<Instance>, R, G>(\n    fields: &mut F,\n    class_name: &'static str,\n    field_name: &'static str,\n    field_getter: G,\n) where\n    R: IntoLua,\n    G: 'static + Fn(&Lua, &Instance) -> LuaResult<R>,\n{\n    fields.add_field_method_get(field_name, move |lua, this| {\n        if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {\n            field_getter(lua, this)\n        } else {\n            Err(LuaError::RuntimeError(format!(\n                \"{field_name} is not a valid member of {class_name}\",\n            )))\n        }\n    });\n}\n\n#[allow(dead_code)]\npub(crate) fn add_class_restricted_setter<F: LuaUserDataFields<Instance>, A, G>(\n    fields: &mut F,\n    class_name: &'static str,\n    field_name: &'static str,\n    field_getter: G,\n) where\n    A: FromLua,\n    G: 'static + Fn(&Lua, &Instance, A) -> LuaResult<()>,\n{\n    fields.add_field_method_set(field_name, move |lua, this, value| {\n        if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {\n            field_getter(lua, this, value)\n        } else {\n            Err(LuaError::RuntimeError(format!(\n                \"{field_name} is not a valid member of {class_name}\",\n            )))\n        }\n    });\n}\n\npub(crate) fn add_class_restricted_method<M: LuaUserDataMethods<Instance>, A, R, F>(\n    methods: &mut M,\n    class_name: &'static str,\n    method_name: &'static str,\n    method: F,\n) where\n    A: FromLuaMulti,\n    R: IntoLuaMulti,\n    F: 'static + Fn(&Lua, &Instance, A) -> LuaResult<R>,\n{\n    methods.add_method(method_name, move |lua, this, args| {\n        if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {\n            method(lua, this, args)\n        } else {\n            Err(LuaError::RuntimeError(format!(\n                \"{method_name} is not a valid member of {class_name}\",\n            )))\n        }\n    });\n}\n\npub(crate) fn add_class_restricted_method_mut<M: LuaUserDataMethods<Instance>, A, R, F>(\n    methods: &mut M,\n    class_name: &'static str,\n    method_name: &'static str,\n    method: F,\n) where\n    A: FromLuaMulti,\n    R: IntoLuaMulti,\n    F: 'static + Fn(&Lua, &mut Instance, A) -> LuaResult<R>,\n{\n    methods.add_method_mut(method_name, move |lua, this, args| {\n        if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {\n            method(lua, this, args)\n        } else {\n            Err(LuaError::RuntimeError(format!(\n                \"{method_name} is not a valid member of {class_name}\",\n            )))\n        }\n    });\n}\n\n/**\n    Gets or creates the instance child with the given reference prop name and class name.\n\n    Note that the class name here must be an exact match, it is not checked using `IsA`.\n\n    The instance may be in one of several states but this function will guarantee that the\n    property reference is correct and that the instance exists after it has been called:\n\n    1. Instance exists as property ref - just return it\n    2. Instance exists under workspace but not as a property ref - save it and return it\n    3. Instance does not exist - create it, save it, then return it\n*/\npub(crate) fn get_or_create_property_ref_instance(\n    this: &Instance,\n    prop_name: &'static str,\n    class_name: &'static str,\n) -> LuaResult<Instance> {\n    if let Some(DomValue::Ref(inst_ref)) = this.get_property(prop_name)\n        && let Some(inst) = Instance::new_opt(inst_ref)\n    {\n        return Ok(inst);\n    }\n    if let Some(inst) = this.find_child(|child| child.class == class_name) {\n        this.set_property(prop_name, DomValue::Ref(inst.dom_ref));\n        Ok(inst)\n    } else {\n        let inst = Instance::new_orphaned(class_name);\n        inst.set_parent(Some(*this));\n        this.set_property(prop_name, DomValue::Ref(inst.dom_ref));\n        Ok(inst)\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/shared/instance.rs",
    "content": "#![allow(dead_code)]\n\nuse std::borrow::{Borrow, BorrowMut, Cow};\n\nuse rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};\nuse rbx_reflection::{ClassTag, DataType};\n\n#[derive(Debug, Clone, Default)]\npub(crate) struct PropertyInfo {\n    pub enum_name: Option<Cow<'static, str>>,\n    pub enum_default: Option<u32>,\n    pub value_type: Option<DomType>,\n    pub value_default: Option<&'static DomValue>,\n}\n\n/**\n    Finds the info of a property of the given class.\n\n    This will also check superclasses if the property\n    was not directly found for the given class.\n\n    Returns `None` if the class or property does not exist.\n*/\npub(crate) fn find_property_info(\n    instance_class: impl AsRef<str>,\n    property_name: impl AsRef<str>,\n) -> Option<PropertyInfo> {\n    let db = rbx_reflection_database::get().unwrap();\n\n    let instance_class = instance_class.as_ref();\n    let property_name = property_name.as_ref();\n\n    // Attributes and tags are *technically* properties but we don't\n    // want to treat them as such when looking up property info, any\n    // reading or modification of these should always be explicit\n    if matches!(property_name, \"Attributes\" | \"Tags\") {\n        return None;\n    }\n\n    // FUTURE: We can probably cache the result of calling this\n    // function, if property access is being used in a tight loop\n    // in a build step or something similar then it would be beneficial\n\n    let mut class_name = Cow::Borrowed(instance_class);\n    let mut class_info = None;\n\n    while let Some(class) = db.classes.get(class_name.as_ref()) {\n        if let Some(prop_definition) = class.properties.get(property_name) {\n            /*\n                We found a property, create a property info containing name/type\n\n                Note that we might have found the property in the\n                base class but the default value can be part of\n                some separate class, it will be checked below\n            */\n            class_info = Some(match &prop_definition.data_type {\n                DataType::Enum(enum_name) => PropertyInfo {\n                    enum_name: Some(Cow::Borrowed(enum_name)),\n                    ..Default::default()\n                },\n                DataType::Value(value_type) => PropertyInfo {\n                    value_type: Some(*value_type),\n                    ..Default::default()\n                },\n                _ => PropertyInfo::default(),\n            });\n            break;\n        } else if let Some(sup) = &class.superclass {\n            // No property found, we should look at the superclass\n            class_name = Cow::Borrowed(sup);\n        } else {\n            break;\n        }\n    }\n\n    if let Some(class_info) = class_info.borrow_mut() {\n        class_name = Cow::Borrowed(instance_class);\n        while let Some(class) = db.classes.get(class_name.as_ref()) {\n            if let Some(default) = class.default_properties.get(property_name) {\n                // We found a default value, map it to a more useful value for us\n                if class_info.enum_name.is_some() {\n                    class_info.enum_default = match default {\n                        DomValue::Enum(enum_default) => Some(enum_default.to_u32()),\n                        _ => None,\n                    };\n                } else if class_info.value_type.is_some() {\n                    class_info.value_default = Some(default);\n                }\n                break;\n            } else if let Some(sup) = &class.superclass {\n                // No default value found, we should look at the superclass\n                class_name = Cow::Borrowed(sup);\n            } else {\n                break;\n            }\n        }\n    }\n\n    class_info\n}\n\n/**\n    Checks if an instance class exists in the reflection database.\n*/\npub fn class_exists(class_name: impl AsRef<str>) -> bool {\n    let db = rbx_reflection_database::get().unwrap();\n    db.classes.contains_key(class_name.as_ref())\n}\n\n/**\n    Checks if an instance class matches a given class or superclass, similar to\n    [Instance::IsA](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)\n    from the Roblox standard library.\n\n    Note that this function may return `None` if it encounters a class or superclass\n    that does not exist in the currently known class reflection database.\n*/\npub fn class_is_a(instance_class: impl AsRef<str>, class_name: impl AsRef<str>) -> Option<bool> {\n    let mut instance_class = instance_class.as_ref();\n    let class_name = class_name.as_ref();\n\n    if class_name == \"Instance\" || instance_class == class_name {\n        Some(true)\n    } else {\n        let db = rbx_reflection_database::get().unwrap();\n\n        while instance_class != class_name {\n            let class_descriptor = db.classes.get(instance_class)?;\n            if let Some(sup) = &class_descriptor.superclass {\n                instance_class = sup.borrow();\n            } else {\n                return Some(false);\n            }\n        }\n\n        Some(true)\n    }\n}\n\n/**\n    Checks if an instance class is a service.\n\n    This is separate from [`class_is_a`] since services do not share a\n    common base class, and are instead determined through reflection tags.\n\n    Note that this function may return `None` if it encounters a class or superclass\n    that does not exist in the currently known class reflection database.\n*/\npub fn class_is_a_service(instance_class: impl AsRef<str>) -> Option<bool> {\n    let mut instance_class = instance_class.as_ref();\n\n    let db = rbx_reflection_database::get().unwrap();\n\n    loop {\n        let class_descriptor = db.classes.get(instance_class)?;\n        if class_descriptor.tags.contains(&ClassTag::Service) {\n            return Some(true);\n        } else if let Some(sup) = &class_descriptor.superclass {\n            instance_class = sup.borrow();\n        } else {\n            break;\n        }\n    }\n\n    Some(false)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn is_a_class_valid() {\n        assert_eq!(class_is_a(\"Part\", \"Part\"), Some(true));\n        assert_eq!(class_is_a(\"Part\", \"BasePart\"), Some(true));\n        assert_eq!(class_is_a(\"Part\", \"PVInstance\"), Some(true));\n        assert_eq!(class_is_a(\"Part\", \"Instance\"), Some(true));\n\n        assert_eq!(class_is_a(\"Workspace\", \"Workspace\"), Some(true));\n        assert_eq!(class_is_a(\"Workspace\", \"Model\"), Some(true));\n        assert_eq!(class_is_a(\"Workspace\", \"Instance\"), Some(true));\n    }\n\n    #[test]\n    fn is_a_class_invalid() {\n        assert_eq!(class_is_a(\"Part\", \"part\"), Some(false));\n        assert_eq!(class_is_a(\"Part\", \"Base-Part\"), Some(false));\n        assert_eq!(class_is_a(\"Part\", \"Model\"), Some(false));\n        assert_eq!(class_is_a(\"Part\", \"Paart\"), Some(false));\n\n        assert_eq!(class_is_a(\"Workspace\", \"Service\"), Some(false));\n        assert_eq!(class_is_a(\"Workspace\", \".\"), Some(false));\n        assert_eq!(class_is_a(\"Workspace\", \"\"), Some(false));\n    }\n\n    #[test]\n    fn is_a_service_valid() {\n        assert_eq!(class_is_a_service(\"Workspace\"), Some(true));\n        assert_eq!(class_is_a_service(\"PhysicsService\"), Some(true));\n        assert_eq!(class_is_a_service(\"ReplicatedFirst\"), Some(true));\n        assert_eq!(class_is_a_service(\"CSGDictionaryService\"), Some(true));\n    }\n\n    #[test]\n    fn is_a_service_invalid() {\n        assert_eq!(class_is_a_service(\"Camera\"), Some(false));\n        assert_eq!(class_is_a_service(\"Terrain\"), Some(false));\n        assert_eq!(class_is_a_service(\"Work-space\"), None);\n        assert_eq!(class_is_a_service(\"CSG Dictionary Service\"), None);\n    }\n}\n"
  },
  {
    "path": "crates/lune-roblox/src/shared/mod.rs",
    "content": "pub(crate) mod instance;\n\n#[cfg(feature = \"mlua\")]\npub(crate) mod classes;\n\n#[cfg(feature = \"mlua\")]\npub(crate) mod userdata;\n"
  },
  {
    "path": "crates/lune-roblox/src/shared/userdata.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n\nuse std::{any::type_name, cell::RefCell, fmt, ops};\n\nuse mlua::prelude::*;\n\n// Utility functions\n\ntype ListWriter = dyn Fn(&mut fmt::Formatter<'_>, bool, &str) -> fmt::Result;\n\n#[must_use]\npub fn make_list_writer() -> Box<ListWriter> {\n    let first = RefCell::new(true);\n    Box::new(move |f, flag, literal| {\n        if flag {\n            if first.take() {\n                write!(f, \"{literal}\")?;\n            } else {\n                write!(f, \", {literal}\")?;\n            }\n        }\n        Ok::<_, fmt::Error>(())\n    })\n}\n\n/*\n    Userdata metamethod implementations\n\n    Note that many of these return [`LuaResult`] even though they don't\n    return any errors - this is for consistency reasons and to make it\n    easier to add these blanket implementations to [`LuaUserData`] impls.\n*/\n\npub fn userdata_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>\nwhere\n    D: LuaUserData + ToString + 'static,\n{\n    Ok(datatype.to_string())\n}\n\npub fn userdata_impl_eq<D>(_: &Lua, datatype: &D, value: LuaValue) -> LuaResult<bool>\nwhere\n    D: LuaUserData + PartialEq + 'static,\n{\n    if let LuaValue::UserData(ud) = value {\n        if let Ok(value_as_datatype) = ud.borrow::<D>() {\n            Ok(*datatype == *value_as_datatype)\n        } else {\n            Ok(false)\n        }\n    } else {\n        Ok(false)\n    }\n}\n\npub fn userdata_impl_unm<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Neg<Output = D> + Copy,\n{\n    Ok(-*datatype)\n}\n\npub fn userdata_impl_add<D>(_: &Lua, datatype: &D, value: LuaUserDataRef<D>) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Add<Output = D> + Copy,\n{\n    Ok(*datatype + *value)\n}\n\npub fn userdata_impl_sub<D>(_: &Lua, datatype: &D, value: LuaUserDataRef<D>) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Sub<Output = D> + Copy,\n{\n    Ok(*datatype - *value)\n}\n\npub fn userdata_impl_mul_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Mul<D, Output = D> + ops::Mul<f32, Output = D> + Copy + 'static,\n{\n    match &rhs {\n        LuaValue::Number(n) => return Ok(*datatype * *n as f32),\n        LuaValue::Integer(i) => return Ok(*datatype * *i as f32),\n        LuaValue::UserData(ud) => {\n            if let Ok(vec) = ud.borrow::<D>() {\n                return Ok(*datatype * *vec);\n            }\n        }\n        _ => {}\n    }\n    Err(LuaError::FromLuaConversionError {\n        from: rhs.type_name(),\n        to: type_name::<D>().to_string(),\n        message: Some(format!(\n            \"Expected {} or number, got {}\",\n            type_name::<D>(),\n            rhs.type_name()\n        )),\n    })\n}\n\npub fn userdata_impl_mul_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Mul<D, Output = D> + ops::Mul<i32, Output = D> + Copy + 'static,\n{\n    match &rhs {\n        LuaValue::Number(n) => return Ok(*datatype * *n as i32),\n        LuaValue::Integer(i) => return Ok(*datatype * *i as i32),\n        LuaValue::UserData(ud) => {\n            if let Ok(vec) = ud.borrow::<D>() {\n                return Ok(*datatype * *vec);\n            }\n        }\n        _ => {}\n    }\n    Err(LuaError::FromLuaConversionError {\n        from: rhs.type_name(),\n        to: type_name::<D>().to_string(),\n        message: Some(format!(\n            \"Expected {} or number, got {}\",\n            type_name::<D>(),\n            rhs.type_name()\n        )),\n    })\n}\n\npub fn userdata_impl_div_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Div<D, Output = D> + ops::Div<f32, Output = D> + Copy + 'static,\n{\n    match &rhs {\n        LuaValue::Number(n) => return Ok(*datatype / *n as f32),\n        LuaValue::Integer(i) => return Ok(*datatype / *i as f32),\n        LuaValue::UserData(ud) => {\n            if let Ok(vec) = ud.borrow::<D>() {\n                return Ok(*datatype / *vec);\n            }\n        }\n        _ => {}\n    }\n    Err(LuaError::FromLuaConversionError {\n        from: rhs.type_name(),\n        to: type_name::<D>().to_string(),\n        message: Some(format!(\n            \"Expected {} or number, got {}\",\n            type_name::<D>(),\n            rhs.type_name()\n        )),\n    })\n}\n\npub trait IDiv<Rhs = Self> {\n    type Output;\n    #[must_use]\n    fn idiv(self, rhs: Rhs) -> Self::Output;\n}\n\npub fn userdata_impl_idiv_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>\nwhere\n    D: LuaUserData + IDiv<D, Output = D> + IDiv<f32, Output = D> + Copy + 'static,\n{\n    match &rhs {\n        LuaValue::Number(n) => return Ok(datatype.idiv(*n as f32)),\n        LuaValue::Integer(i) => return Ok(datatype.idiv(*i as f32)),\n        LuaValue::UserData(ud) => {\n            if let Ok(vec) = ud.borrow::<D>() {\n                return Ok(datatype.idiv(*vec));\n            }\n        }\n        _ => {}\n    }\n    Err(LuaError::FromLuaConversionError {\n        from: rhs.type_name(),\n        to: type_name::<D>().to_string(),\n        message: Some(format!(\n            \"Expected {} or number, got {}\",\n            type_name::<D>(),\n            rhs.type_name()\n        )),\n    })\n}\n\npub fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>\nwhere\n    D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,\n{\n    match &rhs {\n        LuaValue::Number(n) => return Ok(*datatype / *n as i32),\n        LuaValue::Integer(i) => return Ok(*datatype / *i as i32),\n        LuaValue::UserData(ud) => {\n            if let Ok(vec) = ud.borrow::<D>() {\n                return Ok(*datatype / *vec);\n            }\n        }\n        _ => {}\n    }\n    Err(LuaError::FromLuaConversionError {\n        from: rhs.type_name(),\n        to: type_name::<D>().to_string(),\n        message: Some(format!(\n            \"Expected {} or number, got {}\",\n            type_name::<D>(),\n            rhs.type_name()\n        )),\n    })\n}\n"
  },
  {
    "path": "crates/lune-std/Cargo.toml",
    "content": "[package]\nname = \"lune-std\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[features]\ndefault = [\n    \"datetime\",\n    \"fs\",\n    \"luau\",\n    \"net\",\n    \"process\",\n    \"regex\",\n    \"roblox\",\n    \"serde\",\n    \"stdio\",\n    \"task\",\n]\n\ndatetime = [\"dep:lune-std-datetime\"]\nfs = [\"dep:lune-std-fs\"]\nluau = [\"dep:lune-std-luau\"]\nnet = [\"dep:lune-std-net\"]\nprocess = [\"dep:lune-std-process\"]\nregex = [\"dep:lune-std-regex\"]\nroblox = [\"dep:lune-std-roblox\"]\nserde = [\"dep:lune-std-serde\"]\nstdio = [\"dep:lune-std-stdio\"]\ntask = [\"dep:lune-std-task\"]\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\nasync-channel = \"2.3\"\nasync-fs = \"2.1\"\nasync-lock = \"3.4\"\n\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n\nlune-std-datetime = { optional = true, version = \"0.3.4\", path = \"../lune-std-datetime\" }\nlune-std-fs = { optional = true, version = \"0.3.4\", path = \"../lune-std-fs\" }\nlune-std-luau = { optional = true, version = \"0.3.4\", path = \"../lune-std-luau\" }\nlune-std-net = { optional = true, version = \"0.3.4\", path = \"../lune-std-net\" }\nlune-std-process = { optional = true, version = \"0.3.4\", path = \"../lune-std-process\" }\nlune-std-regex = { optional = true, version = \"0.3.4\", path = \"../lune-std-regex\" }\nlune-std-roblox = { optional = true, version = \"0.3.4\", path = \"../lune-std-roblox\" }\nlune-std-serde = { optional = true, version = \"0.3.4\", path = \"../lune-std-serde\" }\nlune-std-stdio = { optional = true, version = \"0.3.4\", path = \"../lune-std-stdio\" }\nlune-std-task = { optional = true, version = \"0.3.4\", path = \"../lune-std-task\" }\n"
  },
  {
    "path": "crates/lune-std/src/global.rs",
    "content": "use std::str::FromStr;\n\nuse mlua::prelude::*;\n\n/**\n    A standard global provided by Lune.\n*/\n#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]\npub enum LuneStandardGlobal {\n    GTable,\n    Print,\n    Require,\n    Version,\n    Warn,\n}\n\nimpl LuneStandardGlobal {\n    /**\n        All available standard globals.\n    */\n    pub const ALL: &'static [Self] = &[\n        Self::GTable,\n        Self::Print,\n        Self::Require,\n        Self::Version,\n        Self::Warn,\n    ];\n\n    /**\n        Gets the name of the global, such as `_G` or `require`.\n    */\n    #[must_use]\n    pub fn name(&self) -> &'static str {\n        match self {\n            Self::GTable => \"_G\",\n            Self::Print => \"print\",\n            Self::Require => \"require\",\n            Self::Version => \"_VERSION\",\n            Self::Warn => \"warn\",\n        }\n    }\n\n    /**\n        Creates the Lua value for the global.\n\n        # Errors\n\n        If the global could not be created.\n    */\n    #[rustfmt::skip]\n    #[allow(unreachable_patterns)]\n    pub fn create(&self, lua: Lua) -> LuaResult<LuaValue> {\n        let res = match self {\n            Self::GTable => crate::globals::g_table::create(lua),\n            Self::Print => crate::globals::print::create(lua),\n            Self::Require => crate::globals::require::create(lua),\n            Self::Version => crate::globals::version::create(lua),\n            Self::Warn => crate::globals::warn::create(lua),\n        };\n        match res {\n            Ok(v) => Ok(v),\n            Err(e) => Err(e.context(format!(\n                \"Failed to create standard global '{}'\",\n                self.name()\n            ))),\n        }\n    }\n}\n\nimpl FromStr for LuneStandardGlobal {\n    type Err = String;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let low = s.trim().to_ascii_lowercase();\n        Ok(match low.as_str() {\n            \"_g\" => Self::GTable,\n            \"print\" => Self::Print,\n            \"require\" => Self::Require,\n            \"_version\" => Self::Version,\n            \"warn\" => Self::Warn,\n            _ => {\n                return Err(format!(\n                    \"Unknown standard global '{low}'\\nValid globals are: {}\",\n                    Self::ALL\n                        .iter()\n                        .map(Self::name)\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                ));\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-std/src/globals/g_table.rs",
    "content": "use mlua::prelude::*;\n\npub fn create(lua: Lua) -> LuaResult<LuaValue> {\n    lua.create_table()?.into_lua(&lua)\n}\n"
  },
  {
    "path": "crates/lune-std/src/globals/mod.rs",
    "content": "pub mod g_table;\npub mod print;\npub mod require;\npub mod version;\npub mod warn;\n"
  },
  {
    "path": "crates/lune-std/src/globals/print.rs",
    "content": "use std::io::Write;\n\nuse lune_utils::fmt::{ValueFormatConfig, pretty_format_multi_value};\nuse mlua::prelude::*;\n\nconst FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new()\n    .with_max_depth(4)\n    .with_colors_enabled(true);\n\npub fn create(lua: Lua) -> LuaResult<LuaValue> {\n    let f = lua.create_function(|_: &Lua, args: LuaMultiValue| {\n        let formatted = format!(\"{}\\n\", pretty_format_multi_value(&args, &FORMAT_CONFIG));\n        let mut stdout = std::io::stdout();\n        stdout.write_all(formatted.as_bytes())?;\n        stdout.flush()?;\n        Ok(())\n    })?;\n    f.into_lua(&lua)\n}\n"
  },
  {
    "path": "crates/lune-std/src/globals/require.rs",
    "content": "use mlua::prelude::*;\n\nuse crate::require::RequireResolver;\n\npub fn create(lua: Lua) -> LuaResult<LuaValue> {\n    lua.create_require_function(RequireResolver::new())\n        .map(LuaValue::Function)\n}\n"
  },
  {
    "path": "crates/lune-std/src/globals/version.rs",
    "content": "use mlua::prelude::*;\n\nuse lune_utils::get_version_string;\n\nstruct Version(String);\n\nimpl LuaUserData for Version {}\n\npub fn create(lua: Lua) -> LuaResult<LuaValue> {\n    let v = match lua.app_data_ref::<Version>() {\n        Some(v) => v.0.to_string(),\n        None => env!(\"CARGO_PKG_VERSION\").to_string(),\n    };\n    let s = get_version_string(v);\n    lua.create_string(s)?.into_lua(&lua)\n}\n\n/**\n    Overrides the version string to be used by the `_VERSION` global.\n\n    The global will be a string in the format `Lune x.y.z+luau`,\n    where `x.y.z` is the string passed to this function.\n\n    The version string passed should be the version of the Lune runtime,\n    obtained from `env!(\"CARGO_PKG_VERSION\")` or a similar mechanism.\n\n    # Panics\n\n    Panics if the version string is empty or contains invalid characters.\n*/\npub fn set_global_version(lua: &Lua, version: impl Into<String>) {\n    let v = version.into();\n    let _ = get_version_string(&v); // Validate version string\n    lua.set_app_data(Version(v));\n}\n"
  },
  {
    "path": "crates/lune-std/src/globals/warn.rs",
    "content": "use std::io::Write;\n\nuse lune_utils::fmt::{Label, ValueFormatConfig, pretty_format_multi_value};\nuse mlua::prelude::*;\n\nconst FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new()\n    .with_max_depth(4)\n    .with_colors_enabled(true);\n\npub fn create(lua: Lua) -> LuaResult<LuaValue> {\n    let f = lua.create_function(|_: &Lua, args: LuaMultiValue| {\n        let formatted = format!(\n            \"{}\\n{}\\n\",\n            Label::Warn,\n            pretty_format_multi_value(&args, &FORMAT_CONFIG)\n        );\n        let mut stdout = std::io::stdout();\n        stdout.write_all(formatted.as_bytes())?;\n        stdout.flush()?;\n        Ok(())\n    })?;\n    f.into_lua(&lua)\n}\n"
  },
  {
    "path": "crates/lune-std/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse mlua::prelude::*;\n\nmod global;\nmod globals;\nmod library;\nmod require;\n\npub use self::global::LuneStandardGlobal;\npub use self::globals::version::set_global_version;\npub use self::library::LuneStandardLibrary;\n\n/**\n    Injects all standard globals into the given Lua state / VM.\n\n    This **does not** include standard libraries - see `inject_std`.\n\n    # Errors\n\n    Errors when out of memory, or if *default* Lua globals are missing.\n*/\npub fn inject_globals(lua: Lua) -> LuaResult<()> {\n    for global in LuneStandardGlobal::ALL {\n        lua.globals()\n            .set(global.name(), global.create(lua.clone())?)?;\n    }\n    Ok(())\n}\n\n/**\n    Injects all standard libraries into the given Lua state / VM.\n\n    # Errors\n\n    Errors when out of memory, or if *default* Lua globals are missing.\n*/\npub fn inject_std(lua: Lua) -> LuaResult<()> {\n    for library in LuneStandardLibrary::ALL {\n        let alias = format!(\"@lune/{}\", library.name());\n        let module = library.module(lua.clone())?;\n        lua.register_module(&alias, module)?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune-std/src/library.rs",
    "content": "use std::str::FromStr;\n\nuse mlua::prelude::*;\n\n/**\n    A standard library provided by Lune.\n*/\n#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]\n#[rustfmt::skip]\npub enum LuneStandardLibrary {\n    #[cfg(feature = \"datetime\")] DateTime,\n    #[cfg(feature = \"fs\")]       Fs,\n    #[cfg(feature = \"luau\")]     Luau,\n    #[cfg(feature = \"net\")]      Net,\n    #[cfg(feature = \"task\")]     Task,\n    #[cfg(feature = \"process\")]  Process,\n    #[cfg(feature = \"regex\")]    Regex,\n    #[cfg(feature = \"serde\")]    Serde,\n    #[cfg(feature = \"stdio\")]    Stdio,\n    #[cfg(feature = \"roblox\")]   Roblox,\n}\n\nimpl LuneStandardLibrary {\n    /**\n        All available standard libraries.\n    */\n    #[rustfmt::skip]\n    pub const ALL: &'static [Self] = &[\n        #[cfg(feature = \"datetime\")] Self::DateTime,\n        #[cfg(feature = \"fs\")]       Self::Fs,\n        #[cfg(feature = \"luau\")]     Self::Luau,\n        #[cfg(feature = \"net\")]      Self::Net,\n        #[cfg(feature = \"task\")]     Self::Task,\n        #[cfg(feature = \"process\")]  Self::Process,\n        #[cfg(feature = \"regex\")]    Self::Regex,\n        #[cfg(feature = \"serde\")]    Self::Serde,\n        #[cfg(feature = \"stdio\")]    Self::Stdio,\n        #[cfg(feature = \"roblox\")]   Self::Roblox,\n    ];\n\n    /**\n        Gets the name of the library, such as `datetime` or `fs`.\n    */\n    #[must_use]\n    #[rustfmt::skip]\n    #[allow(unreachable_patterns)]\n    pub fn name(&self) -> &'static str {\n        match self {\n            #[cfg(feature = \"datetime\")] Self::DateTime => \"datetime\",\n            #[cfg(feature = \"fs\")]       Self::Fs       => \"fs\",\n            #[cfg(feature = \"luau\")]     Self::Luau     => \"luau\",\n            #[cfg(feature = \"net\")]      Self::Net      => \"net\",\n            #[cfg(feature = \"task\")]     Self::Task     => \"task\",\n            #[cfg(feature = \"process\")]  Self::Process  => \"process\",\n            #[cfg(feature = \"regex\")]    Self::Regex    => \"regex\",\n            #[cfg(feature = \"serde\")]    Self::Serde    => \"serde\",\n            #[cfg(feature = \"stdio\")]    Self::Stdio    => \"stdio\",\n            #[cfg(feature = \"roblox\")]   Self::Roblox   => \"roblox\",\n\n            _ => unreachable!(\"no standard library enabled\"),\n        }\n    }\n\n    /**\n        Returns type definitions for the library.\n    */\n    #[must_use]\n    #[rustfmt::skip]\n    #[allow(unreachable_patterns)]\n    pub fn typedefs(&self) -> String {\n    \tmatch self {\n            #[cfg(feature = \"datetime\")] Self::DateTime => lune_std_datetime::typedefs(),\n            #[cfg(feature = \"fs\")]       Self::Fs       => lune_std_fs::typedefs(),\n            #[cfg(feature = \"luau\")]     Self::Luau     => lune_std_luau::typedefs(),\n            #[cfg(feature = \"net\")]      Self::Net      => lune_std_net::typedefs(),\n            #[cfg(feature = \"task\")]     Self::Task     => lune_std_task::typedefs(),\n            #[cfg(feature = \"process\")]  Self::Process  => lune_std_process::typedefs(),\n            #[cfg(feature = \"regex\")]    Self::Regex    => lune_std_regex::typedefs(),\n            #[cfg(feature = \"serde\")]    Self::Serde    => lune_std_serde::typedefs(),\n            #[cfg(feature = \"stdio\")]    Self::Stdio    => lune_std_stdio::typedefs(),\n            #[cfg(feature = \"roblox\")]   Self::Roblox   => lune_std_roblox::typedefs(),\n\n            _ => unreachable!(\"no standard library enabled\"),\n        }\n    }\n\n    /**\n        Creates the Lua module for the library.\n\n        # Errors\n\n        If the library could not be created.\n    */\n    #[rustfmt::skip]\n    #[allow(unreachable_patterns)]\n    pub fn module(&self, lua: Lua) -> LuaResult<LuaTable> {\n        let mod_lua = lua.clone();\n        let res: LuaResult<LuaTable> = match self {\n            #[cfg(feature = \"datetime\")] Self::DateTime => lune_std_datetime::module(mod_lua),\n            #[cfg(feature = \"fs\")]       Self::Fs       => lune_std_fs::module(mod_lua),\n            #[cfg(feature = \"luau\")]     Self::Luau     => lune_std_luau::module(mod_lua),\n            #[cfg(feature = \"net\")]      Self::Net      => lune_std_net::module(mod_lua),\n            #[cfg(feature = \"task\")]     Self::Task     => lune_std_task::module(mod_lua),\n            #[cfg(feature = \"process\")]  Self::Process  => lune_std_process::module(mod_lua),\n            #[cfg(feature = \"regex\")]    Self::Regex    => lune_std_regex::module(mod_lua),\n            #[cfg(feature = \"serde\")]    Self::Serde    => lune_std_serde::module(mod_lua),\n            #[cfg(feature = \"stdio\")]    Self::Stdio    => lune_std_stdio::module(mod_lua),\n            #[cfg(feature = \"roblox\")]   Self::Roblox   => lune_std_roblox::module(mod_lua),\n\n            _ => unreachable!(\"no standard library enabled\"),\n        };\n        match res {\n            Ok(v) => Ok(v),\n            Err(e) => Err(e.context(format!(\n                \"Failed to create standard library '{}'\",\n                self.name()\n            ))),\n        }\n    }\n}\n\nimpl FromStr for LuneStandardLibrary {\n    type Err = String;\n    #[rustfmt::skip]\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let low = s.trim().to_ascii_lowercase();\n        Ok(match low.as_str() {\n            #[cfg(feature = \"datetime\")] \"datetime\" => Self::DateTime,\n            #[cfg(feature = \"fs\")]       \"fs\"       => Self::Fs,\n            #[cfg(feature = \"luau\")]     \"luau\"     => Self::Luau,\n            #[cfg(feature = \"net\")]      \"net\"      => Self::Net,\n            #[cfg(feature = \"task\")]     \"task\"     => Self::Task,\n            #[cfg(feature = \"process\")]  \"process\"  => Self::Process,\n            #[cfg(feature = \"regex\")]    \"regex\"    => Self::Regex,\n            #[cfg(feature = \"serde\")]    \"serde\"    => Self::Serde,\n            #[cfg(feature = \"stdio\")]    \"stdio\"    => Self::Stdio,\n            #[cfg(feature = \"roblox\")]   \"roblox\"   => Self::Roblox,\n\n            _ => {\n                return Err(format!(\n                    \"Unknown standard library '{low}'\\nValid libraries are: {}\",\n                    Self::ALL\n                        .iter()\n                        .map(Self::name)\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                ))\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-std/src/require/loader.rs",
    "content": "use std::{\n    cell::RefCell,\n    collections::HashMap,\n    path::{Path, PathBuf},\n    rc::Rc,\n};\n\nuse async_channel::{Receiver, Sender};\nuse async_fs::read as read_file;\n\nuse lune_utils::path::constants::FILE_CHUNK_PREFIX;\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::LuaSchedulerExt;\n\ntype RequireResult = LuaResult<LuaMultiValue>;\ntype RequireResultSender = Sender<RequireResult>;\ntype RequireResultReceiver = Receiver<RequireResult>;\n\n/**\n    Inner clonable state for the require loader.\n*/\n#[derive(Debug, Clone)]\nstruct RequireLoaderState {\n    tx: Rc<RefCell<HashMap<PathBuf, RequireResultSender>>>,\n    rx: Rc<RefCell<HashMap<PathBuf, RequireResultReceiver>>>,\n}\n\nimpl RequireLoaderState {\n    fn new() -> Self {\n        Self {\n            tx: Rc::new(RefCell::new(HashMap::new())),\n            rx: Rc::new(RefCell::new(HashMap::new())),\n        }\n    }\n\n    fn get_pending_at_path(&self, path: &Path) -> Option<RequireResultReceiver> {\n        self.rx.borrow().get(path).cloned()\n    }\n\n    fn create_pending_at_path(&self, path: &Path) -> RequireResultSender {\n        let (tx, rx) = async_channel::bounded(1);\n        self.tx.borrow_mut().insert(path.to_path_buf(), tx.clone());\n        self.rx.borrow_mut().insert(path.to_path_buf(), rx);\n        tx\n    }\n\n    fn remove_pending_at_path(&self, path: &Path) {\n        self.tx.borrow_mut().remove(path);\n        self.rx.borrow_mut().remove(path);\n    }\n}\n\n/**\n    A loader implementation for `require` that ensures modules only load\n    exactly once - even if they yield / async during the loading process.\n*/\n#[derive(Debug, Clone)]\npub(crate) struct RequireLoader {\n    state: RequireLoaderState,\n}\n\nimpl RequireLoader {\n    pub(crate) fn new() -> Self {\n        Self {\n            state: RequireLoaderState::new(),\n        }\n    }\n\n    pub(crate) fn load(\n        &self,\n        lua: &Lua,\n        relative_path: &Path,\n        absolute_path: &Path,\n    ) -> LuaResult<LuaFunction> {\n        let relative_path = relative_path.to_path_buf();\n        let absolute_path = absolute_path.to_path_buf();\n\n        let state = self.state.clone();\n\n        lua.create_async_function(move |lua, (): ()| {\n            let relative_path = relative_path.clone();\n            let absolute_path = absolute_path.clone();\n\n            let state = state.clone();\n\n            async move {\n                if let Some(rx) = state.get_pending_at_path(&absolute_path) {\n                    rx.recv()\n                        .await\n                        .into_lua_err()\n                        .context(\"require process was interrupted (future dropped)\")?\n                } else {\n                    let tx = state.create_pending_at_path(&absolute_path);\n\n                    let chunk_name = format!(\"{FILE_CHUNK_PREFIX}{}\", relative_path.display());\n                    let chunk_bytes = read_file(&absolute_path).await?;\n\n                    let chunk = lua.load(chunk_bytes).set_name(chunk_name);\n\n                    let thread_id = lua.push_thread_back(chunk, ())?;\n                    lua.track_thread(thread_id);\n                    lua.wait_for_thread(thread_id).await;\n\n                    let thread_res = lua\n                        .get_thread_result(thread_id)\n                        .expect(\"running in scheduler + thread is tracked\");\n\n                    if tx.receiver_count() > 0 {\n                        tx.send(thread_res.clone()).await.ok();\n                        tx.close();\n                    }\n\n                    state.remove_pending_at_path(&absolute_path);\n\n                    thread_res\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-std/src/require/mod.rs",
    "content": "mod loader;\nmod resolver;\n\npub(crate) use self::resolver::RequireResolver;\n"
  },
  {
    "path": "crates/lune-std/src/require/resolver.rs",
    "content": "use std::{\n    fs::read as read_file,\n    io::Result as IoResult,\n    path::{Path, PathBuf},\n};\n\nuse lune_utils::path::{\n    LuauModulePath, clean_path_and_make_absolute,\n    constants::{FILE_CHUNK_PREFIX, FILE_NAME_CONFIG},\n    relative_path_normalize, relative_path_parent,\n};\nuse mlua::prelude::*;\n\nuse super::loader::RequireLoader;\n\n#[derive(Debug)]\npub(crate) struct RequireResolver {\n    /// Path to the current module, absolute.\n    ///\n    /// Not guaranteed to be a valid filesystem\n    /// path, and should not be used as such.\n    absolute: PathBuf,\n    /// Path to the current module, relative.\n    ///\n    /// Not guaranteed to be a valid filesystem\n    /// path, and should not be used as such.\n    relative: PathBuf,\n    /// Path to the current filesystem entry that\n    /// directly represents the current module path.\n    resolved: Option<LuauModulePath>,\n    /// Loader and accompanying state.\n    loader: RequireLoader,\n}\n\nimpl RequireResolver {\n    pub(crate) fn new() -> Self {\n        Self {\n            relative: PathBuf::new(),\n            absolute: PathBuf::new(),\n            resolved: None,\n            loader: RequireLoader::new(),\n        }\n    }\n\n    fn navigate_reset(&mut self) {\n        self.relative = PathBuf::new();\n        self.absolute = PathBuf::new();\n        self.resolved = None;\n    }\n\n    fn navigate_to(\n        &mut self,\n        relative: PathBuf,\n        absolute: PathBuf,\n    ) -> Result<(), LuaNavigateError> {\n        // Avoid unnecessary filesystem calls when possible - ResolvedPath\n        // should always point to the same filesystem entry as long as the\n        // relative and absolute module paths stay the same.\n        if self.relative == relative && self.absolute == absolute {\n            return Ok(());\n        }\n\n        // Make sure to resolve path **before** updating any paths state\n        let resolved = LuauModulePath::resolve(&absolute)?;\n\n        self.absolute = absolute;\n        self.relative = relative;\n        self.resolved = Some(resolved);\n\n        Ok(())\n    }\n}\n\nimpl LuaRequire for RequireResolver {\n    fn is_require_allowed(&self, chunk_name: &str) -> bool {\n        chunk_name.starts_with(FILE_CHUNK_PREFIX)\n    }\n\n    fn reset(&mut self, chunk_name: &str) -> Result<(), LuaNavigateError> {\n        // NOTE: This is not actually necessary, but makes our resolver state\n        // behave a bit more consistently - it ensures `resolved == None` when\n        // no file has been resolved from the current module path navigation.\n        // It is really only useful when debugging the require resolver state.\n        self.navigate_reset();\n\n        if let Some(path) = chunk_name.strip_prefix(FILE_CHUNK_PREFIX) {\n            let rel = relative_path_normalize(Path::new(path));\n            let abs = clean_path_and_make_absolute(&rel);\n\n            self.navigate_to(rel, abs)\n        } else {\n            Err(LuaNavigateError::Other(LuaError::runtime(\n                \"cannot reset require state from non-file chunk\",\n            )))\n        }\n    }\n\n    fn jump_to_alias(&mut self, path: &str) -> Result<(), LuaNavigateError> {\n        let rel = relative_path_normalize(Path::new(path));\n        let abs = clean_path_and_make_absolute(&rel);\n\n        self.navigate_to(rel, abs)\n    }\n\n    fn to_parent(&mut self) -> Result<(), LuaNavigateError> {\n        let mut rel = self.relative.clone();\n        let mut abs = self.absolute.clone();\n\n        if abs.pop() {\n            relative_path_parent(&mut rel);\n            self.navigate_to(rel, abs)\n        } else {\n            /*\n                We have reached the root and can not navigate further up.\n\n                Require-by-string has some special behavior here - for path aliases,\n                including the special \"@self\" alias to work, we must return exactly\n                `NotFound`, not success or any other kind of error.\n\n                This is due to how rbs traverses parents, when searching for a\n                config file that may have aliases in it.\n\n                When `NotFound` is returned, rbs will call `reset` if the alias\n                was \"@self\", or, error mentioning that no alias config was found.\n            */\n            Err(LuaNavigateError::NotFound)\n        }\n    }\n\n    fn to_child(&mut self, name: &str) -> Result<(), LuaNavigateError> {\n        let rel = self.relative.join(name);\n        let abs = self.absolute.join(name);\n\n        self.navigate_to(rel, abs)\n    }\n\n    fn has_module(&self) -> bool {\n        let resolved = self.resolved.as_ref();\n        resolved.is_some_and(|p| p.target().is_file())\n    }\n\n    fn cache_key(&self) -> String {\n        let resolved = self.resolved.as_ref();\n        resolved.expect(\"called has_module first\").to_string()\n    }\n\n    fn has_config(&self) -> bool {\n        self.absolute.is_dir() && self.absolute.join(FILE_NAME_CONFIG).is_file()\n    }\n\n    fn config(&self) -> IoResult<Vec<u8>> {\n        read_file(self.absolute.join(FILE_NAME_CONFIG))\n    }\n\n    fn loader(&self, lua: &Lua) -> LuaResult<LuaFunction> {\n        let resolved = self.resolved.as_ref();\n        let resolved = resolved.expect(\"called has_module first\");\n        let resolved = resolved.target().as_file().expect(\"tried to require a dir\");\n        self.loader.load(lua, self.relative.as_path(), resolved)\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-datetime/Cargo.toml",
    "content": "[package]\nname = \"lune-std-datetime\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - DateTime\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\n\nthiserror = \"2.0\"\nchrono = \"0.4.38\"\nchrono_lc = \"0.1.6\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-datetime/src/date_time.rs",
    "content": "use std::cmp::Ordering;\n\nuse mlua::prelude::*;\n\nuse chrono::DateTime as ChronoDateTime;\nuse chrono::prelude::*;\nuse chrono_lc::LocaleDate;\n\nuse crate::result::{DateTimeError, DateTimeResult};\nuse crate::values::DateTimeValues;\n\nconst DEFAULT_FORMAT: &str = \"%Y-%m-%d %H:%M:%S\";\nconst DEFAULT_LOCALE: &str = \"en\";\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\npub struct DateTime {\n    // NOTE: We store this as the UTC time zone since it is the most commonly\n    // used and getting the generics right for TimeZone is somewhat tricky,\n    // but none of the method implementations below should rely on this tz\n    inner: ChronoDateTime<Utc>,\n}\n\nimpl DateTime {\n    /**\n        Creates a new `DateTime` struct representing the current moment in time.\n\n        See [`chrono::DateTime::now`] for additional details.\n    */\n    #[must_use]\n    pub fn now() -> Self {\n        Self { inner: Utc::now() }\n    }\n\n    /**\n        Creates a new `DateTime` struct from the given `unix_timestamp`,\n        which is a float of seconds passed since the UNIX epoch.\n\n        This is somewhat unconventional, but fits our Luau interface and dynamic types quite well.\n        To use this method the same way you would use a more traditional `from_unix_timestamp`\n        that takes a `u64` of seconds or similar type, casting the value is sufficient:\n\n        ```rust ignore\n        DateTime::from_unix_timestamp_float(123456789u64 as f64)\n        ```\n\n        See [`chrono::DateTime::from_timestamp`] for additional details.\n\n        # Errors\n\n        Returns an error if the input value is out of range.\n    */\n    pub fn from_unix_timestamp_float(unix_timestamp: f64) -> DateTimeResult<Self> {\n        let whole = unix_timestamp.trunc() as i64;\n        let fract = unix_timestamp.fract();\n        let nanos = (fract * 1_000_000_000f64)\n            .round()\n            .clamp(u32::MIN as f64, u32::MAX as f64) as u32;\n        let inner = ChronoDateTime::<Utc>::from_timestamp(whole, nanos)\n            .ok_or(DateTimeError::OutOfRangeUnspecified)?;\n        Ok(Self { inner })\n    }\n\n    /**\n        Transforms individual date & time values into a new\n        `DateTime` struct, using the universal (UTC) time zone.\n\n        See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]\n        for additional details and cases where this constructor may return an error.\n\n        # Errors\n\n        Returns an error if the date or time values are invalid.\n    */\n    pub fn from_universal_time(values: &DateTimeValues) -> DateTimeResult<Self> {\n        let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)\n            .ok_or(DateTimeError::InvalidDate)?;\n\n        let time = NaiveTime::from_hms_milli_opt(\n            values.hour,\n            values.minute,\n            values.second,\n            values.millisecond,\n        )\n        .ok_or(DateTimeError::InvalidTime)?;\n\n        let inner = Utc.from_utc_datetime(&NaiveDateTime::new(date, time));\n\n        Ok(Self { inner })\n    }\n\n    /**\n        Transforms individual date & time values into a new\n        `DateTime` struct, using the current local time zone.\n\n        See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]\n        for additional details and cases where this constructor may return an error.\n\n        # Errors\n\n        Returns an error if the date or time values are invalid or ambiguous.\n    */\n    pub fn from_local_time(values: &DateTimeValues) -> DateTimeResult<Self> {\n        let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)\n            .ok_or(DateTimeError::InvalidDate)?;\n\n        let time = NaiveTime::from_hms_milli_opt(\n            values.hour,\n            values.minute,\n            values.second,\n            values.millisecond,\n        )\n        .ok_or(DateTimeError::InvalidTime)?;\n\n        let inner = Local\n            .from_local_datetime(&NaiveDateTime::new(date, time))\n            .single()\n            .ok_or(DateTimeError::Ambiguous)?\n            .with_timezone(&Utc);\n\n        Ok(Self { inner })\n    }\n\n    /**\n        Formats the `DateTime` using the universal (UTC) time\n        zone, the given format string, and the given locale.\n\n        `format` and `locale` default to `\"%Y-%m-%d %H:%M:%S\"` and `\"en\"` respectively.\n\n        See [`chrono_lc::DateTime::formatl`] for additional details.\n    */\n    #[must_use]\n    pub fn format_string_local(&self, format: Option<&str>, locale: Option<&str>) -> String {\n        self.inner\n            .with_timezone(&Local)\n            .formatl(\n                format.unwrap_or(DEFAULT_FORMAT),\n                locale.unwrap_or(DEFAULT_LOCALE),\n            )\n            .to_string()\n    }\n\n    /**\n        Formats the `DateTime` using the universal (UTC) time\n        zone, the given format string, and the given locale.\n\n        `format` and `locale` default to `\"%Y-%m-%d %H:%M:%S\"` and `\"en\"` respectively.\n\n        See [`chrono_lc::DateTime::formatl`] for additional details.\n    */\n    #[must_use]\n    pub fn format_string_universal(&self, format: Option<&str>, locale: Option<&str>) -> String {\n        self.inner\n            .with_timezone(&Utc)\n            .formatl(\n                format.unwrap_or(DEFAULT_FORMAT),\n                locale.unwrap_or(DEFAULT_LOCALE),\n            )\n            .to_string()\n    }\n\n    /**\n        Parses a time string in the RFC 3339 format, such as\n        `1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.\n\n        See [`chrono::DateTime::parse_from_rfc3339`] for additional details.\n\n        # Errors\n\n        Returns an error if the input string is not a valid RFC 3339 date-time.\n    */\n    pub fn from_rfc_3339(date: impl AsRef<str>) -> DateTimeResult<Self> {\n        let inner = ChronoDateTime::parse_from_rfc3339(date.as_ref())?.with_timezone(&Utc);\n        Ok(Self { inner })\n    }\n\n    /**\n        Parses a time string in the RFC 2822 format, such as\n        `Tue, 1 Jul 2003 10:52:37 +0200`, into a new `DateTime` struct.\n\n        See [`chrono::DateTime::parse_from_rfc2822`] for additional details.\n\n        # Errors\n\n        Returns an error if the input string is not a valid RFC 2822 date-time.\n    */\n    pub fn from_rfc_2822(date: impl AsRef<str>) -> DateTimeResult<Self> {\n        let inner = ChronoDateTime::parse_from_rfc2822(date.as_ref())?.with_timezone(&Utc);\n        Ok(Self { inner })\n    }\n\n    /**\n        Extracts individual date & time values from this\n        `DateTime`, using the current local time zone.\n    */\n    #[must_use]\n    pub fn to_local_time(self) -> DateTimeValues {\n        DateTimeValues::from(self.inner.with_timezone(&Local))\n    }\n\n    /**\n        Extracts individual date & time values from this\n        `DateTime`, using the universal (UTC) time zone.\n    */\n    #[must_use]\n    pub fn to_universal_time(self) -> DateTimeValues {\n        DateTimeValues::from(self.inner.with_timezone(&Utc))\n    }\n\n    /**\n        Formats a time string in the RFC 3339 format, such as `1996-12-19T16:39:57-08:00`.\n\n        See [`chrono::DateTime::to_rfc3339`] for additional details.\n    */\n    #[must_use]\n    pub fn to_rfc_3339(self) -> String {\n        self.inner.to_rfc3339()\n    }\n\n    /**\n        Formats a time string in the RFC 2822 format, such as `Tue, 1 Jul 2003 10:52:37 +0200`.\n\n        See [`chrono::DateTime::to_rfc2822`] for additional details.\n    */\n    #[must_use]\n    pub fn to_rfc_2822(self) -> String {\n        self.inner.to_rfc2822()\n    }\n}\n\nimpl LuaUserData for DateTime {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"unixTimestamp\", |_, this| Ok(this.inner.timestamp()));\n        fields.add_field_method_get(\"unixTimestampMillis\", |_, this| {\n            Ok(this.inner.timestamp_millis())\n        });\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        // Metamethods to compare DateTime as instants in time\n        methods.add_meta_method(\n            LuaMetaMethod::Eq,\n            |_, this: &Self, other: LuaUserDataRef<Self>| Ok(this.eq(&other)),\n        );\n        methods.add_meta_method(\n            LuaMetaMethod::Lt,\n            |_, this: &Self, other: LuaUserDataRef<Self>| {\n                Ok(matches!(this.cmp(&other), Ordering::Less))\n            },\n        );\n        methods.add_meta_method(\n            LuaMetaMethod::Le,\n            |_, this: &Self, other: LuaUserDataRef<Self>| {\n                Ok(matches!(this.cmp(&other), Ordering::Less | Ordering::Equal))\n            },\n        );\n        // Normal methods\n        methods.add_method(\"toIsoDate\", |_, this, ()| Ok(this.to_rfc_3339())); // FUTURE: Remove this rfc3339 alias method\n        methods.add_method(\"toRfc3339\", |_, this, ()| Ok(this.to_rfc_3339()));\n        methods.add_method(\"toRfc2822\", |_, this, ()| Ok(this.to_rfc_2822()));\n        methods.add_method(\n            \"formatUniversalTime\",\n            |_, this, (format, locale): (Option<String>, Option<String>)| {\n                Ok(this.format_string_universal(format.as_deref(), locale.as_deref()))\n            },\n        );\n        methods.add_method(\n            \"formatLocalTime\",\n            |_, this, (format, locale): (Option<String>, Option<String>)| {\n                Ok(this.format_string_local(format.as_deref(), locale.as_deref()))\n            },\n        );\n        methods.add_method(\"toUniversalTime\", |_, this: &Self, ()| {\n            Ok(this.to_universal_time())\n        });\n        methods.add_method(\"toLocalTime\", |_, this: &Self, ()| Ok(this.to_local_time()));\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-datetime/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse mlua::prelude::*;\n\nuse lune_utils::TableBuilder;\n\nmod date_time;\nmod result;\nmod values;\n\npub use self::date_time::DateTime;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `datetime` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `datetime` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    TableBuilder::new(lua)?\n        .with_function(\"fromIsoDate\", |_, date: String| {\n            Ok(DateTime::from_rfc_3339(date)?) // FUTURE: Remove this rfc3339 alias method\n        })?\n        .with_function(\"fromRfc3339\", |_, date: String| {\n            Ok(DateTime::from_rfc_3339(date)?)\n        })?\n        .with_function(\"fromRfc2822\", |_, date: String| {\n            Ok(DateTime::from_rfc_2822(date)?)\n        })?\n        .with_function(\"fromLocalTime\", |_, values| {\n            Ok(DateTime::from_local_time(&values)?)\n        })?\n        .with_function(\"fromUniversalTime\", |_, values| {\n            Ok(DateTime::from_universal_time(&values)?)\n        })?\n        .with_function(\"fromUnixTimestamp\", |_, timestamp| {\n            Ok(DateTime::from_unix_timestamp_float(timestamp)?)\n        })?\n        .with_function(\"now\", |_, ()| Ok(DateTime::now()))?\n        .build_readonly()\n}\n"
  },
  {
    "path": "crates/lune-std-datetime/src/result.rs",
    "content": "use mlua::prelude::*;\n\nuse thiserror::Error;\n\npub type DateTimeResult<T, E = DateTimeError> = Result<T, E>;\n\n#[derive(Debug, Clone, Error)]\npub enum DateTimeError {\n    #[error(\"invalid date\")]\n    InvalidDate,\n    #[error(\"invalid time\")]\n    InvalidTime,\n    #[error(\"ambiguous date or time\")]\n    Ambiguous,\n    #[error(\"date or time is outside allowed range\")]\n    OutOfRangeUnspecified,\n    #[error(\"{name} must be within range {min} -> {max}, got {value}\")]\n    OutOfRange {\n        name: &'static str,\n        value: String,\n        min: String,\n        max: String,\n    },\n    #[error(transparent)]\n    ParseError(#[from] chrono::ParseError),\n}\n\nimpl From<DateTimeError> for LuaError {\n    fn from(value: DateTimeError) -> Self {\n        LuaError::runtime(value.to_string())\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-datetime/src/values.rs",
    "content": "use mlua::prelude::*;\n\nuse chrono::prelude::*;\n\nuse lune_utils::TableBuilder;\n\nuse super::result::{DateTimeError, DateTimeResult};\n\n#[derive(Debug, Clone, Copy)]\npub struct DateTimeValues {\n    pub year: i32,\n    pub month: u32,\n    pub day: u32,\n    pub hour: u32,\n    pub minute: u32,\n    pub second: u32,\n    pub millisecond: u32,\n}\n\nimpl DateTimeValues {\n    /**\n        Verifies that all of the date & time values are within allowed ranges:\n\n        | Name          | Range          |\n        |---------------|----------------|\n        | `year`        | `1400 -> 9999` |\n        | `month`       | `1 -> 12`      |\n        | `day`         | `1 -> 31`      |\n        | `hour`        | `0 -> 23`      |\n        | `minute`      | `0 -> 59`      |\n        | `second`      | `0 -> 60`      |\n        | `millisecond` | `0 -> 999`     |\n    */\n    pub fn verify(self) -> DateTimeResult<Self> {\n        verify_in_range(\"year\", self.year, 1400, 9999)?;\n        verify_in_range(\"month\", self.month, 1, 12)?;\n        verify_in_range(\"day\", self.day, 1, 31)?;\n        verify_in_range(\"hour\", self.hour, 0, 23)?;\n        verify_in_range(\"minute\", self.minute, 0, 59)?;\n        verify_in_range(\"second\", self.second, 0, 60)?;\n        verify_in_range(\"millisecond\", self.millisecond, 0, 999)?;\n        Ok(self)\n    }\n}\n\nfn verify_in_range<T>(name: &'static str, value: T, min: T, max: T) -> DateTimeResult<T>\nwhere\n    T: PartialOrd + std::fmt::Display,\n{\n    assert!(max > min);\n    if value < min || value > max {\n        Err(DateTimeError::OutOfRange {\n            name,\n            min: min.to_string(),\n            max: max.to_string(),\n            value: value.to_string(),\n        })\n    } else {\n        Ok(value)\n    }\n}\n\n/*\n    Conversion methods between `DateTimeValues` and plain lua tables\n\n    Note that the `IntoLua` implementation here uses a read-only table,\n    since we generally want to convert into lua when we know we have\n    a fixed point in time, and we guarantee that it doesn't change\n*/\n\nimpl FromLua for DateTimeValues {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if !value.is_table() {\n            return Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"DateTimeValues\".to_string(),\n                message: Some(\"value must be a table\".to_string()),\n            });\n        }\n\n        let value = value.as_table().unwrap();\n        let values = Self {\n            year: value.get(\"year\")?,\n            month: value.get(\"month\")?,\n            day: value.get(\"day\")?,\n            hour: value.get(\"hour\")?,\n            minute: value.get(\"minute\")?,\n            second: value.get(\"second\")?,\n            millisecond: value.get(\"millisecond\").unwrap_or(0),\n        };\n\n        match values.verify() {\n            Ok(dt) => Ok(dt),\n            Err(e) => Err(LuaError::FromLuaConversionError {\n                from: \"table\",\n                to: \"DateTimeValues\".to_string(),\n                message: Some(e.to_string()),\n            }),\n        }\n    }\n}\n\nimpl IntoLua for DateTimeValues {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        let tab = TableBuilder::new(lua.clone())?\n            .with_value(\"year\", self.year)?\n            .with_values(vec![\n                (\"month\", self.month),\n                (\"day\", self.day),\n                (\"hour\", self.hour),\n                (\"minute\", self.minute),\n                (\"second\", self.second),\n                (\"millisecond\", self.millisecond),\n            ])?\n            .build_readonly()?;\n        Ok(LuaValue::Table(tab))\n    }\n}\n\n/*\n    Conversion methods between chrono's timezone-aware `DateTime` to\n    and from our non-timezone-aware `DateTimeValues` values struct\n*/\n\nimpl<T: TimeZone> From<DateTime<T>> for DateTimeValues {\n    fn from(value: DateTime<T>) -> Self {\n        Self {\n            year: value.year(),\n            month: value.month(),\n            day: value.day(),\n            hour: value.hour(),\n            minute: value.minute(),\n            second: value.second(),\n            millisecond: value.timestamp_subsec_millis(),\n        }\n    }\n}\n\nimpl TryFrom<DateTimeValues> for DateTime<Utc> {\n    type Error = DateTimeError;\n    fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {\n        Utc.with_ymd_and_hms(\n            value.year,\n            value.month,\n            value.day,\n            value.hour,\n            value.minute,\n            value.second,\n        )\n        .single()\n        .ok_or(DateTimeError::Ambiguous)\n    }\n}\n\nimpl TryFrom<DateTimeValues> for DateTime<Local> {\n    type Error = DateTimeError;\n    fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {\n        Local\n            .with_ymd_and_hms(\n                value.year,\n                value.month,\n                value.day,\n                value.hour,\n                value.minute,\n                value.second,\n            )\n            .single()\n            .ok_or(DateTimeError::Ambiguous)\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-datetime/types.d.luau",
    "content": "--[[\n\tNOTE: We export a couple different DateTimeValues types below to ensure\n\tthat types are completely accurate, for method args milliseconds will\n\talways be optional, but for return values millis are always included\n\n\tIf we figure out some better strategy here where we can\n\texport just a single type while maintaining accuracy we\n\tcan change to that in a future breaking semver release\n]]\n\ntype OptionalMillisecond = {\n\tmillisecond: number?,\n}\n\ntype Millisecond = {\n\tmillisecond: number,\n}\n\n--[=[\n\t@interface Locale\n\t@within DateTime\n\n\tEnum type representing supported DateTime locales.\n\n\tCurrently supported locales are:\n\n\t- `en` - English\n\t- `de` - German\n\t- `es` - Spanish\n\t- `fr` - French\n\t- `it` - Italian\n\t- `ja` - Japanese\n\t- `pl` - Polish\n\t- `pt-br` - Brazilian Portuguese\n\t- `pt` - Portuguese\n\t- `tr` - Turkish\n]=]\nexport type Locale = \"en\" | \"de\" | \"es\" | \"fr\" | \"it\" | \"ja\" | \"pl\" | \"pt-br\" | \"pt\" | \"tr\"\n\n--[=[\n\t@interface DateTimeValues\n\t@within DateTime\n\n\tIndividual date & time values, representing the primitives that make up a `DateTime`.\n\n\tThis is a dictionary that will contain the following values:\n\n\t- `year` - Year(s), in the range 1400 -> 9999\n\t- `month` - Month(s), in the range 1 -> 12\n\t- `day` - Day(s), in the range 1 -> 31\n\t- `hour` - Hour(s), in the range 0 -> 23\n\t- `minute` - Minute(s), in the range 0 -> 59\n\t- `second` - Second(s), in the range 0 -> 60, where 60 is a leap second\n\n\tAn additional `millisecond` value may also be included,\n\tand should be within the range `0 -> 999`, but is optional.\n\n\tHowever, any method returning this type should be guaranteed\n\tto include milliseconds - see individual methods to verify.\n]=]\nexport type DateTimeValues = {\n\tyear: number,\n\tmonth: number,\n\tday: number,\n\thour: number,\n\tminute: number,\n\tsecond: number,\n}\n\n--[=[\n\t@interface DateTimeValueArguments\n\t@within DateTime\n\n\tAlias for `DateTimeValues` with an optional `millisecond` value.\n\n\tRefer to the `DateTimeValues` documentation for additional information.\n]=]\nexport type DateTimeValueArguments = DateTimeValues & OptionalMillisecond\n\n--[=[\n\t@interface DateTimeValueReturns\n\t@within DateTime\n\n\tAlias for `DateTimeValues` with a mandatory `millisecond` value.\n\n\tRefer to the `DateTimeValues` documentation for additional information.\n]=]\nexport type DateTimeValueReturns = DateTimeValues & Millisecond\n\n--[=[\n\t@prop unixTimestamp number\n\t@within DateTime\n\tNumber of seconds passed since the UNIX epoch.\n]=]\n\n--[=[\n\t@prop unixTimestampMillis number\n\t@within DateTime\n\tNumber of milliseconds passed since the UNIX epoch.\n]=]\nlocal DateTime = {\n\tunixTimestamp = (nil :: any) :: number,\n\tunixTimestampMillis = (nil :: any) :: number,\n}\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\tFormats this `DateTime` using the given `formatString` and `locale`, as local time.\n\n\tThe given `formatString` is parsed using a `strftime`/`strptime`-inspired\n\tdate and time formatting syntax, allowing tokens such as the following:\n\n\t| Token | Example  | Description   |\n\t|-------|----------|---------------|\n\t| `%Y`  | `1998`   | Year number   |\n\t| `%m`  | `04`     | Month number  |\n\t| `%d`  | `29`     | Day number    |\n\t| `%A`  | `Monday` | Weekday name  |\n\t| `%M`  | `59`     | Minute number |\n\t| `%S`  | `10`     | Second number |\n\n\tFor a full reference of all available tokens, see the\n\t[chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).\n\n\tIf not provided, `formatString` and `locale` will default\n\tto `\"%Y-%m-%d %H:%M:%S\"` and `\"en\"` (english) respectively.\n\n\t@param formatString -- A string containing formatting tokens\n\t@param locale -- The locale the time should be formatted in\n\t@return string -- The formatting string\n]=]\nfunction DateTime.formatLocalTime(self: DateTime, formatString: string?, locale: Locale?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\tFormats this `DateTime` using the given `formatString` and `locale`, as UTC (universal) time.\n\n\tThe given `formatString` is parsed using a `strftime`/`strptime`-inspired\n\tdate and time formatting syntax, allowing tokens such as the following:\n\n\t| Token | Example  | Description   |\n\t|-------|----------|---------------|\n\t| `%Y`  | `1998`   | Year number   |\n\t| `%m`  | `04`     | Month number  |\n\t| `%d`  | `29`     | Day number    |\n\t| `%A`  | `Monday` | Weekday name  |\n\t| `%M`  | `59`     | Minute number |\n\t| `%S`  | `10`     | Second number |\n\n\tFor a full reference of all available tokens, see the\n\t[chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).\n\n\tIf not provided, `formatString` and `locale` will default\n\tto `\"%Y-%m-%d %H:%M:%S\"` and `\"en\"` (english) respectively.\n\n\t@param formatString -- A string containing formatting tokens\n\t@param locale -- The locale the time should be formatted in\n\t@return string -- The formatting string\n]=]\nfunction DateTime.formatUniversalTime(self: DateTime, formatString: string?, locale: Locale?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\t**DEPRECATED**: Use `DateTime.toRfc3339` instead.\n\n\tFormats this `DateTime` as an ISO 8601 date-time string.\n\n\tSome examples of ISO 8601 date-time strings are:\n\n\t- `2020-02-22T18:12:08Z`\n\t- `2000-01-31T12:34:56+05:00`\n\t- `1970-01-01T00:00:00.055Z`\n\n\t@return string -- The ISO 8601 formatted string\n]=]\nfunction DateTime.toIsoDate(self: DateTime): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\tFormats this `DateTime` as an RFC 2822 date-time string.\n\n\tSome examples of RFC 2822 date-time strings are:\n\n\t- `Fri, 21 Nov 1997 09:55:06 -0600`\n\t- `Tue, 1 Jul 2003 10:52:37 +0200`\n\t- `Mon, 23 Dec 2024 01:58:48 GMT`\n\n\t@return string -- The RFC 2822 formatted string\n]=]\nfunction DateTime.toRfc2822(self: DateTime): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\tFormats this `DateTime` as an RFC 3339 date-time string.\n\n\tSome examples of RFC 3339 date-time strings are:\n\n\t- `2020-02-22T18:12:08Z`\n\t- `2000-01-31T12:34:56+05:00`\n\t- `1970-01-01T00:00:00.055Z`\n\n\t@return string -- The RFC 3339 formatted string\n]=]\nfunction DateTime.toRfc3339(self: DateTime): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\tExtracts separated local date & time values from this `DateTime`.\n\n\tThe returned table contains the following values:\n\n\t| Key           | Type     | Range          |\n\t|---------------|----------|----------------|\n\t| `year`        | `number` | `1400 -> 9999` |\n\t| `month`       | `number` | `1 -> 12`      |\n\t| `day`         | `number` | `1 -> 31`      |\n\t| `hour`        | `number` | `0 -> 23`      |\n\t| `minute`      | `number` | `0 -> 59`      |\n\t| `second`      | `number` | `0 -> 60`      |\n\t| `millisecond` | `number` | `0 -> 999`     |\n\n\t@return DateTimeValueReturns -- A table of DateTime values\n]=]\nfunction DateTime.toLocalTime(self: DateTime): DateTimeValueReturns\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Method\n\n\tExtracts separated UTC (universal) date & time values from this `DateTime`.\n\n\tThe returned table contains the following values:\n\n\t| Key           | Type     | Range          |\n\t|---------------|----------|----------------|\n\t| `year`        | `number` | `1400 -> 9999` |\n\t| `month`       | `number` | `1 -> 12`      |\n\t| `day`         | `number` | `1 -> 31`      |\n\t| `hour`        | `number` | `0 -> 23`      |\n\t| `minute`      | `number` | `0 -> 59`      |\n\t| `second`      | `number` | `0 -> 60`      |\n\t| `millisecond` | `number` | `0 -> 999`     |\n\n\t@return DateTimeValueReturns -- A table of DateTime values\n]=]\nfunction DateTime.toUniversalTime(self: DateTime): DateTimeValueReturns\n\treturn nil :: any\nend\n\nexport type DateTime = typeof(DateTime)\n\n--[=[\n\t@class DateTime\n\n\tBuilt-in library for date & time\n\n\t### Example usage\n\n\t```lua\n\tlocal DateTime = require(\"@lune/datetime\")\n\n\t-- Creates a DateTime for the current exact moment in time\n\tlocal now = DateTime.now()\n\n\t-- Formats the current moment in time as an RFC 3339 string\n\tprint(now:toRfc3339())\n\n\t-- Formats the current moment in time as an RFC 2822 string\n\tprint(now:toRfc2822())\n\n\t-- Formats the current moment in time, using the local\n\t-- time, the French locale, and the specified time string\n\tprint(now:formatLocalTime(\"%A, %d %B %Y\", \"fr\"))\n\n\t-- Returns a specific moment in time as a DateTime instance\n\tlocal someDayInTheFuture = DateTime.fromLocalTime({\n\t\tyear = 3033,\n\t\tmonth = 8,\n\t\tday = 26,\n\t\thour = 16,\n\t\tminute = 56,\n\t\tsecond = 28,\n\t\tmillisecond = 892,\n\t})\n\n\t-- Extracts the current local date & time as separate values (same values as above table)\n\tprint(now:toLocalTime())\n\n\t-- Returns a DateTime instance from a given float, where the whole\n\t-- denotes the seconds and the fraction denotes the milliseconds\n\t-- Note that the fraction for millis here is completely optional\n\tDateTime.fromUnixTimestamp(871978212313.321)\n\n\t-- Extracts the current universal (UTC) date & time as separate values\n\tprint(now:toUniversalTime())\n\t```\n]=]\nlocal dateTime = {}\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\tReturns a `DateTime` representing the current moment in time.\n\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.now(): DateTime\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\tCreates a new `DateTime` from the given UNIX timestamp.\n\n\tThis timestamp may contain both a whole and fractional part -\n\twhere the fractional part denotes milliseconds / nanoseconds.\n\n\tExample usage of fractions:\n\n\t- `DateTime.fromUnixTimestamp(123456789.001)` - one millisecond\n\t- `DateTime.fromUnixTimestamp(123456789.000000001)` - one nanosecond\n\n\tNote that the fractional part has limited precision down to exactly\n\tone nanosecond, any fraction that is more precise will get truncated.\n\n\t@param unixTimestamp -- Seconds passed since the UNIX epoch\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.fromUnixTimestamp(unixTimestamp: number): DateTime\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\tCreates a new `DateTime` from the given date & time values table, in universal (UTC) time.\n\n\tThe given table must contain the following values:\n\n\t| Key      | Type     | Range          |\n\t|----------|----------|----------------|\n\t| `year`   | `number` | `1400 -> 9999` |\n\t| `month`  | `number` | `1 -> 12`      |\n\t| `day`    | `number` | `1 -> 31`      |\n\t| `hour`   | `number` | `0 -> 23`      |\n\t| `minute` | `number` | `0 -> 59`      |\n\t| `second` | `number` | `0 -> 60`      |\n\n\tAn additional `millisecond` value may also be included,\n\tand should be within the range `0 -> 999`, but is optional.\n\n\tAny non-integer values in the given table will be rounded down.\n\n\t### Errors\n\n\tThis constructor is fallible and may throw an error in the following situations:\n\n\t- Date units (year, month, day) were given that produce an invalid date. For example, January 32nd or February 29th on a non-leap year.\n\n\t@param values -- Table containing date & time values\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.fromUniversalTime(values: DateTimeValueArguments): DateTime\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\tCreates a new `DateTime` from the given date & time values table, in local time.\n\n\tThe given table must contain the following values:\n\n\t| Key      | Type     | Range          |\n\t|----------|----------|----------------|\n\t| `year`   | `number` | `1400 -> 9999` |\n\t| `month`  | `number` | `1 -> 12`      |\n\t| `day`    | `number` | `1 -> 31`      |\n\t| `hour`   | `number` | `0 -> 23`      |\n\t| `minute` | `number` | `0 -> 59`      |\n\t| `second` | `number` | `0 -> 60`      |\n\n\tAn additional `millisecond` value may also be included,\n\tand should be within the range `0 -> 999`, but is optional.\n\n\tAny non-integer values in the given table will be rounded down.\n\n\t### Errors\n\n\tThis constructor is fallible and may throw an error in the following situations:\n\n\t- Date units (year, month, day) were given that produce an invalid date. For example, January 32nd or February 29th on a non-leap year.\n\n\t@param values -- Table containing date & time values\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.fromLocalTime(values: DateTimeValueArguments): DateTime\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\t**DEPRECATED**: Use `DateTime.fromRfc3339` instead.\n\n\tCreates a new `DateTime` from an ISO 8601 date-time string.\n\n\t### Errors\n\n\tThis constructor is fallible and may throw an error if the given\n\tstring does not strictly follow the ISO 8601 date-time string format.\n\n\tSome examples of valid ISO 8601 date-time strings are:\n\n\t- `2020-02-22T18:12:08Z`\n\t- `2000-01-31T12:34:56+05:00`\n\t- `1970-01-01T00:00:00.055Z`\n\n\t@param isoDate -- An ISO 8601 formatted string\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.fromIsoDate(isoDate: string): DateTime\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\tCreates a new `DateTime` from an RFC 3339 date-time string.\n\n\t### Errors\n\n\tThis constructor is fallible and may throw an error if the given\n\tstring does not strictly follow the RFC 3339 date-time string format.\n\n\tSome examples of valid RFC 3339 date-time strings are:\n\n\t- `2020-02-22T18:12:08Z`\n\t- `2000-01-31T12:34:56+05:00`\n\t- `1970-01-01T00:00:00.055Z`\n\n\t@param rfc3339Date -- An RFC 3339 formatted string\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.fromRfc3339(rfc3339Date: string): DateTime\n\treturn nil :: any\nend\n\n--[=[\n\t@within DateTime\n\t@tag Constructor\n\n\tCreates a new `DateTime` from an RFC 2822 date-time string.\n\n\t### Errors\n\n\tThis constructor is fallible and may throw an error if the given\n\tstring does not strictly follow the RFC 2822 date-time string format.\n\n\tSome examples of valid RFC 2822 date-time strings are:\n\n\t- `Fri, 21 Nov 1997 09:55:06 -0600`\n\t- `Tue, 1 Jul 2003 10:52:37 +0200`\n\t- `Mon, 23 Dec 2024 01:58:48 GMT`\n\n\t@param rfc2822Date -- An RFC 2822 formatted string\n\t@return DateTime -- The new DateTime object\n]=]\nfunction dateTime.fromRfc2822(rfc2822Date: string): DateTime\n\treturn nil :: any\nend\n\nreturn dateTime\n"
  },
  {
    "path": "crates/lune-std-fs/Cargo.toml",
    "content": "[package]\nname = \"lune-std-fs\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - FS\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\n\nasync-fs = \"2.1\"\nbstr = \"1.9\"\nfutures-lite = \"2.6\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\nlune-std-datetime = { version = \"0.3.4\", path = \"../lune-std-datetime\" }\n"
  },
  {
    "path": "crates/lune-std-fs/src/copy.rs",
    "content": "use std::collections::VecDeque;\nuse std::io::ErrorKind;\nuse std::path::{Path, PathBuf};\n\nuse async_fs as fs;\nuse futures_lite::prelude::*;\nuse mlua::prelude::*;\n\nuse super::options::FsWriteOptions;\n\npub struct CopyContents {\n    // Vec<(relative depth, path)>\n    pub dirs: Vec<(usize, PathBuf)>,\n    pub files: Vec<(usize, PathBuf)>,\n}\n\nasync fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyContents> {\n    let mut dirs = Vec::new();\n    let mut files = Vec::new();\n\n    let mut queue = VecDeque::new();\n\n    let normalized_root = fs::canonicalize(&root).await.map_err(|e| {\n        LuaError::RuntimeError(format!(\"Failed to canonicalize root directory path\\n{e}\"))\n    })?;\n\n    // Push initial children of the root path into the queue\n    let mut reader = fs::read_dir(&normalized_root).await?;\n    while let Some(entry) = reader.try_next().await? {\n        queue.push_back((1, entry.path()));\n    }\n\n    // Go through the current queue, pushing to it\n    // when we find any new descendant directories\n    // FUTURE: Try to do async reading here concurrently to speed it up a bit\n    while let Some((current_depth, current_path)) = queue.pop_front() {\n        let meta = fs::metadata(&current_path).await?;\n        if meta.is_symlink() {\n            return Err(LuaError::RuntimeError(format!(\n                \"Symlinks are not yet supported, encountered at path '{}'\",\n                current_path.display()\n            )));\n        } else if meta.is_dir() {\n            // FUTURE: Add an option in FsWriteOptions for max depth and limit it here\n            let mut entries = fs::read_dir(&current_path).await?;\n            while let Some(entry) = entries.try_next().await? {\n                queue.push_back((current_depth + 1, entry.path()));\n            }\n            dirs.push((current_depth, current_path));\n        } else {\n            files.push((current_depth, current_path));\n        }\n    }\n\n    // Ensure that all directory and file paths are relative to the root path\n    // SAFETY: Since we only ever push dirs and files relative to the root, unwrap is safe\n    for (_, dir) in &mut dirs {\n        *dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf();\n    }\n    for (_, file) in &mut files {\n        *file = file.strip_prefix(&normalized_root).unwrap().to_path_buf();\n    }\n\n    // FUTURE: Deduplicate paths such that these directories:\n    // - foo/\n    // - foo/bar/\n    // - foo/bar/baz/\n    // turn into a single foo/bar/baz/ and let create_dir_all do the heavy lifting\n\n    Ok(CopyContents { dirs, files })\n}\n\nasync fn ensure_no_dir_exists(path: impl AsRef<Path>) -> LuaResult<()> {\n    let path = path.as_ref();\n    match fs::metadata(&path).await {\n        Ok(meta) if meta.is_dir() => Err(LuaError::RuntimeError(format!(\n            \"A directory already exists at the path '{}'\",\n            path.display()\n        ))),\n        _ => Ok(()),\n    }\n}\n\nasync fn ensure_no_file_exists(path: impl AsRef<Path>) -> LuaResult<()> {\n    let path = path.as_ref();\n    match fs::metadata(&path).await {\n        Ok(meta) if meta.is_file() => Err(LuaError::RuntimeError(format!(\n            \"A file already exists at the path '{}'\",\n            path.display()\n        ))),\n        _ => Ok(()),\n    }\n}\n\npub async fn copy(\n    source: impl AsRef<Path>,\n    target: impl AsRef<Path>,\n    options: FsWriteOptions,\n) -> LuaResult<()> {\n    let source = source.as_ref();\n    let target = target.as_ref();\n\n    // Check if we got a file or directory - we will handle them differently below\n    let (is_dir, is_file) = match fs::metadata(&source).await {\n        Ok(meta) => (meta.is_dir(), meta.is_file()),\n        Err(e) if e.kind() == ErrorKind::NotFound => {\n            return Err(LuaError::RuntimeError(format!(\n                \"No file or directory exists at the path '{}'\",\n                source.display()\n            )));\n        }\n        Err(e) => return Err(e.into()),\n    };\n    if !is_file && !is_dir {\n        return Err(LuaError::RuntimeError(format!(\n            \"The given path '{}' is not a file or a directory\",\n            source.display()\n        )));\n    }\n\n    // Perform copying:\n    //\n    // 1. If we are not allowed to overwrite, make sure nothing exists at the target path\n    // 2. If we are allowed to overwrite, remove any previous entry at the path\n    // 3. Write all directories first\n    // 4. Write all files\n\n    if !options.overwrite {\n        if is_file {\n            ensure_no_file_exists(target).await?;\n        } else if is_dir {\n            ensure_no_dir_exists(target).await?;\n        }\n    }\n\n    if is_file {\n        fs::copy(source, target).await?;\n    } else if is_dir {\n        let contents = get_contents_at(source.to_path_buf(), options).await?;\n\n        if options.overwrite {\n            let (is_dir, is_file) = match fs::metadata(&target).await {\n                Ok(meta) => (meta.is_dir(), meta.is_file()),\n                Err(e) if e.kind() == ErrorKind::NotFound => (false, false),\n                Err(e) => return Err(e.into()),\n            };\n            if is_dir {\n                fs::remove_dir_all(target).await?;\n            } else if is_file {\n                fs::remove_file(target).await?;\n            }\n        }\n\n        fs::create_dir_all(target).await?;\n\n        // FUTURE: Write dirs / files concurrently\n        // to potentially speed these operations up\n        for (_, dir) in &contents.dirs {\n            fs::create_dir_all(target.join(dir)).await?;\n        }\n        for (_, file) in &contents.files {\n            fs::copy(source.join(file), target.join(file)).await?;\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune-std-fs/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse std::io::ErrorKind as IoErrorKind;\nuse std::path::PathBuf;\n\nuse async_fs as fs;\nuse bstr::{BString, ByteSlice};\nuse futures_lite::prelude::*;\nuse mlua::prelude::*;\n\nuse lune_utils::TableBuilder;\n\nmod copy;\nmod metadata;\nmod options;\n\nuse self::copy::copy;\nuse self::metadata::FsMetadata;\nuse self::options::FsWriteOptions;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `fs` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `fs` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    TableBuilder::new(lua)?\n        .with_async_function(\"readFile\", fs_read_file)?\n        .with_async_function(\"readDir\", fs_read_dir)?\n        .with_async_function(\"writeFile\", fs_write_file)?\n        .with_async_function(\"writeDir\", fs_write_dir)?\n        .with_async_function(\"removeFile\", fs_remove_file)?\n        .with_async_function(\"removeDir\", fs_remove_dir)?\n        .with_async_function(\"metadata\", fs_metadata)?\n        .with_async_function(\"isFile\", fs_is_file)?\n        .with_async_function(\"isDir\", fs_is_dir)?\n        .with_async_function(\"move\", fs_move)?\n        .with_async_function(\"copy\", fs_copy)?\n        .build_readonly()\n}\n\nasync fn fs_read_file(lua: Lua, path: String) -> LuaResult<LuaString> {\n    let bytes = fs::read(&path).await.into_lua_err()?;\n\n    lua.create_string(bytes)\n}\n\nasync fn fs_read_dir(_: Lua, path: String) -> LuaResult<Vec<String>> {\n    let mut dir_strings = Vec::new();\n    let mut dir = fs::read_dir(&path).await.into_lua_err()?;\n    while let Some(dir_entry) = dir.try_next().await.into_lua_err()? {\n        if let Some(dir_name_str) = dir_entry.file_name().to_str() {\n            dir_strings.push(dir_name_str.to_owned());\n        } else {\n            return Err(LuaError::RuntimeError(format!(\n                \"File name could not be converted into a string: '{}'\",\n                dir_entry.file_name().to_string_lossy()\n            )));\n        }\n    }\n    Ok(dir_strings)\n}\n\nasync fn fs_write_file(_: Lua, (path, contents): (String, BString)) -> LuaResult<()> {\n    fs::write(&path, contents.as_bytes()).await.into_lua_err()\n}\n\nasync fn fs_write_dir(_: Lua, path: String) -> LuaResult<()> {\n    fs::create_dir_all(&path).await.into_lua_err()\n}\n\nasync fn fs_remove_file(_: Lua, path: String) -> LuaResult<()> {\n    fs::remove_file(&path).await.into_lua_err()\n}\n\nasync fn fs_remove_dir(_: Lua, path: String) -> LuaResult<()> {\n    fs::remove_dir_all(&path).await.into_lua_err()\n}\n\nasync fn fs_metadata(_: Lua, path: String) -> LuaResult<FsMetadata> {\n    match fs::metadata(path).await {\n        Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()),\n        Ok(meta) => Ok(FsMetadata::from(meta)),\n        Err(e) => Err(e.into()),\n    }\n}\n\nasync fn fs_is_file(_: Lua, path: String) -> LuaResult<bool> {\n    match fs::metadata(path).await {\n        Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),\n        Ok(meta) => Ok(meta.is_file()),\n        Err(e) => Err(e.into()),\n    }\n}\n\nasync fn fs_is_dir(_: Lua, path: String) -> LuaResult<bool> {\n    match fs::metadata(path).await {\n        Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),\n        Ok(meta) => Ok(meta.is_dir()),\n        Err(e) => Err(e.into()),\n    }\n}\n\nasync fn fs_move(_: Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {\n    let path_from = PathBuf::from(from);\n    if !path_from.exists() {\n        return Err(LuaError::RuntimeError(format!(\n            \"No file or directory exists at the path '{}'\",\n            path_from.display()\n        )));\n    }\n    let path_to = PathBuf::from(to);\n    if !options.overwrite && path_to.exists() {\n        return Err(LuaError::RuntimeError(format!(\n            \"A file or directory already exists at the path '{}'\",\n            path_to.display()\n        )));\n    }\n    fs::rename(path_from, path_to).await.into_lua_err()?;\n    Ok(())\n}\n\nasync fn fs_copy(_: Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {\n    copy(from, to, options).await\n}\n"
  },
  {
    "path": "crates/lune-std-fs/src/metadata.rs",
    "content": "use std::{\n    fmt,\n    fs::{FileType as StdFileType, Metadata as StdMetadata, Permissions as StdPermissions},\n    io::Result as IoResult,\n    str::FromStr,\n    time::SystemTime,\n};\n\nuse mlua::prelude::*;\n\nuse lune_std_datetime::DateTime;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum FsMetadataKind {\n    None,\n    File,\n    Dir,\n    Symlink,\n}\n\nimpl fmt::Display for FsMetadataKind {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Self::None => \"none\",\n                Self::File => \"file\",\n                Self::Dir => \"dir\",\n                Self::Symlink => \"symlink\",\n            }\n        )\n    }\n}\n\nimpl FromStr for FsMetadataKind {\n    type Err = &'static str;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.trim().to_ascii_lowercase().as_ref() {\n            \"none\" => Ok(Self::None),\n            \"file\" => Ok(Self::File),\n            \"dir\" => Ok(Self::Dir),\n            \"symlink\" => Ok(Self::Symlink),\n            _ => Err(\"Invalid metadata kind\"),\n        }\n    }\n}\n\nimpl From<StdFileType> for FsMetadataKind {\n    fn from(value: StdFileType) -> Self {\n        if value.is_file() {\n            Self::File\n        } else if value.is_dir() {\n            Self::Dir\n        } else if value.is_symlink() {\n            Self::Symlink\n        } else {\n            panic!(\"Encountered unknown filesystem filetype\")\n        }\n    }\n}\n\nimpl IntoLua for FsMetadataKind {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        if self == Self::None {\n            Ok(LuaValue::Nil)\n        } else {\n            self.to_string().into_lua(lua)\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct FsPermissions {\n    pub(crate) read_only: bool,\n}\n\nimpl From<StdPermissions> for FsPermissions {\n    fn from(value: StdPermissions) -> Self {\n        Self {\n            read_only: value.readonly(),\n        }\n    }\n}\n\nimpl IntoLua for FsPermissions {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        let tab = lua.create_table_with_capacity(0, 1)?;\n        tab.set(\"readOnly\", self.read_only)?;\n        tab.set_readonly(true);\n        Ok(LuaValue::Table(tab))\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct FsMetadata {\n    pub(crate) kind: FsMetadataKind,\n    pub(crate) exists: bool,\n    pub(crate) created_at: Option<DateTime>,\n    pub(crate) modified_at: Option<DateTime>,\n    pub(crate) accessed_at: Option<DateTime>,\n    pub(crate) permissions: Option<FsPermissions>,\n}\n\nimpl FsMetadata {\n    pub fn not_found() -> Self {\n        Self {\n            kind: FsMetadataKind::None,\n            exists: false,\n            created_at: None,\n            modified_at: None,\n            accessed_at: None,\n            permissions: None,\n        }\n    }\n}\n\nimpl IntoLua for FsMetadata {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        let tab = lua.create_table_with_capacity(0, 6)?;\n        tab.set(\"kind\", self.kind)?;\n        tab.set(\"exists\", self.exists)?;\n        tab.set(\"createdAt\", self.created_at)?;\n        tab.set(\"modifiedAt\", self.modified_at)?;\n        tab.set(\"accessedAt\", self.accessed_at)?;\n        tab.set(\"permissions\", self.permissions)?;\n        tab.set_readonly(true);\n        Ok(LuaValue::Table(tab))\n    }\n}\n\nimpl From<StdMetadata> for FsMetadata {\n    fn from(value: StdMetadata) -> Self {\n        Self {\n            kind: value.file_type().into(),\n            exists: true,\n            created_at: system_time_to_timestamp(value.created()),\n            modified_at: system_time_to_timestamp(value.modified()),\n            accessed_at: system_time_to_timestamp(value.accessed()),\n            permissions: Some(FsPermissions::from(value.permissions())),\n        }\n    }\n}\n\nfn system_time_to_timestamp(res: IoResult<SystemTime>) -> Option<DateTime> {\n    match res {\n        Ok(t) => match t.duration_since(SystemTime::UNIX_EPOCH) {\n            Ok(d) => DateTime::from_unix_timestamp_float(d.as_secs_f64()).ok(),\n            Err(_) => None,\n        },\n        Err(_) => None,\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-fs/src/options.rs",
    "content": "use mlua::prelude::*;\n\n#[derive(Debug, Clone, Copy)]\npub struct FsWriteOptions {\n    pub(crate) overwrite: bool,\n}\n\nimpl FromLua for FsWriteOptions {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        Ok(match value {\n            LuaValue::Nil => Self { overwrite: false },\n            LuaValue::Boolean(b) => Self { overwrite: b },\n            LuaValue::Table(t) => {\n                let overwrite: Option<bool> = t.get(\"overwrite\")?;\n                Self {\n                    overwrite: overwrite.unwrap_or(false),\n                }\n            }\n            _ => {\n                return Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"FsWriteOptions\".to_string(),\n                    message: Some(format!(\n                        \"Invalid write options - expected boolean or table, got {}\",\n                        value.type_name()\n                    )),\n                });\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-fs/types.d.luau",
    "content": "--!nocheck\n\nlocal DateTime = require(\"@lune/datetime\")\ntype DateTime = DateTime.DateTime\n\nexport type MetadataKind = \"file\" | \"dir\" | \"symlink\"\n\n--[=[\n\t@interface MetadataPermissions\n\t@within FS\n\n\tPermissions for the given file or directory.\n\n\tThis is a dictionary that will contain the following values:\n\n\t* `readOnly` - If the target path is read-only or not\n]=]\nexport type MetadataPermissions = {\n\treadOnly: boolean,\n}\n\n-- FIXME: We lose doc comments here below in Metadata because of the union type\n\n--[=[\n\t@interface Metadata\n\t@within FS\n\n\tMetadata for the given file or directory.\n\n\tThis is a dictionary that will contain the following values:\n\n\t* `kind` - If the target path is a `file`, `dir` or `symlink`\n\t* `exists` - If the target path exists\n\t* `createdAt` - The timestamp represented as a `DateTime` object at which the file or directory was created\n\t* `modifiedAt` - The timestamp represented as a `DateTime` object at which the file or directory was last modified\n\t* `accessedAt` - The timestamp represented as a `DateTime` object at which the file or directory was last accessed\n\t* `permissions` - Current permissions for the file or directory\n\n\tNote that timestamps are relative to the unix epoch, and\n\tmay not be accurate if the system clock is not accurate.\n]=]\nexport type Metadata = {\n\tkind: MetadataKind,\n\texists: true,\n\tcreatedAt: DateTime,\n\tmodifiedAt: DateTime,\n\taccessedAt: DateTime,\n\tpermissions: MetadataPermissions,\n} | {\n\tkind: nil,\n\texists: false,\n\tcreatedAt: nil,\n\tmodifiedAt: nil,\n\taccessedAt: nil,\n\tpermissions: nil,\n}\n\n--[=[\n\t@interface WriteOptions\n\t@within FS\n\n\tOptions for filesystem APIs what write to files and/or directories.\n\n\tThis is a dictionary that may contain one or more of the following values:\n\n\t* `overwrite` - If the target path should be overwritten or not, in the case that it already exists\n]=]\nexport type WriteOptions = {\n\toverwrite: boolean?,\n}\n\n--[=[\n\t@class FS\n\n\tBuilt-in library for filesystem access\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\n\t-- Reading a file\n\tlocal myTextFile: string = fs.readFile(\"myFileName.txt\")\n\n\t-- Reading entries (files & dirs) in a directory\n\tfor _, entryName in fs.readDir(\"myDirName\") do\n\t\tif fs.isFile(\"myDirName/\" .. entryName) then\n\t\t\tprint(\"Found file \" .. entryName)\n\t\telseif fs.isDir(\"myDirName/\" .. entryName) then\n\t\t\tprint(\"Found subdirectory \" .. entryName)\n\t\tend\n\tend\n\t```\n]=]\nlocal fs = {}\n\n--[=[\n\t@within FS\n\t@tag must_use\n\n\tReads a file at `path`.\n\n\tAn error will be thrown in the following situations:\n\n\t* `path` does not point to an existing file.\n\t* The current process lacks permissions to read the file.\n\t* Some other I/O error occurred.\n\n\t@param path The path to the file to read\n\t@return The contents of the file\n]=]\nfunction fs.readFile(path: string): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within FS\n\t@tag must_use\n\n\tReads entries in a directory at `path`.\n\n\tAn error will be thrown in the following situations:\n\n\t* `path` does not point to an existing directory.\n\t* The current process lacks permissions to read the contents of the directory.\n\t* Some other I/O error occurred.\n\n\t@param path The directory path to search in\n\t@return A list of files & directories found\n]=]\nfunction fs.readDir(path: string): { string }\n\treturn {}\nend\n\n--[=[\n\t@within FS\n\n\tWrites to a file at `path`.\n\n\tAn error will be thrown in the following situations:\n\n\t* The file's parent directory does not exist.\n\t* The current process lacks permissions to write to the file.\n\t* Some other I/O error occurred.\n\n\t@param path The path of the file\n\t@param contents The contents of the file\n]=]\nfunction fs.writeFile(path: string, contents: buffer | string) end\n\n--[=[\n\t@within FS\n\n\tCreates a directory and its parent directories if they are missing.\n\n\tAn error will be thrown in the following situations:\n\n\t* `path` already points to an existing file or directory.\n\t* The current process lacks permissions to create the directory or its missing parents.\n\t* Some other I/O error occurred.\n\n\t@param path The directory to create\n]=]\nfunction fs.writeDir(path: string) end\n\n--[=[\n\t@within FS\n\n\tRemoves a file.\n\n\tAn error will be thrown in the following situations:\n\n\t* `path` does not point to an existing file.\n\t* The current process lacks permissions to remove the file.\n\t* Some other I/O error occurred.\n\n\t@param path The file to remove\n]=]\nfunction fs.removeFile(path: string) end\n\n--[=[\n\t@within FS\n\n\tRemoves a directory and all of its contents.\n\n\tAn error will be thrown in the following situations:\n\n\t* `path` is not an existing and empty directory.\n\t* The current process lacks permissions to remove the directory.\n\t* Some other I/O error occurred.\n\n\t@param path The directory to remove\n]=]\nfunction fs.removeDir(path: string) end\n\n--[=[\n\t@within FS\n\t@tag must_use\n\n\tGets metadata for the given path.\n\n\tAn error will be thrown in the following situations:\n\n\t* The current process lacks permissions to read at `path`.\n\t* Some other I/O error occurred.\n\n\t@param path The path to get metadata for\n\t@return Metadata for the path\n]=]\nfunction fs.metadata(path: string): Metadata\n\treturn nil :: any\nend\n\n--[=[\n\t@within FS\n\t@tag must_use\n\n\tChecks if a given path is a file.\n\n\tAn error will be thrown in the following situations:\n\n\t* The current process lacks permissions to read at `path`.\n\t* Some other I/O error occurred.\n\n\t@param path The file path to check\n\t@return If the path is a file or not\n]=]\nfunction fs.isFile(path: string): boolean\n\treturn nil :: any\nend\n\n--[=[\n\t@within FS\n\t@tag must_use\n\n\tChecks if a given path is a directory.\n\n\tAn error will be thrown in the following situations:\n\n\t* The current process lacks permissions to read at `path`.\n\t* Some other I/O error occurred.\n\n\t@param path The directory path to check\n\t@return If the path is a directory or not\n]=]\nfunction fs.isDir(path: string): boolean\n\treturn nil :: any\nend\n\n--[=[\n\t@within FS\n\n\tMoves a file or directory to a new path.\n\n\tThrows an error if a file or directory already exists at the target path.\n\tThis can be bypassed by passing `true` as the third argument, or a dictionary of options.\n\tRefer to the documentation for `WriteOptions` for specific option keys and their values.\n\n\tAn error will be thrown in the following situations:\n\n\t* The current process lacks permissions to read at `from` or write at `to`.\n\t* The new path exists on a different mount point.\n\t* Some other I/O error occurred.\n\n\t@param from The path to move from\n\t@param to The path to move to\n\t@param overwriteOrOptions Options for the target path, such as if should be overwritten if it already exists\n]=]\nfunction fs.move(from: string, to: string, overwriteOrOptions: (boolean | WriteOptions)?) end\n\n--[=[\n\t@within FS\n\n\tCopies a file or directory recursively to a new path.\n\n\tThrows an error if a file or directory already exists at the target path.\n\tThis can be bypassed by passing `true` as the third argument, or a dictionary of options.\n\tRefer to the documentation for `WriteOptions` for specific option keys and their values.\n\n\tAn error will be thrown in the following situations:\n\n\t* The current process lacks permissions to read at `from` or write at `to`.\n\t* Some other I/O error occurred.\n\n\t@param from The path to copy from\n\t@param to The path to copy to\n\t@param overwriteOrOptions Options for the target path, such as if should be overwritten if it already exists\n]=]\nfunction fs.copy(from: string, to: string, overwriteOrOptions: (boolean | WriteOptions)?) end\n\nreturn fs\n"
  },
  {
    "path": "crates/lune-std-luau/Cargo.toml",
    "content": "[package]\nname = \"lune-std-luau\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Luau\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\", \"luau-jit\"] }\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-luau/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse mlua::prelude::*;\n\nuse lune_utils::{TableBuilder, jit::JitEnablement};\n\nmod options;\n\nuse self::options::{LuauCompileOptions, LuauLoadOptions};\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `luau` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `luau` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    TableBuilder::new(lua)?\n        .with_function(\"compile\", compile_source)?\n        .with_function(\"load\", load_source)?\n        .build_readonly()\n}\n\nfn compile_source(\n    lua: &Lua,\n    (source, options): (LuaString, LuauCompileOptions),\n) -> LuaResult<LuaString> {\n    options\n        .into_compiler()\n        .compile(source.as_bytes())\n        .and_then(|s| lua.create_string(s))\n}\n\nfn load_source(\n    lua: &Lua,\n    (source, options): (LuaString, LuauLoadOptions),\n) -> LuaResult<LuaFunction> {\n    let mut chunk = lua\n        .load(source.as_bytes().to_vec())\n        .set_name(options.debug_name);\n    let env_changed = options.environment.is_some();\n\n    if let Some(custom_environment) = options.environment {\n        let environment = lua.create_table()?;\n\n        // Inject all globals into the environment\n        if options.inject_globals {\n            for pair in lua.globals().pairs() {\n                let (key, value): (LuaValue, LuaValue) = pair?;\n                environment.set(key, value)?;\n            }\n\n            if let Some(global_metatable) = lua.globals().metatable() {\n                environment.set_metatable(Some(global_metatable))?;\n            }\n        } else if let Some(custom_metatable) = custom_environment.metatable() {\n            // Since we don't need to set the global metatable,\n            // we can just set a custom metatable if it exists\n            environment.set_metatable(Some(custom_metatable))?;\n        }\n\n        // Inject the custom environment\n        for pair in custom_environment.pairs() {\n            let (key, value): (LuaValue, LuaValue) = pair?;\n            environment.set(key, value)?;\n        }\n\n        chunk = chunk.set_environment(environment);\n    }\n\n    // Enable JIT if codegen is enabled and the environment hasn't\n    // changed, otherwise disable JIT since it'll fall back anyways\n    lua.enable_jit(options.codegen_enabled && !env_changed);\n    let function = chunk.into_function()?;\n    lua.enable_jit(\n        lua.app_data_ref::<JitEnablement>()\n            .ok_or(LuaError::runtime(\n                \"Failed to get current JitStatus ref from AppData\",\n            ))?\n            .enabled(),\n    );\n\n    Ok(function)\n}\n"
  },
  {
    "path": "crates/lune-std-luau/src/options.rs",
    "content": "#![allow(clippy::struct_field_names)]\n\nuse mlua::Compiler as LuaCompiler;\nuse mlua::prelude::*;\n\nconst DEFAULT_DEBUG_NAME: &str = \"luau.load(...)\";\n\n/**\n    Options for compiling Lua source code.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct LuauCompileOptions {\n    pub(crate) optimization_level: u8,\n    pub(crate) coverage_level: u8,\n    pub(crate) debug_level: u8,\n}\n\nimpl LuauCompileOptions {\n    pub fn into_compiler(self) -> LuaCompiler {\n        LuaCompiler::default()\n            .set_optimization_level(self.optimization_level)\n            .set_coverage_level(self.coverage_level)\n            .set_debug_level(self.debug_level)\n    }\n}\n\nimpl Default for LuauCompileOptions {\n    fn default() -> Self {\n        // NOTE: This is the same as LuaCompiler::default() values, but they are\n        // not accessible from outside of mlua so we need to recreate them here.\n        Self {\n            optimization_level: 1,\n            coverage_level: 0,\n            debug_level: 1,\n        }\n    }\n}\n\nimpl FromLua for LuauCompileOptions {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        Ok(match value {\n            LuaValue::Nil => Self::default(),\n            LuaValue::Table(t) => {\n                let mut options = Self::default();\n\n                let get_and_check = |name: &'static str| -> LuaResult<Option<u8>> {\n                    match t.get(name)? {\n                        Some(n @ (0..=2)) => Ok(Some(n)),\n                        Some(n) => Err(LuaError::runtime(format!(\n                            \"'{name}' must be one of: 0, 1, or 2 - got {n}\"\n                        ))),\n                        None => Ok(None),\n                    }\n                };\n\n                if let Some(optimization_level) = get_and_check(\"optimizationLevel\")? {\n                    options.optimization_level = optimization_level;\n                }\n                if let Some(coverage_level) = get_and_check(\"coverageLevel\")? {\n                    options.coverage_level = coverage_level;\n                }\n                if let Some(debug_level) = get_and_check(\"debugLevel\")? {\n                    options.debug_level = debug_level;\n                }\n\n                options\n            }\n            _ => {\n                return Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"CompileOptions\".to_string(),\n                    message: Some(format!(\n                        \"Invalid compile options - expected table, got {}\",\n                        value.type_name()\n                    )),\n                });\n            }\n        })\n    }\n}\n\npub struct LuauLoadOptions {\n    pub(crate) debug_name: String,\n    pub(crate) environment: Option<LuaTable>,\n    pub(crate) inject_globals: bool,\n    pub(crate) codegen_enabled: bool,\n}\n\nimpl Default for LuauLoadOptions {\n    fn default() -> Self {\n        Self {\n            debug_name: DEFAULT_DEBUG_NAME.to_string(),\n            environment: None,\n            inject_globals: true,\n            codegen_enabled: false,\n        }\n    }\n}\n\nimpl FromLua for LuauLoadOptions {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        Ok(match value {\n            LuaValue::Nil => Self::default(),\n            LuaValue::Table(t) => {\n                let mut options = Self::default();\n\n                if let Some(debug_name) = t.get(\"debugName\")? {\n                    options.debug_name = debug_name;\n                }\n\n                if let Some(environment) = t.get(\"environment\")? {\n                    options.environment = Some(environment);\n                }\n\n                if let Some(inject_globals) = t.get(\"injectGlobals\")? {\n                    options.inject_globals = inject_globals;\n                }\n\n                if let Some(codegen_enabled) = t.get(\"codegenEnabled\")? {\n                    options.codegen_enabled = codegen_enabled;\n                }\n\n                options\n            }\n            LuaValue::String(s) => Self {\n                debug_name: s.to_string_lossy().to_string(),\n                environment: None,\n                inject_globals: true,\n                codegen_enabled: false,\n            },\n            _ => {\n                return Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"LoadOptions\".to_string(),\n                    message: Some(format!(\n                        \"Invalid load options - expected string or table, got {}\",\n                        value.type_name()\n                    )),\n                });\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-luau/types.d.luau",
    "content": "--[=[\n\t@interface CompileOptions\n\t@within Luau\n\n\tThe options passed to the luau compiler while compiling bytecode.\n\n\tThis is a dictionary that may contain one or more of the following values:\n\n\t* `optimizationLevel` - Sets the compiler option \"optimizationLevel\". Defaults to `1`.\n\t* `coverageLevel` - Sets the compiler option \"coverageLevel\". Defaults to `0`.\n\t* `debugLevel` - Sets the compiler option \"debugLevel\". Defaults to `1`.\n\n\tDocumentation regarding what these values represent can be found [here](https://github.com/Roblox/luau/blob/bd229816c0a82a8590395416c81c333087f541fd/Compiler/include/luacode.h#L13-L39).\n]=]\nexport type CompileOptions = {\n\toptimizationLevel: number?,\n\tcoverageLevel: number?,\n\tdebugLevel: number?,\n}\n\n--[=[\n\t@interface LoadOptions\n\t@within Luau\n\n\tThe options passed while loading a luau chunk from an arbitrary string, or bytecode.\n\n\tThis is a dictionary that may contain one or more of the following values:\n\n\t* `debugName` - The debug name of the closure. Defaults to `luau.load(...)`.\n\t* `environment` - A custom environment to load the chunk in. Setting a custom environment will deoptimize the chunk and forcefully disable codegen. Defaults to the global environment.\n\t* `injectGlobals` - Whether or not to inject globals in the custom environment. Has no effect if no custom environment is provided. Defaults to `true`.\n\t* `codegenEnabled` - Whether or not to enable codegen. Defaults to `false`.\n]=]\nexport type LoadOptions = {\n\tdebugName: string?,\n\tenvironment: { [string]: any }?,\n\tinjectGlobals: boolean?,\n\tcodegenEnabled: boolean?,\n}\n\n--[=[\n\t@class Luau\n\n\tBuilt-in library for generating luau bytecode & functions.\n\n\t### Example usage\n\n\t```lua\n\tlocal luau = require(\"@lune/luau\")\n\n\tlocal bytecode = luau.compile(\"print('Hello, World!')\")\n\tlocal callableFn = luau.load(bytecode)\n\n\t-- Additionally, we can skip the bytecode generation and load a callable function directly from the code itself.\n\t-- local callableFn = luau.load(\"print('Hello, World!')\")\n\n\tcallableFn()\n\t```\n\n\tSince luau bytecode is highly compressible, it may also make sense to compress it using the `serde` library\n\twhile transmitting large amounts of it.\n]=]\nlocal luau = {}\n\n--[=[\n\t@within Luau\n\n\tCompiles sourcecode into Luau bytecode\n\n\tAn error will be thrown if the sourcecode given isn't valid Luau code.\n\n\t### Example usage\n\n\t```lua\n\tlocal luau = require(\"@lune/luau\")\n\n\t-- Compile the source to some highly optimized bytecode\n\tlocal bytecode = luau.compile(\"print('Hello, World!')\", {\n\t\toptimizationLevel = 2,\n\t\tcoverageLevel = 0,\n\t\tdebugLevel = 1,\n\t})\n\t```\n\n\t@param source The string that will be compiled into bytecode\n\t@param compileOptions The options passed to the luau compiler that will output the bytecode\n\n\t@return luau bytecode\n]=]\nfunction luau.compile(source: string, compileOptions: CompileOptions?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Luau\n\n\tGenerates a function from either bytecode or sourcecode\n\n\tAn error will be thrown if the sourcecode given isn't valid luau code.\n\n\t### Example usage\n\n\t```lua\n\tlocal luau = require(\"@lune/luau\")\n\n\tlocal bytecode = luau.compile(\"print('Hello, World!')\")\n\tlocal callableFn = luau.load(bytecode, {\n\t\tdebugName = \"'Hello, World'\"\n\t})\n\n\tcallableFn()\n\t```\n\n\t@param source Either luau bytecode or string source code\n\t@param loadOptions The options passed to luau for loading the chunk\n\n\t@return luau chunk\n]=]\nfunction luau.load(source: string, loadOptions: LoadOptions?): (...any) -> ...any\n\treturn nil :: any\nend\n\nreturn luau\n"
  },
  {
    "path": "crates/lune-std-net/Cargo.toml",
    "content": "[package]\nname = \"lune-std-net\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Net\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\nasync-channel = \"2.3\"\nasync-executor = \"1.13\"\nasync-io = \"2.4\"\nasync-lock = \"3.4\"\nasync-net = \"2.0\"\nasync-tungstenite = \"0.31\"\nblocking = \"1.6\"\nbstr = \"1.9\"\nform_urlencoded = \"1.2\"\nfutures = { version = \"0.3\", default-features = false, features = [\"std\"] }\nfutures-lite = \"2.6\"\nfutures-rustls = \"0.26\"\nhttp-body-util = \"0.1\"\nhyper = { version = \"1.6\", default-features = false, features = [\"http1\", \"client\", \"server\"] }\npin-project-lite = \"0.2\"\nrustls = { version = \"0.23\", default-features = false, features = [\"std\", \"tls12\", \"ring\"] }\nrustls-pki-types = \"1.11\"\nurl = \"2.5\"\nurlencoding = \"2.1\"\nwebpki = \"0.22\"\nwebpki-roots = \"1.0\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\nlune-std-serde = { version = \"0.3.4\", path = \"../lune-std-serde\" }\n"
  },
  {
    "path": "crates/lune-std-net/src/body/cursor.rs",
    "content": "use hyper::body::{Buf, Bytes};\n\nuse super::inner::ReadableBodyInner;\n\n/**\n    The cursor keeping track of inner data and its position for a readable body.\n*/\n#[derive(Debug, Clone)]\npub struct ReadableBodyCursor {\n    inner: ReadableBodyInner,\n    start: usize,\n}\n\nimpl ReadableBodyCursor {\n    pub fn len(&self) -> usize {\n        self.inner.len()\n    }\n\n    pub fn as_slice(&self) -> &[u8] {\n        &self.inner.as_slice()[self.start..]\n    }\n\n    pub fn advance(&mut self, cnt: usize) {\n        self.start += cnt;\n        if self.start > self.inner.len() {\n            self.start = self.inner.len();\n        }\n    }\n\n    pub fn into_bytes(self) -> Bytes {\n        self.inner.into_bytes()\n    }\n}\n\nimpl Buf for ReadableBodyCursor {\n    fn remaining(&self) -> usize {\n        self.len().saturating_sub(self.start)\n    }\n\n    fn chunk(&self) -> &[u8] {\n        self.as_slice()\n    }\n\n    fn advance(&mut self, cnt: usize) {\n        self.advance(cnt);\n    }\n}\n\nimpl<T> From<T> for ReadableBodyCursor\nwhere\n    T: Into<ReadableBodyInner>,\n{\n    fn from(value: T) -> Self {\n        Self {\n            inner: value.into(),\n            start: 0,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/body/incoming.rs",
    "content": "use http_body_util::BodyExt;\nuse hyper::{\n    HeaderMap,\n    body::{Bytes, Incoming},\n    header::CONTENT_ENCODING,\n};\n\nuse mlua::prelude::*;\n\nuse lune_std_serde::{CompressDecompressFormat, decompress};\n\npub async fn handle_incoming_body(\n    headers: &HeaderMap,\n    body: Incoming,\n    should_decompress: bool,\n) -> LuaResult<(Bytes, bool)> {\n    let mut body = body.collect().await.into_lua_err()?.to_bytes();\n\n    let was_decompressed = if should_decompress {\n        let decompress_format = headers\n            .get(CONTENT_ENCODING)\n            .and_then(|value| value.to_str().ok())\n            .and_then(CompressDecompressFormat::detect_from_header_str);\n        if let Some(format) = decompress_format {\n            body = Bytes::from(decompress(body, format).await?);\n            true\n        } else {\n            false\n        }\n    } else {\n        false\n    };\n\n    Ok((body, was_decompressed))\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/body/inner.rs",
    "content": "use hyper::body::{Buf as _, Bytes};\nuse mlua::{Buffer as LuaBuffer, prelude::*};\n\n/**\n    The inner data for a readable body.\n*/\n#[derive(Debug, Clone)]\npub enum ReadableBodyInner {\n    Bytes(Bytes),\n    String(String),\n    LuaString(LuaString),\n    LuaBuffer(LuaBuffer),\n}\n\nimpl ReadableBodyInner {\n    pub fn len(&self) -> usize {\n        match self {\n            Self::Bytes(b) => b.len(),\n            Self::String(s) => s.len(),\n            Self::LuaString(s) => s.as_bytes().len(),\n            Self::LuaBuffer(b) => b.len(),\n        }\n    }\n\n    pub fn as_slice(&self) -> &[u8] {\n        /*\n            SAFETY: Reading lua strings and lua buffers as raw slices is safe while we can\n            guarantee that the inner Lua value + main lua struct has not yet been dropped\n\n            1. Buffers are fixed-size and guaranteed to never resize\n            2. We do not expose any method for writing to the body, only reading\n            3. We guarantee that net.request and net.serve futures are only driven forward\n                while we also know that the Lua + scheduler pair have not yet been dropped\n            4. Any writes from within lua to a buffer, are considered user error,\n               and are not unsafe, since the only possible outcome with the above\n               guarantees is invalid / mangled contents in request / response bodies\n        */\n        match self {\n            Self::Bytes(b) => b.chunk(),\n            Self::String(s) => s.as_bytes(),\n            Self::LuaString(s) => unsafe {\n                // BorrowedBytes would not let us return a plain slice here,\n                // which is what the Buf implementation below needs - we need to\n                // do a little hack here to re-create the slice without a lifetime\n                let b = s.as_bytes();\n\n                let ptr = b.as_ptr();\n                let len = b.len();\n\n                std::slice::from_raw_parts(ptr, len)\n            },\n            Self::LuaBuffer(b) => unsafe {\n                // Similar to above, we need to get the raw slice for the buffer,\n                // which is a bit trickier here because Buffer has a read + write\n                // interface instead of using slices for some unknown reason\n                let v = LuaValue::Buffer(b.clone());\n\n                let ptr = v.to_pointer().cast::<u8>();\n                let len = b.len();\n\n                std::slice::from_raw_parts(ptr, len)\n            },\n        }\n    }\n\n    pub fn into_bytes(self) -> Bytes {\n        match self {\n            Self::Bytes(b) => b,\n            Self::String(s) => Bytes::from(s),\n            Self::LuaString(s) => Bytes::from(s.as_bytes().to_vec()),\n            Self::LuaBuffer(b) => Bytes::from(b.to_vec()),\n        }\n    }\n}\n\nimpl From<&'static str> for ReadableBodyInner {\n    fn from(value: &'static str) -> Self {\n        Self::Bytes(Bytes::from(value))\n    }\n}\n\nimpl From<Vec<u8>> for ReadableBodyInner {\n    fn from(value: Vec<u8>) -> Self {\n        Self::Bytes(Bytes::from(value))\n    }\n}\n\nimpl From<Bytes> for ReadableBodyInner {\n    fn from(value: Bytes) -> Self {\n        Self::Bytes(value)\n    }\n}\n\nimpl From<String> for ReadableBodyInner {\n    fn from(value: String) -> Self {\n        Self::String(value)\n    }\n}\n\nimpl From<LuaString> for ReadableBodyInner {\n    fn from(value: LuaString) -> Self {\n        Self::LuaString(value)\n    }\n}\n\nimpl From<LuaBuffer> for ReadableBodyInner {\n    fn from(value: LuaBuffer) -> Self {\n        Self::LuaBuffer(value)\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/body/mod.rs",
    "content": "#![allow(unused_imports)]\n\nmod cursor;\nmod incoming;\nmod inner;\nmod readable;\n\npub use self::cursor::ReadableBodyCursor;\npub use self::incoming::handle_incoming_body;\npub use self::inner::ReadableBodyInner;\npub use self::readable::ReadableBody;\n"
  },
  {
    "path": "crates/lune-std-net/src/body/readable.rs",
    "content": "use std::convert::Infallible;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\nuse hyper::body::{Body, Bytes, Frame, SizeHint};\nuse mlua::prelude::*;\n\nuse super::cursor::ReadableBodyCursor;\n\n/**\n    Zero-copy wrapper for a readable body.\n\n    Provides methods to read bytes that can be safely used if, and only\n    if, the respective Lua struct for the body has not yet been dropped.\n\n    If the body was created from a `Vec<u8>`, `Bytes`, or a `String`, reading\n    bytes is always safe and does not go through any additional indirections.\n*/\n#[derive(Debug, Clone)]\npub struct ReadableBody {\n    cursor: Option<ReadableBodyCursor>,\n}\n\nimpl ReadableBody {\n    pub const fn empty() -> Self {\n        Self { cursor: None }\n    }\n\n    pub fn as_slice(&self) -> &[u8] {\n        match self.cursor.as_ref() {\n            Some(cursor) => cursor.as_slice(),\n            None => &[],\n        }\n    }\n\n    pub fn into_bytes(self) -> Bytes {\n        match self.cursor {\n            Some(cursor) => cursor.into_bytes(),\n            None => Bytes::new(),\n        }\n    }\n}\n\nimpl Body for ReadableBody {\n    type Data = ReadableBodyCursor;\n    type Error = Infallible;\n\n    fn poll_frame(\n        mut self: Pin<&mut Self>,\n        _cx: &mut Context<'_>,\n    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {\n        Poll::Ready(self.cursor.take().map(|d| Ok(Frame::data(d))))\n    }\n\n    fn is_end_stream(&self) -> bool {\n        self.cursor.is_none()\n    }\n\n    fn size_hint(&self) -> SizeHint {\n        self.cursor.as_ref().map_or_else(\n            || SizeHint::with_exact(0),\n            |c| SizeHint::with_exact(c.len() as u64),\n        )\n    }\n}\n\nimpl<T> From<T> for ReadableBody\nwhere\n    T: Into<ReadableBodyCursor>,\n{\n    fn from(value: T) -> Self {\n        Self {\n            cursor: Some(value.into()),\n        }\n    }\n}\n\nimpl<T> From<Option<T>> for ReadableBody\nwhere\n    T: Into<ReadableBodyCursor>,\n{\n    fn from(value: Option<T>) -> Self {\n        Self {\n            cursor: value.map(Into::into),\n        }\n    }\n}\n\nimpl FromLua for ReadableBody {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        match value {\n            LuaValue::Nil => Ok(Self::empty()),\n            LuaValue::String(str) => Ok(Self::from(str)),\n            LuaValue::Buffer(buf) => Ok(Self::from(buf)),\n            v => Err(LuaError::FromLuaConversionError {\n                from: v.type_name(),\n                to: \"Body\".to_string(),\n                message: Some(format!(\n                    \"Invalid body - expected string or buffer, got {}\",\n                    v.type_name()\n                )),\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/client/fetch.rs",
    "content": "use std::{collections::HashMap, str::FromStr};\n\nuse async_executor::Executor;\nuse http_body_util::Full;\nuse hyper::{\n    Method, Request as HyperRequest,\n    body::Bytes,\n    client::conn::http1::handshake,\n    header::{ACCEPT, CONTENT_LENGTH, HOST, HeaderName, HeaderValue, USER_AGENT},\n};\n\nuse url::Url;\n\nuse crate::{\n    client::stream::HttpStream,\n    shared::{hyper::HyperIo, request::Request, response::Response},\n};\n\n/**\n    Sends a simple request and returns the final response.\n\n    This will follow any redirects returned by the server,\n    modifying the request method and body as necessary.\n\n    # WARNING\n\n    This is an API meant only for private consumption by the main `lune`\n    crate - unlike other functions in *this* crate, it is NOT guaranteed\n    to follow semver or be otherwise stable outside of the private usage.\n*/\n#[doc(hidden)]\n#[allow(clippy::implicit_hasher)]\npub async fn fetch(\n    url: Url,\n    method: Option<Method>,\n    headers: Option<HashMap<String, String>>,\n    body: Option<Vec<u8>>,\n) -> Result<Response, String> {\n    let body = match body {\n        Some(body) => Bytes::from(body),\n        None => Bytes::new(),\n    };\n\n    let mut request = HyperRequest::new(body);\n    *request.uri_mut() = url.to_string().parse().unwrap();\n    if let Some(method) = method {\n        *request.method_mut() = method;\n    }\n    if let Some(headers) = headers {\n        for (key, val) in headers {\n            let key = HeaderName::from_str(key.as_str()).map_err(|e| e.to_string())?;\n            let val = HeaderValue::from_str(val.as_str()).map_err(|e| e.to_string())?;\n            request.headers_mut().insert(key, val);\n        }\n    }\n\n    // Some headers are required by most if not\n    // all servers, make sure those are present...\n    if !request.headers().contains_key(USER_AGENT.as_str()) {\n        let ua = concat!(env!(\"CARGO_PKG_NAME\"), \"/\", env!(\"CARGO_PKG_VERSION\"));\n        let ua = HeaderValue::from_str(ua).unwrap();\n        request.headers_mut().insert(USER_AGENT, ua);\n    }\n    if !request.headers().contains_key(CONTENT_LENGTH.as_str()) && request.method() != Method::GET {\n        let len = request.body().len().to_string();\n        let len = HeaderValue::from_str(&len).unwrap();\n        request.headers_mut().insert(CONTENT_LENGTH, len);\n    }\n    if !request.headers().contains_key(ACCEPT.as_str()) {\n        let accept = HeaderValue::from_static(\"*/*\");\n        request.headers_mut().insert(ACCEPT, accept);\n    }\n\n    // ... we can now safely continue and send the request\n    let mut req = Request::from(request);\n    req.decompress = true;\n\n    let exec = Executor::new();\n    let fut = fetch_inner(&exec, url, req);\n    exec.run(fut).await\n}\n\nasync fn fetch_inner(\n    exec: &Executor<'_>,\n    mut url: Url,\n    mut request: Request,\n) -> Result<Response, String> {\n    loop {\n        let stream = HttpStream::connect_url(url.clone())\n            .await\n            .map_err(|e| e.to_string())?;\n\n        let (mut sender, conn) = handshake(HyperIo::from(stream))\n            .await\n            .map_err(|e| e.to_string())?;\n\n        exec.spawn(conn).detach();\n\n        let (mut parts, body) = request.clone_inner().into_parts();\n        if let Some(host) = parts.uri.host() {\n            let host = HeaderValue::from_str(host).unwrap();\n            parts.headers.insert(HOST, host);\n        }\n\n        let data = HyperRequest::from_parts(parts, Full::new(body.into_bytes()));\n        let incoming = sender.send_request(data).await.map_err(|e| e.to_string())?;\n\n        if super::try_follow_redirect(&mut url, &mut request, &incoming)\n            .map_err(ToString::to_string)?\n        {\n            continue;\n        }\n\n        break Response::from_incoming(incoming, request.decompress)\n            .await\n            .map_err(|e| e.to_string());\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/client/mod.rs",
    "content": "use hyper::{Method, Response as HyperResponse, Uri, body::Incoming, header::LOCATION};\n\nuse mlua::prelude::*;\nuse url::Url;\n\nuse crate::{\n    body::ReadableBody,\n    client::{\n        stream::{MaybeTlsStream, WsStream},\n        tcp::TcpConfig,\n    },\n    shared::{request::Request, tcp::Tcp, websocket::Websocket},\n};\n\npub mod rustls;\npub mod stream;\npub mod tcp;\n\nmod fetch;\nmod send;\n\npub use self::fetch::fetch;\npub use self::send::send;\n\nconst MAX_REDIRECTS: usize = 10;\n\n/**\n    Connects to a websocket at the given URL.\n*/\npub async fn connect_ws(url: Url) -> LuaResult<Websocket<WsStream>> {\n    let stream = WsStream::connect_url(url).await?;\n    Ok(Websocket::from(stream))\n}\n\n/**\n    Connects using plain TCP using the given host, port, and config.\n*/\npub async fn connect_tcp(host: String, port: u16, config: TcpConfig) -> LuaResult<Tcp> {\n    let tls = config.tls.unwrap_or_default();\n\n    let stream = MaybeTlsStream::connect(&host, port, tls)\n        .await\n        .into_lua_err()?;\n\n    if let Some(ttl) = config.ttl {\n        stream.set_ttl(ttl).into_lua_err()?;\n    }\n\n    Ok(Tcp::from(stream))\n}\n\nfn try_follow_redirect(\n    url: &mut Url,\n    request: &mut Request,\n    response: &HyperResponse<Incoming>,\n) -> Result<bool, &'static str> {\n    if let Some((new_method, new_uri)) = check_redirect(request.inner.method().clone(), response) {\n        if request.redirects.is_some_and(|r| r >= MAX_REDIRECTS) {\n            return Err(\"Too many redirects\");\n        }\n\n        if new_uri.host().is_some() {\n            let new_url = new_uri\n                .to_string()\n                .parse()\n                .map_err(|_| \"Invalid redirect URL\")?;\n            *url = new_url;\n        } else {\n            url.set_path(new_uri.path());\n        }\n\n        if new_method == Method::GET {\n            *request.inner.body_mut() = ReadableBody::empty();\n        }\n\n        *request.inner.method_mut() = new_method;\n        *request.inner.uri_mut() = new_uri;\n\n        *request.redirects.get_or_insert_default() += 1;\n\n        Ok(true)\n    } else {\n        Ok(false)\n    }\n}\n\nfn check_redirect(method: Method, response: &HyperResponse<Incoming>) -> Option<(Method, Uri)> {\n    if !response.status().is_redirection() {\n        return None;\n    }\n\n    let location = response.headers().get(LOCATION)?;\n    let location = location.to_str().ok()?;\n    let location = location.parse().ok()?;\n\n    let method = match response.status().as_u16() {\n        301..=303 => Method::GET,\n        _ => method,\n    };\n\n    Some((method, location))\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/client/rustls.rs",
    "content": "use std::sync::{\n    Arc, LazyLock,\n    atomic::{AtomicBool, Ordering},\n};\n\nuse rustls::{ClientConfig, crypto::ring};\n\nstatic PROVIDER_INITIALIZED: AtomicBool = AtomicBool::new(false);\n\npub fn initialize_provider() {\n    if !PROVIDER_INITIALIZED.load(Ordering::Relaxed) {\n        PROVIDER_INITIALIZED.store(true, Ordering::Relaxed);\n        // Only errors if already installed, which is fine\n        ring::default_provider().install_default().ok();\n    }\n}\n\npub static CLIENT_CONFIG: LazyLock<Arc<ClientConfig>> = LazyLock::new(|| {\n    initialize_provider();\n    rustls::ClientConfig::builder()\n        .with_root_certificates(rustls::RootCertStore {\n            roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),\n        })\n        .with_no_client_auth()\n        .into()\n});\n"
  },
  {
    "path": "crates/lune-std-net/src/client/send.rs",
    "content": "use http_body_util::Full;\nuse hyper::{\n    Method, Request as HyperRequest,\n    client::conn::http1::handshake,\n    header::{ACCEPT, CONTENT_LENGTH, HOST, HeaderValue, USER_AGENT},\n};\n\nuse mlua::prelude::*;\nuse url::Url;\n\nuse crate::{\n    client::stream::HttpStream,\n    shared::{\n        headers::create_user_agent_header,\n        hyper::{HyperExecutor, HyperIo},\n        request::Request,\n        response::Response,\n    },\n};\n\n/**\n    Sends the request and returns the final response.\n\n    This will follow any redirects returned by the server,\n    modifying the request method and body as necessary.\n*/\npub async fn send(mut request: Request, lua: Lua) -> LuaResult<Response> {\n    let mut url = request\n        .inner\n        .uri()\n        .to_string()\n        .parse::<Url>()\n        .into_lua_err()?;\n\n    // Some headers are required by most if not\n    // all servers, make sure those are present...\n    if !request.headers().contains_key(USER_AGENT.as_str()) {\n        let ua = create_user_agent_header(&lua)?;\n        let ua = HeaderValue::from_str(&ua).unwrap();\n        request.inner.headers_mut().insert(USER_AGENT, ua);\n    }\n    if !request.headers().contains_key(CONTENT_LENGTH.as_str()) && request.method() != Method::GET {\n        let len = request.body().len().to_string();\n        let len = HeaderValue::from_str(&len).unwrap();\n        request.inner.headers_mut().insert(CONTENT_LENGTH, len);\n    }\n    if !request.headers().contains_key(ACCEPT.as_str()) {\n        let accept = HeaderValue::from_static(\"*/*\");\n        request.inner.headers_mut().insert(ACCEPT, accept);\n    }\n\n    // ... we can now safely continue and send the request\n    loop {\n        let stream = HttpStream::connect_url(url.clone()).await?;\n\n        let (mut sender, conn) = handshake(HyperIo::from(stream)).await.into_lua_err()?;\n\n        HyperExecutor::execute(lua.clone(), conn);\n\n        let (mut parts, body) = request.clone_inner().into_parts();\n        if let Some(host) = parts.uri.host() {\n            let host = HeaderValue::from_str(host).unwrap();\n            parts.headers.insert(HOST, host);\n        }\n\n        let data = HyperRequest::from_parts(parts, Full::new(body.into_bytes()));\n        let incoming = sender.send_request(data).await.into_lua_err()?;\n\n        if super::try_follow_redirect(&mut url, &mut request, &incoming)\n            .map_err(LuaError::external)?\n        {\n            continue;\n        }\n\n        break Response::from_incoming(incoming, request.decompress).await;\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/client/stream.rs",
    "content": "use std::{\n    io::{Error, Result},\n    net::SocketAddr,\n    pin::Pin,\n    sync::Arc,\n    task::{Context, Poll},\n};\n\nuse async_net::TcpStream;\nuse async_tungstenite::{\n    WebSocketStream as TungsteniteStream,\n    tungstenite::{Error as TungsteniteError, Message, Result as TungsteniteResult},\n};\nuse futures::Sink;\nuse futures_lite::prelude::*;\nuse futures_rustls::{TlsConnector, TlsStream};\nuse rustls_pki_types::ServerName;\nuse url::Url;\n\nuse crate::client::rustls::CLIENT_CONFIG;\n\n/**\n    Type alias for differentiating between a [`MaybeTlsStream`]\n    from a [`WsStream`] when using HTTP a bit clearer.\n*/\npub type HttpStream = MaybeTlsStream;\n\n/**\n    A TCP stream that may or may not be encrypted using TLS.\n\n    Implements both `AsyncRead` and `AsyncWrite` such that\n    any consumers of this stream do not need to care about\n    the inner TLS-or-not stream and any associated details.\n*/\n#[derive(Debug)]\npub enum MaybeTlsStream {\n    Plain(Box<TcpStream>),\n    Tls(Box<TlsStream<TcpStream>>),\n}\n\nimpl MaybeTlsStream {\n    /**\n        Connects to a host and port, additionally using TLS if specified.\n\n        Using this constructor is likely unergonomic - prefer using\n        [`MaybeTlsStream::connect_url`] instead, if possible.\n\n        The given `host` must be a valid DNS name, when using TLS.\n    */\n    pub async fn connect(host: &str, port: u16, tls: bool) -> Result<Self> {\n        let stream = TcpStream::connect((host, port)).await?;\n\n        let stream = if tls {\n            let servname = ServerName::try_from(host).map_err(Error::other)?.to_owned();\n            let connector = TlsConnector::from(Arc::clone(&CLIENT_CONFIG));\n            let stream = connector.connect(servname, stream).await?;\n            Self::Tls(Box::new(TlsStream::Client(stream)))\n        } else {\n            Self::Plain(Box::new(stream))\n        };\n\n        Ok(stream)\n    }\n\n    /**\n       Connects to the given URL.\n\n       Automatically determines whether or not to use TLS based on the URL scheme.\n    */\n    pub async fn connect_url(url: Url) -> Result<Self> {\n        let Some(host) = url.host() else {\n            return Err(Error::other(\"unknown or missing host\"));\n        };\n        let Some(port) = url.port_or_known_default() else {\n            return Err(Error::other(\"unknown or missing port\"));\n        };\n\n        let use_tls = match url.scheme() {\n            \"http\" | \"ws\" => false,\n            \"https\" | \"wss\" => true,\n            s => return Err(Error::other(format!(\"unsupported scheme: {s}\"))),\n        };\n\n        let host = host.to_string();\n        Self::connect(&host, port, use_tls).await\n    }\n\n    /**\n        Returns the local address of the stream.\n    */\n    pub fn local_addr(&self) -> Result<SocketAddr> {\n        self.as_ref().local_addr()\n    }\n\n    /**\n        Returns the remote address of the stream.\n    */\n    pub fn remote_addr(&self) -> Result<SocketAddr> {\n        self.as_ref().peer_addr()\n    }\n\n    /**\n        Sets the TTL (Time To Live) for packets in the stream.\n\n        See [`TcpStream::set_ttl`] for additional information.\n    */\n    pub fn set_ttl(&self, ttl: u32) -> Result<()> {\n        self.as_ref().set_ttl(ttl)\n    }\n}\n\nimpl AsRef<TcpStream> for MaybeTlsStream {\n    fn as_ref(&self) -> &TcpStream {\n        match self {\n            MaybeTlsStream::Plain(stream) => stream,\n            MaybeTlsStream::Tls(stream) => stream.get_ref().0,\n        }\n    }\n}\n\nimpl From<TcpStream> for MaybeTlsStream {\n    fn from(stream: TcpStream) -> Self {\n        MaybeTlsStream::Plain(Box::new(stream))\n    }\n}\n\nimpl From<TlsStream<TcpStream>> for MaybeTlsStream {\n    fn from(stream: TlsStream<TcpStream>) -> Self {\n        MaybeTlsStream::Tls(Box::new(stream))\n    }\n}\n\nimpl AsyncRead for MaybeTlsStream {\n    fn poll_read(\n        mut self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &mut [u8],\n    ) -> Poll<Result<usize>> {\n        match &mut *self {\n            MaybeTlsStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),\n            MaybeTlsStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),\n        }\n    }\n}\n\nimpl AsyncWrite for MaybeTlsStream {\n    fn poll_write(\n        mut self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<Result<usize>> {\n        match &mut *self {\n            MaybeTlsStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),\n            MaybeTlsStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),\n        }\n    }\n\n    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {\n        match &mut *self {\n            MaybeTlsStream::Plain(stream) => Pin::new(stream).poll_close(cx),\n            MaybeTlsStream::Tls(stream) => Pin::new(stream).poll_close(cx),\n        }\n    }\n\n    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {\n        match &mut *self {\n            MaybeTlsStream::Plain(stream) => Pin::new(stream).poll_flush(cx),\n            MaybeTlsStream::Tls(stream) => Pin::new(stream).poll_flush(cx),\n        }\n    }\n}\n\n/**\n    A WebSocket stream.\n\n    Implements both `Sink` and `Stream` traits.\n*/\n#[derive(Debug)]\npub struct WsStream {\n    inner: TungsteniteStream<MaybeTlsStream>,\n}\n\nimpl WsStream {\n    /**\n       Connects to the given URL.\n\n       Automatically determines whether or not to use TLS based on the URL scheme.\n    */\n    pub async fn connect_url(url: Url) -> Result<Self> {\n        let url_str = url.to_string();\n\n        let stream = MaybeTlsStream::connect_url(url).await?;\n        let (inner, _) = async_tungstenite::client_async(url_str, stream)\n            .await\n            .map_err(Error::other)?;\n\n        Ok(Self { inner })\n    }\n}\n\nimpl Sink<Message> for WsStream {\n    type Error = TungsteniteError;\n\n    fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<TungsteniteResult<()>> {\n        Pin::new(&mut self.inner).poll_ready(cx)\n    }\n\n    fn start_send(mut self: Pin<&mut Self>, item: Message) -> TungsteniteResult<()> {\n        Pin::new(&mut self.inner).start_send(item)\n    }\n\n    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<TungsteniteResult<()>> {\n        Pin::new(&mut self.inner).poll_flush(cx)\n    }\n\n    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<TungsteniteResult<()>> {\n        Pin::new(&mut self.inner).poll_close(cx)\n    }\n}\n\nimpl Stream for WsStream {\n    type Item = TungsteniteResult<Message>;\n\n    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        Pin::new(&mut self.inner).poll_next(cx)\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/client/tcp.rs",
    "content": "use mlua::prelude::*;\n\n#[derive(Debug, Default, Clone, Copy)]\npub struct TcpConfig {\n    pub tls: Option<bool>,\n    pub ttl: Option<u32>,\n}\n\nimpl FromLua for TcpConfig {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::Nil = value {\n            Ok(TcpConfig::default())\n        } else if let LuaValue::Boolean(tls) = value {\n            Ok(TcpConfig {\n                tls: Some(tls),\n                ttl: None,\n            })\n        } else if let LuaValue::Table(tab) = value {\n            let mut this = TcpConfig::default();\n\n            if let Some(tls) = tab.get::<Option<_>>(\"tls\")? {\n                this.tls = Some(tls);\n            }\n            if let Some(ttl) = tab.get::<Option<_>>(\"ttl\")? {\n                this.ttl = Some(ttl);\n            }\n\n            Ok(this)\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: String::from(\"TcpConfig\"),\n                message: None,\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse lune_utils::TableBuilder;\nuse mlua::prelude::*;\n\npub(crate) mod body;\npub(crate) mod client;\npub(crate) mod server;\npub(crate) mod shared;\npub(crate) mod url;\n\nuse crate::shared::{hyper::HyperExecutor, tcp::Tcp};\n\nuse self::{\n    client::{stream::WsStream, tcp::TcpConfig},\n    server::config::ServeConfig,\n    shared::{request::Request, response::Response, websocket::Websocket},\n};\n\npub use self::client::fetch;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `net` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `net` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    HyperExecutor::attach(&lua);\n\n    let submodule_http = TableBuilder::new(lua.clone())?\n        .with_async_function(\"request\", net_http_request)?\n        .with_async_function(\"serve\", net_http_serve)?\n        .build_readonly()?;\n\n    let submodule_tcp = TableBuilder::new(lua.clone())?\n        .with_async_function(\"connect\", net_tcp_connect)?\n        .build_readonly()?;\n\n    let submodule_ws = TableBuilder::new(lua.clone())?\n        .with_async_function(\"connect\", net_ws_connect)?\n        .build_readonly()?;\n\n    TableBuilder::new(lua)?\n        .with_async_function(\"request\", net_http_request)?\n        .with_async_function(\"socket\", net_ws_connect)?\n        .with_async_function(\"serve\", net_http_serve)?\n        .with_function(\"urlEncode\", net_url_encode)?\n        .with_function(\"urlDecode\", net_url_decode)?\n        .with_value(\"http\", submodule_http)?\n        .with_value(\"tcp\", submodule_tcp)?\n        .with_value(\"ws\", submodule_ws)?\n        .build_readonly()\n}\n\nasync fn net_http_request(lua: Lua, req: Request) -> LuaResult<Response> {\n    self::client::send(req, lua).await\n}\n\nasync fn net_http_serve(lua: Lua, (port, config): (u16, ServeConfig)) -> LuaResult<LuaTable> {\n    self::server::serve(lua.clone(), port, config)\n        .await?\n        .into_lua_table(lua)\n}\n\nasync fn net_tcp_connect(_: Lua, (host, port, config): (String, u16, TcpConfig)) -> LuaResult<Tcp> {\n    self::client::connect_tcp(host, port, config).await\n}\n\nasync fn net_ws_connect(_: Lua, url: String) -> LuaResult<Websocket<WsStream>> {\n    let url = url.parse().into_lua_err()?;\n    self::client::connect_ws(url).await\n}\n\nfn net_url_encode(\n    lua: &Lua,\n    (lua_string, as_binary): (LuaString, Option<bool>),\n) -> LuaResult<LuaString> {\n    let as_binary = as_binary.unwrap_or_default();\n    let bytes = self::url::encode(lua_string, as_binary)?;\n    lua.create_string(bytes)\n}\n\nfn net_url_decode(\n    lua: &Lua,\n    (lua_string, as_binary): (LuaString, Option<bool>),\n) -> LuaResult<LuaString> {\n    let as_binary = as_binary.unwrap_or_default();\n    let bytes = self::url::decode(lua_string, as_binary)?;\n    lua.create_string(bytes)\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/server/config.rs",
    "content": "use std::net::{IpAddr, Ipv4Addr};\n\nuse mlua::prelude::*;\n\nconst DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);\n\nconst WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#\"\nreturn {\n    status = 426,\n    body = \"Upgrade Required\",\n    headers = {\n        Upgrade = \"websocket\",\n    },\n}\n\"#;\n\n#[derive(Debug, Clone)]\npub struct ServeConfig {\n    pub address: IpAddr,\n    pub handle_request: LuaFunction,\n    pub handle_web_socket: Option<LuaFunction>,\n}\n\nimpl FromLua for ServeConfig {\n    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {\n        if let LuaValue::Function(f) = &value {\n            // Single function = request handler, rest is default\n            Ok(ServeConfig {\n                handle_request: f.clone(),\n                handle_web_socket: None,\n                address: DEFAULT_IP_ADDRESS,\n            })\n        } else if let LuaValue::Table(t) = &value {\n            // Table means custom options\n            let address: Option<LuaString> = t.get(\"address\")?;\n            let handle_request: Option<LuaFunction> = t.get(\"handleRequest\")?;\n            let handle_web_socket: Option<LuaFunction> = t.get(\"handleWebSocket\")?;\n            if handle_request.is_some() || handle_web_socket.is_some() {\n                let address: IpAddr = match &address {\n                    Some(addr) => {\n                        let addr_str = addr.to_str()?;\n\n                        addr_str\n                            .trim_start_matches(\"http://\")\n                            .trim_start_matches(\"https://\")\n                            .parse()\n                            .map_err(|_e| LuaError::FromLuaConversionError {\n                                from: value.type_name(),\n                                to: \"ServeConfig\".to_string(),\n                                message: Some(format!(\n                                    \"IP address format is incorrect - \\\n                                    expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', \\\n                                    got '{addr_str}'\"\n                                )),\n                            })?\n                    }\n                    None => DEFAULT_IP_ADDRESS,\n                };\n\n                Ok(Self {\n                    address,\n                    handle_request: handle_request.unwrap_or_else(|| {\n                        lua.load(WEB_SOCKET_UPDGRADE_REQUEST_HANDLER)\n                            .into_function()\n                            .expect(\"Failed to create default http responder function\")\n                    }),\n                    handle_web_socket,\n                })\n            } else {\n                Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"ServeConfig\".to_string(),\n                    message: Some(String::from(\n                        \"Invalid serve config - expected table with 'handleRequest' or 'handleWebSocket' function\",\n                    )),\n                })\n            }\n        } else {\n            // Anything else is invalid\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"ServeConfig\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/server/handle.rs",
    "content": "use std::{\n    net::SocketAddr,\n    sync::{\n        Arc,\n        atomic::{AtomicBool, Ordering},\n    },\n};\n\nuse async_channel::{Receiver, Sender, unbounded};\n\nuse lune_utils::TableBuilder;\nuse mlua::prelude::*;\n\n#[derive(Debug, Clone)]\npub struct ServeHandle {\n    addr: SocketAddr,\n    shutdown: Arc<AtomicBool>,\n    sender: Sender<()>,\n}\n\nimpl ServeHandle {\n    pub fn new(addr: SocketAddr) -> (Self, Receiver<()>) {\n        let (sender, receiver) = unbounded();\n        let this = Self {\n            addr,\n            shutdown: Arc::new(AtomicBool::new(false)),\n            sender,\n        };\n        (this, receiver)\n    }\n\n    // TODO: Remove this in the next major release to use colon/self\n    // based call syntax and userdata implementation below instead\n    pub fn into_lua_table(self, lua: Lua) -> LuaResult<LuaTable> {\n        let shutdown = self.shutdown.clone();\n        let sender = self.sender.clone();\n        TableBuilder::new(lua)?\n            .with_value(\"ip\", self.addr.ip().to_string())?\n            .with_value(\"port\", self.addr.port())?\n            .with_function(\"stop\", move |_, ()| {\n                if shutdown.load(Ordering::SeqCst) {\n                    Err(LuaError::runtime(\"Server already stopped\"))\n                } else {\n                    shutdown.store(true, Ordering::SeqCst);\n                    sender.try_send(()).ok();\n                    sender.close();\n                    Ok(())\n                }\n            })?\n            .build()\n    }\n}\n\nimpl LuaUserData for ServeHandle {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"ip\", |_, this| Ok(this.addr.ip().to_string()));\n        fields.add_field_method_get(\"port\", |_, this| Ok(this.addr.port()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_method(\"stop\", |_, this, ()| {\n            if this.shutdown.load(Ordering::SeqCst) {\n                Err(LuaError::runtime(\"Server already stopped\"))\n            } else {\n                this.shutdown.store(true, Ordering::SeqCst);\n                this.sender.try_send(()).ok();\n                this.sender.close();\n                Ok(())\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/server/mod.rs",
    "content": "use std::{cell::Cell, net::SocketAddr, rc::Rc};\n\nuse async_net::TcpListener;\nuse futures_lite::pin;\nuse hyper::server::conn::http1::Builder as Http1Builder;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::LuaSpawnExt;\n\nuse crate::{\n    server::{config::ServeConfig, handle::ServeHandle, service::Service},\n    shared::{\n        futures::{Either, either},\n        hyper::{HyperIo, HyperTimer},\n    },\n};\n\npub mod config;\npub mod handle;\npub mod service;\npub mod upgrade;\n\n/**\n    Starts an HTTP server using the given port and configuration.\n\n    Returns a `ServeHandle` that can be used to gracefully stop the server.\n*/\npub async fn serve(lua: Lua, port: u16, config: ServeConfig) -> LuaResult<ServeHandle> {\n    let address = SocketAddr::from((config.address, port));\n    let service = Service {\n        lua: lua.clone(),\n        address,\n        config,\n    };\n\n    let listener = TcpListener::bind(address).await?;\n    let (handle, shutdown_rx) = ServeHandle::new(address);\n\n    lua.spawn_local({\n        let lua = lua.clone();\n        async move {\n            let handle_dropped = Rc::new(Cell::new(false));\n            loop {\n                // 1. Keep accepting new connections until we should shutdown\n                let (conn, addr) = if handle_dropped.get() {\n                    // 1a. Handle has been dropped, and we don't need to listen for shutdown\n                    match listener.accept().await {\n                        Ok(acc) => acc,\n                        Err(_err) => {\n                            // TODO: Propagate error somehow\n                            continue;\n                        }\n                    }\n                } else {\n                    // 1b. Handle is possibly active, we must listen for shutdown\n                    match either(shutdown_rx.recv(), listener.accept()).await {\n                        Either::Left(Ok(())) => break,\n                        Either::Left(Err(_)) => {\n                            // NOTE #1: We will only get a RecvError if the serve handle is dropped,\n                            // this means lua has garbage collected it and the user does not want\n                            // to manually stop the server using the serve handle. Run forever.\n                            handle_dropped.set(true);\n                            continue;\n                        }\n                        Either::Right(Ok(acc)) => acc,\n                        Either::Right(Err(_err)) => {\n                            // TODO: Propagate error somehow\n                            continue;\n                        }\n                    }\n                };\n\n                // 2. For each connection, spawn a new task to handle it\n                lua.spawn_local({\n                    let rx = shutdown_rx.clone();\n                    let io = HyperIo::from(conn);\n\n                    let mut svc = service.clone();\n                    svc.address = addr;\n\n                    let handle_dropped = Rc::clone(&handle_dropped);\n                    async move {\n                        let conn = Http1Builder::new()\n                            .writev(false)\n                            .timer(HyperTimer)\n                            .keep_alive(true)\n                            .serve_connection(io, svc)\n                            .with_upgrades();\n                        if handle_dropped.get() {\n                            if let Err(_err) = conn.await {\n                                // TODO: Propagate error somehow\n                            }\n                        } else {\n                            // NOTE #2: Because we use keep_alive for websockets above, we need to\n                            // also manually poll this future and handle the graceful shutdown,\n                            // otherwise the already accepted connection will linger and run\n                            // even if the stop method has been called on the serve handle\n                            pin!(conn);\n                            match either(rx.recv(), conn.as_mut()).await {\n                                Either::Left(Ok(())) => conn.as_mut().graceful_shutdown(),\n                                Either::Left(Err(_)) => {\n                                    // Same as note #1\n                                    handle_dropped.set(true);\n                                    if let Err(_err) = conn.await {\n                                        // TODO: Propagate error somehow\n                                    }\n                                }\n                                Either::Right(Ok(())) => {}\n                                Either::Right(Err(_err)) => {\n                                    // TODO: Propagate error somehow\n                                }\n                            }\n                        }\n                    }\n                });\n            }\n        }\n    });\n\n    Ok(handle)\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/server/service.rs",
    "content": "use std::{future::Future, net::SocketAddr, pin::Pin};\n\nuse async_tungstenite::{WebSocketStream, tungstenite::protocol::Role};\nuse hyper::{\n    Request as HyperRequest, Response as HyperResponse, StatusCode, body::Incoming,\n    service::Service as HyperService,\n};\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};\n\nuse crate::{\n    body::ReadableBody,\n    server::{\n        config::ServeConfig,\n        upgrade::{is_upgrade_request, make_upgrade_response},\n    },\n    shared::{hyper::HyperIo, request::Request, response::Response, websocket::Websocket},\n};\n\n#[derive(Debug, Clone)]\npub(super) struct Service {\n    pub(super) lua: Lua,\n    pub(super) address: SocketAddr, // NOTE: This must be the remote address of the connected client\n    pub(super) config: ServeConfig,\n}\n\nimpl HyperService<HyperRequest<Incoming>> for Service {\n    type Response = HyperResponse<ReadableBody>;\n    type Error = LuaError;\n    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;\n\n    fn call(&self, req: HyperRequest<Incoming>) -> Self::Future {\n        if is_upgrade_request(&req)\n            && let Some(handler) = self.config.handle_web_socket.clone()\n        {\n            let lua = self.lua.clone();\n            return Box::pin(async move {\n                let response = match make_upgrade_response(&req) {\n                    Ok(res) => res,\n                    Err(err) => {\n                        return Ok(HyperResponse::builder()\n                            .status(StatusCode::BAD_REQUEST)\n                            .body(ReadableBody::from(err.to_string()))\n                            .unwrap());\n                    }\n                };\n\n                lua.spawn_local({\n                    let lua = lua.clone();\n                    async move {\n                        if let Err(_err) = handle_websocket(lua, handler, req).await {\n                            // TODO: Propagate the error somehow?\n                        }\n                    }\n                });\n\n                Ok(response)\n            });\n        }\n\n        let lua = self.lua.clone();\n        let address = self.address;\n        let handler = self.config.handle_request.clone();\n        Box::pin(async move {\n            match handle_request(lua, handler, req, address).await {\n                Ok(response) => Ok(response),\n                Err(_err) => {\n                    // TODO: Propagate the error somehow?\n                    Ok(HyperResponse::builder()\n                        .status(StatusCode::INTERNAL_SERVER_ERROR)\n                        .body(ReadableBody::from(\"Lune: Internal server error\"))\n                        .unwrap())\n                }\n            }\n        })\n    }\n}\n\nasync fn handle_request(\n    lua: Lua,\n    handler: LuaFunction,\n    request: HyperRequest<Incoming>,\n    address: SocketAddr,\n) -> LuaResult<HyperResponse<ReadableBody>> {\n    let request = Request::from_incoming(request, true)\n        .await?\n        .with_address(address);\n\n    let thread_id = lua.push_thread_back(handler, request)?;\n    lua.track_thread(thread_id);\n    lua.wait_for_thread(thread_id).await;\n\n    let thread_res = lua\n        .get_thread_result(thread_id)\n        .expect(\"Missing handler thread result\")?;\n\n    let response = Response::from_lua_multi(thread_res, &lua)?;\n    Ok(response.into_inner())\n}\n\nasync fn handle_websocket(\n    lua: Lua,\n    handler: LuaFunction,\n    request: HyperRequest<Incoming>,\n) -> LuaResult<()> {\n    let upgraded = hyper::upgrade::on(request).await.into_lua_err()?;\n\n    let stream =\n        WebSocketStream::from_raw_socket(HyperIo::from(upgraded), Role::Server, None).await;\n\n    let websocket = Websocket::from(stream);\n    lua.push_thread_back(handler, websocket)?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/server/upgrade.rs",
    "content": "use async_tungstenite::tungstenite::{error::ProtocolError, handshake::derive_accept_key};\n\nuse hyper::{\n    HeaderMap, Request as HyperRequest, Response as HyperResponse, StatusCode,\n    body::Incoming,\n    header::{CONNECTION, HeaderName, UPGRADE},\n};\n\nuse crate::body::ReadableBody;\n\nconst SEC_WEBSOCKET_VERSION: HeaderName = HeaderName::from_static(\"sec-websocket-version\");\nconst SEC_WEBSOCKET_KEY: HeaderName = HeaderName::from_static(\"sec-websocket-key\");\nconst SEC_WEBSOCKET_ACCEPT: HeaderName = HeaderName::from_static(\"sec-websocket-accept\");\n\npub fn is_upgrade_request(request: &HyperRequest<Incoming>) -> bool {\n    fn check_header_contains(headers: &HeaderMap, header_name: HeaderName, value: &str) -> bool {\n        headers.get(header_name).is_some_and(|header| {\n            header.to_str().map_or_else(\n                |_| false,\n                |header_str| {\n                    header_str\n                        .split(',')\n                        .any(|part| part.trim().eq_ignore_ascii_case(value))\n                },\n            )\n        })\n    }\n\n    check_header_contains(request.headers(), CONNECTION, \"Upgrade\")\n        && check_header_contains(request.headers(), UPGRADE, \"websocket\")\n}\n\npub fn make_upgrade_response(\n    request: &HyperRequest<Incoming>,\n) -> Result<HyperResponse<ReadableBody>, ProtocolError> {\n    let key = request\n        .headers()\n        .get(SEC_WEBSOCKET_KEY)\n        .ok_or(ProtocolError::MissingSecWebSocketKey)?;\n\n    if request\n        .headers()\n        .get(SEC_WEBSOCKET_VERSION)\n        .is_none_or(|v| v.as_bytes() != b\"13\")\n    {\n        return Err(ProtocolError::MissingSecWebSocketVersionHeader);\n    }\n\n    Ok(HyperResponse::builder()\n        .status(StatusCode::SWITCHING_PROTOCOLS)\n        .header(CONNECTION, \"upgrade\")\n        .header(UPGRADE, \"websocket\")\n        .header(SEC_WEBSOCKET_ACCEPT, derive_accept_key(key.as_bytes()))\n        .body(ReadableBody::from(\"switching to websocket protocol\"))\n        .unwrap())\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/futures.rs",
    "content": "use futures_lite::prelude::*;\n\npub use http_body_util::Either;\n\n/**\n    Combines the left and right futures into a single future\n    that resolves to either the left or right output.\n\n    This combinator is biased - if both futures resolve at\n    the same time, the left future's output is returned.\n*/\npub fn either<L: Future, R: Future>(\n    left: L,\n    right: R,\n) -> impl Future<Output = Either<L::Output, R::Output>> {\n    let fut_left = async move { Either::Left(left.await) };\n    let fut_right = async move { Either::Right(right.await) };\n    fut_left.or(fut_right)\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/headers.rs",
    "content": "use std::collections::HashMap;\n\nuse hyper::{\n    HeaderMap,\n    header::{CONTENT_ENCODING, CONTENT_LENGTH},\n};\n\nuse lune_utils::TableBuilder;\nuse mlua::prelude::*;\n\npub fn create_user_agent_header(lua: &Lua) -> LuaResult<String> {\n    let version_global = lua\n        .globals()\n        .get::<LuaString>(\"_VERSION\")\n        .expect(\"Missing _VERSION global\");\n\n    let version_global_str = version_global\n        .to_str()\n        .context(\"Invalid utf8 found in _VERSION global\")?;\n\n    let (package_name, full_version) = version_global_str.split_once(' ').unwrap();\n\n    Ok(format!(\"{}/{}\", package_name.to_lowercase(), full_version))\n}\n\npub fn header_map_to_table(\n    lua: &Lua,\n    headers: HeaderMap,\n    remove_content_headers: bool,\n) -> LuaResult<LuaTable> {\n    let mut string_map = HashMap::<String, Vec<String>>::new();\n\n    for (name, value) in headers {\n        if let Some(name) = name\n            && let Ok(value) = value.to_str()\n        {\n            string_map\n                .entry(name.to_string())\n                .or_default()\n                .push(value.to_owned());\n        }\n    }\n\n    hash_map_to_table(lua, string_map, remove_content_headers)\n}\n\npub fn hash_map_to_table(\n    lua: &Lua,\n    map: impl IntoIterator<Item = (String, Vec<String>)>,\n    remove_content_headers: bool,\n) -> LuaResult<LuaTable> {\n    let mut string_map = HashMap::<String, Vec<String>>::new();\n    for (name, values) in map {\n        let name = name.as_str();\n\n        if remove_content_headers {\n            let content_encoding_header_str = CONTENT_ENCODING.as_str();\n            let content_length_header_str = CONTENT_LENGTH.as_str();\n            if name == content_encoding_header_str || name == content_length_header_str {\n                continue;\n            }\n        }\n\n        for value in values {\n            let value = value.as_str();\n            string_map\n                .entry(name.to_owned())\n                .or_default()\n                .push(value.to_owned());\n        }\n    }\n\n    let mut builder = TableBuilder::new(lua.clone())?;\n    for (name, mut values) in string_map {\n        if values.len() == 1 {\n            let value = values.pop().unwrap().into_lua(lua)?;\n            builder = builder.with_value(name, value)?;\n        } else {\n            let values = TableBuilder::new(lua.clone())?\n                .with_sequential_values(values)?\n                .build_readonly()?\n                .into_lua(lua)?;\n            builder = builder.with_value(name, values)?;\n        }\n    }\n\n    builder.build_readonly()\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/hyper.rs",
    "content": "use std::{\n    future::Future,\n    io,\n    pin::Pin,\n    slice,\n    task::{Context, Poll},\n    time::{Duration, Instant},\n};\n\nuse async_io::Timer;\nuse futures_lite::{prelude::*, ready};\nuse hyper::rt::{self, Executor, ReadBuf, ReadBufCursor};\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::LuaSpawnExt;\n\n// Hyper executor that spawns futures onto our Lua scheduler\n\n#[derive(Debug, Clone)]\npub struct HyperExecutor {\n    lua: Lua,\n}\n\n#[allow(dead_code)]\nimpl HyperExecutor {\n    pub fn attach(lua: &Lua) -> mlua::AppDataRef<'_, Self> {\n        lua.set_app_data(Self { lua: lua.clone() });\n        lua.app_data_ref::<Self>().unwrap()\n    }\n\n    pub fn execute<Fut>(lua: Lua, fut: Fut)\n    where\n        Fut: Future + Send + 'static,\n        Fut::Output: Send + 'static,\n    {\n        let exec = lua\n            .app_data_ref::<Self>()\n            .unwrap_or_else(|| Self::attach(&lua));\n\n        exec.execute(fut);\n    }\n}\n\nimpl<Fut: Future + Send + 'static> rt::Executor<Fut> for HyperExecutor\nwhere\n    Fut::Output: Send + 'static,\n{\n    fn execute(&self, fut: Fut) {\n        self.lua.spawn(fut).detach();\n    }\n}\n\n// Hyper timer & sleep future wrapper for async-io\n\n#[derive(Debug)]\npub struct HyperTimer;\n\nimpl rt::Timer for HyperTimer {\n    fn sleep(&self, duration: Duration) -> Pin<Box<dyn rt::Sleep>> {\n        Box::pin(HyperSleep::from(Timer::after(duration)))\n    }\n\n    fn sleep_until(&self, at: Instant) -> Pin<Box<dyn rt::Sleep>> {\n        Box::pin(HyperSleep::from(Timer::at(at)))\n    }\n\n    fn reset(&self, sleep: &mut Pin<Box<dyn rt::Sleep>>, new_deadline: Instant) {\n        if let Some(mut sleep) = sleep.as_mut().downcast_mut_pin::<HyperSleep>() {\n            sleep.inner.set_at(new_deadline);\n        } else {\n            *sleep = Box::pin(HyperSleep::from(Timer::at(new_deadline)));\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct HyperSleep {\n    inner: Timer,\n}\n\nimpl From<Timer> for HyperSleep {\n    fn from(inner: Timer) -> Self {\n        Self { inner }\n    }\n}\n\nimpl Future for HyperSleep {\n    type Output = ();\n\n    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {\n        match Pin::new(&mut self.inner).poll(cx) {\n            Poll::Ready(_) => Poll::Ready(()),\n            Poll::Pending => Poll::Pending,\n        }\n    }\n}\n\nimpl rt::Sleep for HyperSleep {}\n\n// Hyper I/O wrapper for bidirectional compatibility\n// between hyper & futures-lite async read/write traits\n\npin_project_lite::pin_project! {\n    #[derive(Debug)]\n    pub struct HyperIo<T> {\n        #[pin]\n        inner: T\n    }\n}\n\nimpl<T> From<T> for HyperIo<T> {\n    fn from(inner: T) -> Self {\n        Self { inner }\n    }\n}\n\nimpl<T> HyperIo<T> {\n    pub fn pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {\n        self.project().inner\n    }\n}\n\n// Compat for futures-lite -> hyper runtime\n\nimpl<T: AsyncRead> rt::Read for HyperIo<T> {\n    fn poll_read(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        mut buf: ReadBufCursor<'_>,\n    ) -> Poll<io::Result<()>> {\n        // Fill the read buffer with initialized data\n        let read_slice = unsafe {\n            let buffer = buf.as_mut();\n            buffer.as_mut_ptr().write_bytes(0, buffer.len());\n            slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), buffer.len())\n        };\n\n        // Read bytes from the underlying source\n        let n = match self.pin_mut().poll_read(cx, read_slice) {\n            Poll::Ready(Ok(n)) => n,\n            Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),\n            Poll::Pending => return Poll::Pending,\n        };\n\n        unsafe {\n            buf.advance(n);\n        }\n\n        Poll::Ready(Ok(()))\n    }\n}\n\nimpl<T: AsyncWrite> rt::Write for HyperIo<T> {\n    fn poll_write(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<io::Result<usize>> {\n        self.pin_mut().poll_write(cx, buf)\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\n        self.pin_mut().poll_flush(cx)\n    }\n\n    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\n        self.pin_mut().poll_close(cx)\n    }\n}\n\n// Compat for hyper runtime -> futures-lite\n\nimpl<T: rt::Read> AsyncRead for HyperIo<T> {\n    fn poll_read(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &mut [u8],\n    ) -> Poll<io::Result<usize>> {\n        let mut buf = ReadBuf::new(buf);\n        ready!(self.pin_mut().poll_read(cx, buf.unfilled()))?;\n        Poll::Ready(Ok(buf.filled().len()))\n    }\n}\n\nimpl<T: rt::Write> AsyncWrite for HyperIo<T> {\n    fn poll_write(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<Result<usize, std::io::Error>> {\n        self.pin_mut().poll_write(cx, buf)\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {\n        self.pin_mut().poll_flush(cx)\n    }\n\n    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\n        self.pin_mut().poll_shutdown(cx)\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/lua.rs",
    "content": "use hyper::{\n    HeaderMap, Method,\n    header::{HeaderName, HeaderValue},\n};\n\nuse mlua::prelude::*;\n\npub fn lua_value_to_method(value: &LuaValue) -> LuaResult<Method> {\n    match value {\n        LuaValue::Nil => Ok(Method::GET),\n        LuaValue::String(str) => {\n            let bytes = str.as_bytes().trim_ascii().to_ascii_uppercase();\n            Method::from_bytes(&bytes).into_lua_err()\n        }\n        LuaValue::Buffer(buf) => {\n            let bytes = buf.to_vec().trim_ascii().to_ascii_uppercase();\n            Method::from_bytes(&bytes).into_lua_err()\n        }\n        v => Err(LuaError::FromLuaConversionError {\n            from: v.type_name(),\n            to: \"Method\".to_string(),\n            message: Some(format!(\n                \"Invalid method - expected string or buffer, got {}\",\n                v.type_name()\n            )),\n        }),\n    }\n}\n\npub fn lua_table_to_header_map(table: &LuaTable) -> LuaResult<HeaderMap> {\n    let mut headers = HeaderMap::new();\n\n    for pair in table.pairs::<LuaString, LuaString>() {\n        let (key, val) = pair?;\n        let key = HeaderName::from_bytes(&key.as_bytes()).into_lua_err()?;\n        let val = HeaderValue::from_bytes(&val.as_bytes()).into_lua_err()?;\n        headers.insert(key, val);\n    }\n\n    Ok(headers)\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/mod.rs",
    "content": "pub mod futures;\npub mod headers;\npub mod hyper;\npub mod lua;\npub mod request;\npub mod response;\npub mod tcp;\npub mod websocket;\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/request.rs",
    "content": "use std::{collections::HashMap, net::SocketAddr};\n\nuse url::Url;\n\nuse hyper::{HeaderMap, Method, Request as HyperRequest, body::Incoming};\n\nuse mlua::prelude::*;\n\nuse crate::{\n    body::{ReadableBody, handle_incoming_body},\n    shared::{\n        headers::{hash_map_to_table, header_map_to_table},\n        lua::{lua_table_to_header_map, lua_value_to_method},\n    },\n};\n\n#[derive(Debug, Clone)]\npub struct RequestOptions {\n    pub decompress: bool,\n}\n\nimpl Default for RequestOptions {\n    fn default() -> Self {\n        Self { decompress: true }\n    }\n}\n\nimpl FromLua for RequestOptions {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::Nil = value {\n            // Nil means default options\n            Ok(Self::default())\n        } else if let LuaValue::Table(tab) = value {\n            // Table means custom options\n            let decompress = match tab.get::<Option<bool>>(\"decompress\") {\n                Ok(decomp) => Ok(decomp.unwrap_or(true)),\n                Err(_) => Err(LuaError::RuntimeError(\n                    \"Invalid option value for 'decompress' in request options\".to_string(),\n                )),\n            }?;\n            Ok(Self { decompress })\n        } else {\n            // Anything else is invalid\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"RequestOptions\".to_string(),\n                message: Some(format!(\n                    \"Invalid request options - expected table or nil, got {}\",\n                    value.type_name()\n                )),\n            })\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct Request {\n    pub(crate) inner: HyperRequest<ReadableBody>,\n    pub(crate) address: Option<SocketAddr>,\n    pub(crate) redirects: Option<usize>,\n    pub(crate) decompress: bool,\n}\n\nimpl Request {\n    /**\n        Creates a new request from a raw incoming request.\n    */\n    pub async fn from_incoming(\n        incoming: HyperRequest<Incoming>,\n        decompress: bool,\n    ) -> LuaResult<Self> {\n        let (parts, body) = incoming.into_parts();\n\n        let (body, decompress) = handle_incoming_body(&parts.headers, body, decompress).await?;\n\n        Ok(Self {\n            inner: HyperRequest::from_parts(parts, ReadableBody::from(body)),\n            address: None,\n            redirects: None,\n            decompress,\n        })\n    }\n\n    /**\n        Attaches a socket address to the request.\n\n        This will make the `ip` and `port` fields available on the request.\n    */\n    pub fn with_address(mut self, address: SocketAddr) -> Self {\n        self.address = Some(address);\n        self\n    }\n\n    /**\n        Returns the method of the request.\n    */\n    pub fn method(&self) -> Method {\n        self.inner.method().clone()\n    }\n\n    /**\n        Returns the path of the request.\n    */\n    pub fn path(&self) -> &str {\n        self.inner.uri().path()\n    }\n\n    /**\n        Returns the query parameters of the request.\n    */\n    pub fn query(&self) -> HashMap<String, Vec<String>> {\n        let uri = self.inner.uri();\n\n        let mut result = HashMap::<String, Vec<String>>::new();\n\n        if let Some(query) = uri.query() {\n            for (key, value) in form_urlencoded::parse(query.as_bytes()) {\n                result\n                    .entry(key.to_string())\n                    .or_default()\n                    .push(value.to_string());\n            }\n        }\n\n        result\n    }\n\n    /**\n        Returns the headers of the request.\n    */\n    pub fn headers(&self) -> &HeaderMap {\n        self.inner.headers()\n    }\n\n    /**\n        Returns the body of the request.\n    */\n    pub fn body(&self) -> &[u8] {\n        self.inner.body().as_slice()\n    }\n\n    /**\n        Clones the inner `hyper` request.\n    */\n    #[allow(dead_code)]\n    pub fn clone_inner(&self) -> HyperRequest<ReadableBody> {\n        self.inner.clone()\n    }\n\n    /**\n        Takes the inner `hyper` request by ownership.\n    */\n    #[allow(dead_code)]\n    pub fn into_inner(self) -> HyperRequest<ReadableBody> {\n        self.inner\n    }\n}\n\nimpl<B: Into<ReadableBody>> From<HyperRequest<B>> for Request {\n    fn from(inner: HyperRequest<B>) -> Self {\n        let (parts, body) = inner.into_parts();\n        Self {\n            inner: HyperRequest::from_parts(parts, body.into()),\n            address: None,\n            redirects: None,\n            decompress: false,\n        }\n    }\n}\n\nimpl FromLua for Request {\n    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {\n        if let LuaValue::String(s) = value {\n            // If we just got a string we assume\n            // its a GET request to a given url\n            let uri = s.to_str()?;\n            let uri = uri.parse().into_lua_err()?;\n\n            let mut request = HyperRequest::new(ReadableBody::empty());\n            *request.uri_mut() = uri;\n\n            Ok(Self {\n                inner: request,\n                address: None,\n                redirects: None,\n                decompress: RequestOptions::default().decompress,\n            })\n        } else if let LuaValue::Table(tab) = value {\n            // If we got a table we are able to configure the\n            // entire request, maybe with extra options too\n            let options = match tab.get::<LuaValue>(\"options\") {\n                Ok(opts) => RequestOptions::from_lua(opts, lua)?,\n                Err(_) => RequestOptions::default(),\n            };\n\n            // Extract url (required) + optional structured query params\n            let url = tab.get::<LuaString>(\"url\")?;\n            let mut url = url.to_str()?.parse::<Url>().into_lua_err()?;\n            if let Some(t) = tab.get::<Option<LuaTable>>(\"query\")? {\n                let mut query = url.query_pairs_mut();\n                for pair in t.pairs::<LuaString, LuaString>() {\n                    let (key, value) = pair?;\n                    let key = key.to_str()?;\n                    let value = value.to_str()?;\n                    query.append_pair(&key, &value);\n                }\n            }\n\n            // Extract method\n            let method = tab.get::<LuaValue>(\"method\")?;\n            let method = lua_value_to_method(&method)?;\n\n            // Extract headers\n            let headers = tab.get::<Option<LuaTable>>(\"headers\")?;\n            let headers = headers\n                .map(|t| lua_table_to_header_map(&t))\n                .transpose()?\n                .unwrap_or_default();\n\n            // Extract body\n            let body = tab.get::<ReadableBody>(\"body\")?;\n\n            // Build the full request\n            let mut request = HyperRequest::new(body);\n            request.headers_mut().extend(headers);\n            *request.uri_mut() = url.to_string().parse().unwrap();\n            *request.method_mut() = method;\n\n            // All good, validated and we got what we need\n            Ok(Self {\n                inner: request,\n                address: None,\n                redirects: None,\n                decompress: options.decompress,\n            })\n        } else {\n            // Anything else is invalid\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"Request\".to_string(),\n                message: Some(format!(\n                    \"Invalid request - expected string or table, got {}\",\n                    value.type_name()\n                )),\n            })\n        }\n    }\n}\n\nimpl LuaUserData for Request {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"ip\", |_, this| {\n            Ok(this.address.map(|address| address.ip().to_string()))\n        });\n        fields.add_field_method_get(\"port\", |_, this| {\n            Ok(this.address.map(|address| address.port()))\n        });\n        fields.add_field_method_get(\"method\", |_, this| Ok(this.method().to_string()));\n        fields.add_field_method_get(\"path\", |_, this| Ok(this.path().to_string()));\n        fields.add_field_method_get(\"query\", |lua, this| {\n            hash_map_to_table(lua, this.query(), false)\n        });\n        fields.add_field_method_get(\"headers\", |lua, this| {\n            header_map_to_table(lua, this.headers().clone(), this.decompress)\n        });\n        fields.add_field_method_get(\"body\", |lua, this| lua.create_string(this.body()));\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/response.rs",
    "content": "use hyper::{\n    HeaderMap, Response as HyperResponse, StatusCode,\n    body::Incoming,\n    header::{CONTENT_TYPE, HeaderValue},\n};\n\nuse mlua::prelude::*;\n\nuse crate::{\n    body::{ReadableBody, handle_incoming_body},\n    shared::{headers::header_map_to_table, lua::lua_table_to_header_map},\n};\n\n#[derive(Debug, Clone)]\npub struct Response {\n    pub(crate) inner: HyperResponse<ReadableBody>,\n    pub(crate) decompressed: bool,\n}\n\nimpl Response {\n    /**\n        Creates a new response from a raw incoming response.\n    */\n    pub async fn from_incoming(\n        incoming: HyperResponse<Incoming>,\n        decompress: bool,\n    ) -> LuaResult<Self> {\n        let (parts, body) = incoming.into_parts();\n\n        let (body, decompressed) = handle_incoming_body(&parts.headers, body, decompress).await?;\n\n        Ok(Self {\n            inner: HyperResponse::from_parts(parts, ReadableBody::from(body)),\n            decompressed,\n        })\n    }\n\n    /**\n        Returns whether the request was successful or not.\n    */\n    pub fn status_ok(&self) -> bool {\n        self.inner.status().is_success()\n    }\n\n    /**\n        Returns the status code of the response.\n    */\n    pub fn status_code(&self) -> u16 {\n        self.inner.status().as_u16()\n    }\n\n    /**\n        Returns the status message of the response.\n    */\n    pub fn status_message(&self) -> &str {\n        self.inner.status().canonical_reason().unwrap_or_default()\n    }\n\n    /**\n        Returns the headers of the response.\n    */\n    pub fn headers(&self) -> &HeaderMap {\n        self.inner.headers()\n    }\n\n    /**\n        Returns the body of the response.\n    */\n    pub fn body(&self) -> &[u8] {\n        self.inner.body().as_slice()\n    }\n\n    /**\n        Clones the inner `hyper` response.\n    */\n    #[allow(dead_code)]\n    pub fn clone_inner(&self) -> HyperResponse<ReadableBody> {\n        self.inner.clone()\n    }\n\n    /**\n        Takes the inner `hyper` response by ownership.\n    */\n    #[allow(dead_code)]\n    pub fn into_inner(self) -> HyperResponse<ReadableBody> {\n        self.inner\n    }\n}\n\nimpl FromLua for Response {\n    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {\n        if let Ok(body) = ReadableBody::from_lua(value.clone(), lua) {\n            // String or buffer is always a 200 text/plain response\n            let mut response = HyperResponse::new(body);\n            response\n                .headers_mut()\n                .insert(CONTENT_TYPE, HeaderValue::from_static(\"text/plain\"));\n            Ok(Self {\n                inner: response,\n                decompressed: false,\n            })\n        } else if let LuaValue::Table(tab) = value {\n            // Extract status (required)\n            let status = tab.get::<u16>(\"status\")?;\n            let status = StatusCode::from_u16(status).into_lua_err()?;\n\n            // Extract headers\n            let headers = tab.get::<Option<LuaTable>>(\"headers\")?;\n            let headers = headers\n                .map(|t| lua_table_to_header_map(&t))\n                .transpose()?\n                .unwrap_or_default();\n\n            // Extract body\n            let body = tab.get::<ReadableBody>(\"body\")?;\n\n            // Build the full response\n            let mut response = HyperResponse::new(body);\n            response.headers_mut().extend(headers);\n            *response.status_mut() = status;\n\n            // All good, validated and we got what we need\n            Ok(Self {\n                inner: response,\n                decompressed: false,\n            })\n        } else {\n            // Anything else is invalid\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"Response\".to_string(),\n                message: Some(format!(\n                    \"Invalid response - expected table/string/buffer, got {}\",\n                    value.type_name()\n                )),\n            })\n        }\n    }\n}\n\nimpl LuaUserData for Response {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"ok\", |_, this| Ok(this.status_ok()));\n        fields.add_field_method_get(\"statusCode\", |_, this| Ok(this.status_code()));\n        fields.add_field_method_get(\"statusMessage\", |lua, this| {\n            lua.create_string(this.status_message())\n        });\n        fields.add_field_method_get(\"headers\", |lua, this| {\n            header_map_to_table(lua, this.headers().clone(), this.decompressed)\n        });\n        fields.add_field_method_get(\"body\", |lua, this| lua.create_string(this.body()));\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/tcp.rs",
    "content": "use std::{io::Error, net::SocketAddr, sync::Arc};\n\nuse async_lock::Mutex as AsyncMutex;\nuse bstr::BString;\nuse futures::{\n    io::{ReadHalf, WriteHalf},\n    prelude::*,\n};\n\nuse mlua::prelude::*;\n\nuse crate::client::stream::MaybeTlsStream;\n\nconst DEFAULT_BUFFER_SIZE: usize = 1024;\n\n#[derive(Debug, Clone)]\npub struct Tcp {\n    local_addr: Arc<Option<SocketAddr>>,\n    remote_addr: Arc<Option<SocketAddr>>,\n    read_half: Arc<AsyncMutex<ReadHalf<MaybeTlsStream>>>,\n    write_half: Arc<AsyncMutex<WriteHalf<MaybeTlsStream>>>,\n}\n\nimpl Tcp {\n    async fn read(&self, size: usize) -> Result<Vec<u8>, Error> {\n        let mut buf = vec![0; size];\n\n        let mut handle = self.read_half.lock().await;\n        let read = handle.read(&mut buf).await?;\n\n        buf.truncate(read);\n\n        Ok(buf)\n    }\n\n    async fn write(&self, data: Vec<u8>) -> Result<(), Error> {\n        let mut handle = self.write_half.lock().await;\n        handle.write_all(&data).await?;\n\n        Ok(())\n    }\n\n    async fn close(&self) -> Result<(), Error> {\n        let mut handle = self.write_half.lock().await;\n\n        handle.close().await?;\n\n        Ok(())\n    }\n}\n\nimpl<T> From<T> for Tcp\nwhere\n    T: Into<MaybeTlsStream>,\n{\n    fn from(value: T) -> Self {\n        let stream = value.into();\n\n        let local_addr = stream.local_addr().ok();\n        let remote_addr = stream.remote_addr().ok();\n\n        let (read, write) = stream.split();\n\n        Self {\n            local_addr: Arc::new(local_addr),\n            remote_addr: Arc::new(remote_addr),\n            read_half: Arc::new(AsyncMutex::new(read)),\n            write_half: Arc::new(AsyncMutex::new(write)),\n        }\n    }\n}\n\nimpl LuaUserData for Tcp {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"localIp\", |_, this| {\n            Ok(this.local_addr.map(|address| address.ip().to_string()))\n        });\n        fields.add_field_method_get(\"localPort\", |_, this| {\n            Ok(this.local_addr.map(|address| address.port()))\n        });\n        fields.add_field_method_get(\"remoteIp\", |_, this| {\n            Ok(this.remote_addr.map(|address| address.ip().to_string()))\n        });\n        fields.add_field_method_get(\"remotePort\", |_, this| {\n            Ok(this.remote_addr.map(|address| address.port()))\n        });\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_async_method(\"read\", |lua, this, size: Option<usize>| {\n            let this = this.clone();\n            let size = size.unwrap_or(DEFAULT_BUFFER_SIZE);\n            async move {\n                let bytes = this.read(size).await.into_lua_err()?;\n                lua.create_string(bytes)\n            }\n        });\n        methods.add_async_method(\"write\", |_, this, data: BString| {\n            let this = this.clone();\n            let data = data.to_vec();\n            async move { this.write(data).await.into_lua_err() }\n        });\n        methods.add_async_method(\"close\", |_, this, (): ()| {\n            let this = this.clone();\n            async move { this.close().await.into_lua_err() }\n        });\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/shared/websocket.rs",
    "content": "use std::{\n    error::Error,\n    sync::{\n        Arc,\n        atomic::{AtomicBool, AtomicU16, Ordering},\n    },\n};\n\nuse async_lock::Mutex as AsyncMutex;\nuse async_tungstenite::tungstenite::{\n    Message as TungsteniteMessage, Result as TungsteniteResult, Utf8Bytes,\n    protocol::{CloseFrame, frame::coding::CloseCode},\n};\nuse bstr::{BString, ByteSlice};\nuse futures::{\n    Sink, SinkExt, Stream, StreamExt,\n    stream::{SplitSink, SplitStream},\n};\nuse hyper::body::Bytes;\n\nuse mlua::prelude::*;\n\n#[derive(Debug, Clone)]\npub struct Websocket<T> {\n    close_code_exists: Arc<AtomicBool>,\n    close_code_value: Arc<AtomicU16>,\n    read_stream: Arc<AsyncMutex<SplitStream<T>>>,\n    write_stream: Arc<AsyncMutex<SplitSink<T, TungsteniteMessage>>>,\n}\n\nimpl<T> Websocket<T>\nwhere\n    T: Stream<Item = TungsteniteResult<TungsteniteMessage>> + Sink<TungsteniteMessage> + 'static,\n    <T as Sink<TungsteniteMessage>>::Error: Into<Box<dyn Error + Send + Sync + 'static>>,\n{\n    fn get_close_code(&self) -> Option<u16> {\n        if self.close_code_exists.load(Ordering::Relaxed) {\n            Some(self.close_code_value.load(Ordering::Relaxed))\n        } else {\n            None\n        }\n    }\n\n    fn set_close_code(&self, code: u16) {\n        self.close_code_exists.store(true, Ordering::Relaxed);\n        self.close_code_value.store(code, Ordering::Relaxed);\n    }\n\n    pub async fn send(&self, msg: TungsteniteMessage) -> LuaResult<()> {\n        let mut ws = self.write_stream.lock().await;\n        ws.send(msg).await.into_lua_err()\n    }\n\n    pub async fn next(&self) -> LuaResult<Option<TungsteniteMessage>> {\n        let mut ws = self.read_stream.lock().await;\n        ws.next().await.transpose().into_lua_err()\n    }\n\n    pub async fn close(&self, code: Option<u16>) -> LuaResult<()> {\n        if self.close_code_exists.load(Ordering::Relaxed) {\n            return Err(LuaError::runtime(\"Socket has already been closed\"));\n        }\n\n        self.send(TungsteniteMessage::Close(Some(CloseFrame {\n            code: match code {\n                Some(code) if (1000..=4999).contains(&code) => CloseCode::from(code),\n                Some(code) => {\n                    return Err(LuaError::runtime(format!(\n                        \"Close code must be between 1000 and 4999, got {code}\"\n                    )));\n                }\n                None => CloseCode::Normal,\n            },\n            reason: \"\".into(),\n        })))\n        .await?;\n\n        let mut ws = self.write_stream.lock().await;\n        ws.close().await.into_lua_err()\n    }\n}\n\nimpl<T> From<T> for Websocket<T>\nwhere\n    T: Stream<Item = TungsteniteResult<TungsteniteMessage>> + Sink<TungsteniteMessage> + 'static,\n    <T as Sink<TungsteniteMessage>>::Error: Into<Box<dyn Error + Send + Sync + 'static>>,\n{\n    fn from(value: T) -> Self {\n        let (write, read) = value.split();\n\n        Self {\n            close_code_exists: Arc::new(AtomicBool::new(false)),\n            close_code_value: Arc::new(AtomicU16::new(0)),\n            read_stream: Arc::new(AsyncMutex::new(read)),\n            write_stream: Arc::new(AsyncMutex::new(write)),\n        }\n    }\n}\n\nimpl<T> LuaUserData for Websocket<T>\nwhere\n    T: Stream<Item = TungsteniteResult<TungsteniteMessage>> + Sink<TungsteniteMessage> + 'static,\n    <T as Sink<TungsteniteMessage>>::Error: Into<Box<dyn Error + Send + Sync + 'static>>,\n{\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"closeCode\", |_, this| Ok(this.get_close_code()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_async_method(\"close\", |_, this, code: Option<u16>| async move {\n            this.close(code).await\n        });\n\n        methods.add_async_method(\n            \"send\",\n            |_, this, (string, as_binary): (BString, Option<bool>)| async move {\n                this.send(if as_binary.unwrap_or_default() {\n                    TungsteniteMessage::Binary(Bytes::from(string.to_vec()))\n                } else {\n                    let s = string.to_str().into_lua_err()?;\n                    TungsteniteMessage::Text(Utf8Bytes::from(s))\n                })\n                .await\n            },\n        );\n\n        methods.add_async_method(\"next\", |lua, this, (): ()| async move {\n            let msg = this.next().await?;\n\n            if let Some(TungsteniteMessage::Close(Some(frame))) = msg.as_ref() {\n                this.set_close_code(frame.code.into());\n            }\n\n            Ok(match msg {\n                Some(TungsteniteMessage::Binary(bin)) => LuaValue::String(lua.create_string(bin)?),\n                Some(TungsteniteMessage::Text(txt)) => LuaValue::String(lua.create_string(txt)?),\n                Some(TungsteniteMessage::Close(_)) | None => LuaValue::Nil,\n                // Ignore ping/pong/frame messages, they are handled by tungstenite\n                msg => unreachable!(\"Unhandled message: {:?}\", msg),\n            })\n        });\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/url/decode.rs",
    "content": "use mlua::prelude::*;\n\npub fn decode(lua_string: LuaString, as_binary: bool) -> LuaResult<Vec<u8>> {\n    if as_binary {\n        Ok(urlencoding::decode_binary(&lua_string.as_bytes()).into_owned())\n    } else {\n        Ok(urlencoding::decode(&lua_string.to_str()?)\n            .map_err(|e| LuaError::RuntimeError(format!(\"Encountered invalid encoding - {e}\")))?\n            .into_owned()\n            .into_bytes())\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/url/encode.rs",
    "content": "use mlua::prelude::*;\n\npub fn encode(lua_string: LuaString, as_binary: bool) -> LuaResult<Vec<u8>> {\n    if as_binary {\n        Ok(urlencoding::encode_binary(&lua_string.as_bytes())\n            .into_owned()\n            .into_bytes())\n    } else {\n        Ok(urlencoding::encode(&lua_string.to_str()?)\n            .into_owned()\n            .into_bytes())\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-net/src/url/mod.rs",
    "content": "mod decode;\nmod encode;\n\npub use self::decode::decode;\npub use self::encode::encode;\n"
  },
  {
    "path": "crates/lune-std-net/types.d.luau",
    "content": "export type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"HEAD\" | \"OPTIONS\" | \"PATCH\"\n\ntype HttpQueryOrHeaderMap = { [string]: string | { string } }\nexport type HttpQueryMap = HttpQueryOrHeaderMap\nexport type HttpHeaderMap = HttpQueryOrHeaderMap\n\n--[=[\n\t@interface FetchParamsOptions\n\t@within Net\n\n\tExtra options for `FetchParams`.\n\n\tThis is a dictionary that may contain one or more of the following values:\n\n\t* `decompress` - If the request body should be automatically decompressed when possible. Defaults to `true`\n]=]\nexport type FetchParamsOptions = {\n\tdecompress: boolean?,\n}\n\n--[=[\n\t@interface FetchParams\n\t@within Net\n\n\tParameters for sending network requests with `net.request`.\n\n\tThis is a dictionary that may contain one or more of the following values:\n\n\t* `url` - The URL to send a request to. This is always required\n\t* `method` - The HTTP method verb, such as `\"GET\"`, `\"POST\"`, `\"PATCH\"`, `\"PUT\"`, or `\"DELETE\"`. Defaults to `\"GET\"`\n\t* `body` - The request body\n\t* `query` - A table of key-value pairs representing query parameters in the request path\n\t* `headers` - A table of key-value pairs representing headers\n\t* `options` - Extra options for things such as automatic decompression of response bodies\n]=]\nexport type FetchParams = {\n\turl: string,\n\tmethod: HttpMethod?,\n\tbody: (string | buffer)?,\n\tquery: HttpQueryMap?,\n\theaders: HttpHeaderMap?,\n\toptions: FetchParamsOptions?,\n}\n\n--[=[\n\t@interface FetchResponse\n\t@within Net\n\n\tResponse type for sending network requests with `net.request`.\n\n\tThis is a dictionary containing the following values:\n\n\t* `ok` - If the status code is a canonical success status code, meaning within the range 200 -> 299\n\t* `statusCode` - The status code returned for the request\n\t* `statusMessage` - The canonical status message for the returned status code, such as `\"Not Found\"` for status code 404\n\t* `headers` - A table of key-value pairs representing headers\n\t* `body` - The request body, or an empty string if one was not given\n]=]\nexport type FetchResponse = {\n\tok: boolean,\n\tstatusCode: number,\n\tstatusMessage: string,\n\theaders: HttpHeaderMap,\n\tbody: string,\n}\n\n--[=[\n\t@interface ServeRequest\n\t@within Net\n\n\tData type for requests in `net.serve`.\n\n\tThis is a dictionary containing the following values:\n\n\t* `path` - The path being requested, relative to the root. Will be `/` if not specified\n\t* `query` - A table of key-value pairs representing query parameters in the request path\n\t* `method` - The HTTP method verb, such as `\"GET\"`, `\"POST\"`, `\"PATCH\"`, `\"PUT\"`, or `\"DELETE\"`. Will always be uppercase\n\t* `headers` - A table of key-value pairs representing headers\n\t* `body` - The request body, or an empty string if one was not given\n]=]\nexport type ServeRequest = {\n\tpath: string,\n\tquery: { [string]: string? },\n\tmethod: HttpMethod,\n\theaders: { [string]: string },\n\tbody: string,\n}\n\n--[=[\n\t@interface ServeResponse\n\t@within Net\n\n\tResponse type for requests in `net.serve`.\n\n\tThis is a dictionary that may contain one or more of the following values:\n\n\t* `status` - The status code for the request, in the range `100` -> `599`\n\t* `headers` - A table of key-value pairs representing headers\n\t* `body` - The response body\n]=]\nexport type ServeResponse = {\n\tstatus: number?,\n\theaders: { [string]: string }?,\n\tbody: (string | buffer)?,\n}\n\ntype ServeHttpHandler = (request: ServeRequest) -> string | ServeResponse\ntype ServeWebSocketHandler = (socket: WebSocket) -> ()\n\n--[=[\n\t@interface ServeConfig\n\t@within Net\n\n\tConfiguration for `net.serve`.\n\n\tThis may contain one of or more of the following values:\n\n\t* `address` for setting the IP address to serve from. Defaults to the loopback interface (`http://localhost`).\n\t* `handleRequest` for handling normal http requests, equivalent to just passing a function to `net.serve`\n\t* `handleWebSocket` for handling web socket requests, which will receive a `WebSocket` object as its first and only parameter\n\n\tWhen setting `address`, the `handleRequest` callback must also be defined.\n\n\t### Example Usage\n\n\t```luau\n\tnet.serve(8080, {\n\t\taddress = \"http://0.0.0.0\",\n\t\thandleRequest = function(request)\n\t\t\treturn {\n\t\t\t\tstatus = 200,\n\t\t\t\tbody = \"Echo:\\n\" .. request.body,\n\t\t\t}\n\t\tend\n\t})\n\t```\n]=]\nexport type ServeConfig = {\n\taddress: string?,\n\thandleRequest: ServeHttpHandler?,\n\thandleWebSocket: ServeWebSocketHandler?,\n}\n\n--[=[\n\t@interface ServeHandle\n\t@within Net\n\n\tA handle to a currently running web server, containing a single `stop` function to gracefully shut down the web server.\n]=]\nexport type ServeHandle = {\n\tstop: () -> (),\n}\n\n--[=[\n\t@interface WebSocket\n\t@within Net\n\n\tA reference to a web socket connection.\n\n\tThe web socket may be in either an \"open\" or a \"closed\" state, changing its current behavior.\n\n\tWhen open:\n\n\t* Any function on the socket such as `send`, `next` or `close` can be called without erroring\n\t* `next` can be called to yield until the next message is received or the socket becomes closed\n\n\tWhen closed:\n\n\t* `next` will no longer return any message(s) and instead instantly return nil\n\t* `send` will throw an error stating that the socket has been closed\n\n\tOnce the websocket has been closed, `closeCode` will no longer be nil, and will be populated with a close\n\tcode according to the [WebSocket specification](https://www.iana.org/assignments/websocket/websocket.xhtml).\n\tThis will be an integer between 1000 and 4999, where 1000 is the canonical code for normal, error-free closure.\n]=]\nexport type WebSocket = {\n\tcloseCode: number?,\n\tclose: (self: WebSocket, code: number?) -> (),\n\tsend: (self: WebSocket, message: (string | buffer)?, asBinaryMessage: boolean?) -> (),\n\tnext: (self: WebSocket) -> string?,\n}\n\n--[=[\n\t@interface TcpConfig\n\t@within Net\n\n\tConfiguration options for a TCP stream.\n\n\t### Example Usage\n\n\t```luau\n\t-- Plain TCP connection\n\tlocal stream = net.tcp.connect(\"example.com\", 80)\n\n\t-- TLS connection (shorthand)\n\tlocal stream = net.tcp.connect(\"example.com\", 443, true)\n\n\t-- TLS connection (explicit config)\n\tlocal stream = net.tcp.connect(\"example.com\", 443, { tls = true })\n\n\t-- Connection with custom TTL\n\tlocal stream = net.tcp.connect(\"192.168.1.100\", 8080, {\n\t\ttls = false,\n\t\tttl = 128\n\t})\n\t```\n]=]\nexport type TcpConfig = {\n\t--[=[\n\t\tWhether or not to use TLS encryption.\n\n\t\tDefaults to `false`.\n\t]=]\n\ttls: boolean?,\n\t--[=[\n\t\tThe TTL to use for packets sent over the socket.\n\t]=]\n\tttl: number?,\n}\n\n--[=[\n\t@interface TcpStream\n\t@within Net\n\n\tA plain TCP stream, which may also be backed by a TLS connection.\n\n\t### Example Usage\n\n\t```luau\n\tlocal net = require(\"@lune/net\")\n\n\tlocal conn = net.tcp.connect(\"example.com\", 80)\n\n\tconn:write(\"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")\n\n\tlocal response = conn:read()\n\tprint(response)\n\n\tconn:close()\n\t```\n]=]\nexport type TcpStream = {\n\t--[=[\n\t\tThe local IP address of the socket, if any.\n\t]=]\n\tlocalIp: string?,\n\t--[=[\n\t\tThe local port of the socket, if any.\n\t]=]\n\tlocalPort: number?,\n\t--[=[\n\t\tThe remote IP address of the socket, if any.\n\t]=]\n\tremoteIp: string?,\n\t--[=[\n\t\tThe remote port of the socket, if any.\n\t]=]\n\tremotePort: number?,\n\t--[=[\n\t\tCloses the underlying I/O for the stream.\n\n\t\tAny writes will throw an error after this method is called.\n\t]=]\n\tclose: (self: TcpStream) -> (),\n\t--[=[\n\t\tWrites the given data to the stream.\n\n\t\t- If the stream is closed, this will throw an error.\n\t]=]\n\twrite: (self: TcpStream, data: string | buffer) -> (),\n\t--[=[\n\t\tReads data from the stream, returning a string up to the given `size`.\n\n\t\t- If there is no data to read, this will yield until data is available.\n\t\t- If the stream is closed, this will return `nil`.\n\t]=]\n\tread: (self: TcpStream, size: number?) -> string?,\n}\n\n--[=[\n\tTCP primitives for the `net` library\n\n\tProvides low-level TCP socket functionality for creating custom network\n\tprotocols or communicating with services that don't use HTTP - for all\n\tHTTP usage, please use the `request` and `serve` HTTP functions instead.\n]=]\nlocal tcp = {}\n\n--[=[\n\tConnects to the given host and port, returning a `TcpStream`.\n\n\tFor additional details, see the documentation for the `TcpConfig` and `TcpStream` types.\n\n\tWill throw an error if the connection fails.\n\n\t@param host The host to connect to, either a DNS name or IP address\n\t@param port The port to connect to\n\t@param config The optional configuration to use for the stream\n\t@return A connected TcpStream ready for reading and writing\n]=]\nfunction tcp.connect(host: string, port: number, config: (true | TcpConfig)?): TcpStream\n\treturn nil :: any\nend\n\n--[=[\n\t@class Net\n\n\tBuilt-in library for network access\n\n\t### Example usage\n\n\t```luau\n\tlocal net = require(\"@lune/net\")\n\tlocal serde = require(\"@lune/serde\")\n\n\t-- Sending a web request\n\tlocal response = net.request(\"https://www.google.com\")\n\tprint(response.ok)\n\tprint(response.statusCode, response.statusMessage)\n\tprint(response.headers)\n\n\t-- Using a JSON web API\n\tlocal response = net.request({\n\t\turl = \"https://dummyjson.com/products/add\",\n\t\tmethod = \"POST\",\n\t\theaders = { [\"Content-Type\"] = \"application/json\" },\n\t\tbody = serde.encode(\"json\", {\n\t\t\ttitle = \"Cool Pencil\",\n\t\t})\n\t})\n\tlocal product = serde.decode(\"json\", response.body)\n\tprint(product.id, \"-\", product.title)\n\n\t-- Starting up an http server\n\tnet.serve(8080, function(request)\n\t\treturn {\n\t\t\tstatus = 200,\n\t\t\tbody = \"Echo:\\n\" .. request.body,\n\t\t}\n\tend)\n\n\t-- Writing to a plain TCP stream\n\tlocal conn = net.tcp.connect(\"example.com\", 80)\n\n\tconn:write(\"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")\n\t```\n]=]\nlocal net = {}\n\nnet.tcp = tcp\n\n--[=[\n\t@within Net\n\n\tSends an HTTP request using the given url and / or parameters, and returns a dictionary that describes the response received.\n\n\tOnly throws an error if a miscellaneous network or I/O error occurs, never for unsuccessful status codes.\n\n\t@param config The URL or request config to use\n\t@return A dictionary representing the response for the request\n]=]\nfunction net.request(config: string | FetchParams): FetchResponse\n\treturn nil :: any\nend\n\n--[=[\n\t@within Net\n\t@tag must_use\n\n\tConnects to a web socket at the given URL.\n\n\tThrows an error if the server at the given URL does not support\n\tweb sockets, or if a miscellaneous network or I/O error occurs.\n\n\t@param url The URL to connect to\n\t@return A web socket handle\n]=]\nfunction net.socket(url: string): WebSocket\n\treturn nil :: any\nend\n\n--[=[\n\t@within Net\n\n\tCreates an HTTP server that listens on the given `port`.\n\n\tThis will ***not*** block and will keep listening for requests on the given `port`\n\tuntil the `stop` function on the returned `ServeHandle` has been called.\n\n\t@param port The port to use for the server\n\t@param handlerOrConfig The handler function or config to use for the server\n]=]\nfunction net.serve(port: number, handlerOrConfig: ServeHttpHandler | ServeConfig): ServeHandle\n\treturn nil :: any\nend\n\n--[=[\n\t@within Net\n\t@tag must_use\n\n\tEncodes the given string using URL encoding.\n\n\t@param s The string to encode\n\t@param binary If the string should be treated as binary data and/or is not valid utf-8. Defaults to false\n\t@return The encoded string\n]=]\nfunction net.urlEncode(s: string, binary: boolean?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Net\n\t@tag must_use\n\n\tDecodes the given string using URL decoding.\n\n\t@param s The string to decode\n\t@param binary If the string should be treated as binary data and/or is not valid utf-8. Defaults to false\n\t@return The decoded string\n]=]\nfunction net.urlDecode(s: string, binary: boolean?): string\n\treturn nil :: any\nend\n\nreturn net\n"
  },
  {
    "path": "crates/lune-std-process/Cargo.toml",
    "content": "[package]\nname = \"lune-std-process\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Process\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\ndirectories = \"6.0\"\npin-project = \"1.0\"\n\nbstr = \"1.9\"\nbytes = \"1.6.0\"\n\nasync-channel = \"2.3\"\nasync-lock = \"3.4\"\nasync-process = \"2.3\"\nblocking = \"1.6\"\nfutures-lite = \"2.6\"\nfutures-util = \"0.3\" # Needed for select! macro...\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-process/src/create/child.rs",
    "content": "use std::process::ExitStatus;\n\nuse async_channel::{Receiver, Sender, unbounded};\nuse async_process::Child as AsyncChild;\nuse futures_util::{FutureExt, select};\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::LuaSpawnExt;\n\nuse lune_utils::TableBuilder;\n\nuse super::{ChildReader, ChildWriter};\n\n#[derive(Debug, Clone)]\npub struct Child {\n    stdin: ChildWriter,\n    stdout: ChildReader,\n    stderr: ChildReader,\n    kill_tx: Sender<()>,\n    status_rx: Receiver<Option<ExitStatus>>,\n}\n\nimpl Child {\n    pub fn new(lua: &Lua, mut child: AsyncChild) -> Self {\n        let stdin = ChildWriter::from(child.stdin.take());\n        let stdout = ChildReader::from(child.stdout.take());\n        let stderr = ChildReader::from(child.stderr.take());\n\n        // NOTE: Kill channel is zero size, status is very small\n        // and implements Copy, unbounded will be just fine here\n        let (kill_tx, kill_rx) = unbounded();\n        let (status_tx, status_rx) = unbounded();\n        lua.spawn(handle_child(child, kill_rx, status_tx)).detach();\n\n        Self {\n            stdin,\n            stdout,\n            stderr,\n            kill_tx,\n            status_rx,\n        }\n    }\n}\n\nimpl LuaUserData for Child {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_field_method_get(\"stdin\", |_, this| Ok(this.stdin.clone()));\n        fields.add_field_method_get(\"stdout\", |_, this| Ok(this.stdout.clone()));\n        fields.add_field_method_get(\"stderr\", |_, this| Ok(this.stderr.clone()));\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_method(\"kill\", |_, this, (): ()| {\n            let _ = this.kill_tx.try_send(());\n            Ok(())\n        });\n        methods.add_async_method(\"status\", |lua, this, (): ()| {\n            let rx = this.status_rx.clone();\n            async move {\n                let status = rx.recv().await.ok().flatten();\n                let code = status.and_then(|c| c.code()).unwrap_or(9);\n                TableBuilder::new(lua.clone())?\n                    .with_value(\"ok\", code == 0)?\n                    .with_value(\"code\", code)?\n                    .build_readonly()\n            }\n        });\n    }\n}\n\nasync fn handle_child(\n    mut child: AsyncChild,\n    kill_rx: Receiver<()>,\n    status_tx: Sender<Option<ExitStatus>>,\n) {\n    let status = select! {\n        s = child.status().fuse() => s.ok(), // FUTURE: Propagate this error somehow?\n        _ = kill_rx.recv().fuse() => {\n            let _ = child.kill(); // Will only error if already killed\n            None\n        }\n    };\n\n    // Will only error if there are no receivers waiting for the status\n    let _ = status_tx.send(status).await;\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/create/child_reader.rs",
    "content": "use std::sync::Arc;\n\nuse async_lock::Mutex as AsyncMutex;\nuse async_process::{ChildStderr as AsyncChildStderr, ChildStdout as AsyncChildStdout};\nuse futures_lite::{io, prelude::*};\n\nuse mlua::prelude::*;\n\nconst DEFAULT_BUFFER_SIZE: usize = 1024;\n\n// Inner (plumbing) implementation\n\n#[derive(Debug)]\nenum ChildReaderInner {\n    None,\n    Stdout(AsyncChildStdout),\n    Stderr(AsyncChildStderr),\n}\n\nimpl ChildReaderInner {\n    async fn read(&mut self, size: usize) -> Result<Vec<u8>, std::io::Error> {\n        if matches!(self, ChildReaderInner::None) {\n            return Ok(Vec::new());\n        }\n\n        let mut buf = vec![0; size];\n\n        let read = match self {\n            ChildReaderInner::None => unreachable!(),\n            ChildReaderInner::Stdout(stdout) => stdout.read(&mut buf).await?,\n            ChildReaderInner::Stderr(stderr) => stderr.read(&mut buf).await?,\n        };\n\n        buf.truncate(read);\n\n        Ok(buf)\n    }\n\n    async fn read_to_end(&mut self) -> Result<Vec<u8>, std::io::Error> {\n        let mut buf = Vec::new();\n\n        match self {\n            ChildReaderInner::None => {}\n            // `copy` is much faster compared to `read_to_end` when reading a large amount of data.\n            ChildReaderInner::Stdout(stdout) => {\n                io::copy(stdout, &mut buf).await?;\n            }\n            ChildReaderInner::Stderr(stderr) => {\n                io::copy(stderr, &mut buf).await?;\n            }\n        }\n\n        Ok(buf)\n    }\n}\n\nimpl From<AsyncChildStdout> for ChildReaderInner {\n    fn from(stdout: AsyncChildStdout) -> Self {\n        Self::Stdout(stdout)\n    }\n}\n\nimpl From<AsyncChildStderr> for ChildReaderInner {\n    fn from(stderr: AsyncChildStderr) -> Self {\n        Self::Stderr(stderr)\n    }\n}\n\nimpl From<Option<AsyncChildStdout>> for ChildReaderInner {\n    fn from(stdout: Option<AsyncChildStdout>) -> Self {\n        stdout.map_or(Self::None, Into::into)\n    }\n}\n\nimpl From<Option<AsyncChildStderr>> for ChildReaderInner {\n    fn from(stderr: Option<AsyncChildStderr>) -> Self {\n        stderr.map_or(Self::None, Into::into)\n    }\n}\n\n// Outer (lua-accessible, clonable) implementation\n\n#[derive(Debug, Clone)]\npub struct ChildReader {\n    inner: Arc<AsyncMutex<ChildReaderInner>>,\n}\n\nimpl LuaUserData for ChildReader {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_async_method(\"read\", |lua, this, size: Option<usize>| {\n            let inner = this.inner.clone();\n            let size = size.unwrap_or(DEFAULT_BUFFER_SIZE);\n            async move {\n                let mut inner = inner.lock().await;\n                let bytes = inner.read(size).await.into_lua_err()?;\n                if bytes.is_empty() {\n                    Ok(LuaValue::Nil)\n                } else {\n                    Ok(LuaValue::String(lua.create_string(bytes)?))\n                }\n            }\n        });\n        methods.add_async_method(\"readToEnd\", |lua, this, (): ()| {\n            let inner = this.inner.clone();\n            async move {\n                let mut inner = inner.lock().await;\n                let bytes = inner.read_to_end().await.into_lua_err()?;\n                Ok(lua.create_string(bytes))\n            }\n        });\n    }\n}\n\nimpl<T: Into<ChildReaderInner>> From<T> for ChildReader {\n    fn from(inner: T) -> Self {\n        Self {\n            inner: Arc::new(AsyncMutex::new(inner.into())),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/create/child_writer.rs",
    "content": "use std::sync::Arc;\n\nuse async_lock::Mutex as AsyncMutex;\nuse async_process::ChildStdin as AsyncChildStdin;\nuse futures_lite::prelude::*;\n\nuse bstr::BString;\nuse mlua::prelude::*;\n\n// Inner (plumbing) implementation\n\n#[derive(Debug)]\nenum ChildWriterInner {\n    None,\n    Stdin(AsyncChildStdin),\n}\n\nimpl ChildWriterInner {\n    async fn write(&mut self, data: Vec<u8>) -> Result<(), std::io::Error> {\n        match self {\n            ChildWriterInner::None => Ok(()),\n            ChildWriterInner::Stdin(stdin) => stdin.write_all(&data).await,\n        }\n    }\n\n    async fn close(&mut self) -> Result<(), std::io::Error> {\n        match self {\n            ChildWriterInner::None => Ok(()),\n            ChildWriterInner::Stdin(stdin) => stdin.close().await,\n        }\n    }\n}\n\nimpl From<AsyncChildStdin> for ChildWriterInner {\n    fn from(stdin: AsyncChildStdin) -> Self {\n        ChildWriterInner::Stdin(stdin)\n    }\n}\n\nimpl From<Option<AsyncChildStdin>> for ChildWriterInner {\n    fn from(stdin: Option<AsyncChildStdin>) -> Self {\n        stdin.map_or(Self::None, Into::into)\n    }\n}\n\n// Outer (lua-accessible, clonable) implementation\n\n#[derive(Debug, Clone)]\npub struct ChildWriter {\n    inner: Arc<AsyncMutex<ChildWriterInner>>,\n}\n\nimpl LuaUserData for ChildWriter {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_async_method(\"write\", |_, this, data: BString| {\n            let inner = this.inner.clone();\n            let data = data.to_vec();\n            async move {\n                let mut inner = inner.lock().await;\n                inner.write(data).await.into_lua_err()\n            }\n        });\n        methods.add_async_method(\"close\", |_, this, (): ()| {\n            let inner = this.inner.clone();\n            async move {\n                let mut inner = inner.lock().await;\n                inner.close().await.into_lua_err()\n            }\n        });\n    }\n}\n\nimpl<T: Into<ChildWriterInner>> From<T> for ChildWriter {\n    fn from(inner: T) -> Self {\n        Self {\n            inner: Arc::new(AsyncMutex::new(inner.into())),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/create/mod.rs",
    "content": "mod child;\nmod child_reader;\nmod child_writer;\n\npub use self::child::Child;\npub use self::child_reader::ChildReader;\npub use self::child_writer::ChildWriter;\n"
  },
  {
    "path": "crates/lune-std-process/src/exec/mod.rs",
    "content": "use async_process::Child;\nuse futures_lite::prelude::*;\n\nuse mlua::prelude::*;\n\nuse lune_utils::TableBuilder;\n\nuse super::options::ProcessSpawnOptionsStdioKind;\n\nmod tee_writer;\nmod wait_for_child;\n\nuse self::wait_for_child::wait_for_child;\n\npub async fn exec(\n    lua: Lua,\n    mut child: Child,\n    stdin: Option<Vec<u8>>,\n    stdout: ProcessSpawnOptionsStdioKind,\n    stderr: ProcessSpawnOptionsStdioKind,\n) -> LuaResult<LuaTable> {\n    // Write to stdin before anything else - if we got it\n    if let Some(stdin) = stdin {\n        let mut child_stdin = child.stdin.take().unwrap();\n        child_stdin.write_all(&stdin).await.into_lua_err()?;\n    }\n\n    let res = wait_for_child(child, stdout, stderr).await?;\n\n    /*\n        NOTE: If an exit code was not given by the child process,\n        we default to 1 if it yielded any error output, otherwise 0\n\n        An exit code may be missing if the process was terminated by\n        some external signal, which is the only time we use this default\n    */\n    let code = res\n        .status\n        .code()\n        .unwrap_or(i32::from(!res.stderr.is_empty()));\n\n    // Construct and return a readonly lua table with results\n    let stdout = lua.create_string(&res.stdout)?;\n    let stderr = lua.create_string(&res.stderr)?;\n    TableBuilder::new(lua)?\n        .with_value(\"ok\", code == 0)?\n        .with_value(\"code\", code)?\n        .with_value(\"stdout\", stdout)?\n        .with_value(\"stderr\", stderr)?\n        .build_readonly()\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/exec/tee_writer.rs",
    "content": "use std::{\n    io::Write,\n    pin::Pin,\n    task::{Context, Poll},\n};\n\nuse futures_lite::{io, prelude::*};\nuse pin_project::pin_project;\n\n#[pin_project]\npub struct AsyncTeeWriter<'a, W>\nwhere\n    W: AsyncWrite + Unpin,\n{\n    #[pin]\n    writer: &'a mut W,\n    buffer: Vec<u8>,\n}\n\nimpl<'a, W> AsyncTeeWriter<'a, W>\nwhere\n    W: AsyncWrite + Unpin,\n{\n    pub fn new(writer: &'a mut W) -> Self {\n        Self {\n            writer,\n            buffer: Vec::new(),\n        }\n    }\n\n    pub fn into_vec(self) -> Vec<u8> {\n        self.buffer\n    }\n}\n\nimpl<W> AsyncWrite for AsyncTeeWriter<'_, W>\nwhere\n    W: AsyncWrite + Unpin,\n{\n    fn poll_write(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<io::Result<usize>> {\n        let mut this = self.project();\n        match this.writer.as_mut().poll_write(cx, buf) {\n            Poll::Ready(res) => {\n                Write::write_all(&mut this.buffer, buf)\n                    .expect(\"Failed to write to internal tee buffer\");\n                Poll::Ready(res)\n            }\n            Poll::Pending => Poll::Pending,\n        }\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\n        self.project().writer.as_mut().poll_flush(cx)\n    }\n\n    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\n        self.project().writer.as_mut().poll_close(cx)\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/exec/wait_for_child.rs",
    "content": "use std::{io::stdout, process::ExitStatus};\n\nuse mlua::prelude::*;\n\nuse async_process::Child;\nuse blocking::Unblock;\nuse futures_lite::{io, prelude::*};\nuse futures_util::try_join;\n\nuse super::tee_writer::AsyncTeeWriter;\nuse crate::options::ProcessSpawnOptionsStdioKind;\n\n#[derive(Debug, Clone)]\npub(super) struct WaitForChildResult {\n    pub status: ExitStatus,\n    pub stdout: Vec<u8>,\n    pub stderr: Vec<u8>,\n}\n\nasync fn read_with_stdio_kind<R>(\n    read_from: Option<R>,\n    kind: ProcessSpawnOptionsStdioKind,\n) -> LuaResult<Vec<u8>>\nwhere\n    R: AsyncRead + Unpin,\n{\n    Ok(match kind {\n        ProcessSpawnOptionsStdioKind::None | ProcessSpawnOptionsStdioKind::Forward => Vec::new(),\n        ProcessSpawnOptionsStdioKind::Default => {\n            let mut read_from =\n                read_from.expect(\"read_from must be Some when stdio kind is Default\");\n\n            let mut buffer = Vec::new();\n\n            io::copy(&mut read_from, &mut buffer).await.into_lua_err()?;\n\n            buffer\n        }\n        ProcessSpawnOptionsStdioKind::Inherit => {\n            let mut read_from =\n                read_from.expect(\"read_from must be Some when stdio kind is Inherit\");\n\n            let mut stdout = Unblock::new(stdout());\n            let mut tee = AsyncTeeWriter::new(&mut stdout);\n\n            io::copy(&mut read_from, &mut tee).await.into_lua_err()?;\n\n            tee.into_vec()\n        }\n    })\n}\n\npub(super) async fn wait_for_child(\n    mut child: Child,\n    stdout_kind: ProcessSpawnOptionsStdioKind,\n    stderr_kind: ProcessSpawnOptionsStdioKind,\n) -> LuaResult<WaitForChildResult> {\n    let stdout_opt = child.stdout.take();\n    let stderr_opt = child.stderr.take();\n\n    let (status, stdout, stderr) = try_join!(\n        async { child.status().await.into_lua_err() },\n        read_with_stdio_kind(stdout_opt, stdout_kind),\n        read_with_stdio_kind(stderr_opt, stderr_kind)\n    )?;\n\n    Ok(WaitForChildResult {\n        status,\n        stdout,\n        stderr,\n    })\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse std::{\n    env::consts::{ARCH, OS},\n    path::MAIN_SEPARATOR,\n    process::Stdio,\n};\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::Functions;\n\nuse lune_utils::{\n    TableBuilder,\n    path::get_current_dir,\n    process::{ProcessArgs, ProcessEnv},\n};\n\nmod create;\nmod exec;\nmod options;\n\nuse self::options::ProcessSpawnOptions;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `process` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `process` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\n#[allow(clippy::missing_panics_doc)]\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    let mut cwd_str = get_current_dir()\n        .to_str()\n        .expect(\"cwd should be valid UTF-8\")\n        .to_string();\n    if !cwd_str.ends_with(MAIN_SEPARATOR) {\n        cwd_str.push(MAIN_SEPARATOR);\n    }\n\n    // Create constants for OS & processor architecture\n    let os = lua.create_string(OS.to_lowercase())?;\n    let arch = lua.create_string(ARCH.to_lowercase())?;\n    let endianness = lua.create_string(if cfg!(target_endian = \"big\") {\n        \"big\"\n    } else {\n        \"little\"\n    })?;\n\n    // Extract stored userdatas for args + env, the runtime struct\n    // should always contain and then provide through lua app data\n    let process_args = lua\n        .app_data_ref::<ProcessArgs>()\n        .ok_or_else(|| LuaError::runtime(\"Missing process args in Lua app data\"))?\n        .into_plain_lua_table(lua.clone())?;\n    let process_env = lua\n        .app_data_ref::<ProcessEnv>()\n        .ok_or_else(|| LuaError::runtime(\"Missing process env in Lua app data\"))?\n        .into_plain_lua_table(lua.clone())?;\n\n    process_args.set_readonly(true);\n\n    // Create our process exit function, the scheduler crate provides this\n    let fns = Functions::new(lua.clone())?;\n    let process_exit = fns.exit;\n\n    // Create the full process table\n    TableBuilder::new(lua)?\n        .with_value(\"os\", os)?\n        .with_value(\"arch\", arch)?\n        .with_value(\"endianness\", endianness)?\n        .with_value(\"args\", process_args)?\n        .with_value(\"cwd\", cwd_str)?\n        .with_value(\"env\", process_env)?\n        .with_value(\"exit\", process_exit)?\n        .with_async_function(\"exec\", process_exec)?\n        .with_function(\"create\", process_create)?\n        .build_readonly()\n}\n\nasync fn process_exec(\n    lua: Lua,\n    (program, args, mut options): (String, ProcessArgs, ProcessSpawnOptions),\n) -> LuaResult<LuaTable> {\n    let stdin = options.stdio.stdin.take();\n    let stdout = options.stdio.stdout;\n    let stderr = options.stdio.stderr;\n\n    let stdin_stdio = if stdin.is_some() {\n        Stdio::piped()\n    } else {\n        Stdio::null()\n    };\n\n    let child = options\n        .into_command(program, args)\n        .stdin(stdin_stdio)\n        .stdout(stdout.as_stdio())\n        .stderr(stderr.as_stdio())\n        .spawn()?;\n\n    exec::exec(lua, child, stdin, stdout, stderr).await\n}\n\nfn process_create(\n    lua: &Lua,\n    (program, args, options): (String, ProcessArgs, ProcessSpawnOptions),\n) -> LuaResult<LuaValue> {\n    let child = options\n        .into_command(program, args)\n        .stdin(Stdio::piped())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .spawn()?;\n\n    create::Child::new(lua, child).into_lua(lua)\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/options/kind.rs",
    "content": "use std::{fmt, process::Stdio, str::FromStr};\n\nuse mlua::prelude::*;\n\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]\npub enum ProcessSpawnOptionsStdioKind {\n    // TODO: We need better more obvious names\n    // for these, but that is a breaking change\n    #[default]\n    Default,\n    Forward,\n    Inherit,\n    None,\n}\n\nimpl ProcessSpawnOptionsStdioKind {\n    pub fn all() -> &'static [Self] {\n        &[Self::Default, Self::Forward, Self::Inherit, Self::None]\n    }\n\n    pub fn as_stdio(self) -> Stdio {\n        match self {\n            Self::None => Stdio::null(),\n            Self::Forward => Stdio::inherit(),\n            _ => Stdio::piped(),\n        }\n    }\n}\n\nimpl fmt::Display for ProcessSpawnOptionsStdioKind {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let s = match *self {\n            Self::Default => \"default\",\n            Self::Forward => \"forward\",\n            Self::Inherit => \"inherit\",\n            Self::None => \"none\",\n        };\n        f.write_str(s)\n    }\n}\n\nimpl FromStr for ProcessSpawnOptionsStdioKind {\n    type Err = LuaError;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Ok(match s.trim().to_ascii_lowercase().as_str() {\n            \"default\" => Self::Default,\n            \"forward\" => Self::Forward,\n            \"inherit\" => Self::Inherit,\n            \"none\" => Self::None,\n            _ => {\n                return Err(LuaError::RuntimeError(format!(\n                    \"Invalid spawn options stdio kind - got '{}', expected one of {}\",\n                    s,\n                    ProcessSpawnOptionsStdioKind::all()\n                        .iter()\n                        .map(|k| format!(\"'{k}'\"))\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                )));\n            }\n        })\n    }\n}\n\nimpl FromLua for ProcessSpawnOptionsStdioKind {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        match value {\n            LuaValue::Nil => Ok(Self::default()),\n            LuaValue::String(s) => s.to_str()?.parse(),\n            _ => Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"ProcessSpawnOptionsStdioKind\".to_string(),\n                message: Some(format!(\n                    \"Invalid spawn options stdio kind - expected string, got {}\",\n                    value.type_name()\n                )),\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/options/mod.rs",
    "content": "use std::{\n    collections::HashMap,\n    env::{self},\n    ffi::OsString,\n    path::PathBuf,\n};\n\nuse lune_utils::process::ProcessArgs;\nuse mlua::prelude::*;\n\nuse async_process::Command;\nuse directories::UserDirs;\n\nmod kind;\nmod stdio;\n\npub(super) use kind::*;\npub(super) use stdio::*;\n\n#[derive(Debug, Clone, Default)]\npub(super) struct ProcessSpawnOptions {\n    pub cwd: Option<PathBuf>,\n    pub envs: HashMap<String, String>,\n    pub shell: Option<String>,\n    pub stdio: ProcessSpawnOptionsStdio,\n}\n\nimpl FromLua for ProcessSpawnOptions {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        let mut this = Self::default();\n        let value = match value {\n            LuaValue::Nil => return Ok(this),\n            LuaValue::Table(t) => t,\n            _ => {\n                return Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"ProcessSpawnOptions\".to_string(),\n                    message: Some(format!(\n                        \"Invalid spawn options - expected table, got {}\",\n                        value.type_name()\n                    )),\n                });\n            }\n        };\n\n        /*\n            If we got a working directory to use:\n\n            1. Substitute leading tilde (~) for the users home dir\n            2. Make sure it exists\n        */\n        match value.get(\"cwd\")? {\n            LuaValue::Nil => {}\n            LuaValue::String(s) => {\n                let mut cwd = PathBuf::from(s.to_str()?.to_string());\n                if let Ok(stripped) = cwd.strip_prefix(\"~\") {\n                    let user_dirs = UserDirs::new().ok_or_else(|| {\n                        LuaError::runtime(\n                            \"Invalid value for option 'cwd' - failed to get home directory\",\n                        )\n                    })?;\n                    cwd = user_dirs.home_dir().join(stripped);\n                }\n                if !cwd.exists() {\n                    return Err(LuaError::runtime(\n                        \"Invalid value for option 'cwd' - path does not exist\",\n                    ));\n                }\n                this.cwd = Some(cwd);\n            }\n            value => {\n                return Err(LuaError::RuntimeError(format!(\n                    \"Invalid type for option 'cwd' - expected string, got '{}'\",\n                    value.type_name()\n                )));\n            }\n        }\n\n        /*\n            If we got environment variables, make sure they are strings\n        */\n        match value.get(\"env\")? {\n            LuaValue::Nil => {}\n            LuaValue::Table(e) => {\n                for pair in e.pairs::<String, String>() {\n                    let (k, v) = pair.context(\"Environment variables must be strings\")?;\n                    this.envs.insert(k, v);\n                }\n            }\n            value => {\n                return Err(LuaError::RuntimeError(format!(\n                    \"Invalid type for option 'env' - expected table, got '{}'\",\n                    value.type_name()\n                )));\n            }\n        }\n\n        /*\n            If we got a shell to use:\n\n            1. When given as a string, use that literally\n            2. When set to true, use a default shell for the platform\n        */\n        match value.get(\"shell\")? {\n            LuaValue::Nil => {}\n            LuaValue::String(s) => this.shell = Some(s.to_string_lossy().to_string()),\n            LuaValue::Boolean(true) => {\n                this.shell = match env::consts::FAMILY {\n                    \"unix\" => Some(\"/bin/sh\".to_string()),\n                    \"windows\" => Some(\"powershell\".to_string()),\n                    _ => None,\n                };\n            }\n            value => {\n                return Err(LuaError::RuntimeError(format!(\n                    \"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'\",\n                    value.type_name()\n                )));\n            }\n        }\n\n        /*\n            If we got options for stdio handling, parse those as well\n\n            This may optionally contain configuration for any or all of: stdin, stdout, stderr\n        */\n        this.stdio = value.get(\"stdio\")?;\n\n        Ok(this)\n    }\n}\n\nimpl ProcessSpawnOptions {\n    pub fn into_command(self, program: impl Into<OsString>, args: ProcessArgs) -> Command {\n        let mut program: OsString = program.into();\n        let mut args = args.into_iter().collect::<Vec<_>>();\n\n        // Run a shell using the command param if wanted\n        if let Some(shell) = self.shell {\n            let mut shell_command = program.clone();\n            for arg in args {\n                shell_command.push(\" \");\n                shell_command.push(arg);\n            }\n            args = vec![OsString::from(\"-c\"), shell_command];\n            program = shell.into();\n        }\n\n        // Create command with the wanted options\n        let mut cmd = Command::new(program);\n        cmd.args(args);\n\n        // Set dir to run in and env variables\n        if let Some(cwd) = self.cwd {\n            cmd.current_dir(cwd);\n        }\n        if !self.envs.is_empty() {\n            cmd.envs(self.envs);\n        }\n\n        cmd\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-process/src/options/stdio.rs",
    "content": "use bstr::BString;\nuse mlua::prelude::*;\n\nuse super::kind::ProcessSpawnOptionsStdioKind;\n\n#[derive(Debug, Clone, Default)]\npub struct ProcessSpawnOptionsStdio {\n    pub stdout: ProcessSpawnOptionsStdioKind,\n    pub stderr: ProcessSpawnOptionsStdioKind,\n    pub stdin: Option<Vec<u8>>,\n}\n\nimpl From<ProcessSpawnOptionsStdioKind> for ProcessSpawnOptionsStdio {\n    fn from(value: ProcessSpawnOptionsStdioKind) -> Self {\n        Self {\n            stdout: value,\n            stderr: value,\n            ..Default::default()\n        }\n    }\n}\n\nimpl FromLua for ProcessSpawnOptionsStdio {\n    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {\n        match value {\n            LuaValue::Nil => Ok(Self::default()),\n            LuaValue::String(s) => {\n                Ok(ProcessSpawnOptionsStdioKind::from_lua(LuaValue::String(s), lua)?.into())\n            }\n            LuaValue::Table(t) => {\n                let mut this = Self::default();\n\n                if let Some(stdin) = t.get::<Option<BString>>(\"stdin\")? {\n                    this.stdin = Some(stdin.to_vec());\n                }\n\n                if let Some(stdout) = t.get(\"stdout\")? {\n                    this.stdout = stdout;\n                }\n\n                if let Some(stderr) = t.get(\"stderr\")? {\n                    this.stderr = stderr;\n                }\n\n                Ok(this)\n            }\n            _ => Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"ProcessSpawnOptionsStdio\".to_string(),\n                message: Some(format!(\n                    \"Invalid spawn options stdio - expected string or table, got {}\",\n                    value.type_name()\n                )),\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-process/types.d.luau",
    "content": "export type OS = \"linux\" | \"macos\" | \"windows\"\nexport type Arch = \"x86_64\" | \"aarch64\"\nexport type Endianness = \"big\" | \"little\"\n\n--[=[\n\t@interface ExecStdioKind\n\t@within Process\n\n\tEnum determining how to treat a standard input/output stream for `process.exec`.\n\n\tCan be one of the following values:\n\n\t* `default` - The default behavior, writing to the final result table only\n\t* `inherit` - Inherit the stream from the parent process, writing to both the result table and the respective stream for the parent process\n\t* `forward` - Forward the stream to the parent process, without writing to the result table, only respective stream for the parent process\n\t* `none` - Do not create any input/output stream\n]=]\nexport type ExecStdioKind = \"default\" | \"inherit\" | \"forward\" | \"none\"\n\n--[=[\n\t@interface ExecStdioOptions\n\t@within Process\n\n\tA dictionary of stdio-specific options for `process.exec`, with the following available values:\n\n\t* `stdin` - A buffer or string to write to the stdin of the process\n\t* `stdout` - How to treat the stdout stream from the child process - see `ExecStdioKind` for more info\n\t* `stderr` - How to treat the stderr stream from the child process - see `ExecStdioKind` for more info\n]=]\nexport type ExecStdioOptions = {\n\tstdin: (buffer | string)?,\n\tstdout: ExecStdioKind?,\n\tstderr: ExecStdioKind?,\n}\n\n--[=[\n\t@interface ExecOptions\n\t@within Process\n\n\tA dictionary of options for `process.exec`, with the following available values:\n\n\t* `cwd` - The current working directory for the process\n\t* `env` - Extra environment variables to give to the process\n\t* `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell\n\t* `stdio` - How to treat output and error streams from the child process - see `StdioKind` and `StdioOptions` for more info\n]=]\nexport type ExecOptions = {\n\tcwd: string?,\n\tenv: { [string]: string }?,\n\tshell: (boolean | string)?,\n\tstdio: (ExecStdioKind | ExecStdioOptions)?,\n}\n\n--[=[\n\t@interface CreateOptions\n\t@within Process\n\n\tA dictionary of options for `process.create`, with the following available values:\n\n\t* `cwd` - The current working directory for the process\n\t* `env` - Extra environment variables to give to the process\n\t* `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell\n]=]\nexport type CreateOptions = {\n\tcwd: string?,\n\tenv: { [string]: string }?,\n\tshell: (boolean | string)?,\n}\n\n--[=[\n\t@class ChildProcessReader\n\t@within Process\n\n\tA reader class to read data from a child process' streams in realtime.\n]=]\nlocal ChildProcessReader = {}\n\n--[=[\n\t@within ChildProcessReader\n\n\tReads a chunk of data up to the specified length, or a default of 1KB at a time.\n\n\tReturns nil if there is no more data to read.\n\n\tThis function may yield until there is new data to read from reader, if all data\n\ttill present has already been read, and the process has not exited.\n\n\t@return The string containing the data read from the reader\n]=]\nfunction ChildProcessReader:read(chunkSize: number?): string?\n\treturn nil :: any\nend\n\n--[=[\n\t@within ChildProcessReader\n\n\tReads all the data currently present in the reader as a string.\n\tThis function will yield until the process exits.\n\n\t@return The string containing the data read from the reader\n]=]\nfunction ChildProcessReader:readToEnd(): string\n\treturn nil :: any\nend\n\n--[=[\n\t@class ChildProcessWriter\n\t@within Process\n\n\tA writer class to write data to a child process' streams in realtime.\n]=]\nlocal ChildProcessWriter = {}\n\n--[=[\n\t@within ChildProcessWriter\n\n\tWrites a buffer or string of data to the writer.\n\n\t@param data The data to write to the writer\n]=]\nfunction ChildProcessWriter:write(data: buffer | string): ()\n\treturn nil :: any\nend\n\n--[=[\n\t@within ChildProcessWriter\n\n\tCloses the underlying I/O stream for the writer.\n]=]\nfunction ChildProcessWriter:close(): ()\n\treturn nil :: any\nend\n\n--[=[\n\t@interface ChildProcess\n\t@within Process\n\n\tResult type for child processes in `process.create`.\n\n\tThis is a dictionary containing the following values:\n\n\t* `stdin` - A writer to write to the child process' stdin - see `ChildProcessWriter` for more info\n\t* `stdout` - A reader to read from the child process' stdout - see `ChildProcessReader` for more info\n\t* `stderr` - A reader to read from the child process' stderr - see `ChildProcessReader` for more info\n\t* `kill` - A method that kills the child process\n\t* `status` - A method that yields and returns the exit status of the child process\n]=]\nexport type ChildProcess = {\n\tstdin: typeof(ChildProcessWriter),\n\tstdout: typeof(ChildProcessReader),\n\tstderr: typeof(ChildProcessReader),\n\tkill: (self: ChildProcess) -> (),\n\tstatus: (self: ChildProcess) -> {\n\t\tok: boolean,\n\t\tcode: number,\n\t},\n}\n\n--[=[\n\t@interface ExecResult\n\t@within Process\n\n\tResult type for child processes in `process.exec`.\n\n\tThis is a dictionary containing the following values:\n\n\t* `ok` - If the child process exited successfully or not, meaning the exit code was zero or not set\n\t* `code` - The exit code set by the child process, or 0 if one was not set\n\t* `stdout` - The full contents written to stdout by the child process, or an empty string if nothing was written\n\t* `stderr` - The full contents written to stderr by the child process, or an empty string if nothing was written\n]=]\nexport type ExecResult = {\n\tok: boolean,\n\tcode: number,\n\tstdout: string,\n\tstderr: string,\n}\n\n--[=[\n\t@class Process\n\n\tBuilt-in functions for the current process & child processes\n\n\t### Example usage\n\n\t```lua\n\tlocal process = require(\"@lune/process\")\n\n\t-- Getting the arguments passed to the Lune script\n\tfor index, arg in process.args do\n\t\tprint(\"Process argument #\" .. tostring(index) .. \": \" .. arg)\n\tend\n\n\t-- Getting the currently available environment variables\n\tlocal PORT: string? = process.env.PORT\n\tlocal HOME: string? = process.env.HOME\n\tfor name, value in process.env do\n\t\tprint(\"Environment variable \" .. name .. \" is set to \" .. value)\n\tend\n\n\t-- Getting the current os and processor architecture\n\tprint(\"Running \" .. process.os .. \" on \" .. process.arch .. \"!\")\n\n\t-- Executing a command\n\tlocal result = process.exec(\"program\", {\n\t\t\"cli argument\",\n\t\t\"other cli argument\"\n\t})\n\tif result.ok then\n\t\tprint(result.stdout)\n\telse\n\t\tprint(result.stderr)\n\tend\n\n\t-- Spawning a child process\n\tlocal child = process.create(\"program\", {\n\t\t\"cli argument\",\n\t\t\"other cli argument\"\n\t})\n\n\t-- Writing to the child process' stdin\n\tchild.stdin:write(\"Hello from Lune!\")\n\n\t-- Reading from the child process' stdout\n\tlocal data = child.stdout:read()\n\tprint(data)\n\t```\n]=]\nlocal process = {}\n\n--[=[\n\t@within Process\n\t@prop os OS\n\t@tag read_only\n\n\tThe current operating system being used.\n\n\tPossible values:\n\n\t* `\"linux\"`\n\t* `\"macos\"`\n\t* `\"windows\"`\n]=]\nprocess.os = (nil :: any) :: OS\n\n--[=[\n\t@within Process\n\t@prop arch Arch\n\t@tag read_only\n\n\tThe architecture of the processor currently being used.\n\n\tPossible values:\n\n\t* `\"x86_64\"`\n\t* `\"aarch64\"`\n]=]\nprocess.arch = (nil :: any) :: Arch\n\n--[=[\n\t@within Process\n\t@prop endianness Endianness\n\t@tag read_only\n\n\tThe endianness of the processor currently being used.\n\n\tPossible values:\n\n\t* `\"big\"`\n\t* `\"little\"`\n]=]\nprocess.endianness = (nil :: any) :: Endianness\n\n--[=[\n\t@within Process\n\t@prop args { string }\n\t@tag read_only\n\n\tThe arguments given when running the Lune script.\n]=]\nprocess.args = (nil :: any) :: { string }\n\n--[=[\n\t@within Process\n\t@prop cwd string\n\t@tag read_only\n\n\tThe current working directory in which the Lune script is running.\n]=]\nprocess.cwd = (nil :: any) :: string\n\n--[=[\n\t@within Process\n\t@prop env { [string]: string? }\n\t@tag read_write\n\n\tCurrent environment variables for this process.\n\n\tSetting a value on this table will set the corresponding environment variable.\n]=]\nprocess.env = (nil :: any) :: { [string]: string? }\n\n--[=[\n\t@within Process\n\n\tExits the currently running script as soon as possible with the given exit code.\n\n\tExit code 0 is treated as a successful exit, any other value is treated as an error.\n\n\tSetting the exit code using this function will override any otherwise automatic exit code.\n\n\t@param code The exit code to set\n]=]\nfunction process.exit(code: number?): never\n\treturn nil :: any\nend\n\n--[=[\n\t@within Process\n\n\tSpawns a child process in the background that runs the program `program`,\n\tand immediately returns readers and writers to communicate with it.\n\n\tIn order to execute a command and wait for its output, see `process.exec`.\n\n\tThe second argument, `params`, can be passed as a list of string parameters to give to the program.\n\n\tThe third argument, `options`, can be passed as a dictionary of options to give to the child process.\n\tRefer to the documentation for `SpawnOptions` for specific option keys and their values.\n\n\t@param program The program to Execute as a child process\n\t@param params Additional parameters to pass to the program\n\t@param options A dictionary of options for the child process\n\t@return A dictionary with the readers and writers to communicate with the child process\n]=]\nfunction process.create(program: string, params: { string }?, options: CreateOptions?): ChildProcess\n\treturn nil :: any\nend\n\n--[=[\n\t@within Process\n\n\tExecutes a child process that will execute the command `program`, waiting for it to exit.\n\tUpon exit, it returns a dictionary that describes the final status and output of the child process.\n\n\tIn order to spawn a child process in the background, see `process.create`.\n\n\tThe second argument, `params`, can be passed as a list of string parameters to give to the program.\n\n\tThe third argument, `options`, can be passed as a dictionary of options to give to the child process.\n\tRefer to the documentation for `ExecOptions` for specific option keys and their values.\n\n\t@param program The program to Execute as a child process\n\t@param params Additional parameters to pass to the program\n\t@param options A dictionary of options for the child process\n\t@return A dictionary representing the result of the child process\n]=]\nfunction process.exec(program: string, params: { string }?, options: ExecOptions?): ExecResult\n\treturn nil :: any\nend\n\nreturn process\n"
  },
  {
    "path": "crates/lune-std-regex/Cargo.toml",
    "content": "[package]\nname = \"lune-std-regex\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - RegEx\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\n\nregex = \"1.10\"\nself_cell = \"1.0\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-regex/src/captures.rs",
    "content": "use std::sync::Arc;\n\nuse mlua::prelude::*;\nuse regex::{Captures, Regex};\nuse self_cell::self_cell;\n\nuse super::matches::LuaMatch;\n\ntype OptionalCaptures<'a> = Option<Captures<'a>>;\n\nself_cell! {\n    struct LuaCapturesInner {\n        owner: Arc<String>,\n        #[covariant]\n        dependent: OptionalCaptures,\n    }\n}\n\n/**\n    A wrapper over the `regex::Captures` struct that can be used from Lua.\n*/\npub struct LuaCaptures {\n    inner: LuaCapturesInner,\n}\n\nimpl LuaCaptures {\n    /**\n        Create a new `LuaCaptures` instance from a `Regex` pattern and a `String` text.\n\n        Returns `Some(_)` if captures were found, `None` if no captures were found.\n    */\n    pub fn new(pattern: &Regex, text: String) -> Option<Self> {\n        let inner =\n            LuaCapturesInner::new(Arc::from(text), |owned| pattern.captures(owned.as_str()));\n        if inner.borrow_dependent().is_some() {\n            Some(Self { inner })\n        } else {\n            None\n        }\n    }\n\n    fn captures(&self) -> &Captures<'_> {\n        self.inner\n            .borrow_dependent()\n            .as_ref()\n            .expect(\"None captures should not be used\")\n    }\n\n    fn num_captures(&self) -> usize {\n        // NOTE: Here we exclude the match for the entire regex\n        // pattern, only counting the named and numbered captures\n        self.captures().len() - 1\n    }\n\n    fn text(&self) -> Arc<String> {\n        Arc::clone(self.inner.borrow_owner())\n    }\n}\n\nimpl LuaUserData for LuaCaptures {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_method(\"get\", |_, this, index: usize| {\n            Ok(this\n                .captures()\n                .get(index)\n                .map(|m| LuaMatch::new(this.text(), m)))\n        });\n\n        methods.add_method(\"group\", |_, this, group: String| {\n            Ok(this\n                .captures()\n                .name(&group)\n                .map(|m| LuaMatch::new(this.text(), m)))\n        });\n\n        methods.add_method(\"format\", |_, this, format: String| {\n            let mut new = String::new();\n            this.captures().expand(&format, &mut new);\n            Ok(new)\n        });\n\n        methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));\n        methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {\n            Ok(format!(\"{}\", this.num_captures()))\n        });\n    }\n\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_meta_field(LuaMetaMethod::Type, \"RegexCaptures\");\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-regex/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse mlua::prelude::*;\n\nuse lune_utils::TableBuilder;\n\nmod captures;\nmod matches;\nmod regex;\n\nuse self::regex::LuaRegex;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `regex` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `regex` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    TableBuilder::new(lua)?\n        .with_function(\"new\", new_regex)?\n        .build_readonly()\n}\n\nfn new_regex(_: &Lua, pattern: String) -> LuaResult<LuaRegex> {\n    LuaRegex::new(pattern)\n}\n"
  },
  {
    "path": "crates/lune-std-regex/src/matches.rs",
    "content": "use std::{ops::Range, sync::Arc};\n\nuse mlua::prelude::*;\nuse regex::Match;\n\n/**\n    A wrapper over the `regex::Match` struct that can be used from Lua.\n*/\npub struct LuaMatch {\n    text: Arc<String>,\n    start: usize,\n    end: usize,\n}\n\nimpl LuaMatch {\n    /**\n        Create a new `LuaMatch` instance from a `String` text and a `regex::Match`.\n    */\n    pub fn new(text: Arc<String>, matched: Match) -> Self {\n        Self {\n            text,\n            start: matched.start(),\n            end: matched.end(),\n        }\n    }\n\n    fn range(&self) -> Range<usize> {\n        self.start..self.end\n    }\n\n    fn slice(&self) -> &str {\n        &self.text[self.range()]\n    }\n}\n\nimpl LuaUserData for LuaMatch {\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        // NOTE: Strings are 0 based in Rust but 1 based in Luau, and end of range in Rust is exclusive\n        fields.add_field_method_get(\"start\", |_, this| Ok(this.start.saturating_add(1)));\n        fields.add_field_method_get(\"finish\", |_, this| Ok(this.end));\n        fields.add_field_method_get(\"len\", |_, this| Ok(this.range().len()));\n        fields.add_field_method_get(\"text\", |_, this| Ok(this.slice().to_string()));\n\n        fields.add_meta_field(LuaMetaMethod::Type, \"RegexMatch\");\n    }\n\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));\n        methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {\n            Ok(this.slice().to_string())\n        });\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-regex/src/regex.rs",
    "content": "use std::sync::Arc;\n\nuse mlua::prelude::*;\nuse regex::Regex;\n\nuse super::{captures::LuaCaptures, matches::LuaMatch};\n\n/**\n    A wrapper over the `regex::Regex` struct that can be used from Lua.\n*/\n#[derive(Debug, Clone)]\npub struct LuaRegex {\n    inner: Regex,\n}\n\nimpl LuaRegex {\n    /**\n        Create a new `LuaRegex` instance from a `String` pattern.\n    */\n    pub fn new(pattern: String) -> LuaResult<Self> {\n        Regex::new(&pattern)\n            .map(|inner| Self { inner })\n            .map_err(LuaError::external)\n    }\n}\n\nimpl LuaUserData for LuaRegex {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_method(\"isMatch\", |_, this, text: String| {\n            Ok(this.inner.is_match(&text))\n        });\n\n        methods.add_method(\"find\", |_, this, text: String| {\n            let arc = Arc::new(text);\n            Ok(this\n                .inner\n                .find(&arc)\n                .map(|m| LuaMatch::new(Arc::clone(&arc), m)))\n        });\n\n        methods.add_method(\"captures\", |_, this, text: String| {\n            Ok(LuaCaptures::new(&this.inner, text))\n        });\n\n        methods.add_method(\"split\", |_, this, text: String| {\n            Ok(this\n                .inner\n                .split(&text)\n                .map(ToString::to_string)\n                .collect::<Vec<_>>())\n        });\n\n        // TODO: Determine whether it's desirable and / or feasible to support\n        // using a function or table for `replace` like in the lua string library\n        methods.add_method(\n            \"replace\",\n            |_, this, (haystack, replacer): (String, String)| {\n                Ok(this.inner.replace(&haystack, replacer).to_string())\n            },\n        );\n        methods.add_method(\n            \"replaceAll\",\n            |_, this, (haystack, replacer): (String, String)| {\n                Ok(this.inner.replace_all(&haystack, replacer).to_string())\n            },\n        );\n\n        methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {\n            Ok(this.inner.as_str().to_string())\n        });\n    }\n\n    fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {\n        fields.add_meta_field(LuaMetaMethod::Type, \"Regex\");\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-regex/types.d.luau",
    "content": "--[=[\n\t@class RegexMatch\n\n\tA match from a regular expression.\n\n\tContains the following values:\n\n\t- `start` -- The start index of the match in the original string.\n\t- `finish` -- The end index of the match in the original string.\n\t- `text` -- The text that was matched.\n\t- `len` -- The length of the text that was matched.\n]=]\nlocal RegexMatch = {\n\tstart = 0,\n\tfinish = 0,\n\ttext = \"\",\n\tlen = 0,\n}\n\ntype RegexMatch = typeof(RegexMatch)\n\nlocal RegexCaptures = {}\n\nfunction RegexCaptures.get(self: RegexCaptures, index: number): RegexMatch?\n\treturn nil :: any\nend\n\nfunction RegexCaptures.group(self: RegexCaptures, group: string): RegexMatch?\n\treturn nil :: any\nend\n\nfunction RegexCaptures.format(self: RegexCaptures, format: string): string\n\treturn nil :: any\nend\n\n--[=[\n\t@class RegexCaptures\n\n\tCaptures from a regular expression.\n]=]\nexport type RegexCaptures = typeof(setmetatable(\n\t{} :: {\n\t\t--[=[\n\t\t\t@within RegexCaptures\n\t\t\t@tag Method\n\t\t\t@method get\n\n\t\t\tReturns the match at the given index, if one exists.\n\n\t\t\t@param index -- The index of the match to get\n\t\t\t@return RegexMatch -- The match, if one exists\n\t\t]=]\n\n\t\tget: (self: RegexCaptures, index: number) -> RegexMatch?,\n\n\t\t--[=[\n\t\t\t@within RegexCaptures\n\t\t\t@tag Method\n\t\t\t@method group\n\n\t\t\tReturns the match for the given named match group, if one exists.\n\n\t\t\t@param group -- The name of the group to get\n\t\t\t@return RegexMatch -- The match, if one exists\n\t\t]=]\n\t\tgroup: (self: RegexCaptures, group: string) -> RegexMatch?,\n\n\t\t--[=[\n\t\t\t@within RegexCaptures\n\t\t\t@tag Method\n\t\t\t@method format\n\n\t\t\tFormats the captures using the given format string.\n\n\t\t\t### Example usage\n\n\t\t\t```lua\n\t\t\tlocal regex = require(\"@lune/regex\")\n\n\t\t\tlocal re = regex.new(\"(?<day>[0-9]{2})-(?<month>[0-9]{2})-(?<year>[0-9]{4})\")\n\n\t\t\tlocal caps = re:captures(\"On 14-03-2010, I became a Tenneessee lamb.\");\n\t\t\tassert(caps ~= nil, \"Example pattern should match example text\")\n\n\t\t\tlocal formatted = caps:format(\"year=$year, month=$month, day=$day\")\n\t\t\tprint(formatted) -- \"year=2010, month=03, day=14\"\n\t\t\t```\n\n\t\t\t@param format -- The format string to use\n\t\t\t@return string -- The formatted string\n\t\t]=]\n\t\tformat: (self: RegexCaptures, format: string) -> string,\n\t},\n\t{} :: {\n\t\t__len: (self: RegexCaptures) -> number,\n\t}\n))\n\nlocal Regex = {}\n\n--[=[\n\t@within Regex\n\t@tag Method\n\n\tCheck if the given text matches the regular expression.\n\n\tThis method may be slightly more efficient than calling `find`\n\tif you only need to know if the text matches the pattern.\n\n\t@param text -- The text to search\n\t@return boolean -- Whether the text matches the pattern\n]=]\nfunction Regex.isMatch(self: Regex, text: string): boolean\n\treturn nil :: any\nend\n\n--[=[\n\t@within Regex\n\t@tag Method\n\n\tFinds the first match in the given text.\n\n\tReturns `nil` if no match was found.\n\n\t@param text -- The text to search\n\t@return RegexMatch? -- The match object\n]=]\nfunction Regex.find(self: Regex, text: string): RegexMatch?\n\treturn nil :: any\nend\n\n--[=[\n\t@within Regex\n\t@tag Method\n\n\tFinds all matches in the given text as a `RegexCaptures` object.\n\n\tReturns `nil` if no matches are found.\n\n\t@param text -- The text to search\n\t@return RegexCaptures? -- The captures object\n]=]\nfunction Regex.captures(self: Regex, text: string): RegexCaptures?\n\treturn nil :: any\nend\n\n--[=[\n\t@within Regex\n\t@tag Method\n\n\tSplits the given text using the regular expression.\n\n\t@param text -- The text to split\n\t@return { string } -- The split text\n]=]\nfunction Regex.split(self: Regex, text: string): { string }\n\treturn nil :: any\nend\n\n--[=[\n\t@within Regex\n\t@tag Method\n\n\tReplaces the first match in the given text with the given replacer string.\n\n\t@param haystack -- The text to search\n\t@param replacer -- The string to replace matches with\n\t@return string -- The text with the first match replaced\n]=]\nfunction Regex.replace(self: Regex, haystack: string, replacer: string): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Regex\n\t@tag Method\n\n\tReplaces all matches in the given text with the given replacer string.\n\n\t@param haystack -- The text to search\n\t@param replacer -- The string to replace matches with\n\t@return string -- The text with all matches replaced\n]=]\nfunction Regex.replaceAll(self: Regex, haystack: string, replacer: string): string\n\treturn nil :: any\nend\n\nexport type Regex = typeof(Regex)\n\n--[=[\n\t@class Regex\n\n\tBuilt-in library for regular expressions\n\n\t### Example usage\n\n\t```lua\n\tlocal Regex = require(\"@lune/regex\")\n\n\tlocal re = Regex.new(\"hello\")\n\n\tif re:isMatch(\"hello, world!\") then\n\t\tprint(\"Matched!\")\n\tend\n\n\tlocal caps = re:captures(\"hello, world! hello, again!\")\n\n\tprint(#caps) -- 2\n\tprint(caps:get(1)) -- \"hello\"\n\tprint(caps:get(2)) -- \"hello\"\n\tprint(caps:get(3)) -- nil\n\t```\n]=]\nlocal regex = {}\n\n--[=[\n\t@within Regex\n\t@tag Constructor\n\n\tCreates a new `Regex` from a given string pattern.\n\n\t### Errors\n\n\tThis constructor throws an error if the given pattern is invalid.\n\n\t@param pattern -- The string pattern to use\n\t@return Regex -- The new Regex object\n]=]\nfunction regex.new(pattern: string): Regex\n\treturn nil :: any\nend\n\nreturn regex\n"
  },
  {
    "path": "crates/lune-std-roblox/Cargo.toml",
    "content": "[package]\nname = \"lune-std-roblox\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Roblox\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\nrbx_cookie = { version = \"0.1.4\", default-features = false }\nroblox_install = \"1.0\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\nlune-roblox = { version = \"0.3.4\", path = \"../lune-roblox\" }\n"
  },
  {
    "path": "crates/lune-std-roblox/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse std::sync::OnceLock;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::LuaSpawnExt;\n\nuse lune_roblox::{\n    document::{Document, DocumentError, DocumentFormat, DocumentKind},\n    instance::{Instance, registry::InstanceRegistry},\n    reflection::Database as ReflectionDatabase,\n};\n\nstatic REFLECTION_DATABASE: OnceLock<ReflectionDatabase> = OnceLock::new();\n\nuse lune_utils::TableBuilder;\nuse roblox_install::RobloxStudio;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `roblox` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `roblox` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    let mut roblox_constants = Vec::new();\n\n    let roblox_module = lune_roblox::module(lua.clone())?;\n    for pair in roblox_module.pairs::<LuaValue, LuaValue>() {\n        roblox_constants.push(pair?);\n    }\n\n    TableBuilder::new(lua)?\n        .with_values(roblox_constants)?\n        .with_async_function(\"deserializePlace\", deserialize_place)?\n        .with_async_function(\"deserializeModel\", deserialize_model)?\n        .with_async_function(\"serializePlace\", serialize_place)?\n        .with_async_function(\"serializeModel\", serialize_model)?\n        .with_function(\"getAuthCookie\", get_auth_cookie)?\n        .with_function(\"getReflectionDatabase\", get_reflection_database)?\n        .with_function(\"implementProperty\", implement_property)?\n        .with_function(\"implementMethod\", implement_method)?\n        .with_function(\"studioApplicationPath\", studio_application_path)?\n        .with_function(\"studioContentPath\", studio_content_path)?\n        .with_function(\"studioPluginPath\", studio_plugin_path)?\n        .with_function(\"studioBuiltinPluginPath\", studio_builtin_plugin_path)?\n        .build_readonly()\n}\n\nasync fn deserialize_place(lua: Lua, contents: LuaString) -> LuaResult<LuaValue> {\n    let bytes = contents.as_bytes().to_vec();\n    let fut = lua.spawn_blocking(move || {\n        let doc = Document::from_bytes(bytes, DocumentKind::Place)?;\n        let data_model = doc.into_data_model_instance()?;\n        Ok::<_, DocumentError>(data_model)\n    });\n    fut.await.into_lua_err()?.into_lua(&lua)\n}\n\nasync fn deserialize_model(lua: Lua, contents: LuaString) -> LuaResult<LuaValue> {\n    let bytes = contents.as_bytes().to_vec();\n    let fut = lua.spawn_blocking(move || {\n        let doc = Document::from_bytes(bytes, DocumentKind::Model)?;\n        let instance_array = doc.into_instance_array()?;\n        Ok::<_, DocumentError>(instance_array)\n    });\n    fut.await.into_lua_err()?.into_lua(&lua)\n}\n\nasync fn serialize_place(\n    lua: Lua,\n    (data_model, as_xml): (LuaUserDataRef<Instance>, Option<bool>),\n) -> LuaResult<LuaString> {\n    let data_model = *data_model;\n    let fut = lua.spawn_blocking(move || {\n        let doc = Document::from_data_model_instance(data_model)?;\n        let bytes = doc.to_bytes_with_format(match as_xml {\n            Some(true) => DocumentFormat::Xml,\n            _ => DocumentFormat::Binary,\n        })?;\n        Ok::<_, DocumentError>(bytes)\n    });\n    let bytes = fut.await.into_lua_err()?;\n    lua.create_string(bytes)\n}\n\nasync fn serialize_model(\n    lua: Lua,\n    (instances, as_xml): (Vec<LuaUserDataRef<Instance>>, Option<bool>),\n) -> LuaResult<LuaString> {\n    let instances = instances.iter().map(|i| **i).collect();\n    let fut = lua.spawn_blocking(move || {\n        let doc = Document::from_instance_array(instances)?;\n        let bytes = doc.to_bytes_with_format(match as_xml {\n            Some(true) => DocumentFormat::Xml,\n            _ => DocumentFormat::Binary,\n        })?;\n        Ok::<_, DocumentError>(bytes)\n    });\n    let bytes = fut.await.into_lua_err()?;\n    lua.create_string(bytes)\n}\n\nfn get_auth_cookie(_: &Lua, raw: Option<bool>) -> LuaResult<Option<String>> {\n    if matches!(raw, Some(true)) {\n        Ok(rbx_cookie::get_value())\n    } else {\n        Ok(rbx_cookie::get())\n    }\n}\n\nfn get_reflection_database(_: &Lua, _: ()) -> LuaResult<ReflectionDatabase> {\n    Ok(*REFLECTION_DATABASE.get_or_init(ReflectionDatabase::new))\n}\n\nfn implement_property(\n    lua: &Lua,\n    (class_name, property_name, property_getter, property_setter): (\n        String,\n        String,\n        LuaFunction,\n        Option<LuaFunction>,\n    ),\n) -> LuaResult<()> {\n    let property_setter = if let Some(setter) = property_setter {\n        setter\n    } else {\n        let property_name = property_name.clone();\n        lua.create_function(move |_, _: LuaMultiValue| {\n            Err::<(), _>(LuaError::runtime(format!(\n                \"Property '{property_name}' is read-only\"\n            )))\n        })?\n    };\n    InstanceRegistry::insert_property_getter(lua, &class_name, &property_name, property_getter)\n        .into_lua_err()?;\n    InstanceRegistry::insert_property_setter(lua, &class_name, &property_name, property_setter)\n        .into_lua_err()?;\n    Ok(())\n}\n\nfn implement_method(\n    lua: &Lua,\n    (class_name, method_name, method): (String, String, LuaFunction),\n) -> LuaResult<()> {\n    InstanceRegistry::insert_method(lua, &class_name, &method_name, method).into_lua_err()?;\n    Ok(())\n}\n\nfn studio_application_path(_: &Lua, _: ()) -> LuaResult<String> {\n    RobloxStudio::locate()\n        .map(|rs| rs.application_path().display().to_string())\n        .map_err(LuaError::external)\n}\n\nfn studio_content_path(_: &Lua, _: ()) -> LuaResult<String> {\n    RobloxStudio::locate()\n        .map(|rs| rs.content_path().display().to_string())\n        .map_err(LuaError::external)\n}\n\nfn studio_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {\n    RobloxStudio::locate()\n        .map(|rs| rs.plugins_path().display().to_string())\n        .map_err(LuaError::external)\n}\n\nfn studio_builtin_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {\n    RobloxStudio::locate()\n        .map(|rs| rs.built_in_plugins_path().display().to_string())\n        .map_err(LuaError::external)\n}\n"
  },
  {
    "path": "crates/lune-std-roblox/types.d.luau",
    "content": "export type DatabaseScriptability = \"None\" | \"Custom\" | \"Read\" | \"ReadWrite\" | \"Write\"\n\nexport type DatabasePropertyTag =\n\t\"Deprecated\"\n\t| \"Hidden\"\n\t| \"NotBrowsable\"\n\t| \"NotReplicated\"\n\t| \"NotScriptable\"\n\t| \"ReadOnly\"\n\t| \"WriteOnly\"\n\nexport type DatabaseClassTag =\n\t\"Deprecated\"\n\t| \"NotBrowsable\"\n\t| \"NotCreatable\"\n\t| \"NotReplicated\"\n\t| \"PlayerReplicated\"\n\t| \"Service\"\n\t| \"Settings\"\n\t| \"UserSettings\"\n\nexport type DatabaseProperty = {\n\t--[=[\n\t\tThe name of the property.\n\t]=]\n\tName: string,\n\t--[=[\n\t\tThe datatype of the property.\n\n\t\tFor normal datatypes this will be a string such as `string`, `Color3`, ...\n\n\t\tFor enums this will be a string formatted as `Enum.EnumName`.\n\t]=]\n\tDatatype: string,\n\t--[=[\n\t\tThe scriptability of this property, meaning if it can be written / read at runtime.\n\n\t\tAll properties are writable and readable in Lune even if scriptability is not.\n\t]=]\n\tScriptability: DatabaseScriptability,\n\t--[=[\n\t\tTags describing the property.\n\n\t\tThese include information such as if the property can be replicated to players\n\t\tat runtime, if the property should be hidden in Roblox Studio, and more.\n\t]=]\n\tTags: { DatabasePropertyTag },\n}\n\nexport type DatabaseClass = {\n\t--[=[\n\t\tThe name of the class.\n\t]=]\n\tName: string,\n\t--[=[\n\t\tThe superclass (parent class) of this class.\n\n\t\tMay be nil if no parent class exists.\n\t]=]\n\tSuperclass: string?,\n\t--[=[\n\t\tKnown properties for this class.\n\t]=]\n\tProperties: { [string]: DatabaseProperty },\n\t--[=[\n\t\tDefault values for properties of this class.\n\n\t\tNote that these default properties use Lune's built-in datatype\n\t\tuserdatas, and that if there is a new datatype that Lune does\n\t\tnot yet know about, it may be missing from this table.\n\t]=]\n\tDefaultProperties: { [string]: any },\n\t--[=[\n\t\tTags describing the class.\n\n\t\tThese include information such as if the class can be replicated\n\t\tto players at runtime, and top-level class categories.\n\t]=]\n\tTags: { DatabaseClassTag },\n}\n\nexport type DatabaseEnum = {\n\t--[=[\n\t\tThe name of this enum, for example `PartType` or `UserInputState`.\n\t]=]\n\tName: string,\n\t--[=[\n\t\tMembers of this enum.\n\n\t\tNote that this is a direct map of name -> enum values,\n\t\tand does not actually use the EnumItem datatype itself.\n\t]=]\n\tItems: { [string]: number },\n}\n\nexport type Database = {\n\t--[=[\n\t\tThe current version of the reflection database.\n\n\t\tThis will follow the format `x.y.z.w`, which most commonly looks something like `0.567.0.123456789`\n\t]=]\n\tVersion: string,\n\t--[=[\n\t\tRetrieves a list of all currently known class names.\n\t]=]\n\tGetClassNames: (self: Database) -> { string },\n\t--[=[\n\t\tRetrieves a list of all currently known enum names.\n\t]=]\n\tGetEnumNames: (self: Database) -> { string },\n\t--[=[\n\t\tGets a class with the exact given name, if one exists.\n\t]=]\n\tGetClass: (self: Database, name: string) -> DatabaseClass?,\n\t--[=[\n\t\tGets an enum with the exact given name, if one exists.\n\t]=]\n\tGetEnum: (self: Database, name: string) -> DatabaseEnum?,\n\t--[=[\n\t\tFinds a class with the given name.\n\n\t\tThis will use case-insensitive matching and ignore leading and trailing whitespace.\n\t]=]\n\tFindClass: (self: Database, name: string) -> DatabaseClass?,\n\t--[=[\n\t\tFinds an enum with the given name.\n\n\t\tThis will use case-insensitive matching and ignore leading and trailing whitespace.\n\t]=]\n\tFindEnum: (self: Database, name: string) -> DatabaseEnum?,\n}\n\ntype InstanceProperties = {\n\tParent: Instance?,\n\tClassName: string,\n\tName: string,\n\t-- FIXME: This breaks intellisense, but we need some way to access\n\t-- instance properties without casting the entire instance to any...\n\t-- [string]: any,\n}\n\ntype InstanceMetatable = {\n\tClone: (self: Instance) -> Instance,\n\tDestroy: (self: Instance) -> (),\n\tClearAllChildren: (self: Instance) -> (),\n\n\tGetChildren: (self: Instance) -> { Instance },\n\tGetDebugId: (self: Instance) -> string,\n\tGetDescendants: (self: Instance) -> { Instance },\n\tGetFullName: (self: Instance) -> string,\n\n\tFindFirstAncestor: (self: Instance, name: string) -> Instance?,\n\tFindFirstAncestorOfClass: (self: Instance, className: string) -> Instance?,\n\tFindFirstAncestorWhichIsA: (self: Instance, className: string) -> Instance?,\n\tFindFirstChild: (self: Instance, name: string, recursive: boolean?) -> Instance?,\n\tFindFirstChildOfClass: (self: Instance, className: string, recursive: boolean?) -> Instance?,\n\tFindFirstChildWhichIsA: (self: Instance, className: string, recursive: boolean?) -> Instance?,\n\n\tIsA: (self: Instance, className: string) -> boolean,\n\tIsAncestorOf: (self: Instance, descendant: Instance) -> boolean,\n\tIsDescendantOf: (self: Instance, ancestor: Instance) -> boolean,\n\n\tGetAttribute: (self: Instance, name: string) -> any,\n\tGetAttributes: (self: Instance) -> { [string]: any },\n\tSetAttribute: (self: Instance, name: string, value: any) -> (),\n\n\tGetTags: (self: Instance) -> { string },\n\tHasTag: (self: Instance, name: string) -> boolean,\n\tAddTag: (self: Instance, name: string) -> (),\n\tRemoveTag: (self: Instance, name: string) -> (),\n}\n\nexport type Instance = typeof(setmetatable(\n\t(nil :: any) :: InstanceProperties,\n\t(nil :: any) :: { __index: InstanceMetatable }\n))\n\nexport type DataModelProperties = {}\nexport type DataModelMetatable = {\n\tGetService: (self: DataModel, name: string) -> Instance,\n\tFindService: (self: DataModel, name: string) -> Instance?,\n}\n\nexport type DataModel =\n\tInstance\n\t& typeof(setmetatable((nil :: any) :: DataModelProperties, (nil :: any) :: { __index: DataModelMetatable }))\n\n--[=[\n\t@class Roblox\n\n\tBuilt-in library for manipulating Roblox place & model files\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\tlocal roblox = require(\"@lune/roblox\")\n\n\t-- Reading a place file\n\tlocal placeFile = fs.readFile(\"myPlaceFile.rbxl\")\n\tlocal game = roblox.deserializePlace(placeFile)\n\n\t-- Manipulating and reading instances - just like in Roblox!\n\tlocal workspace = game:GetService(\"Workspace\")\n\tfor _, child in workspace:GetChildren() do\n\t\tprint(\"Found child \" .. child.Name .. \" of class \" .. child.ClassName)\n\tend\n\n\t-- Writing a place file\n\tlocal newPlaceFile = roblox.serializePlace(game)\n\tfs.writeFile(\"myPlaceFile.rbxl\", newPlaceFile)\n\t```\n]=]\nlocal roblox = {}\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tDeserializes a place into a DataModel instance.\n\n\tThis function accepts a string of contents, *not* a file path.\n\tIf reading a place file from a file path is desired, `fs.readFile`\n\tcan be used and the resulting string may be passed to this function.\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal placeFile = fs.readFile(\"filePath.rbxl\")\n\tlocal game = roblox.deserializePlace(placeFile)\n\t```\n\n\t@param contents The contents of the place to read\n]=]\nfunction roblox.deserializePlace(contents: string): DataModel\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tDeserializes a model into an array of instances.\n\n\tThis function accepts a string of contents, *not* a file path.\n\tIf reading a model file from a file path is desired, `fs.readFile`\n\tcan be used and the resulting string may be passed to this function.\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal modelFile = fs.readFile(\"filePath.rbxm\")\n\tlocal instances = roblox.deserializeModel(modelFile)\n\t```\n\n\t@param contents The contents of the model to read\n]=]\nfunction roblox.deserializeModel(contents: string): { Instance }\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tSerializes a place from a DataModel instance.\n\n\tThis string can then be written to a file, or sent over the network.\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal placeFile = roblox.serializePlace(game)\n\tfs.writeFile(\"filePath.rbxl\", placeFile)\n\t```\n\n\t@param dataModel The DataModel for the place to serialize\n\t@param xml If the place should be serialized as xml or not. Defaults to `false`, meaning the place gets serialized using the binary format and not xml.\n]=]\nfunction roblox.serializePlace(dataModel: DataModel, xml: boolean?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tSerializes one or more instances as a model.\n\n\tThis string can then be written to a file, or sent over the network.\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal modelFile = roblox.serializeModel({ instance1, instance2, ... })\n\tfs.writeFile(\"filePath.rbxm\", modelFile)\n\t```\n\n\t@param instances The array of instances to serialize\n\t@param xml If the model should be serialized as xml or not. Defaults to `false`, meaning the model gets serialized using the binary format and not xml.\n]=]\nfunction roblox.serializeModel(instances: { Instance }, xml: boolean?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tGets the current auth cookie, for usage with Roblox web APIs.\n\n\tNote that this auth cookie is formatted for use as a \"Cookie\" header,\n\tand that it contains restrictions so that it may only be used for\n\tofficial Roblox endpoints. To get the raw cookie value without any\n\tadditional formatting, you can pass `true` as the first and only parameter.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\tlocal serde = require(\"@lune/serde\")\n\tlocal net = require(\"@lune/net\")\n\n\tlocal cookie = roblox.getAuthCookie()\n\tassert(cookie ~= nil, \"Failed to get roblox auth cookie\")\n\n\tlocal myPrivatePlaceId = 1234567890\n\n\tlocal response = net.request({\n\t\turl = \"https://assetdelivery.roblox.com/v2/assetId/\" .. tostring(myPrivatePlaceId),\n\t\theaders = {\n\t\t\tCookie = cookie,\n\t\t},\n\t})\n\n\tlocal responseTable = serde.decode(\"json\", response.body)\n\tlocal responseLocation = responseTable.locations[1].location\n\tprint(\"Download link to place: \" .. responseLocation)\n\t```\n\n\t@param raw If the cookie should be returned as a pure value or not. Defaults to false\n]=]\nfunction roblox.getAuthCookie(raw: boolean?): string?\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tGets the bundled reflection database.\n\n\tThis database contains information about Roblox enums, classes, and their properties.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal db = roblox.getReflectionDatabase()\n\n\tprint(\"There are\", #db:GetClassNames(), \"classes in the reflection database\")\n\n\tprint(\"All base instance properties:\")\n\n\tlocal class = db:GetClass(\"Instance\")\n\tfor name, prop in class.Properties do\n\t\tprint(string.format(\n\t\t\t\"- %s with datatype %s and default value %s\",\n\t\t\tprop.Name,\n\t\t\tprop.Datatype,\n\t\t\ttostring(class.DefaultProperties[prop.Name])\n\t\t))\n\tend\n\t```\n]=]\nfunction roblox.getReflectionDatabase(): Database\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\n\tImplements a property for all instances of the given `className`.\n\n\tThis takes into account class hierarchies, so implementing a property\n\tfor the `BasePart` class will also implement it for `Part` and others,\n\tunless a more specific implementation is added to the `Part` class directly.\n\n\t### Behavior\n\n\tThe given `getter` callback will be called each time the property is\n\tindexed, with the instance as its one and only argument. The `setter`\n\tcallback, if given, will be called each time the property should be set,\n\twith the instance as the first argument and the property value as second.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal part = roblox.Instance.new(\"Part\")\n\n\tlocal propertyValues = {}\n\troblox.implementProperty(\n\t\t\"BasePart\",\n\t\t\"CoolProp\",\n\t\tfunction(instance)\n\t\t\tif propertyValues[instance] == nil then\n\t\t\t\tpropertyValues[instance] = 0\n\t\t\tend\n\t\t\tpropertyValues[instance] += 1\n\t\t\treturn propertyValues[instance]\n\t\tend,\n\t\tfunction(instance, value)\n\t\t\tpropertyValues[instance] = value\n\t\tend\n\t)\n\n\tprint(part.CoolProp) --> 1\n\tprint(part.CoolProp) --> 2\n\tprint(part.CoolProp) --> 3\n\n\tpart.CoolProp = 10\n\n\tprint(part.CoolProp) --> 11\n\tprint(part.CoolProp) --> 12\n\tprint(part.CoolProp) --> 13\n\t```\n\n\t@param className The class to implement the property for.\n\t@param propertyName The name of the property to implement.\n\t@param getter The function which will be called to get the property value when indexed.\n\t@param setter The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only.\n]=]\nfunction roblox.implementProperty<T>(\n\tclassName: string,\n\tpropertyName: string,\n\tgetter: (instance: Instance) -> T,\n\tsetter: ((instance: Instance, value: T) -> ())?\n)\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\n\tImplements a method for all instances of the given `className`.\n\n\tThis takes into account class hierarchies, so implementing a method\n\tfor the `BasePart` class will also implement it for `Part` and others,\n\tunless a more specific implementation is added to the `Part` class directly.\n\n\t### Behavior\n\n\tThe given `callback` will be called every time the method is called,\n\tand will receive the instance it was called on as its first argument.\n\tThe remaining arguments will be what the caller passed to the method, and\n\tall values returned from the callback will then be returned to the caller.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal part = roblox.Instance.new(\"Part\")\n\n\troblox.implementMethod(\"BasePart\", \"TestMethod\", function(instance, ...)\n\t    print(\"Called TestMethod on instance\", instance, \"with\", ...)\n\tend)\n\n\tpart:TestMethod(\"Hello\", \"world!\")\n\t--> Called TestMethod on instance Part with Hello, world!\n\t```\n\n\t@param className The class to implement the method for.\n\t@param methodName The name of the method to implement.\n\t@param callback The function which will be called when the method is called.\n]=]\nfunction roblox.implementMethod(className: string, methodName: string, callback: (instance: Instance, ...any) -> ...any)\n\treturn nil :: any\nend\n\n-- TODO: Make typedefs for all of the datatypes as well...\nroblox.Instance = (nil :: any) :: {\n\tnew: ((className: \"DataModel\") -> DataModel) & ((className: string) -> Instance),\n}\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tReturns the path to the system's Roblox Studio executable.\n\n\tThere is no guarantee that this will exist, but if Studio is installed this\n\tis where it will be.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal pathToStudio = roblox.studioApplicationPath()\n\tprint(\"Studio is located at:\", pathToStudio)\n\t```\n]=]\nfunction roblox.studioApplicationPath(): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tReturns the path to the `Content` folder of the system's current Studio\n\tinstall.\n\n\tThis folder will always exist if Studio is installed.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal pathToContent = roblox.studioContentPath()\n\tprint(\"Studio's content folder is located at:\", pathToContent)\n\t```\n]=]\nfunction roblox.studioContentPath(): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tReturns the path to the `plugin` folder of the system's current Studio\n\tinstall. This is the path where local plugins are installed.\n\n\tThis folder may not exist if the user has never installed a local plugin.\n\tIt will also not necessarily take into account custom plugin directories\n\tset from Studio.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal pathToPluginFolder = roblox.studioPluginPath()\n\tprint(\"Studio's plugin folder is located at:\", pathToPluginFolder)\n\t```\n]=]\nfunction roblox.studioPluginPath(): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Roblox\n\t@tag must_use\n\n\tReturns the path to the `BuiltInPlugin` folder of the system's current\n\tStudio install. This is the path where built-in plugins like the ToolBox\n\tare installed.\n\n\tThis folder will always exist if Studio is installed.\n\n\t### Example usage\n\n\t```lua\n\tlocal roblox = require(\"@lune/roblox\")\n\n\tlocal pathToPluginFolder = roblox.studioBuiltinPluginPath()\n\tprint(\"Studio's built-in plugin folder is located at:\", pathToPluginFolder)\n\t```\n]=]\nfunction roblox.studioBuiltinPluginPath(): string\n\treturn nil :: any\nend\n\nreturn roblox\n"
  },
  {
    "path": "crates/lune-std-serde/Cargo.toml",
    "content": "[package]\nname = \"lune-std-serde\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Serde\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\", \"serialize\", \"error-send\"] }\n\nasync-compression = { version = \"0.4\", features = [\n    \"futures-io\",\n    \"brotli\",\n    \"deflate\",\n    \"gzip\",\n    \"zlib\",\n    \"zstd\",\n] }\n\nblocking = \"1.6\"\nbstr = \"1.9\"\nfutures-lite = \"2.6\"\nlz4 = \"1.26\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = { version = \"1.0\", features = [\"preserve_order\"] }\nserde_yaml2 = \"0.1.3\" # FUTURE: Look into using saphyr (successor to yaml-rust2, which serde_yaml2 wraps)\njsonc-parser = { version = \"0.26\", features = [\"serde\"] }\ntoml = { version = \"0.9\", features = [\"preserve_order\"] }\n\ndigest = \"0.10.7\"\nhmac = \"0.12.1\"\nmd-5 = \"0.10.6\"\nsha1 = \"0.10.6\"\nsha2 = \"0.10.8\"\nsha3 = \"0.10.8\"\n# This feature MIGHT break due to the unstable nature of the digest crate.\n# Check before updating it.\nblake3 = { version = \"=1.5.0\", features = [\"traits-preview\"] }\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-serde/src/compress_decompress.rs",
    "content": "use std::io::{Cursor, Read as _, Write as _, copy as copy_std};\n\nuse mlua::prelude::*;\n\nuse blocking::unblock;\nuse futures_lite::io::{BufReader, copy};\nuse lz4::{Decoder, EncoderBuilder};\n\nuse async_compression::{\n    Level::Best as CompressionQuality,\n    Level::Precise as PreciseCompressionQuality,\n    futures::bufread::{\n        BrotliDecoder, BrotliEncoder, GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder,\n        ZstdDecoder, ZstdEncoder,\n    },\n};\n\n/**\n    A compression and decompression format supported by Lune.\n*/\n#[derive(Debug, Clone, Copy)]\npub enum CompressDecompressFormat {\n    Brotli,\n    GZip,\n    LZ4,\n    ZLib,\n    Zstd,\n}\n\n#[allow(dead_code)]\nimpl CompressDecompressFormat {\n    /**\n        Detects a supported compression format from the given bytes.\n    */\n    #[allow(clippy::missing_panics_doc)]\n    pub fn detect_from_bytes(bytes: impl AsRef<[u8]>) -> Option<Self> {\n        match bytes.as_ref() {\n            // https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#zstandard-frames\n            b if b.len() >= 4\n                && matches!(u32::from_le_bytes(b[0..4].try_into().unwrap()), 0xFD2FB528) =>\n            {\n                Some(Self::Zstd)\n            }\n            // https://github.com/PSeitz/lz4_flex/blob/main/src/frame/header.rs#L28\n            b if b.len() >= 4\n                && matches!(\n                    u32::from_le_bytes(b[0..4].try_into().unwrap()),\n                    0x184D2204 | 0x184C2102\n                ) =>\n            {\n                Some(Self::LZ4)\n            }\n            // https://github.com/dropbox/rust-brotli/blob/master/src/enc/brotli_bit_stream.rs#L2805\n            b if b.len() >= 4\n                && matches!(\n                    b[0..3],\n                    [0xE1, 0x97, 0x81] | [0xE1, 0x97, 0x82] | [0xE1, 0x97, 0x80]\n                ) =>\n            {\n                Some(Self::Brotli)\n            }\n            // https://github.com/rust-lang/flate2-rs/blob/main/src/gz/mod.rs#L135\n            b if b.len() >= 3 && matches!(b[0..3], [0x1F, 0x8B, 0x08]) => Some(Self::GZip),\n            // https://stackoverflow.com/a/43170354\n            b if b.len() >= 2\n                && matches!(\n                    b[0..2],\n                    [0x78, 0x01] | [0x78, 0x5E] | [0x78, 0x9C] | [0x78, 0xDA]\n                ) =>\n            {\n                Some(Self::ZLib)\n            }\n            _ => None,\n        }\n    }\n\n    /**\n        Detects a supported compression format from the given header string.\n\n        The given header script should be a valid `Content-Encoding` header value.\n    */\n    pub fn detect_from_header_str(header: impl AsRef<str>) -> Option<Self> {\n        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#directives\n        match header.as_ref().to_ascii_lowercase().trim() {\n            \"br\" | \"brotli\" => Some(Self::Brotli),\n            \"deflate\" => Some(Self::ZLib),\n            \"gz\" | \"gzip\" => Some(Self::GZip),\n            \"zst\" | \"zstd\" => Some(Self::Zstd),\n            _ => None,\n        }\n    }\n}\n\nimpl FromLua for CompressDecompressFormat {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::String(s) = &value {\n            match s.to_string_lossy().to_ascii_lowercase().trim() {\n                \"brotli\" => Ok(Self::Brotli),\n                \"gzip\" => Ok(Self::GZip),\n                \"lz4\" => Ok(Self::LZ4),\n                \"zlib\" => Ok(Self::ZLib),\n                \"zstd\" => Ok(Self::Zstd),\n                kind => Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"CompressDecompressFormat\".to_string(),\n                    message: Some(format!(\n                        \"Invalid format '{kind}', valid formats are:  brotli, gzip, lz4, zlib, zstd\"\n                    )),\n                }),\n            }\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"CompressDecompressFormat\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\n/**\n    Compresses the given bytes using the specified format.\n\n    # Errors\n\n    Errors when the compression fails.\n*/\npub async fn compress(\n    source: impl AsRef<[u8]>,\n    format: CompressDecompressFormat,\n    level: Option<i32>,\n) -> LuaResult<Vec<u8>> {\n    if let CompressDecompressFormat::LZ4 = format {\n        let source = source.as_ref().to_vec();\n        return unblock(move || compress_lz4(source)).await.into_lua_err();\n    }\n\n    let mut bytes = Vec::new();\n    let reader = BufReader::new(source.as_ref());\n    let compression_quality = match level {\n        Some(l) => PreciseCompressionQuality(l),\n        None => CompressionQuality,\n    };\n\n    match format {\n        CompressDecompressFormat::Brotli => {\n            let mut encoder = BrotliEncoder::with_quality(reader, compression_quality);\n            copy(&mut encoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::GZip => {\n            let mut encoder = GzipEncoder::with_quality(reader, compression_quality);\n            copy(&mut encoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::ZLib => {\n            let mut encoder = ZlibEncoder::with_quality(reader, compression_quality);\n            copy(&mut encoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::Zstd => {\n            let mut encoder = ZstdEncoder::with_quality(reader, compression_quality);\n            copy(&mut encoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::LZ4 => unreachable!(),\n    }\n\n    Ok(bytes)\n}\n\n/**\n    Decompresses the given bytes using the specified format.\n\n    # Errors\n\n    Errors when the decompression fails.\n*/\npub async fn decompress(\n    source: impl AsRef<[u8]>,\n    format: CompressDecompressFormat,\n) -> LuaResult<Vec<u8>> {\n    if let CompressDecompressFormat::LZ4 = format {\n        let source = source.as_ref().to_vec();\n        return unblock(move || decompress_lz4(source)).await.into_lua_err();\n    }\n\n    let mut bytes = Vec::new();\n    let reader = BufReader::new(source.as_ref());\n\n    match format {\n        CompressDecompressFormat::Brotli => {\n            let mut decoder = BrotliDecoder::new(reader);\n            copy(&mut decoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::GZip => {\n            let mut decoder = GzipDecoder::new(reader);\n            copy(&mut decoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::ZLib => {\n            let mut decoder = ZlibDecoder::new(reader);\n            copy(&mut decoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::Zstd => {\n            let mut decoder = ZstdDecoder::new(reader);\n            copy(&mut decoder, &mut bytes).await?;\n        }\n        CompressDecompressFormat::LZ4 => unreachable!(),\n    }\n\n    Ok(bytes)\n}\n\n// TODO: Remove the compatibility layer. Prepending size is no longer\n// necessary, using lz4 create instead of lz4-flex, but we must remove\n// it in a major version to not unexpectedly break compatibility\n\nfn compress_lz4(input: Vec<u8>) -> LuaResult<Vec<u8>> {\n    let mut input = Cursor::new(input);\n    let mut output = Cursor::new(Vec::new());\n\n    // Prepend size for compatibility with old lz4-flex implementation\n    let len = input.get_ref().len() as u32;\n    output.write_all(len.to_le_bytes().as_ref())?;\n\n    let mut encoder = EncoderBuilder::new()\n        .level(16)\n        .checksum(lz4::ContentChecksum::ChecksumEnabled)\n        .block_mode(lz4::BlockMode::Independent)\n        .build(output)?;\n\n    copy_std(&mut input, &mut encoder)?;\n    let (output, result) = encoder.finish();\n    result?;\n\n    Ok(output.into_inner())\n}\n\nfn decompress_lz4(input: Vec<u8>) -> LuaResult<Vec<u8>> {\n    let mut input = Cursor::new(input);\n\n    // Skip size for compatibility with old lz4-flex implementation\n    // Note that right now we use it for preallocating the output buffer\n    // and a small efficiency gain, maybe we can expose this as some kind\n    // of \"size hint\" parameter instead in the serde library in the future\n    let mut size = [0; 4];\n    input.read_exact(&mut size)?;\n\n    let capacity = u32::from_le_bytes(size) as usize;\n    let mut output = Cursor::new(Vec::with_capacity(capacity));\n\n    let mut decoder = Decoder::new(input)?;\n    copy_std(&mut decoder, &mut output)?;\n\n    Ok(output.into_inner())\n}\n"
  },
  {
    "path": "crates/lune-std-serde/src/encode_decode.rs",
    "content": "use mlua::prelude::*;\n\nuse serde_json::Value as JsonValue;\nuse serde_yaml2::wrapper::YamlNodeWrapper as YamlValue;\nuse toml::Value as TomlValue;\n\n// NOTE: These are options for going from other format -> lua (\"serializing\" lua values)\nconst LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new()\n    .set_array_metatable(false)\n    .serialize_none_to_null(false)\n    .serialize_unit_to_null(false);\n\n// NOTE: These are options for going from lua -> other format (\"deserializing\" lua values)\nconst LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new()\n    .sort_keys(true)\n    .deny_recursive_tables(false)\n    .deny_unsupported_types(true);\n\n/**\n    An encoding and decoding format supported by Lune.\n\n    Encode / decode in this case is synonymous with serialize / deserialize.\n*/\n#[derive(Debug, Clone, Copy)]\npub enum EncodeDecodeFormat {\n    Json,\n    JsonC,\n    Yaml,\n    Toml,\n}\n\nimpl FromLua for EncodeDecodeFormat {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::String(s) = &value {\n            match s.to_string_lossy().to_ascii_lowercase().trim() {\n                \"json\" => Ok(Self::Json),\n                \"jsonc\" => Ok(Self::JsonC),\n                \"yaml\" => Ok(Self::Yaml),\n                \"toml\" => Ok(Self::Toml),\n                kind => Err(LuaError::FromLuaConversionError {\n                    from: value.type_name(),\n                    to: \"EncodeDecodeFormat\".to_string(),\n                    message: Some(format!(\n                        \"Invalid format '{kind}', valid formats are:  json, yaml, toml\"\n                    )),\n                }),\n            }\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"EncodeDecodeFormat\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\n/**\n    Configuration for encoding and decoding values.\n\n    Encoding / decoding in this case is synonymous with serialize / deserialize.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct EncodeDecodeConfig {\n    pub format: EncodeDecodeFormat,\n    pub pretty: bool,\n}\n\nimpl From<EncodeDecodeFormat> for EncodeDecodeConfig {\n    fn from(format: EncodeDecodeFormat) -> Self {\n        Self {\n            format,\n            pretty: false,\n        }\n    }\n}\n\nimpl From<(EncodeDecodeFormat, bool)> for EncodeDecodeConfig {\n    fn from(value: (EncodeDecodeFormat, bool)) -> Self {\n        Self {\n            format: value.0,\n            pretty: value.1,\n        }\n    }\n}\n\n/**\n    Encodes / serializes the given value into a string, using the specified configuration.\n\n    # Errors\n\n    Errors when the encoding fails.\n*/\npub fn encode(value: LuaValue, lua: &Lua, config: EncodeDecodeConfig) -> LuaResult<LuaString> {\n    let bytes = match config.format {\n        EncodeDecodeFormat::Json | EncodeDecodeFormat::JsonC => {\n            let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;\n            if config.pretty {\n                serde_json::to_vec_pretty(&serialized).into_lua_err()?\n            } else {\n                serde_json::to_vec(&serialized).into_lua_err()?\n            }\n        }\n        EncodeDecodeFormat::Yaml => {\n            let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;\n            serde_yaml2::to_string(serialized)\n                .into_lua_err()?\n                .into_bytes()\n        }\n        EncodeDecodeFormat::Toml => {\n            let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;\n            let s = if config.pretty {\n                toml::to_string_pretty(&serialized).into_lua_err()?\n            } else {\n                toml::to_string(&serialized).into_lua_err()?\n            };\n            s.as_bytes().to_vec()\n        }\n    };\n    lua.create_string(bytes)\n}\n\n/**\n    Decodes / deserializes the given string into a value, using the specified configuration.\n\n    # Errors\n\n    Errors when the decoding fails.\n*/\npub fn decode(\n    bytes: impl AsRef<[u8]>,\n    lua: &Lua,\n    config: EncodeDecodeConfig,\n) -> LuaResult<LuaValue> {\n    let bytes = bytes.as_ref();\n    match config.format {\n        EncodeDecodeFormat::Json => {\n            let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;\n            lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)\n        }\n        EncodeDecodeFormat::JsonC => {\n            let string: String = String::from_utf8(bytes.to_vec()).into_lua_err()?;\n            let value: JsonValue =\n                jsonc_parser::parse_to_serde_value(&string, &jsonc_parser::ParseOptions::default())\n                    .map(|v| v.unwrap_or(JsonValue::Null))\n                    .into_lua_err()?;\n            lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)\n        }\n        EncodeDecodeFormat::Yaml => {\n            let string: String = String::from_utf8(bytes.to_vec()).into_lua_err()?;\n            let value: YamlValue = serde_yaml2::from_str(&string).into_lua_err()?;\n            lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)\n        }\n        EncodeDecodeFormat::Toml => {\n            if let Ok(s) = String::from_utf8(bytes.to_vec()) {\n                let value: TomlValue = toml::from_str(&s).into_lua_err()?;\n                lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)\n            } else {\n                Err(LuaError::RuntimeError(\n                    \"TOML must be valid utf-8\".to_string(),\n                ))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-serde/src/hash.rs",
    "content": "use std::fmt::Write;\n\nuse bstr::BString;\nuse md5::Md5;\nuse mlua::prelude::*;\n\nuse blake3::Hasher as Blake3;\nuse sha1::Sha1;\nuse sha2::{Sha224, Sha256, Sha384, Sha512};\nuse sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};\n\npub struct HashOptions {\n    algorithm: HashAlgorithm,\n    message: BString,\n    secret: Option<BString>,\n    // seed: Option<BString>,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum HashAlgorithm {\n    Md5,\n    Sha1,\n    // SHA-2 variants\n    Sha2_224,\n    Sha2_256,\n    Sha2_384,\n    Sha2_512,\n    // SHA-3 variants\n    Sha3_224,\n    Sha3_256,\n    Sha3_384,\n    Sha3_512,\n    // Blake3\n    Blake3,\n}\n\nimpl HashAlgorithm {\n    pub const ALL: [Self; 11] = [\n        Self::Md5,\n        Self::Sha1,\n        Self::Sha2_224,\n        Self::Sha2_256,\n        Self::Sha2_384,\n        Self::Sha2_512,\n        Self::Sha3_224,\n        Self::Sha3_256,\n        Self::Sha3_384,\n        Self::Sha3_512,\n        Self::Blake3,\n    ];\n\n    pub const fn name(self) -> &'static str {\n        match self {\n            Self::Md5 => \"md5\",\n            Self::Sha1 => \"sha1\",\n            Self::Sha2_224 => \"sha224\",\n            Self::Sha2_256 => \"sha256\",\n            Self::Sha2_384 => \"sha384\",\n            Self::Sha2_512 => \"sha512\",\n            Self::Sha3_224 => \"sha3-224\",\n            Self::Sha3_256 => \"sha3-256\",\n            Self::Sha3_384 => \"sha3-384\",\n            Self::Sha3_512 => \"sha3-512\",\n            Self::Blake3 => \"blake3\",\n        }\n    }\n}\n\nimpl HashOptions {\n    /**\n        Computes the hash for the `message` using whatever `algorithm` is\n        contained within this struct and returns it as a string of hex digits.\n    */\n    #[inline]\n    #[must_use = \"hashing a message is useless without using the resulting hash\"]\n    pub fn hash(self) -> String {\n        use digest::Digest;\n\n        let message = self.message;\n        let bytes = match self.algorithm {\n            HashAlgorithm::Md5 => Md5::digest(message).to_vec(),\n            HashAlgorithm::Sha1 => Sha1::digest(message).to_vec(),\n            HashAlgorithm::Sha2_224 => Sha224::digest(message).to_vec(),\n            HashAlgorithm::Sha2_256 => Sha256::digest(message).to_vec(),\n            HashAlgorithm::Sha2_384 => Sha384::digest(message).to_vec(),\n            HashAlgorithm::Sha2_512 => Sha512::digest(message).to_vec(),\n\n            HashAlgorithm::Sha3_224 => Sha3_224::digest(message).to_vec(),\n            HashAlgorithm::Sha3_256 => Sha3_256::digest(message).to_vec(),\n            HashAlgorithm::Sha3_384 => Sha3_384::digest(message).to_vec(),\n            HashAlgorithm::Sha3_512 => Sha3_512::digest(message).to_vec(),\n\n            HashAlgorithm::Blake3 => Blake3::digest(message).to_vec(),\n        };\n\n        // We don't want to return raw binary data generally, since that's not\n        // what most people want a hash for. So we have to make a hex string.\n        bytes\n            .iter()\n            .fold(String::with_capacity(bytes.len() * 2), |mut output, b| {\n                let _ = write!(output, \"{b:02x}\");\n                output\n            })\n    }\n\n    /**\n        Computes the HMAC for the `message` using whatever `algorithm` and\n        `secret` are contained within this struct. The computed value is\n        returned as a string of hex digits.\n\n        # Errors\n\n        If the `secret` is not provided or is otherwise invalid.\n    */\n    #[inline]\n    pub fn hmac(self) -> LuaResult<String> {\n        use hmac::{Hmac, Mac, SimpleHmac};\n\n        let secret = self\n            .secret\n            .ok_or_else(|| LuaError::FromLuaConversionError {\n                from: \"nil\",\n                to: \"string or buffer\".to_string(),\n                message: Some(\"Argument #3 missing or nil\".to_string()),\n            })?;\n\n        /*\n            These macros exist to remove what would ultimately be dozens of\n            repeating lines. Essentially, there's several step to processing\n            HMacs, which expands into the 3 lines you see below. However,\n            the Hmac struct is specialized towards eager block-based processes.\n            In order to support anything else, like blake3, there's a second\n            type named `SimpleHmac`. This results in duplicate macros like\n            there are below.\n        */\n        macro_rules! hmac {\n            ($Type:ty) => {{\n                let mut mac: Hmac<$Type> = Hmac::new_from_slice(&secret).into_lua_err()?;\n                mac.update(&self.message);\n                mac.finalize().into_bytes().to_vec()\n            }};\n        }\n        macro_rules! hmac_no_blocks {\n            ($Type:ty) => {{\n                let mut mac: SimpleHmac<$Type> =\n                    SimpleHmac::new_from_slice(&secret).into_lua_err()?;\n                mac.update(&self.message);\n                mac.finalize().into_bytes().to_vec()\n            }};\n        }\n\n        let bytes = match self.algorithm {\n            HashAlgorithm::Md5 => hmac!(Md5),\n            HashAlgorithm::Sha1 => hmac!(Sha1),\n\n            HashAlgorithm::Sha2_224 => hmac!(Sha224),\n            HashAlgorithm::Sha2_256 => hmac!(Sha256),\n            HashAlgorithm::Sha2_384 => hmac!(Sha384),\n            HashAlgorithm::Sha2_512 => hmac!(Sha512),\n\n            HashAlgorithm::Sha3_224 => hmac!(Sha3_224),\n            HashAlgorithm::Sha3_256 => hmac!(Sha3_256),\n            HashAlgorithm::Sha3_384 => hmac!(Sha3_384),\n            HashAlgorithm::Sha3_512 => hmac!(Sha3_512),\n\n            HashAlgorithm::Blake3 => hmac_no_blocks!(Blake3),\n        };\n        Ok(bytes\n            .iter()\n            .fold(String::with_capacity(bytes.len() * 2), |mut output, b| {\n                let _ = write!(output, \"{b:02x}\");\n                output\n            }))\n    }\n}\n\nimpl FromLua for HashAlgorithm {\n    fn from_lua(value: LuaValue, _lua: &Lua) -> LuaResult<Self> {\n        if let LuaValue::String(str) = value {\n            /*\n                Casing tends to vary for algorithms, so rather than force\n                people to remember it we'll just accept any casing.\n            */\n            let str = str.to_str()?.to_ascii_lowercase();\n            match str.as_str() {\n                \"md5\" => Ok(Self::Md5),\n                \"sha1\" => Ok(Self::Sha1),\n\n                \"sha2-224\" | \"sha2_224\" | \"sha224\" => Ok(Self::Sha2_224),\n                \"sha2-256\" | \"sha2_256\" | \"sha256\" => Ok(Self::Sha2_256),\n                \"sha2-384\" | \"sha2_384\" | \"sha384\" => Ok(Self::Sha2_384),\n                \"sha2-512\" | \"sha2_512\" | \"sha512\" => Ok(Self::Sha2_512),\n\n                \"sha3-224\" | \"sha3_224\" => Ok(Self::Sha3_224),\n                \"sha3-256\" | \"sha3_256\" => Ok(Self::Sha3_256),\n                \"sha3-384\" | \"sha3_384\" => Ok(Self::Sha3_384),\n                \"sha3-512\" | \"sha3_512\" => Ok(Self::Sha3_512),\n\n                \"blake3\" => Ok(Self::Blake3),\n\n                _ => Err(LuaError::FromLuaConversionError {\n                    from: \"string\",\n                    to: \"HashAlgorithm\".to_string(),\n                    message: Some(format!(\n                        \"Invalid hashing algorithm '{str}', valid kinds are:\\n{}\",\n                        HashAlgorithm::ALL\n                            .into_iter()\n                            .map(HashAlgorithm::name)\n                            .collect::<Vec<_>>()\n                            .join(\", \")\n                    )),\n                }),\n            }\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"HashAlgorithm\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\nimpl FromLuaMulti for HashOptions {\n    fn from_lua_multi(mut values: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {\n        let algorithm = values\n            .pop_front()\n            .map(|value| HashAlgorithm::from_lua(value, lua))\n            .transpose()?\n            .ok_or_else(|| LuaError::FromLuaConversionError {\n                from: \"nil\",\n                to: \"HashOptions\".to_string(),\n                message: Some(\"Argument #1 missing or nil\".to_string()),\n            })?;\n        let message = values\n            .pop_front()\n            .map(|value| BString::from_lua(value, lua))\n            .transpose()?\n            .ok_or_else(|| LuaError::FromLuaConversionError {\n                from: \"nil\",\n                to: \"string or buffer\".to_string(),\n                message: Some(\"Argument #2 missing or nil\".to_string()),\n            })?;\n        let secret = values\n            .pop_front()\n            .map(|value| BString::from_lua(value, lua))\n            .transpose()?;\n        // let seed = values\n        //     .pop_front()\n        //     .map(|value| BString::from_lua(value, lua))\n        //     .transpose()?;\n\n        Ok(HashOptions {\n            algorithm,\n            message,\n            secret,\n            // seed,\n        })\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-serde/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse bstr::BString;\nuse mlua::prelude::*;\n\nuse lune_utils::TableBuilder;\n\nmod compress_decompress;\nmod encode_decode;\nmod hash;\n\npub use self::compress_decompress::{CompressDecompressFormat, compress, decompress};\npub use self::encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat, decode, encode};\npub use self::hash::HashOptions;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `serde` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `serde` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    TableBuilder::new(lua)?\n        .with_function(\"encode\", serde_encode)?\n        .with_function(\"decode\", serde_decode)?\n        .with_async_function(\"compress\", serde_compress)?\n        .with_async_function(\"decompress\", serde_decompress)?\n        .with_function(\"hash\", hash_message)?\n        .with_function(\"hmac\", hmac_message)?\n        .build_readonly()\n}\n\nfn serde_encode(\n    lua: &Lua,\n    (format, value, pretty): (EncodeDecodeFormat, LuaValue, Option<bool>),\n) -> LuaResult<LuaString> {\n    let config = EncodeDecodeConfig::from((format, pretty.unwrap_or_default()));\n    encode(value, lua, config)\n}\n\nfn serde_decode(lua: &Lua, (format, bs): (EncodeDecodeFormat, BString)) -> LuaResult<LuaValue> {\n    let config = EncodeDecodeConfig::from(format);\n    decode(bs, lua, config)\n}\n\nasync fn serde_compress(\n    lua: Lua,\n    (format, bs, level): (CompressDecompressFormat, BString, Option<i32>),\n) -> LuaResult<LuaString> {\n    let bytes = compress(bs, format, level).await?;\n    lua.create_string(bytes)\n}\n\nasync fn serde_decompress(\n    lua: Lua,\n    (format, bs): (CompressDecompressFormat, BString),\n) -> LuaResult<LuaString> {\n    let bytes = decompress(bs, format).await?;\n    lua.create_string(bytes)\n}\n\nfn hash_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {\n    lua.create_string(options.hash())\n}\n\nfn hmac_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {\n    lua.create_string(options.hmac()?)\n}\n"
  },
  {
    "path": "crates/lune-std-serde/types.d.luau",
    "content": "--[=[\n\t@within Serde\n\t@interface EncodeDecodeFormat\n\n\tA serialization/deserialization format supported by the Serde library.\n\n\tCurrently supported formats:\n\n\t| Name    | Learn More           | Note                        |\n\t|:--------|:---------------------|:----------------------------|\n\t| `json`  | https://www.json.org |                             |\n\t| `jsonc` | https://www.json.org | JSON, with comments allowed |\n\t| `yaml`  | https://yaml.org     |                             |\n\t| `toml`  | https://toml.io      |                             |\n]=]\nexport type EncodeDecodeFormat = \"json\" | \"jsonc\" | \"yaml\" | \"toml\"\n\n--[=[\n\t@within Serde\n\t@interface CompressDecompressFormat\n\n\tA compression/decompression format supported by the Serde library.\n\n\tCurrently supported formats:\n\n\t| Name     | Learn More                        |\n\t|:---------|:----------------------------------|\n\t| `brotli` | https://github.com/google/brotli  |\n\t| `gzip`   | https://www.gnu.org/software/gzip |\n\t| `lz4`    | https://github.com/lz4/lz4        |\n\t| `zlib`   | https://www.zlib.net              |\n\t| `zstd`   | https://github.com/facebook/zstd  |\n]=]\nexport type CompressDecompressFormat = \"brotli\" | \"gzip\" | \"lz4\" | \"zlib\" | \"zstd\"\n\n--[=[\n\t@within Serde\n\t@interface HashAlgorithm\n\n\tA hash algorithm supported by the Serde library.\n\n\tCurrently supported algorithms:\n\n\t| Name       | Learn More                           |\n\t|:-----------|:-------------------------------------|\n\t| `md5`      | https://en.wikipedia.org/wiki/MD5    |\n\t| `sha1`     | https://en.wikipedia.org/wiki/SHA-1  |\n\t| `sha224`   | https://en.wikipedia.org/wiki/SHA-2  |\n\t| `sha256`   | https://en.wikipedia.org/wiki/SHA-2  |\n\t| `sha384`   | https://en.wikipedia.org/wiki/SHA-2  |\n\t| `sha512`   | https://en.wikipedia.org/wiki/SHA-2  |\n\t| `sha3-224` | https://en.wikipedia.org/wiki/SHA-3  |\n\t| `sha3-256` | https://en.wikipedia.org/wiki/SHA-3  |\n\t| `sha3-384` | https://en.wikipedia.org/wiki/SHA-3  |\n\t| `sha3-512` | https://en.wikipedia.org/wiki/SHA-3  |\n\t| `blake3`   | https://en.wikipedia.org/wiki/BLAKE3 |\n]=]\nexport type HashAlgorithm =\n\t\"md5\"\n\t| \"sha1\"\n\t| \"sha224\"\n\t| \"sha256\"\n\t| \"sha384\"\n\t| \"sha512\"\n\t| \"sha3-224\"\n\t| \"sha3-256\"\n\t| \"sha3-384\"\n\t| \"sha3-512\"\n\t| \"blake3\"\n\n--[=[\n\t@class Serde\n\n\tBuilt-in library for:\n\t- serialization & deserialization\n\t- encoding & decoding\n\t- compression\n\n\t### Example usage\n\n\t```lua\n\tlocal fs = require(\"@lune/fs\")\n\tlocal serde = require(\"@lune/serde\")\n\n\t-- Parse different file formats into lua values\n\tlocal someJson = serde.decode(\"json\", fs.readFile(\"myFile.json\"))\n\tlocal someToml = serde.decode(\"toml\", fs.readFile(\"myFile.toml\"))\n\tlocal someYaml = serde.decode(\"yaml\", fs.readFile(\"myFile.yaml\"))\n\n\t-- Write lua values to files in different formats\n\tfs.writeFile(\"myFile.json\", serde.encode(\"json\", someJson))\n\tfs.writeFile(\"myFile.toml\", serde.encode(\"toml\", someToml))\n\tfs.writeFile(\"myFile.yaml\", serde.encode(\"yaml\", someYaml))\n\t```\n]=]\nlocal serde = {}\n\n--[=[\n\t@within Serde\n\t@tag must_use\n\n\tEncodes the given value using the given format.\n\n\tSee [`EncodeDecodeFormat`] for a list of supported formats.\n\n\t@param format The format to use\n\t@param value The value to encode\n\t@param pretty If the encoded string should be human-readable, including things such as newlines and spaces. Only supported for json and toml formats, and defaults to false\n\t@return The encoded string\n]=]\nfunction serde.encode(format: EncodeDecodeFormat, value: any, pretty: boolean?): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Serde\n\t@tag must_use\n\n\tDecodes the given string using the given format into a lua value.\n\n\tSee [`EncodeDecodeFormat`] for a list of supported formats.\n\n\t@param format The format to use\n\t@param encoded The string to decode\n\t@return The decoded lua value\n]=]\nfunction serde.decode(format: EncodeDecodeFormat, encoded: buffer | string): any\n\treturn nil :: any\nend\n\n--[=[\n\t@within Serde\n\t@tag must_use\n\n\tCompresses the given string using the given format.\n\n\tSee [`CompressDecompressFormat`] for a list of supported formats.\n\n\t@param format The format to use\n\t@param s The string to compress\n\t@param level The compression level to use, clamped to the format's limits. The best compression level is used by default\n\t@return The compressed string\n]=]\nfunction serde.compress(\n\tformat: CompressDecompressFormat,\n\ts: buffer | string,\n\tlevel: number?\n): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Serde\n\t@tag must_use\n\n\tDecompresses the given string using the given format.\n\n\tSee [`CompressDecompressFormat`] for a list of supported formats.\n\n\t@param format The format to use\n\t@param s The string to decompress\n\t@return The decompressed string\n]=]\nfunction serde.decompress(format: CompressDecompressFormat, s: buffer | string): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Serde\n\t@tag must_use\n\n\tHashes the given message using the given algorithm\n\tand returns the hash as a hex string.\n\n\tSee [`HashAlgorithm`] for a list of supported algorithms.\n\n\t@param algorithm The algorithm to use\n\t@param message The message to hash\n\t@return The hash as a hex string\n]=]\nfunction serde.hash(algorithm: HashAlgorithm, message: string | buffer): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Serde\n\t@tag must_use\n\n\tHashes the given message using HMAC with the given secret\n\tand algorithm, returning the hash as a base64 string.\n\n\tSee [`HashAlgorithm`] for a list of supported algorithms.\n\n\t@param algorithm The algorithm to use\n\t@param message The message to hash\n\t@return The hash as a base64 string\n]=]\nfunction serde.hmac(\n\talgorithm: HashAlgorithm,\n\tmessage: string | buffer,\n\tsecret: string | buffer\n): string\n\treturn nil :: any\nend\n\nreturn serde\n"
  },
  {
    "path": "crates/lune-std-stdio/Cargo.toml",
    "content": "[package]\nname = \"lune-std-stdio\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Stdio\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\", \"error-send\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\nasync-io = \"2.4\"\nasync-lock = \"3.4\"\nblocking = \"1.6\"\ndialoguer = \"0.12\"\nfutures-lite = \"2.6\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-stdio/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse std::{\n    io::{Stdin, stderr, stdin, stdout},\n    sync::{Arc, LazyLock},\n};\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::LuaSpawnExt;\n\nuse async_lock::Mutex as AsyncMutex;\nuse blocking::Unblock;\nuse futures_lite::{io::BufReader, prelude::*};\n\nuse lune_utils::{\n    TableBuilder,\n    fmt::{ValueFormatConfig, pretty_format_multi_value},\n};\n\nmod prompt;\nmod style_and_color;\n\nuse self::prompt::{PromptOptions, PromptResult, prompt};\nuse self::style_and_color::{ColorKind, StyleKind};\n\nconst FORMAT_CONFIG: ValueFormatConfig = ValueFormatConfig::new()\n    .with_max_depth(4)\n    .with_colors_enabled(false);\n\nstatic STDIN: LazyLock<Arc<AsyncMutex<BufReader<Unblock<Stdin>>>>> = LazyLock::new(|| {\n    let stdin = Unblock::new(stdin());\n    let reader = BufReader::new(stdin);\n    Arc::new(AsyncMutex::new(reader))\n});\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `stdio` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `stdio` standard library module.\n\n    # Errors\n\n    Errors when out of memory.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    TableBuilder::new(lua)?\n        .with_function(\"color\", stdio_color)?\n        .with_function(\"style\", stdio_style)?\n        .with_function(\"format\", stdio_format)?\n        .with_async_function(\"write\", stdio_write)?\n        .with_async_function(\"ewrite\", stdio_ewrite)?\n        .with_async_function(\"readLine\", stdio_read_line)?\n        .with_async_function(\"readToEnd\", stdio_read_to_end)?\n        .with_async_function(\"prompt\", stdio_prompt)?\n        .build_readonly()\n}\n\nfn stdio_color(lua: &Lua, color: ColorKind) -> LuaResult<LuaValue> {\n    color.ansi_escape_sequence().into_lua(lua)\n}\n\nfn stdio_style(lua: &Lua, style: StyleKind) -> LuaResult<LuaValue> {\n    style.ansi_escape_sequence().into_lua(lua)\n}\n\nfn stdio_format(_: &Lua, args: LuaMultiValue) -> LuaResult<String> {\n    Ok(pretty_format_multi_value(&args, &FORMAT_CONFIG))\n}\n\nasync fn stdio_write(_: Lua, s: LuaString) -> LuaResult<()> {\n    let mut stdout = Unblock::new(stdout());\n    stdout.write_all(&s.as_bytes()).await?;\n    stdout.flush().await?;\n    Ok(())\n}\n\nasync fn stdio_ewrite(_: Lua, s: LuaString) -> LuaResult<()> {\n    let mut stderr = Unblock::new(stderr());\n    stderr.write_all(&s.as_bytes()).await?;\n    stderr.flush().await?;\n    Ok(())\n}\n\nasync fn stdio_read_line(lua: Lua, (): ()) -> LuaResult<LuaString> {\n    let mut string = String::new();\n    let mut handle = STDIN.lock_arc().await;\n    handle.read_line(&mut string).await?;\n    lua.create_string(&string)\n}\n\nasync fn stdio_read_to_end(lua: Lua, (): ()) -> LuaResult<LuaString> {\n    let mut buffer = Vec::new();\n    let mut handle = STDIN.lock_arc().await;\n    handle.read_to_end(&mut buffer).await?;\n    lua.create_string(&buffer)\n}\n\nasync fn stdio_prompt(lua: Lua, options: PromptOptions) -> LuaResult<PromptResult> {\n    lua.spawn_blocking(move || prompt(options))\n        .await\n        .into_lua_err()\n}\n"
  },
  {
    "path": "crates/lune-std-stdio/src/prompt.rs",
    "content": "use std::{fmt, str::FromStr};\n\nuse dialoguer::{Confirm, Input, MultiSelect, Select, theme::ColorfulTheme};\nuse mlua::prelude::*;\n\n#[derive(Debug, Clone, Copy)]\npub enum PromptKind {\n    Text,\n    Confirm,\n    Select,\n    MultiSelect,\n}\n\nimpl PromptKind {\n    const ALL: [PromptKind; 4] = [Self::Text, Self::Confirm, Self::Select, Self::MultiSelect];\n}\n\nimpl Default for PromptKind {\n    fn default() -> Self {\n        Self::Text\n    }\n}\n\nimpl FromStr for PromptKind {\n    type Err = ();\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.trim().to_ascii_lowercase().as_str() {\n            \"text\" => Ok(Self::Text),\n            \"confirm\" => Ok(Self::Confirm),\n            \"select\" => Ok(Self::Select),\n            \"multiselect\" => Ok(Self::MultiSelect),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl fmt::Display for PromptKind {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Self::Text => \"Text\",\n                Self::Confirm => \"Confirm\",\n                Self::Select => \"Select\",\n                Self::MultiSelect => \"MultiSelect\",\n            }\n        )\n    }\n}\n\nimpl FromLua for PromptKind {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::Nil = value {\n            Ok(Self::default())\n        } else if let LuaValue::String(s) = value {\n            let s = s.to_str()?;\n            s.parse().map_err(|()| LuaError::FromLuaConversionError {\n                from: \"string\",\n                to: \"PromptKind\".to_string(),\n                message: Some(format!(\n                    \"Invalid prompt kind '{s}', valid kinds are:\\n{}\",\n                    PromptKind::ALL\n                        .iter()\n                        .map(ToString::to_string)\n                        .collect::<Vec<_>>()\n                        .join(\", \")\n                )),\n            })\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: \"nil\",\n                to: \"PromptKind\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\npub struct PromptOptions {\n    pub kind: PromptKind,\n    pub text: Option<String>,\n    pub default_string: Option<String>,\n    pub default_bool: Option<bool>,\n    pub options: Option<Vec<String>>,\n}\n\nimpl FromLuaMulti for PromptOptions {\n    fn from_lua_multi(mut values: LuaMultiValue, lua: &Lua) -> LuaResult<Self> {\n        // Argument #1 - prompt kind (optional)\n        let kind = values\n            .pop_front()\n            .map(|value| PromptKind::from_lua(value, lua))\n            .transpose()?\n            .unwrap_or_default();\n        // Argument #2 - prompt text (optional)\n        let text = values\n            .pop_front()\n            .map(|text| String::from_lua(text, lua))\n            .transpose()?;\n        // Argument #3 - default value / options,\n        // this is different per each prompt kind\n        let (default_bool, default_string, options) = match values.pop_front() {\n            None => (None, None, None),\n            Some(options) => match options {\n                LuaValue::Nil => (None, None, None),\n                LuaValue::Boolean(b) => (Some(b), None, None),\n                LuaValue::String(s) => (\n                    None,\n                    Some(String::from_lua(LuaValue::String(s), lua)?),\n                    None,\n                ),\n                LuaValue::Table(t) => (\n                    None,\n                    None,\n                    Some(Vec::<String>::from_lua(LuaValue::Table(t), lua)?),\n                ),\n                value => {\n                    return Err(LuaError::FromLuaConversionError {\n                        from: value.type_name(),\n                        to: \"PromptOptions\".to_string(),\n                        message: Some(\"Argument #3 must be a boolean, table, or nil\".to_string()),\n                    });\n                }\n            },\n        };\n        /*\n            Make sure we got the required values for the specific prompt kind:\n\n            - \"Confirm\" requires a message to be present so the user knows what they are confirming\n            - \"Select\" and \"MultiSelect\" both require a table of options to choose from\n        */\n        if matches!(kind, PromptKind::Confirm) && text.is_none() {\n            return Err(LuaError::FromLuaConversionError {\n                from: \"nil\",\n                to: \"PromptOptions\".to_string(),\n                message: Some(\"Argument #2 missing or nil\".to_string()),\n            });\n        }\n        if matches!(kind, PromptKind::Select | PromptKind::MultiSelect) && options.is_none() {\n            return Err(LuaError::FromLuaConversionError {\n                from: \"nil\",\n                to: \"PromptOptions\".to_string(),\n                message: Some(\"Argument #3 missing or nil\".to_string()),\n            });\n        }\n        // All good, return the prompt options\n        Ok(Self {\n            kind,\n            text,\n            default_string,\n            default_bool,\n            options,\n        })\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum PromptResult {\n    String(String),\n    Boolean(bool),\n    Index(usize),\n    Indices(Vec<usize>),\n    None,\n}\n\nimpl IntoLua for PromptResult {\n    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {\n        Ok(match self {\n            Self::String(s) => LuaValue::String(lua.create_string(&s)?),\n            Self::Boolean(b) => LuaValue::Boolean(b),\n            Self::Index(i) => LuaValue::Number(i as f64),\n            Self::Indices(v) => v.into_lua(lua)?,\n            Self::None => LuaValue::Nil,\n        })\n    }\n}\n\npub fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {\n    let theme = ColorfulTheme::default();\n    match options.kind {\n        PromptKind::Text => {\n            let input: String = Input::with_theme(&theme)\n                .allow_empty(true)\n                .with_prompt(options.text.unwrap_or_default())\n                .with_initial_text(options.default_string.unwrap_or_default())\n                .interact_text()\n                .into_lua_err()?;\n            Ok(PromptResult::String(input))\n        }\n        PromptKind::Confirm => {\n            let mut prompt = Confirm::with_theme(&theme);\n            if let Some(b) = options.default_bool {\n                prompt = prompt.default(b);\n            }\n            let result = prompt\n                .with_prompt(options.text.expect(\"Missing text in prompt options\"))\n                .interact()\n                .into_lua_err()?;\n            Ok(PromptResult::Boolean(result))\n        }\n        PromptKind::Select => {\n            let chosen = Select::with_theme(&theme)\n                .with_prompt(options.text.unwrap_or_default())\n                .items(options.options.expect(\"Missing options in prompt options\"))\n                .interact_opt()\n                .into_lua_err()?;\n            Ok(match chosen {\n                Some(idx) => PromptResult::Index(idx + 1),\n                None => PromptResult::None,\n            })\n        }\n        PromptKind::MultiSelect => {\n            let chosen = MultiSelect::with_theme(&theme)\n                .with_prompt(options.text.unwrap_or_default())\n                .items(options.options.expect(\"Missing options in prompt options\"))\n                .interact_opt()\n                .into_lua_err()?;\n            Ok(match chosen {\n                None => PromptResult::None,\n                Some(indices) => {\n                    PromptResult::Indices(indices.iter().map(|idx| *idx + 1).collect())\n                }\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-stdio/src/style_and_color.rs",
    "content": "use std::str::FromStr;\n\nuse mlua::prelude::*;\n\nconst ESCAPE_SEQ_RESET: &str = \"\\x1b[0m\";\n\n/**\n    A color kind supported by the `stdio` standard library.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ColorKind {\n    Reset,\n    Black,\n    Red,\n    Green,\n    Yellow,\n    Blue,\n    Magenta,\n    Cyan,\n    White,\n}\n\nimpl ColorKind {\n    pub const ALL: [Self; 9] = [\n        Self::Reset,\n        Self::Black,\n        Self::Red,\n        Self::Green,\n        Self::Yellow,\n        Self::Blue,\n        Self::Magenta,\n        Self::Cyan,\n        Self::White,\n    ];\n\n    /**\n        Returns the human-friendly name of this color kind.\n    */\n    pub fn name(self) -> &'static str {\n        match self {\n            Self::Reset => \"reset\",\n            Self::Black => \"black\",\n            Self::Red => \"red\",\n            Self::Green => \"green\",\n            Self::Yellow => \"yellow\",\n            Self::Blue => \"blue\",\n            Self::Magenta => \"magenta\",\n            Self::Cyan => \"cyan\",\n            Self::White => \"white\",\n        }\n    }\n\n    /**\n        Returns the ANSI escape sequence for the color kind.\n    */\n    pub fn ansi_escape_sequence(self) -> &'static str {\n        match self {\n            Self::Reset => ESCAPE_SEQ_RESET,\n            Self::Black => \"\\x1b[30m\",\n            Self::Red => \"\\x1b[31m\",\n            Self::Green => \"\\x1b[32m\",\n            Self::Yellow => \"\\x1b[33m\",\n            Self::Blue => \"\\x1b[34m\",\n            Self::Magenta => \"\\x1b[35m\",\n            Self::Cyan => \"\\x1b[36m\",\n            Self::White => \"\\x1b[37m\",\n        }\n    }\n}\n\nimpl FromStr for ColorKind {\n    type Err = ();\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Ok(match s.trim().to_ascii_lowercase().as_str() {\n            \"reset\" => Self::Reset,\n            \"black\" => Self::Black,\n            \"red\" => Self::Red,\n            \"green\" => Self::Green,\n            \"yellow\" => Self::Yellow,\n            \"blue\" => Self::Blue,\n            // NOTE: Previous versions of Lune had this color as \"purple\" instead\n            // of \"magenta\", so we keep this here for backwards compatibility.\n            \"magenta\" | \"purple\" => Self::Magenta,\n            \"cyan\" => Self::Cyan,\n            \"white\" => Self::White,\n            _ => return Err(()),\n        })\n    }\n}\n\nimpl FromLua for ColorKind {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::String(s) = value {\n            let s = s.to_str()?;\n            match s.parse() {\n                Ok(color) => Ok(color),\n                Err(()) => Err(LuaError::FromLuaConversionError {\n                    from: \"string\",\n                    to: \"ColorKind\".to_string(),\n                    message: Some(format!(\n                        \"Invalid color kind '{s}'\\nValid kinds are: {}\",\n                        Self::ALL\n                            .iter()\n                            .map(|kind| kind.name())\n                            .collect::<Vec<_>>()\n                            .join(\", \")\n                    )),\n                }),\n            }\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"ColorKind\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n\n/**\n    A style kind supported by the `stdio` standard library.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StyleKind {\n    Reset,\n    Bold,\n    Dim,\n}\n\nimpl StyleKind {\n    pub const ALL: [Self; 3] = [Self::Reset, Self::Bold, Self::Dim];\n\n    /**\n        Returns the human-friendly name for this style kind.\n    */\n    pub fn name(self) -> &'static str {\n        match self {\n            Self::Reset => \"reset\",\n            Self::Bold => \"bold\",\n            Self::Dim => \"dim\",\n        }\n    }\n\n    /**\n        Returns the ANSI escape sequence for this style kind.\n    */\n    pub fn ansi_escape_sequence(self) -> &'static str {\n        match self {\n            Self::Reset => ESCAPE_SEQ_RESET,\n            Self::Bold => \"\\x1b[1m\",\n            Self::Dim => \"\\x1b[2m\",\n        }\n    }\n}\n\nimpl FromStr for StyleKind {\n    type Err = ();\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Ok(match s.trim().to_ascii_lowercase().as_str() {\n            \"reset\" => Self::Reset,\n            \"bold\" => Self::Bold,\n            \"dim\" => Self::Dim,\n            _ => return Err(()),\n        })\n    }\n}\n\nimpl FromLua for StyleKind {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::String(s) = value {\n            let s = s.to_str()?;\n            match s.parse() {\n                Ok(style) => Ok(style),\n                Err(()) => Err(LuaError::FromLuaConversionError {\n                    from: \"string\",\n                    to: \"StyleKind\".to_string(),\n                    message: Some(format!(\n                        \"Invalid style kind '{s}'\\nValid kinds are: {}\",\n                        Self::ALL\n                            .iter()\n                            .map(|kind| kind.name())\n                            .collect::<Vec<_>>()\n                            .join(\", \")\n                    )),\n                }),\n            }\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"StyleKind\".to_string(),\n                message: None,\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "crates/lune-std-stdio/types.d.luau",
    "content": "export type Color = \"reset\" | \"black\" | \"red\" | \"green\" | \"yellow\" | \"blue\" | \"purple\" | \"cyan\" | \"white\"\nexport type Style = \"reset\" | \"bold\" | \"dim\"\n\ntype PromptFn = (\n\t(() -> string)\n\t& ((kind: \"text\", message: string?, defaultOrOptions: string?) -> string)\n\t& ((kind: \"confirm\", message: string, defaultOrOptions: boolean?) -> boolean)\n\t& ((kind: \"select\", message: string?, defaultOrOptions: { string }) -> number?)\n\t& ((kind: \"multiselect\", message: string?, defaultOrOptions: { string }) -> { number }?)\n)\n\n--[=[\n\t@within Stdio\n\t@function prompt\n\t@tag must_use\n\n\tPrompts for user input using the wanted kind of prompt:\n\n\t* `\"text\"` - Prompts for a plain text string from the user\n\t* `\"confirm\"` - Prompts the user to confirm with y / n (yes / no)\n\t* `\"select\"` - Prompts the user to select *one* value from a list\n\t* `\"multiselect\"` - Prompts the user to select *one or more* values from a list\n\t* `nil` - Equivalent to `\"text\"` with no extra arguments\n\n\t@param kind The kind of prompt to use\n\t@param message The message to show the user\n\t@param defaultOrOptions The default value for the prompt, or options to choose from for selection prompts\n]=]\nlocal prompt: PromptFn = function(kind: any, message: any, defaultOrOptions: any)\n\treturn nil :: any\nend\n\n--[=[\n\t@class Stdio\n\n\tBuilt-in standard input / output & utility functions\n\n\t### Example usage\n\n\t```lua\n\tlocal stdio = require(\"@lune/stdio\")\n\n\t-- Prompting the user for basic input\n\tlocal text: string = stdio.prompt(\"text\", \"Please write some text\")\n\tlocal confirmed: boolean = stdio.prompt(\"confirm\", \"Please confirm this action\")\n\n\t-- Writing directly to stdout or stderr, without the auto-formatting of print/warn/error\n\tstdio.write(\"Hello, \")\n\tstdio.write(\"World! \")\n\tstdio.write(\"All on the same line\")\n\tstdio.ewrite(\"\\nAnd some error text, too\")\n\n\t-- Reading a single line from stdin\n\tlocal line = stdio.readLine()\n\n\t-- Reading the entire input from stdin\n\tlocal input = stdio.readToEnd()\n\t```\n]=]\nlocal stdio = {}\n\nstdio.prompt = prompt\n\n--[=[\n\t@within Stdio\n\t@tag must_use\n\n\tReturn an ANSI string that can be used to modify the persistent output color.\n\n\tPass `\"reset\"` to get a string that can reset the persistent output color.\n\n\t### Example usage\n\n\t```lua\n\tstdio.write(stdio.color(\"red\"))\n\tprint(\"This text will be red\")\n\tstdio.write(stdio.color(\"reset\"))\n\tprint(\"This text will be normal\")\n\t```\n\n\t@param color The color to use\n\t@return A printable ANSI string\n]=]\nfunction stdio.color(color: Color): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Stdio\n\t@tag must_use\n\n\tReturn an ANSI string that can be used to modify the persistent output style.\n\n\tPass `\"reset\"` to get a string that can reset the persistent output style.\n\n\t### Example usage\n\n\t```lua\n\tstdio.write(stdio.style(\"bold\"))\n\tprint(\"This text will be bold\")\n\tstdio.write(stdio.style(\"reset\"))\n\tprint(\"This text will be normal\")\n\t```\n\n\t@param style The style to use\n\t@return A printable ANSI string\n]=]\nfunction stdio.style(style: Style): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Stdio\n\t@tag must_use\n\n\tFormats arguments into a human-readable string with syntax highlighting for tables.\n\n\t@param ... The values to format\n\t@return The formatted string\n]=]\nfunction stdio.format(...: any): string\n\treturn nil :: any\nend\n\n--[=[\n\t@within Stdio\n\n\tWrites a string directly to stdout, without any newline.\n\n\t@param s The string to write to stdout\n]=]\nfunction stdio.write(s: string) end\n\n--[=[\n\t@within Stdio\n\n\tWrites a string directly to stderr, without any newline.\n\n\t@param s The string to write to stderr\n]=]\nfunction stdio.ewrite(s: string) end\n\n--[=[\n    @within Stdio\n    @tag must_use\n\n    Reads a single line from stdin.\n\n    If stdin is closed, returns all input up until its closure.\n\n    @return The input from stdin\n]=]\nfunction stdio.readLine(): string\n\treturn nil :: any\nend\n\n--[=[\n    @within Stdio\n    @tag must_use\n\n    Reads the entire input from stdin.\n\n    @return The input from stdin\n]=]\nfunction stdio.readToEnd(): string\n\treturn nil :: any\nend\n\nreturn stdio\n"
  },
  {
    "path": "crates/lune-std-task/Cargo.toml",
    "content": "[package]\nname = \"lune-std-task\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Lune standard library - Task\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\"] }\nmlua-luau-scheduler = { version = \"0.2.3\", path = \"../mlua-luau-scheduler\" }\n\nasync-io = \"2.4\"\nfutures-lite = \"2.6\"\n\nlune-utils = { version = \"0.3.4\", path = \"../lune-utils\" }\n"
  },
  {
    "path": "crates/lune-std-task/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nuse std::time::{Duration, Instant};\n\nuse async_io::Timer;\nuse futures_lite::future::yield_now;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::Functions;\n\nuse lune_utils::TableBuilder;\n\nconst TYPEDEFS: &str = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/types.d.luau\"));\n\n/**\n    Returns a string containing type definitions for the `task` standard library.\n*/\n#[must_use]\npub fn typedefs() -> String {\n    TYPEDEFS.to_string()\n}\n\n/**\n    Creates the `task` standard library module.\n\n    # Errors\n\n    Errors when out of memory, or if default Lua globals are missing.\n*/\npub fn module(lua: Lua) -> LuaResult<LuaTable> {\n    let fns = Functions::new(lua.clone())?;\n\n    // Create wait & delay functions\n    let task_wait = lua.create_async_function(wait)?;\n    let task_delay_env = TableBuilder::new(lua.clone())?\n        .with_value(\"select\", lua.globals().get::<LuaFunction>(\"select\")?)?\n        .with_value(\"spawn\", fns.spawn.clone())?\n        .with_value(\"defer\", fns.defer.clone())?\n        .with_value(\"wait\", task_wait.clone())?\n        .build_readonly()?;\n    let task_delay = lua\n        .load(DELAY_IMPL_LUA)\n        .set_name(\"task.delay\")\n        .set_environment(task_delay_env)\n        .into_function()?;\n\n    TableBuilder::new(lua)?\n        .with_value(\"cancel\", fns.cancel)?\n        .with_value(\"defer\", fns.defer)?\n        .with_value(\"delay\", task_delay)?\n        .with_value(\"spawn\", fns.spawn)?\n        .with_value(\"wait\", task_wait)?\n        .build_readonly()\n}\n\nconst DELAY_IMPL_LUA: &str = r\"\nreturn defer(function(...)\n    wait(select(1, ...))\n    spawn(select(2, ...))\nend, ...)\n\";\n\nasync fn wait(lua: Lua, secs: Option<f64>) -> LuaResult<f64> {\n    // NOTE: We must guarantee that the task.wait API always yields\n    // from a lua perspective, even if sleep/timer completes instantly\n    yield_now().await;\n    wait_inner(lua, secs).await\n}\n\nasync fn wait_inner(_: Lua, secs: Option<f64>) -> LuaResult<f64> {\n    // One millisecond is a reasonable minimum sleep duration,\n    // anything lower than this runs the risk of completing the\n    // the below timer instantly, without giving control to the OS ...\n    let duration = Duration::from_secs_f64(secs.unwrap_or_default());\n    let duration = duration.max(Duration::from_millis(1));\n    // ... however, we should still _guarantee_ that whatever\n    // coroutine that calls this sleep function always yields,\n    // even if the timer is able to complete without doing so\n    yield_now().await;\n    // We may then sleep as normal\n    let before = Instant::now();\n    let after = Timer::after(duration).await;\n    Ok((after - before).as_secs_f64())\n}\n"
  },
  {
    "path": "crates/lune-std-task/types.d.luau",
    "content": "--[=[\n\t@class Task\n\n\tBuilt-in task scheduler & thread spawning\n\n\t### Example usage\n\n\t```lua\n\tlocal task = require(\"@lune/task\")\n\n\t-- Waiting for a certain amount of time\n\ttask.wait(1)\n\tprint(\"Waited for one second\")\n\n\t-- Running a task after a given amount of time\n\ttask.delay(2, function()\n\t\tprint(\"Ran after two seconds\")\n\tend)\n\n\t-- Spawning a new task that runs concurrently\n\ttask.spawn(function()\n\t\tprint(\"Running instantly\")\n\t\ttask.wait(1)\n\t\tprint(\"One second passed inside the task\")\n\tend)\n\n\tprint(\"Running after task.spawn yields\")\n\t```\n]=]\nlocal task = {}\n\n--[=[\n\t@within Task\n\n\tStops a currently scheduled thread from resuming.\n\n\t@param thread The thread to cancel\n]=]\nfunction task.cancel(thread: thread) end\n\n--[=[\n\t@within Task\n\n\tDefers a thread or function to run at the end of the current task queue.\n\n\t@param functionOrThread The function or thread to defer\n\t@return The thread that will be deferred\n]=]\nfunction task.defer<T...>(functionOrThread: thread | (T...) -> ...any, ...: T...): thread\n\treturn nil :: any\nend\n\n--[=[\n\t@within Task\n\n\tDelays a thread or function to run after `duration` seconds.\n\n\t@param functionOrThread The function or thread to delay\n\t@return The thread that will be delayed\n]=]\nfunction task.delay<T...>(duration: number, functionOrThread: thread | (T...) -> ...any, ...: T...): thread\n\treturn nil :: any\nend\n\n--[=[\n\t@within Task\n\n\tInstantly runs a thread or function.\n\n\tIf the spawned task yields, the thread that spawned the task\n\twill resume, letting the spawned task run in the background.\n\n\t@param functionOrThread The function or thread to spawn\n\t@return The thread that was spawned\n]=]\nfunction task.spawn<T...>(functionOrThread: thread | (T...) -> ...any, ...: T...): thread\n\treturn nil :: any\nend\n\n--[=[\n\t@within Task\n\n\tWaits for *at least* the given amount of time.\n\n\tThe minimum wait time possible when using `task.wait` is limited by the underlying OS sleep implementation.\n\tFor most systems this means `task.wait` is accurate down to about 5 milliseconds or less.\n\n\t@param duration The amount of time to wait\n\t@return The exact amount of time waited\n]=]\nfunction task.wait(duration: number?): number\n\treturn nil :: any\nend\n\nreturn task\n"
  },
  {
    "path": "crates/lune-utils/Cargo.toml",
    "content": "[package]\nname = \"lune-utils\"\nversion = \"0.3.4\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Utilities library for Lune\"\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nmlua = { version = \"0.11.4\", features = [\"luau\", \"async\"] }\n\nconsole = \"0.16\"\ndunce = \"1.0\"\nos_str_bytes = { version = \"7.0\", features = [\"conversions\"] }\npath-clean = \"1.0\"\nparking_lot = \"0.12.3\"\nsemver = \"1.0\"\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/error/components.rs",
    "content": "use std::{\n    fmt,\n    str::FromStr,\n    sync::{Arc, LazyLock},\n};\n\nuse console::style;\nuse mlua::prelude::*;\n\nuse super::StackTrace;\n\nstatic STYLED_STACK_BEGIN: LazyLock<String> = LazyLock::new(|| {\n    format!(\n        \"{}{}{}\",\n        style(\"[\").dim(),\n        style(\"Stack Begin\").blue(),\n        style(\"]\").dim()\n    )\n});\n\nstatic STYLED_STACK_END: LazyLock<String> = LazyLock::new(|| {\n    format!(\n        \"{}{}{}\",\n        style(\"[\").dim(),\n        style(\"Stack End\").blue(),\n        style(\"]\").dim()\n    )\n});\n\n// NOTE: We indent using 4 spaces instead of tabs since\n// these errors are most likely to be displayed in a terminal\n// or some kind of live output - and tabs don't work well there\nconst STACK_TRACE_INDENT: &str = \"    \";\n\n/**\n    Error components parsed from a [`LuaError`].\n\n    Can be used to display a human-friendly error message\n    and stack trace, in the following Roblox-inspired format:\n\n    ```plaintext\n    Error message\n    [Stack Begin]\n        Stack trace line\n        Stack trace line\n        Stack trace line\n    [Stack End]\n    ```\n*/\n#[derive(Debug, Default, Clone)]\npub struct ErrorComponents {\n    messages: Vec<String>,\n    trace: Option<StackTrace>,\n}\n\nimpl ErrorComponents {\n    /**\n        Returns the error messages.\n    */\n    #[must_use]\n    pub fn messages(&self) -> &[String] {\n        &self.messages\n    }\n\n    /**\n        Returns the stack trace, if it exists.\n    */\n    #[must_use]\n    pub fn trace(&self) -> Option<&StackTrace> {\n        self.trace.as_ref()\n    }\n\n    /**\n        Returns `true` if the error has a non-empty stack trace.\n\n        Note that a trace may still *exist*, but it may be empty.\n    */\n    #[must_use]\n    pub fn has_trace(&self) -> bool {\n        self.trace\n            .as_ref()\n            .is_some_and(|trace| !trace.lines().is_empty())\n    }\n}\n\nimpl fmt::Display for ErrorComponents {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        for message in self.messages() {\n            writeln!(f, \"{message}\")?;\n        }\n        if self.has_trace() {\n            let trace = self.trace.as_ref().expect(\"trace exists and is non-empty\");\n            writeln!(f, \"{}\", *STYLED_STACK_BEGIN)?;\n            for line in trace.lines() {\n                writeln!(f, \"{STACK_TRACE_INDENT}{line}\")?;\n            }\n            writeln!(f, \"{}\", *STYLED_STACK_END)?;\n        }\n        Ok(())\n    }\n}\n\nimpl From<LuaError> for ErrorComponents {\n    fn from(error: LuaError) -> Self {\n        fn lua_error_message(e: &LuaError) -> String {\n            if let LuaError::RuntimeError(s) = e {\n                s.to_string()\n            } else {\n                e.to_string()\n            }\n        }\n\n        fn lua_stack_trace(source: &str) -> Option<StackTrace> {\n            // FUTURE: Preserve a parsing error here somehow?\n            // Maybe we can emit parsing errors using tracing?\n            StackTrace::from_str(source).ok()\n        }\n\n        // Extract any additional \"context\" messages before the actual error(s)\n        // The Arc is necessary here because mlua wraps all inner errors in an Arc\n        #[allow(clippy::arc_with_non_send_sync)]\n        let mut error = Arc::new(error);\n        let mut messages = Vec::new();\n        while let LuaError::WithContext {\n            ref context,\n            ref cause,\n        } = *error\n        {\n            messages.push(context.to_string());\n            error = cause.clone();\n        }\n\n        // We will then try to extract any stack trace\n        let mut trace = if let LuaError::CallbackError {\n            ref traceback,\n            ref cause,\n        } = *error\n        {\n            messages.push(lua_error_message(cause));\n            lua_stack_trace(traceback)\n        } else if let LuaError::RuntimeError(ref s) = *error {\n            // NOTE: Runtime errors may include tracebacks, but they're\n            // joined with error messages, so we need to split them out\n            if let Some(pos) = s.find(\"stack traceback:\") {\n                let (message, traceback) = s.split_at(pos);\n                messages.push(message.trim().to_string());\n                lua_stack_trace(traceback)\n            } else {\n                messages.push(s.to_string());\n                None\n            }\n        } else {\n            messages.push(lua_error_message(&error));\n            None\n        };\n\n        // Sometimes, we can get duplicate stack trace lines that only\n        // mention \"[C]\", without a function name or path, and these can\n        // be safely ignored / removed if the following line has more info\n        if let Some(trace) = &mut trace {\n            let lines = trace.lines_mut();\n            loop {\n                let first_is_c_and_empty = lines\n                    .first()\n                    .is_some_and(|line| line.source().is_c() && line.is_empty());\n                let second_is_c_and_nonempty = lines\n                    .get(1)\n                    .is_some_and(|line| line.source().is_c() && !line.is_empty());\n                if first_is_c_and_empty && second_is_c_and_nonempty {\n                    lines.remove(0);\n                } else {\n                    break;\n                }\n            }\n        }\n\n        // Finally, we do some light postprocessing to remove duplicate\n        // information, such as the location prefix in the error message\n        if let Some(message) = messages.last_mut()\n            && let Some(line) = trace\n                .iter()\n                .flat_map(StackTrace::lines)\n                .find(|line| line.source().is_lua())\n        {\n            if let Some(path) = line.path() {\n                let prefix = format!(\"[string \\\"{path}\\\"]:\");\n                if message.starts_with(&prefix) {\n                    *message = message[prefix.len()..].trim().to_string();\n                }\n            }\n            if let Some(line) = line.line_number() {\n                let prefix = format!(\"{line}:\");\n                if message.starts_with(&prefix) {\n                    *message = message[prefix.len()..].trim().to_string();\n                }\n            }\n        }\n\n        ErrorComponents { messages, trace }\n    }\n}\n\nimpl From<Box<LuaError>> for ErrorComponents {\n    fn from(value: Box<LuaError>) -> Self {\n        Self::from(*value)\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/error/mod.rs",
    "content": "mod components;\nmod stack_trace;\n\n#[cfg(test)]\nmod tests;\n\npub use self::components::ErrorComponents;\npub use self::stack_trace::{StackTrace, StackTraceLine, StackTraceSource};\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/error/stack_trace.rs",
    "content": "use std::fmt;\nuse std::str::FromStr;\n\nfn unwrap_braced_path(s: &str) -> &str {\n    s.strip_prefix(\"[string \\\"\")\n        .and_then(|s2| s2.strip_suffix(\"\\\"]\"))\n        .unwrap_or(s)\n}\n\nfn parse_path(s: &str) -> Option<(&str, &str)> {\n    let (path, after) = unwrap_braced_path(s).split_once(':')?;\n    let path = unwrap_braced_path(path);\n\n    // Remove line number after any found colon, this may\n    // exist if the source path is from a rust source file\n    let path = match path.split_once(':') {\n        Some((before, _)) => before,\n        None => path,\n    };\n\n    Some((path, after))\n}\n\nfn parse_function_name(s: &str) -> Option<&str> {\n    s.strip_prefix(\"in function '\")\n        .and_then(|s| s.strip_suffix('\\''))\n}\n\nfn parse_line_number(s: &str) -> (Option<usize>, &str) {\n    match s.split_once(':') {\n        Some((before, after)) => (before.parse::<usize>().ok(), after),\n        None => (None, s),\n    }\n}\n\n/**\n    Source of a stack trace line parsed from a [`LuaError`].\n*/\n#[derive(Debug, Default, Clone, Copy)]\npub enum StackTraceSource {\n    /// Error originated from a C / Rust function.\n    C,\n    /// Error originated from a Lua (user) function.\n    #[default]\n    Lua,\n}\n\nimpl StackTraceSource {\n    /**\n        Returns `true` if the error originated from a C / Rust function, `false` otherwise.\n    */\n    #[must_use]\n    pub const fn is_c(self) -> bool {\n        matches!(self, Self::C)\n    }\n\n    /**\n        Returns `true` if the error originated from a Lua (user) function, `false` otherwise.\n    */\n    #[must_use]\n    pub const fn is_lua(self) -> bool {\n        matches!(self, Self::Lua)\n    }\n}\n\n/**\n    Stack trace line parsed from a [`LuaError`].\n*/\n#[derive(Debug, Default, Clone)]\npub struct StackTraceLine {\n    source: StackTraceSource,\n    path: Option<String>,\n    line_number: Option<usize>,\n    function_name: Option<String>,\n}\n\nimpl StackTraceLine {\n    /**\n        Returns the source of the stack trace line.\n    */\n    #[must_use]\n    pub fn source(&self) -> StackTraceSource {\n        self.source\n    }\n\n    /**\n        Returns the path, if it exists.\n    */\n    #[must_use]\n    pub fn path(&self) -> Option<&str> {\n        self.path.as_deref()\n    }\n\n    /**\n        Returns the line number, if it exists.\n    */\n    #[must_use]\n    pub fn line_number(&self) -> Option<usize> {\n        self.line_number\n    }\n\n    /**\n        Returns the function name, if it exists.\n    */\n    #[must_use]\n    pub fn function_name(&self) -> Option<&str> {\n        self.function_name.as_deref()\n    }\n\n    /**\n        Returns `true` if the stack trace line contains no \"useful\" information, `false` otherwise.\n\n        Useful information is determined as one of:\n\n        - A path\n        - A line number\n        - A function name\n    */\n    #[must_use]\n    pub const fn is_empty(&self) -> bool {\n        self.path.is_none() && self.line_number.is_none() && self.function_name.is_none()\n    }\n}\n\nimpl FromStr for StackTraceLine {\n    type Err = String;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if let Some(after) = s.strip_prefix(\"[C]: \") {\n            let function_name = parse_function_name(after).map(ToString::to_string);\n\n            Ok(Self {\n                source: StackTraceSource::C,\n                path: None,\n                line_number: None,\n                function_name,\n            })\n        } else if let Some((path, after)) = parse_path(s) {\n            let (line_number, after) = parse_line_number(after);\n            let function_name = parse_function_name(after).map(ToString::to_string);\n\n            Ok(Self {\n                source: StackTraceSource::Lua,\n                path: Some(path.to_string()),\n                line_number,\n                function_name,\n            })\n        } else {\n            Err(String::from(\"unknown format\"))\n        }\n    }\n}\n\nimpl fmt::Display for StackTraceLine {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        if matches!(self.source, StackTraceSource::C) {\n            write!(f, \"Script '[C]'\")?;\n        } else {\n            write!(f, \"Script '{}'\", self.path.as_deref().unwrap_or(\"[?]\"))?;\n            if let Some(line_number) = self.line_number {\n                write!(f, \", Line {line_number}\")?;\n            }\n        }\n        if let Some(function_name) = self.function_name.as_deref() {\n            write!(f, \" - function '{function_name}'\")?;\n        }\n        Ok(())\n    }\n}\n\n/**\n    Stack trace parsed from a [`LuaError`].\n*/\n#[derive(Debug, Default, Clone)]\npub struct StackTrace {\n    lines: Vec<StackTraceLine>,\n}\n\nimpl StackTrace {\n    /**\n        Returns the individual stack trace lines.\n    */\n    #[must_use]\n    pub fn lines(&self) -> &[StackTraceLine] {\n        &self.lines\n    }\n\n    /**\n        Returns the individual stack trace lines, mutably.\n    */\n    #[must_use]\n    pub fn lines_mut(&mut self) -> &mut Vec<StackTraceLine> {\n        &mut self.lines\n    }\n}\n\nimpl FromStr for StackTrace {\n    type Err = String;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let (_, after) = s\n            .split_once(\"stack traceback:\")\n            .ok_or_else(|| String::from(\"missing 'stack traceback:' prefix\"))?;\n        let lines = after\n            .trim()\n            .lines()\n            .filter_map(|line| {\n                let line = line.trim();\n                if line.is_empty() {\n                    None\n                } else {\n                    Some(line.parse())\n                }\n            })\n            .collect::<Result<Vec<_>, _>>()?;\n        Ok(StackTrace { lines })\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/error/tests.rs",
    "content": "use mlua::prelude::*;\n\nuse crate::fmt::ErrorComponents;\n\nfn new_lua_runtime_error() -> LuaResult<()> {\n    let lua = Lua::new();\n\n    lua.globals()\n        .set(\n            \"f\",\n            LuaFunction::wrap(|(): ()| Err::<(), _>(LuaError::runtime(\"oh no, a runtime error\"))),\n        )\n        .unwrap();\n\n    lua.load(\"f()\").set_name(\"chunk_name\").eval()\n}\n\nfn new_lua_script_error() -> LuaResult<()> {\n    let lua = Lua::new();\n\n    lua.load(\n        \"local function inner()\\\n        \\n    error(\\\"oh no, a script error\\\")\\\n        \\nend\\\n        \\n\\\n        \\nlocal function outer()\\\n        \\n    inner()\\\n        \\nend\\\n        \\n\\\n        \\nouter()\\\n        \",\n    )\n    .set_name(\"chunk_name\")\n    .eval()\n}\n\n// Tests for error context stack\nmod context {\n    use super::*;\n\n    #[test]\n    fn preserves_original() {\n        let lua_error = new_lua_runtime_error()\n            .context(\"additional context\")\n            .unwrap_err();\n        let components = ErrorComponents::from(lua_error);\n\n        assert_eq!(components.messages()[0], \"additional context\");\n        assert_eq!(components.messages()[1], \"oh no, a runtime error\");\n    }\n\n    #[test]\n    fn preserves_levels() {\n        // NOTE: The behavior in mlua is to preserve a single level of context\n        // and not all levels (context gets replaced on each call to `context`)\n        let lua_error = new_lua_runtime_error()\n            .context(\"level 1\")\n            .context(\"level 2\")\n            .context(\"level 3\")\n            .unwrap_err();\n        let components = ErrorComponents::from(lua_error);\n\n        assert_eq!(\n            components.messages(),\n            &[\"level 3\", \"oh no, a runtime error\"]\n        );\n    }\n}\n\n// Tests for error components struct: separated messages + stack trace\nmod error_components {\n    use super::*;\n\n    #[test]\n    fn message() {\n        let lua_error = new_lua_runtime_error().unwrap_err();\n        let components = ErrorComponents::from(lua_error);\n\n        assert_eq!(components.messages()[0], \"oh no, a runtime error\");\n    }\n\n    #[test]\n    fn stack_begin_end() {\n        let lua_error = new_lua_runtime_error().unwrap_err();\n        let formatted = format!(\"{}\", ErrorComponents::from(lua_error));\n\n        assert!(formatted.contains(\"Stack Begin\"));\n        assert!(formatted.contains(\"Stack End\"));\n    }\n\n    #[test]\n    fn stack_lines() {\n        let lua_error = new_lua_runtime_error().unwrap_err();\n        let components = ErrorComponents::from(lua_error);\n\n        let mut lines = components.trace().unwrap().lines().iter();\n        let line_1 = lines.next().unwrap().to_string();\n        let line_2 = lines.next().unwrap().to_string();\n        assert!(lines.next().is_none());\n\n        assert_eq!(line_1, \"Script '[C]' - function 'f'\");\n        assert_eq!(line_2, \"Script 'chunk_name', Line 1\");\n    }\n}\n\n// Tests for general formatting\nmod general {\n    use super::*;\n\n    #[test]\n    fn message_does_not_contain_location() {\n        let lua_error = new_lua_script_error().unwrap_err();\n\n        let components = ErrorComponents::from(lua_error);\n        let trace = components.trace().unwrap();\n\n        let first_message = components.messages().first().unwrap();\n        let first_lua_stack_line = trace\n            .lines()\n            .iter()\n            .find(|line| line.source().is_lua())\n            .unwrap();\n\n        let location_prefix = format!(\n            \"[string \\\"{}\\\"]:{}:\",\n            first_lua_stack_line.path().unwrap(),\n            first_lua_stack_line.line_number().unwrap()\n        );\n\n        assert!(!first_message.starts_with(&location_prefix));\n    }\n\n    #[test]\n    fn no_redundant_c_mentions() {\n        let lua_error = new_lua_script_error().unwrap_err();\n\n        let components = ErrorComponents::from(lua_error);\n        let trace = components.trace().unwrap();\n\n        let c_stack_lines = trace\n            .lines()\n            .iter()\n            .filter(|line| line.source().is_c())\n            .collect::<Vec<_>>();\n\n        assert_eq!(c_stack_lines.len(), 1); // Just the \"error\" call\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/label.rs",
    "content": "use std::fmt;\n\nuse console::{Color, style};\n\n/**\n    Label enum used for consistent output formatting throughout Lune.\n\n    # Example usage\n\n    ```rs\n    use lune_utils::fmt::Label;\n\n    println!(\"{} This is an info message\", Label::Info);\n    // [INFO] This is an info message\n\n    println!(\"{} This is a warning message\", Label::Warn);\n    // [WARN] This is a warning message\n\n    println!(\"{} This is an error message\", Label::Error);\n    // [ERROR] This is an error message\n    ```\n*/\n#[derive(Debug, Clone, Copy)]\npub enum Label {\n    Info,\n    Warn,\n    Error,\n}\n\nimpl Label {\n    /**\n        Returns the name of the label in all uppercase.\n    */\n    #[must_use]\n    pub fn name(&self) -> &str {\n        match self {\n            Self::Info => \"INFO\",\n            Self::Warn => \"WARN\",\n            Self::Error => \"ERROR\",\n        }\n    }\n\n    /**\n        Returns the color of the label.\n    */\n    #[must_use]\n    pub fn color(&self) -> Color {\n        match self {\n            Self::Info => Color::Blue,\n            Self::Warn => Color::Yellow,\n            Self::Error => Color::Red,\n        }\n    }\n}\n\nimpl fmt::Display for Label {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{}{}{}\",\n            style(\"[\").dim(),\n            style(self.name()).fg(self.color()),\n            style(\"]\").dim()\n        )\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/mod.rs",
    "content": "mod error;\nmod label;\nmod value;\n\npub use self::error::{ErrorComponents, StackTrace, StackTraceLine, StackTraceSource};\npub use self::label::Label;\npub use self::value::{ValueFormatConfig, pretty_format_multi_value, pretty_format_value};\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/value/basic.rs",
    "content": "use mlua::prelude::*;\n\nuse crate::fmt::ErrorComponents;\n\nuse super::{\n    metamethods::{\n        call_table_tostring_metamethod, call_userdata_tostring_metamethod,\n        get_table_type_metavalue, get_userdata_type_metavalue,\n    },\n    style::{COLOR_CYAN, COLOR_GREEN, COLOR_MAGENTA, COLOR_YELLOW},\n};\n\nconst STRING_REPLACEMENTS: &[(&str, &str)] =\n    &[(\"\\\"\", r#\"\\\"\"#), (\"\\t\", r\"\\t\"), (\"\\r\", r\"\\r\"), (\"\\n\", r\"\\n\")];\n\n/**\n    Tries to return the given value as a plain string key.\n\n    A plain string key must:\n\n    - Start with an alphabetic character.\n    - Only contain alphanumeric characters and underscores.\n*/\npub(crate) fn lua_value_as_plain_string_key(value: &LuaValue) -> Option<String> {\n    if let LuaValue::String(s) = value\n        && let Ok(s) = s.to_str()\n    {\n        let first_valid = s.chars().next().is_some_and(|c| c.is_ascii_alphabetic());\n        let all_valid = s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');\n        if first_valid && all_valid {\n            return Some(s.to_string());\n        }\n    }\n    None\n}\n\n/**\n    Formats a Lua value into a pretty string.\n\n    This does not recursively format tables.\n*/\npub(crate) fn format_value_styled(value: &LuaValue, prefer_plain: bool) -> String {\n    match value {\n        LuaValue::Nil => COLOR_YELLOW.apply_to(\"nil\").to_string(),\n        LuaValue::Boolean(true) => COLOR_YELLOW.apply_to(\"true\").to_string(),\n        LuaValue::Boolean(false) => COLOR_YELLOW.apply_to(\"false\").to_string(),\n        LuaValue::Number(n) => COLOR_CYAN.apply_to(n).to_string(),\n        LuaValue::Integer(i) => COLOR_CYAN.apply_to(i).to_string(),\n        LuaValue::String(s) if prefer_plain => s.to_string_lossy().to_string(),\n        LuaValue::String(s) => COLOR_GREEN\n            .apply_to({\n                let mut s = s.to_string_lossy().to_string();\n                for (from, to) in STRING_REPLACEMENTS {\n                    s = s.replace(from, to);\n                }\n                format!(r#\"\"{s}\"\"#)\n            })\n            .to_string(),\n        LuaValue::Other(_) => COLOR_MAGENTA.apply_to(\"<unknown>\").to_string(),\n        LuaValue::Buffer(_) => COLOR_MAGENTA.apply_to(\"<buffer>\").to_string(),\n        LuaValue::Vector(_) => COLOR_MAGENTA.apply_to(\"<vector>\").to_string(),\n        LuaValue::Thread(_) => COLOR_MAGENTA.apply_to(\"<thread>\").to_string(),\n        LuaValue::Function(_) => COLOR_MAGENTA.apply_to(\"<function>\").to_string(),\n        LuaValue::LightUserData(_) => COLOR_MAGENTA.apply_to(\"<pointer>\").to_string(),\n        LuaValue::UserData(u) => {\n            let formatted = format_typename_and_tostringed(\n                \"userdata\",\n                get_userdata_type_metavalue(u),\n                call_userdata_tostring_metamethod(u),\n            );\n            COLOR_MAGENTA.apply_to(formatted).to_string()\n        }\n        LuaValue::Table(t) => {\n            let formatted = format_typename_and_tostringed(\n                \"table\",\n                get_table_type_metavalue(t),\n                call_table_tostring_metamethod(t),\n            );\n            COLOR_MAGENTA.apply_to(formatted).to_string()\n        }\n        LuaValue::Error(e) => COLOR_MAGENTA\n            .apply_to(format!(\n                \"<LuaError(\\n{})>\",\n                ErrorComponents::from(e.clone())\n            ))\n            .to_string(),\n    }\n}\n\nfn format_typename_and_tostringed(\n    fallback: &'static str,\n    typename: Option<String>,\n    tostringed: Option<String>,\n) -> String {\n    match (typename, tostringed) {\n        (Some(typename), Some(tostringed)) => format!(\"<{typename}({tostringed})>\"),\n        (Some(typename), None) => format!(\"<{typename}>\"),\n        (None, Some(tostringed)) => format!(\"<{tostringed}>\"),\n        (None, None) => format!(\"<{fallback}>\"),\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/value/config.rs",
    "content": "/**\n    Configuration for formatting values.\n*/\n#[derive(Debug, Clone, Copy)]\npub struct ValueFormatConfig {\n    pub(super) max_depth: usize,\n    pub(super) colors_enabled: bool,\n}\n\nimpl ValueFormatConfig {\n    /**\n        Creates a new config with default values.\n    */\n    #[must_use]\n    pub const fn new() -> Self {\n        Self {\n            max_depth: 3,\n            colors_enabled: false,\n        }\n    }\n\n    /**\n        Sets the maximum depth to which tables will be formatted.\n    */\n    #[must_use]\n    pub const fn with_max_depth(self, max_depth: usize) -> Self {\n        Self { max_depth, ..self }\n    }\n\n    /**\n        Sets whether colors should be enabled.\n\n        Colors are disabled by default.\n    */\n    #[must_use]\n    pub const fn with_colors_enabled(self, colors_enabled: bool) -> Self {\n        Self {\n            colors_enabled,\n            ..self\n        }\n    }\n}\n\nimpl Default for ValueFormatConfig {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/value/metamethods.rs",
    "content": "use mlua::prelude::*;\n\npub fn get_table_type_metavalue(tab: &LuaTable) -> Option<String> {\n    let meta = tab.metatable()?;\n    let s = meta.get::<LuaString>(LuaMetaMethod::Type.name()).ok()?;\n    let s = s.to_str().ok()?;\n    Some(s.to_string())\n}\n\npub fn get_userdata_type_metavalue(usr: &LuaAnyUserData) -> Option<String> {\n    let meta = usr.metatable().ok()?;\n    let s = meta.get::<LuaString>(LuaMetaMethod::Type.name()).ok()?;\n    let s = s.to_str().ok()?;\n    Some(s.to_string())\n}\n\npub fn call_table_tostring_metamethod(tab: &LuaTable) -> Option<String> {\n    let meta = tab.metatable()?;\n    let value = meta.get(LuaMetaMethod::ToString.name()).ok()?;\n    match value {\n        LuaValue::String(s) => Some(s.to_string_lossy().to_string()),\n        LuaValue::Function(f) => f.call(tab).ok(),\n        _ => None,\n    }\n}\n\npub fn call_userdata_tostring_metamethod(usr: &LuaAnyUserData) -> Option<String> {\n    let meta = usr.metatable().ok()?;\n    let value = meta.get(LuaMetaMethod::ToString.name()).ok()?;\n    match value {\n        LuaValue::String(s) => Some(s.to_string_lossy().to_string()),\n        LuaValue::Function(f) => f.call(usr).ok(),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/value/mod.rs",
    "content": "use std::{\n    collections::HashSet,\n    sync::{Arc, LazyLock},\n};\n\nuse console::{colors_enabled as get_colors_enabled, set_colors_enabled};\nuse mlua::prelude::*;\nuse parking_lot::ReentrantMutex;\n\nmod basic;\nmod config;\nmod metamethods;\nmod recursive;\nmod style;\n\nuse self::recursive::format_value_recursive;\n\npub use self::config::ValueFormatConfig;\n\n// NOTE: Since the setting for colors being enabled is global,\n// and these functions may be called in parallel, we use this global\n// lock to make sure that we don't mess up the colors for other threads.\nstatic COLORS_LOCK: LazyLock<Arc<ReentrantMutex<()>>> =\n    LazyLock::new(|| Arc::new(ReentrantMutex::new(())));\n\n/**\n    Formats a Lua value into a pretty string using the given config.\n*/\n#[must_use]\n#[allow(clippy::missing_panics_doc)]\npub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> String {\n    let _guard = COLORS_LOCK.lock();\n\n    let were_colors_enabled = get_colors_enabled();\n    set_colors_enabled(were_colors_enabled && config.colors_enabled);\n\n    let mut visited = HashSet::new();\n    let res = format_value_recursive(value, config, &mut visited, 0);\n\n    set_colors_enabled(were_colors_enabled);\n    res.expect(\"using fmt for writing into strings should never fail\")\n}\n\n/**\n    Formats a Lua multi-value into a pretty string using the given config.\n\n    Each value will be separated by a space.\n*/\n#[must_use]\n#[allow(clippy::missing_panics_doc)]\npub fn pretty_format_multi_value(values: &LuaMultiValue, config: &ValueFormatConfig) -> String {\n    let _guard = COLORS_LOCK.lock();\n\n    let were_colors_enabled = get_colors_enabled();\n    set_colors_enabled(were_colors_enabled && config.colors_enabled);\n\n    let mut visited = HashSet::new();\n    let res = values\n        .into_iter()\n        .map(|value| format_value_recursive(value, config, &mut visited, 0))\n        .collect::<Result<Vec<_>, _>>();\n\n    set_colors_enabled(were_colors_enabled);\n    res.expect(\"using fmt for writing into strings should never fail\")\n        .join(\" \")\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/value/recursive.rs",
    "content": "use std::cmp::Ordering;\nuse std::collections::HashSet;\nuse std::fmt::{self, Write as _};\n\nuse mlua::prelude::*;\n\nuse super::metamethods::{call_table_tostring_metamethod, get_table_type_metavalue};\nuse super::{\n    basic::{format_value_styled, lua_value_as_plain_string_key},\n    config::ValueFormatConfig,\n    style::STYLE_DIM,\n};\n\nconst INDENT: &str = \"    \";\n\n/**\n    Representation of a pointer in memory to a Lua value.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub(crate) struct LuaValueId(usize);\n\nimpl From<&LuaValue> for LuaValueId {\n    fn from(value: &LuaValue) -> Self {\n        Self(value.to_pointer() as usize)\n    }\n}\n\nimpl From<&LuaTable> for LuaValueId {\n    fn from(table: &LuaTable) -> Self {\n        Self(table.to_pointer() as usize)\n    }\n}\n\n/**\n    Formats the given value, recursively formatting tables\n    up to the maximum depth specified in the config.\n\n    NOTE: We return a result here but it's really just to make handling\n    of the `write!` calls easier. Writing into a string should never fail.\n*/\npub(crate) fn format_value_recursive(\n    value: &LuaValue,\n    config: &ValueFormatConfig,\n    visited: &mut HashSet<LuaValueId>,\n    depth: usize,\n) -> Result<String, fmt::Error> {\n    let mut buffer = String::new();\n\n    if let LuaValue::Table(t) = value {\n        if let Some(formatted) = format_typename_and_tostringed(\n            get_table_type_metavalue(t),\n            call_table_tostring_metamethod(t),\n        ) {\n            write!(buffer, \"{formatted}\")?;\n        } else if depth >= config.max_depth {\n            write!(buffer, \"{}\", STYLE_DIM.apply_to(\"{ ... }\"))?;\n        } else if !visited.insert(LuaValueId::from(t)) {\n            write!(buffer, \"{}\", STYLE_DIM.apply_to(\"{ recursive }\"))?;\n        } else {\n            write!(buffer, \"{}\", STYLE_DIM.apply_to(\"{\"))?;\n\n            let mut values = t\n                .clone()\n                .pairs::<LuaValue, LuaValue>()\n                .map(|res| res.expect(\"conversion to LuaValue should never fail\"))\n                .collect::<Vec<_>>();\n            sort_for_formatting(&mut values);\n\n            let is_empty = values.is_empty();\n            let is_array = values\n                .iter()\n                .enumerate()\n                .all(|(i, (key, _))| key.as_integer().is_some_and(|x| x == (i as i64) + 1));\n\n            let formatted_values = if is_array {\n                format_array(values, config, visited, depth)?\n            } else {\n                format_table(values, config, visited, depth)?\n            };\n\n            visited.remove(&LuaValueId::from(t));\n\n            if is_empty {\n                write!(buffer, \" {}\", STYLE_DIM.apply_to(\"}\"))?;\n            } else {\n                write!(\n                    buffer,\n                    \"\\n{}\\n{}{}\",\n                    formatted_values.join(\"\\n\"),\n                    INDENT.repeat(depth),\n                    STYLE_DIM.apply_to(\"}\")\n                )?;\n            }\n        }\n    } else {\n        let prefer_plain = depth == 0;\n        write!(buffer, \"{}\", format_value_styled(value, prefer_plain))?;\n    }\n\n    Ok(buffer)\n}\n\nfn sort_for_formatting(values: &mut [(LuaValue, LuaValue)]) {\n    values.sort_by(|(a, _), (b, _)| {\n        if a.type_name() == b.type_name() {\n            // If we have the same type, sort either numerically or alphabetically\n            match (a, b) {\n                (LuaValue::Integer(a), LuaValue::Integer(b)) => a.cmp(b),\n                (LuaValue::Number(a), LuaValue::Number(b)) => a.partial_cmp(b).unwrap(),\n                (LuaValue::String(a), LuaValue::String(b)) => a.to_str().ok().cmp(&b.to_str().ok()),\n                _ => Ordering::Equal,\n            }\n        } else {\n            // If we have different types, sort numbers first, then strings, then others\n            a.is_number()\n                .cmp(&b.is_number())\n                .then_with(|| a.is_string().cmp(&b.is_string()))\n        }\n    });\n}\n\nfn format_array(\n    values: Vec<(LuaValue, LuaValue)>,\n    config: &ValueFormatConfig,\n    visited: &mut HashSet<LuaValueId>,\n    depth: usize,\n) -> Result<Vec<String>, fmt::Error> {\n    values\n        .into_iter()\n        .map(|(_, value)| {\n            Ok(format!(\n                \"{}{}{}\",\n                INDENT.repeat(1 + depth),\n                format_value_recursive(&value, config, visited, depth + 1)?,\n                STYLE_DIM.apply_to(\",\"),\n            ))\n        })\n        .collect()\n}\n\nfn format_table(\n    values: Vec<(LuaValue, LuaValue)>,\n    config: &ValueFormatConfig,\n    visited: &mut HashSet<LuaValueId>,\n    depth: usize,\n) -> Result<Vec<String>, fmt::Error> {\n    values\n        .into_iter()\n        .map(|(key, value)| {\n            if let Some(plain_key) = lua_value_as_plain_string_key(&key) {\n                Ok(format!(\n                    \"{}{plain_key} {} {}{}\",\n                    INDENT.repeat(1 + depth),\n                    STYLE_DIM.apply_to(\"=\"),\n                    format_value_recursive(&value, config, visited, depth + 1)?,\n                    STYLE_DIM.apply_to(\",\"),\n                ))\n            } else {\n                Ok(format!(\n                    \"{}{}{}{} {} {}{}\",\n                    INDENT.repeat(1 + depth),\n                    STYLE_DIM.apply_to(\"[\"),\n                    format_value_recursive(&key, config, visited, depth + 1)?,\n                    STYLE_DIM.apply_to(\"]\"),\n                    STYLE_DIM.apply_to(\"=\"),\n                    format_value_recursive(&value, config, visited, depth + 1)?,\n                    STYLE_DIM.apply_to(\",\"),\n                ))\n            }\n        })\n        .collect()\n}\n\nfn format_typename_and_tostringed(\n    typename: Option<String>,\n    tostringed: Option<String>,\n) -> Option<String> {\n    match (typename, tostringed) {\n        (Some(typename), Some(tostringed)) => Some(format!(\"<{typename}({tostringed})>\")),\n        (Some(typename), None) => Some(format!(\"<{typename}>\")),\n        (None, Some(tostringed)) => Some(tostringed),\n        (None, None) => None,\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/fmt/value/style.rs",
    "content": "use std::sync::LazyLock;\n\nuse console::Style;\n\npub static COLOR_GREEN: LazyLock<Style> = LazyLock::new(|| Style::new().green());\npub static COLOR_YELLOW: LazyLock<Style> = LazyLock::new(|| Style::new().yellow());\npub static COLOR_MAGENTA: LazyLock<Style> = LazyLock::new(|| Style::new().magenta());\npub static COLOR_CYAN: LazyLock<Style> = LazyLock::new(|| Style::new().cyan());\n\npub static STYLE_DIM: LazyLock<Style> = LazyLock::new(|| Style::new().dim());\n"
  },
  {
    "path": "crates/lune-utils/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nmod table_builder;\nmod version_string;\n\npub mod fmt;\npub mod path;\npub mod process;\n\npub use self::table_builder::TableBuilder;\npub use self::version_string::get_version_string;\n\n// TODO: Remove this in the next major semver\npub mod jit {\n    pub use super::process::ProcessJitEnablement as JitEnablement;\n}\n"
  },
  {
    "path": "crates/lune-utils/src/path/constants.rs",
    "content": "/*!\n    Constants for working with Luau module paths.\n*/\n\npub const FILE_CHUNK_PREFIX: char = '@';\npub const FILE_NAME_INIT: &str = \"init\";\npub const FILE_NAME_CONFIG: &str = \".luaurc\";\npub const FILE_EXTENSIONS: [&str; 2] = [\"luau\", \"lua\"];\n"
  },
  {
    "path": "crates/lune-utils/src/path/luau.rs",
    "content": "/*!\n    Utilities for working with Luau module paths.\n*/\n\nuse std::{\n    ffi::OsStr,\n    fmt,\n    path::{Path, PathBuf},\n};\n\nuse mlua::prelude::*;\n\nuse super::constants::{FILE_EXTENSIONS, FILE_NAME_INIT};\nuse super::std::append_extension;\n\n/**\n    A file path for Luau, which has been resolved to either a valid file or directory.\n\n    Not to be confused with [`LuauModulePath`]. This is the path\n    **on the filesystem**, and not the abstracted module path.\n*/\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum LuauFilePath {\n    /// A resolved and valid file path.\n    File(PathBuf),\n    /// A resolved and valid directory path.\n    Directory(PathBuf),\n}\n\nimpl LuauFilePath {\n    fn resolve(module: impl AsRef<Path>) -> Result<Self, LuaNavigateError> {\n        let module = module.as_ref();\n\n        // Modules named \"init\" are ambiguous and not allowed\n        if module\n            .file_name()\n            .is_some_and(|n| n == OsStr::new(FILE_NAME_INIT))\n        {\n            return Err(LuaNavigateError::Ambiguous);\n        }\n\n        let mut found = None;\n\n        // Try files first\n        for ext in FILE_EXTENSIONS {\n            let candidate = append_extension(module, ext);\n            if candidate.is_file() && found.replace(candidate).is_some() {\n                return Err(LuaNavigateError::Ambiguous);\n            }\n        }\n\n        // Try directories with init files in them\n        if module.is_dir() {\n            let init = Path::new(FILE_NAME_INIT);\n            for ext in FILE_EXTENSIONS {\n                let candidate = module.join(append_extension(init, ext));\n                if candidate.is_file() && found.replace(candidate).is_some() {\n                    return Err(LuaNavigateError::Ambiguous);\n                }\n            }\n\n            // If we have not found any luau / lua files, and we also did not find\n            // any init files in this directory, we still found a valid directory\n            if found.is_none() {\n                return Ok(Self::Directory(module.to_path_buf()));\n            }\n        }\n\n        // We have now narrowed down our resulting module\n        // path to be exactly one valid path, or no path\n        found.map(Self::File).ok_or(LuaNavigateError::NotFound)\n    }\n\n    #[must_use]\n    pub const fn is_file(&self) -> bool {\n        matches!(self, Self::File(_))\n    }\n\n    #[must_use]\n    pub const fn is_dir(&self) -> bool {\n        matches!(self, Self::Directory(_))\n    }\n\n    #[must_use]\n    pub fn as_file(&self) -> Option<&Path> {\n        match self {\n            Self::File(path) => Some(path),\n            Self::Directory(_) => None,\n        }\n    }\n\n    #[must_use]\n    pub fn as_dir(&self) -> Option<&Path> {\n        match self {\n            Self::File(_) => None,\n            Self::Directory(path) => Some(path),\n        }\n    }\n}\n\nimpl AsRef<Path> for LuauFilePath {\n    fn as_ref(&self) -> &Path {\n        match self {\n            Self::File(path) | Self::Directory(path) => path.as_ref(),\n        }\n    }\n}\n\nimpl fmt::Display for LuauFilePath {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Directory(path) | Self::File(path) => path.display().fmt(f),\n        }\n    }\n}\n\n/**\n    A resolved module path for Luau, containing both:\n\n    - The **source** Luau module path.\n    - The **target** filesystem path.\n\n    Note the separation here - the source is not necessarily a valid filesystem path,\n    and the target is not necessarily a valid Luau module path for require-by-string.\n\n    See [`LuauFilePath`] and [`LuauModulePath::resolve`] for more information.\n*/\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct LuauModulePath {\n    // The originating module path\n    source: PathBuf,\n    // The target filesystem path\n    target: LuauFilePath,\n}\n\nimpl LuauModulePath {\n    /**\n        Strips Luau file extensions and potential init segments from a given path.\n\n        This is the opposite operation of [`LuauModulePath::resolve`] and is generally\n        useful for converting between paths in a CLI or other similar use cases - but\n        should *never* be used to implement `require` resolution.\n\n        Does not use any filesystem calls and will not panic.\n    */\n    #[must_use]\n    pub fn strip(path: impl Into<PathBuf>) -> PathBuf {\n        let mut path: PathBuf = path.into();\n\n        if path\n            .extension()\n            .and_then(|e| e.to_str())\n            .is_some_and(|e| FILE_EXTENSIONS.contains(&e))\n        {\n            path = path.with_extension(\"\");\n        }\n\n        if path\n            .file_name()\n            .and_then(|e| e.to_str())\n            .is_some_and(|f| f == FILE_NAME_INIT)\n        {\n            path.pop();\n        }\n\n        path\n    }\n\n    /**\n        Resolves an existing file or directory path for the given *module* path.\n\n        Given a *module* path \"path/to/module\", these files will be searched:\n\n        - `path/to/module.luau`\n        - `path/to/module.lua`\n        - `path/to/module/init.luau`\n        - `path/to/module/init.lua`\n\n        If the given path (\"path/to/module\") is a directory instead,\n        and it exists, it will be returned without any modifications.\n\n        # Errors\n\n        - If the given module path is ambiguous.\n        - If the given module path does not resolve to a valid file or directory.\n    */\n    pub fn resolve(module: impl Into<PathBuf>) -> Result<Self, LuaNavigateError> {\n        let source = module.into();\n        let target = LuauFilePath::resolve(&source)?;\n        Ok(Self { source, target })\n    }\n\n    /**\n        Returns the source Luau module path.\n    */\n    #[must_use]\n    pub fn source(&self) -> &Path {\n        &self.source\n    }\n\n    /**\n        Returns the target filesystem file path.\n    */\n    #[must_use]\n    pub fn target(&self) -> &LuauFilePath {\n        &self.target\n    }\n}\n\nimpl fmt::Display for LuauModulePath {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        self.source().display().fmt(f)\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/path/mod.rs",
    "content": "mod luau;\nmod std;\n\npub mod constants;\n\npub use self::std::{\n    append_extension, clean_path, clean_path_and_make_absolute, get_current_dir, get_current_exe,\n    relative_path_normalize, relative_path_parent,\n};\n\npub use self::luau::{LuauFilePath, LuauModulePath};\n"
  },
  {
    "path": "crates/lune-utils/src/path/std.rs",
    "content": "/*!\n    Utilities for working with Rust standard library paths.\n*/\n\nuse std::{\n    env::{current_dir, current_exe},\n    ffi::OsStr,\n    path::{Component, MAIN_SEPARATOR, Path, PathBuf},\n    sync::{Arc, LazyLock},\n};\n\nuse path_clean::PathClean;\n\nstatic CWD: LazyLock<Arc<Path>> = LazyLock::new(create_cwd);\nstatic EXE: LazyLock<Arc<Path>> = LazyLock::new(create_exe);\n\nfn create_cwd() -> Arc<Path> {\n    let mut cwd = current_dir()\n        .expect(\"failed to find current working directory\")\n        .to_str()\n        .expect(\"current working directory is not valid UTF-8\")\n        .to_string();\n    if !cwd.ends_with(MAIN_SEPARATOR) {\n        cwd.push(MAIN_SEPARATOR);\n    }\n    dunce::canonicalize(cwd)\n        .expect(\"failed to canonicalize current working directory\")\n        .into()\n}\n\nfn create_exe() -> Arc<Path> {\n    let exe = current_exe()\n        .expect(\"failed to find current executable\")\n        .to_str()\n        .expect(\"current executable path is not valid UTF-8\")\n        .to_string();\n    dunce::canonicalize(exe)\n        .expect(\"failed to canonicalize current executable path\")\n        .into()\n}\n\n/**\n    Gets the current working directory as an absolute path.\n\n    This absolute path is canonicalized and does not contain any `.` or `..`\n    components, and it is also in a friendly (non-UNC) format.\n\n    This path is also guaranteed to:\n\n    - Be valid UTF-8.\n    - End with the platform's main path separator.\n*/\n#[must_use]\npub fn get_current_dir() -> Arc<Path> {\n    Arc::clone(&CWD)\n}\n\n/**\n    Gets the path to the current executable as an absolute path.\n\n    This absolute path is canonicalized and does not contain any `.` or `..`\n    components, and it is also in a friendly (non-UNC) format.\n\n    This path is also guaranteed to:\n\n    - Be valid UTF-8.\n*/\n#[must_use]\npub fn get_current_exe() -> Arc<Path> {\n    Arc::clone(&EXE)\n}\n\n/**\n    Cleans a path.\n\n    See the [`path_clean`] crate for more information on what cleaning a path does.\n*/\n#[must_use]\npub fn clean_path(path: impl AsRef<Path>) -> PathBuf {\n    path.as_ref().clean()\n}\n\n/**\n    Makes a path absolute, if it is relative, and then cleans it.\n\n    Relative paths are resolved against the current working directory.\n\n    See the [`path_clean`] crate for more information on what cleaning a path does.\n*/\n#[must_use]\npub fn clean_path_and_make_absolute(path: impl AsRef<Path>) -> PathBuf {\n    let path = path.as_ref();\n    if path.is_relative() {\n        CWD.join(path).clean()\n    } else {\n        path.clean()\n    }\n}\n\n/**\n    Appends the given extension to the path.\n\n    Does not replace or modify any existing extension(s).\n*/\n#[must_use]\npub fn append_extension(path: impl AsRef<Path>, ext: impl AsRef<OsStr>) -> PathBuf {\n    let path = path.as_ref();\n    match path.extension() {\n        None => path.with_extension(ext),\n        Some(curr_ext) => {\n            let mut new_ext = curr_ext.to_os_string();\n            new_ext.push(\".\");\n            new_ext.push(ext);\n            path.with_extension(new_ext)\n        }\n    }\n}\n\n/**\n    Normalizes the given relative path.\n\n    This will clean the path, removing any redundant components,\n    and ensure that it has a leading \"current dir\" (`./`) component.\n*/\n#[must_use]\npub fn relative_path_normalize(path: impl AsRef<Path>) -> PathBuf {\n    let path = clean_path(path);\n\n    let mut it = path.components().peekable();\n    if it.peek().is_none_or(|c| matches!(c, Component::Normal(..))) {\n        std::iter::once(Component::CurDir).chain(it).collect()\n    } else {\n        path\n    }\n}\n\n/**\n    Pops the relative path up to the parent directory, pushing \"parent dir\" (`..`)\n    components to the front of the path when it no longer has any normal components.\n\n    This means that unlike [`PathBuf::pop`], this function may be called an arbitrary\n    number of times, and represent parent folders without first canonicalizing paths.\n*/\npub fn relative_path_parent(rel: &mut PathBuf) {\n    if rel.as_os_str() == Component::CurDir.as_os_str() {\n        *rel = PathBuf::from(Component::ParentDir.as_os_str());\n    } else if rel.components().all(|c| c == Component::ParentDir) {\n        rel.push(Component::ParentDir.as_os_str());\n    } else {\n        rel.pop();\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/process/args.rs",
    "content": "#![allow(clippy::missing_panics_doc)]\n\nuse std::{\n    env::args_os,\n    ffi::OsString,\n    sync::{Arc, Mutex},\n};\n\nuse mlua::prelude::*;\nuse os_str_bytes::OsStringBytes;\n\n// Inner (shared) struct\n\n#[derive(Debug, Default)]\nstruct ProcessArgsInner {\n    values: Vec<OsString>,\n}\n\nimpl FromIterator<OsString> for ProcessArgsInner {\n    fn from_iter<T: IntoIterator<Item = OsString>>(iter: T) -> Self {\n        Self {\n            values: iter.into_iter().collect(),\n        }\n    }\n}\n\n/**\n    A struct that can be easily shared, stored in Lua app data,\n    and that also guarantees the values are valid OS strings\n    that can be used for process arguments.\n\n    Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.\n\n    Also provides convenience methods for working with the arguments\n    as either `OsString` or `Vec<u8>`, where using the latter implicitly\n    converts to an `OsString` and fails if the conversion is not possible.\n*/\n#[derive(Debug, Clone)]\npub struct ProcessArgs {\n    inner: Arc<Mutex<ProcessArgsInner>>,\n}\n\nimpl ProcessArgs {\n    #[must_use]\n    pub fn empty() -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(ProcessArgsInner::default())),\n        }\n    }\n\n    #[must_use]\n    pub fn current() -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(args_os().collect())),\n        }\n    }\n\n    #[must_use]\n    pub fn len(&self) -> usize {\n        let inner = self.inner.lock().unwrap();\n        inner.values.len()\n    }\n\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        let inner = self.inner.lock().unwrap();\n        inner.values.is_empty()\n    }\n\n    // OS strings\n\n    #[must_use]\n    pub fn all(&self) -> Vec<OsString> {\n        let inner = self.inner.lock().unwrap();\n        inner.values.clone()\n    }\n\n    #[must_use]\n    pub fn get(&self, index: usize) -> Option<OsString> {\n        let inner = self.inner.lock().unwrap();\n        inner.values.get(index).cloned()\n    }\n\n    pub fn set(&self, index: usize, val: impl Into<OsString>) {\n        let mut inner = self.inner.lock().unwrap();\n        if let Some(arg) = inner.values.get_mut(index) {\n            *arg = val.into();\n        }\n    }\n\n    pub fn push(&self, val: impl Into<OsString>) {\n        let mut inner = self.inner.lock().unwrap();\n        inner.values.push(val.into());\n    }\n\n    #[must_use]\n    pub fn pop(&self) -> Option<OsString> {\n        let mut inner = self.inner.lock().unwrap();\n        inner.values.pop()\n    }\n\n    pub fn insert(&self, index: usize, val: impl Into<OsString>) {\n        let mut inner = self.inner.lock().unwrap();\n        if index <= inner.values.len() {\n            inner.values.insert(index, val.into());\n        }\n    }\n\n    #[must_use]\n    pub fn remove(&self, index: usize) -> Option<OsString> {\n        let mut inner = self.inner.lock().unwrap();\n        if index < inner.values.len() {\n            Some(inner.values.remove(index))\n        } else {\n            None\n        }\n    }\n\n    // Bytes wrappers\n\n    #[must_use]\n    pub fn all_bytes(&self) -> Vec<Vec<u8>> {\n        self.all()\n            .into_iter()\n            .filter_map(OsString::into_io_vec)\n            .collect()\n    }\n\n    #[must_use]\n    pub fn get_bytes(&self, index: usize) -> Option<Vec<u8>> {\n        let val = self.get(index)?;\n        val.into_io_vec()\n    }\n\n    pub fn set_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {\n        if let Some(val_os) = OsString::from_io_vec(val.into()) {\n            self.set(index, val_os);\n        }\n    }\n\n    pub fn push_bytes(&self, val: impl Into<Vec<u8>>) {\n        if let Some(val_os) = OsString::from_io_vec(val.into()) {\n            self.push(val_os);\n        }\n    }\n\n    #[must_use]\n    pub fn pop_bytes(&self) -> Option<Vec<u8>> {\n        self.pop().and_then(OsString::into_io_vec)\n    }\n\n    pub fn insert_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {\n        if let Some(val_os) = OsString::from_io_vec(val.into()) {\n            self.insert(index, val_os);\n        }\n    }\n\n    pub fn remove_bytes(&self, index: usize) -> Option<Vec<u8>> {\n        self.remove(index).and_then(OsString::into_io_vec)\n    }\n\n    // Plain lua table conversion\n\n    #[doc(hidden)]\n    #[allow(clippy::missing_errors_doc)]\n    pub fn into_plain_lua_table(&self, lua: Lua) -> LuaResult<LuaTable> {\n        let all = self.all_bytes();\n        let tab = lua.create_table_with_capacity(all.len(), 0)?;\n\n        for val in all {\n            let val = lua.create_string(val)?;\n            tab.push(val)?;\n        }\n\n        Ok(tab)\n    }\n}\n\n// Iterator implementations\n\nimpl IntoIterator for ProcessArgs {\n    type Item = OsString;\n    type IntoIter = std::vec::IntoIter<OsString>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        let inner = self.inner.lock().unwrap();\n        inner.values.clone().into_iter()\n    }\n}\n\nimpl<S: Into<OsString>> FromIterator<S> for ProcessArgs {\n    fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(iter.into_iter().map(Into::into).collect())),\n        }\n    }\n}\n\nimpl<S: Into<OsString>> Extend<S> for ProcessArgs {\n    fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {\n        let mut inner = self.inner.lock().unwrap();\n        inner.values.extend(iter.into_iter().map(Into::into));\n    }\n}\n\n// Lua implementations\n\nimpl FromLua for ProcessArgs {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::Nil = value {\n            Ok(Self::from_iter([] as [OsString; 0]))\n        } else if let LuaValue::Boolean(true) = value {\n            Ok(Self::current())\n        } else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {\n            Ok(u.clone())\n        } else if let LuaValue::Table(arr) = value {\n            let mut args = Vec::new();\n            for pair in arr.pairs::<LuaValue, LuaValue>() {\n                let val_res = pair.map(|p| p.1.clone());\n                let val = super::lua_value_to_os_string(val_res, \"ProcessArgs\")?;\n\n                super::validate_os_value(&val)?;\n\n                args.push(val);\n            }\n            Ok(Self::from_iter(args))\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: String::from(\"ProcessArgs\"),\n                message: Some(format!(\n                    \"Invalid type for process args - expected table or nil, got '{}'\",\n                    value.type_name()\n                )),\n            })\n        }\n    }\n}\n\nimpl LuaUserData for ProcessArgs {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));\n        methods.add_meta_method(LuaMetaMethod::Index, |_, this, index: usize| {\n            if index == 0 {\n                Ok(None)\n            } else {\n                Ok(this.get(index - 1))\n            }\n        });\n        methods.add_meta_method(LuaMetaMethod::NewIndex, |_, _, (): ()| {\n            Err::<(), _>(LuaError::runtime(\"ProcessArgs is read-only\"))\n        });\n        methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {\n            let mut vars = this\n                .clone()\n                .into_iter()\n                .filter_map(OsStringBytes::into_io_vec)\n                .enumerate();\n            lua.create_function_mut(move |lua, (): ()| match vars.next() {\n                None => Ok((LuaValue::Nil, LuaValue::Nil)),\n                Some((index, value)) => Ok((\n                    LuaValue::Integer(index as i64),\n                    LuaValue::String(lua.create_string(value)?),\n                )),\n            })\n        });\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/process/env.rs",
    "content": "#![allow(clippy::missing_panics_doc)]\n\nuse std::{\n    collections::BTreeMap,\n    env::vars_os,\n    ffi::{OsStr, OsString},\n    sync::{Arc, Mutex},\n};\n\nuse mlua::prelude::*;\nuse os_str_bytes::{OsStrBytes, OsStringBytes};\n\n// Inner (shared) struct\n\n#[derive(Debug, Default)]\nstruct ProcessEnvInner {\n    values: BTreeMap<OsString, OsString>,\n}\n\nimpl FromIterator<(OsString, OsString)> for ProcessEnvInner {\n    fn from_iter<T: IntoIterator<Item = (OsString, OsString)>>(iter: T) -> Self {\n        Self {\n            values: iter.into_iter().collect(),\n        }\n    }\n}\n\n/**\n    A struct that can be easily shared, stored in Lua app data,\n    and that also guarantees the pairs are valid OS strings\n    that can be used for process environment variables.\n\n    Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.\n\n    Also provides convenience methods for working with the variables\n    as either `OsString` or `Vec<u8>`, where using the latter implicitly\n    converts to an `OsString` and fails if the conversion is not possible.\n*/\n#[derive(Debug, Clone)]\npub struct ProcessEnv {\n    inner: Arc<Mutex<ProcessEnvInner>>,\n}\n\nimpl ProcessEnv {\n    #[must_use]\n    pub fn empty() -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(ProcessEnvInner::default())),\n        }\n    }\n\n    #[must_use]\n    pub fn current() -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(vars_os().collect())),\n        }\n    }\n\n    #[must_use]\n    pub fn len(&self) -> usize {\n        let inner = self.inner.lock().unwrap();\n        inner.values.len()\n    }\n\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        let inner = self.inner.lock().unwrap();\n        inner.values.is_empty()\n    }\n\n    // OS strings\n\n    #[must_use]\n    pub fn get_all(&self) -> Vec<(OsString, OsString)> {\n        let inner = self.inner.lock().unwrap();\n        inner.values.clone().into_iter().collect()\n    }\n\n    #[must_use]\n    pub fn get_value(&self, key: impl AsRef<OsStr>) -> Option<OsString> {\n        let key = key.as_ref();\n\n        super::validate_os_key(key).ok()?;\n\n        let inner = self.inner.lock().unwrap();\n        inner.values.get(key).cloned()\n    }\n\n    pub fn set_value(&self, key: impl Into<OsString>, val: impl Into<OsString>) {\n        let key = key.into();\n        let val = val.into();\n\n        if super::validate_os_pair((&key, &val)).is_err() {\n            return;\n        }\n\n        let mut inner = self.inner.lock().unwrap();\n        inner.values.insert(key, val);\n    }\n\n    pub fn remove_value(&self, key: impl AsRef<OsStr>) {\n        let key = key.as_ref();\n\n        if super::validate_os_key(key).is_err() {\n            return;\n        }\n\n        let mut inner = self.inner.lock().unwrap();\n        inner.values.remove(key);\n    }\n\n    // Bytes wrappers\n\n    #[must_use]\n    pub fn get_all_bytes(&self) -> Vec<(Vec<u8>, Vec<u8>)> {\n        self.get_all()\n            .into_iter()\n            .filter_map(|(k, v)| Some((k.into_io_vec()?, v.into_io_vec()?)))\n            .collect()\n    }\n\n    #[must_use]\n    pub fn get_value_bytes(&self, key: impl AsRef<[u8]>) -> Option<Vec<u8>> {\n        let key = OsStr::from_io_bytes(key.as_ref())?;\n        let val = self.get_value(key)?;\n        val.into_io_vec()\n    }\n\n    pub fn set_value_bytes(&self, key: impl AsRef<[u8]>, val: impl Into<Vec<u8>>) {\n        let key = OsStr::from_io_bytes(key.as_ref());\n        let val = OsString::from_io_vec(val.into());\n        if let (Some(key), Some(val)) = (key, val) {\n            self.set_value(key, val);\n        }\n    }\n\n    pub fn remove_value_bytes(&self, key: impl AsRef<[u8]>) {\n        let key = OsStr::from_io_bytes(key.as_ref());\n        if let Some(key) = key {\n            self.remove_value(key);\n        }\n    }\n\n    // Plain lua table conversion\n\n    #[doc(hidden)]\n    #[allow(clippy::missing_errors_doc)]\n    pub fn into_plain_lua_table(&self, lua: Lua) -> LuaResult<LuaTable> {\n        let all = self.get_all_bytes();\n        let tab = lua.create_table_with_capacity(0, all.len())?;\n\n        for (key, val) in all {\n            let key = lua.create_string(key)?;\n            let val = lua.create_string(val)?;\n            tab.set(key, val)?;\n        }\n\n        Ok(tab)\n    }\n}\n\n// Iterator implementations\n\nimpl IntoIterator for ProcessEnv {\n    type Item = (OsString, OsString);\n    type IntoIter = std::collections::btree_map::IntoIter<OsString, OsString>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        let inner = self.inner.lock().unwrap();\n        inner.values.clone().into_iter()\n    }\n}\n\nimpl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for ProcessEnv {\n    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(\n                iter.into_iter()\n                    .map(|(k, v)| (k.into(), v.into()))\n                    .filter(|(k, v)| super::validate_os_pair((k, v)).is_ok())\n                    .collect(),\n            )),\n        }\n    }\n}\n\nimpl<K: Into<OsString>, V: Into<OsString>> Extend<(K, V)> for ProcessEnv {\n    fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {\n        let mut inner = self.inner.lock().unwrap();\n        inner.values.extend(\n            iter.into_iter()\n                .map(|(k, v)| (k.into(), v.into()))\n                .filter(|(k, v)| super::validate_os_pair((k, v)).is_ok()),\n        );\n    }\n}\n\n// Lua implementations\n\nimpl FromLua for ProcessEnv {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        if let LuaValue::Nil = value {\n            Ok(Self::from_iter([] as [(OsString, OsString); 0]))\n        } else if let LuaValue::Boolean(true) = value {\n            Ok(Self::current())\n        } else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {\n            Ok(u.clone())\n        } else if let LuaValue::Table(arr) = value {\n            let mut args = Vec::new();\n            for pair in arr.pairs::<LuaValue, LuaValue>() {\n                let (key_res, val_res) = match pair {\n                    Ok((key, val)) => (Ok(key), Ok(val)),\n                    Err(err) => (Err(err.clone()), Err(err)),\n                };\n\n                let key = super::lua_value_to_os_string(key_res, \"ProcessEnv\")?;\n                let val = super::lua_value_to_os_string(val_res, \"ProcessEnv\")?;\n\n                super::validate_os_pair((&key, &val))?;\n\n                args.push((key, val));\n            }\n            Ok(Self::from_iter(args))\n        } else {\n            Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: String::from(\"ProcessEnv\"),\n                message: Some(format!(\n                    \"Invalid type for process env - expected table or nil, got '{}'\",\n                    value.type_name()\n                )),\n            })\n        }\n    }\n}\n\nimpl LuaUserData for ProcessEnv {\n    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {\n        methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));\n        methods.add_meta_method(LuaMetaMethod::Index, |_, this, key: LuaValue| {\n            let key = super::lua_value_to_os_string(Ok(key), \"OsString\")?;\n            Ok(this.get_value(key))\n        });\n        methods.add_meta_method(\n            LuaMetaMethod::NewIndex,\n            |_, this, (key, val): (LuaValue, Option<LuaValue>)| {\n                let key = super::lua_value_to_os_string(Ok(key), \"OsString\")?;\n                if let Some(val) = val {\n                    let val = super::lua_value_to_os_string(Ok(val), \"OsString\")?;\n                    this.set_value(key, val);\n                } else {\n                    this.remove_value(key);\n                }\n                Ok(())\n            },\n        );\n        methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {\n            let mut vars = this\n                .clone()\n                .into_iter()\n                .filter_map(|(key, val)| Some((key.into_io_vec()?, val.into_io_vec()?)));\n            lua.create_function_mut(move |lua, (): ()| match vars.next() {\n                None => Ok((LuaValue::Nil, LuaValue::Nil)),\n                Some((key, val)) => Ok((\n                    LuaValue::String(lua.create_string(key)?),\n                    LuaValue::String(lua.create_string(val)?),\n                )),\n            })\n        });\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/process/jit.rs",
    "content": "#[derive(Debug, Clone, Copy, Default)]\npub struct ProcessJitEnablement {\n    enabled: bool,\n}\n\nimpl ProcessJitEnablement {\n    #[must_use]\n    pub fn new(enabled: bool) -> Self {\n        Self { enabled }\n    }\n\n    pub fn set_status(&mut self, enabled: bool) {\n        self.enabled = enabled;\n    }\n\n    #[must_use]\n    pub fn enabled(self) -> bool {\n        self.enabled\n    }\n}\n\nimpl From<ProcessJitEnablement> for bool {\n    fn from(val: ProcessJitEnablement) -> Self {\n        val.enabled()\n    }\n}\n\nimpl From<bool> for ProcessJitEnablement {\n    fn from(val: bool) -> Self {\n        Self::new(val)\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/process/mod.rs",
    "content": "use std::ffi::{OsStr, OsString};\n\nuse mlua::prelude::*;\nuse os_str_bytes::{OsStrBytes, OsStringBytes};\n\nmod args;\nmod env;\nmod jit;\n\npub use self::args::ProcessArgs;\npub use self::env::ProcessEnv;\npub use self::jit::ProcessJitEnablement;\n\nfn lua_value_to_os_string(res: LuaResult<LuaValue>, to: &'static str) -> LuaResult<OsString> {\n    let (btype, bs) = match res {\n        Ok(LuaValue::String(s)) => (\"string\", s.as_bytes().to_vec()),\n        Ok(LuaValue::Buffer(b)) => (\"buffer\", b.to_vec()),\n        res => {\n            let vtype = match res {\n                Ok(v) => v.type_name(),\n                Err(_) => \"unknown\",\n            };\n            return Err(LuaError::FromLuaConversionError {\n                from: vtype,\n                to: String::from(to),\n                message: Some(format!(\n                    \"Expected value to be a string or buffer, got '{vtype}'\",\n                )),\n            });\n        }\n    };\n\n    let Some(s) = OsString::from_io_vec(bs) else {\n        return Err(LuaError::FromLuaConversionError {\n            from: btype,\n            to: String::from(to),\n            message: Some(String::from(\"Expected {btype} to contain valid OS bytes\")),\n        });\n    };\n\n    Ok(s)\n}\n\nfn validate_os_key(key: &OsStr) -> LuaResult<()> {\n    let Some(key) = key.to_io_bytes() else {\n        return Err(LuaError::runtime(\"Key must be IO-safe\"));\n    };\n    if key.is_empty() {\n        Err(LuaError::runtime(\"Key must not be empty\"))\n    } else if key.contains(&b'=') {\n        Err(LuaError::runtime(\n            \"Key must not contain the equals character '='\",\n        ))\n    } else if key.contains(&b'\\0') {\n        Err(LuaError::runtime(\"Key must not contain the NUL character\"))\n    } else {\n        Ok(())\n    }\n}\n\nfn validate_os_value(val: &OsStr) -> LuaResult<()> {\n    let Some(val) = val.to_io_bytes() else {\n        return Err(LuaError::runtime(\"Value must be IO-safe\"));\n    };\n    if val.contains(&b'\\0') {\n        Err(LuaError::runtime(\n            \"Value must not contain the NUL character\",\n        ))\n    } else {\n        Ok(())\n    }\n}\n\nfn validate_os_pair((key, value): (&OsStr, &OsStr)) -> LuaResult<()> {\n    validate_os_key(key)?;\n    validate_os_value(value)?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/lune-utils/src/table_builder.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n\nuse std::future::Future;\n\nuse mlua::prelude::*;\n\n/**\n    Utility struct for building Lua tables.\n*/\npub struct TableBuilder {\n    lua: Lua,\n    tab: LuaTable,\n}\n\nimpl TableBuilder {\n    /**\n        Creates a new table builder.\n    */\n    pub fn new(lua: Lua) -> LuaResult<Self> {\n        let tab = lua.create_table()?;\n        Ok(Self { lua, tab })\n    }\n\n    /**\n        Adds a new key-value pair to the table.\n\n        This will overwrite any value that already exists.\n    */\n    pub fn with_value<K, V>(self, key: K, value: V) -> LuaResult<Self>\n    where\n        K: IntoLua,\n        V: IntoLua,\n    {\n        self.tab.raw_set(key, value)?;\n        Ok(self)\n    }\n\n    /**\n        Adds multiple key-value pairs to the table.\n\n        This will overwrite any values that already exist.\n    */\n    pub fn with_values<K, V>(self, values: Vec<(K, V)>) -> LuaResult<Self>\n    where\n        K: IntoLua,\n        V: IntoLua,\n    {\n        for (key, value) in values {\n            self.tab.raw_set(key, value)?;\n        }\n        Ok(self)\n    }\n\n    /**\n        Adds a new key-value pair to the sequential (array) section of the table.\n\n        This will not overwrite any value that already exists,\n        instead adding the value to the end of the array.\n    */\n    pub fn with_sequential_value<V>(self, value: V) -> LuaResult<Self>\n    where\n        V: IntoLua,\n    {\n        self.tab.raw_push(value)?;\n        Ok(self)\n    }\n\n    /**\n        Adds multiple values to the sequential (array) section of the table.\n\n        This will not overwrite any values that already exist,\n        instead adding the values to the end of the array.\n    */\n    pub fn with_sequential_values<V>(self, values: Vec<V>) -> LuaResult<Self>\n    where\n        V: IntoLua,\n    {\n        for value in values {\n            self.tab.raw_push(value)?;\n        }\n        Ok(self)\n    }\n\n    /**\n        Adds a new key-value pair to the table, with a function value.\n\n        This will overwrite any value that already exists.\n    */\n    pub fn with_function<K, A, R, F>(self, key: K, func: F) -> LuaResult<Self>\n    where\n        K: IntoLua,\n        A: FromLuaMulti,\n        R: IntoLuaMulti,\n        F: Fn(&Lua, A) -> LuaResult<R> + 'static,\n    {\n        let f = self.lua.create_function(func)?;\n        self.with_value(key, LuaValue::Function(f))\n    }\n\n    /**\n        Adds a new key-value pair to the table, with an async function value.\n\n        This will overwrite any value that already exists.\n    */\n    pub fn with_async_function<K, A, R, F, FR>(self, key: K, func: F) -> LuaResult<Self>\n    where\n        K: IntoLua,\n        A: FromLuaMulti,\n        R: IntoLuaMulti,\n        F: Fn(Lua, A) -> FR + 'static,\n        FR: Future<Output = LuaResult<R>> + 'static,\n    {\n        let f = self.lua.create_async_function(func)?;\n        self.with_value(key, LuaValue::Function(f))\n    }\n\n    /**\n        Adds a metatable to the table.\n\n        This will overwrite any metatable that already exists.\n    */\n    pub fn with_metatable(self, table: LuaTable) -> LuaResult<Self> {\n        self.tab.set_metatable(Some(table))?;\n        Ok(self)\n    }\n\n    /**\n        Builds the table as a read-only table.\n\n        This will prevent any *direct* modifications to the table.\n    */\n    pub fn build_readonly(self) -> LuaResult<LuaTable> {\n        self.tab.set_readonly(true);\n        Ok(self.tab)\n    }\n\n    /**\n        Builds the table.\n    */\n    pub fn build(self) -> LuaResult<LuaTable> {\n        Ok(self.tab)\n    }\n}\n"
  },
  {
    "path": "crates/lune-utils/src/version_string.rs",
    "content": "use std::sync::{Arc, LazyLock};\n\nuse mlua::prelude::*;\nuse semver::Version;\n\nstatic LUAU_VERSION: LazyLock<Arc<String>> = LazyLock::new(create_luau_version_string);\n\n/**\n    Returns a Lune version string, in the format `Lune x.y.z+luau`.\n\n    The version string passed should be the version of the Lune runtime,\n    obtained from `env!(\"CARGO_PKG_VERSION\")` or a similar mechanism.\n\n    # Panics\n\n    Panics if the version string is empty or contains invalid characters.\n*/\n#[must_use]\npub fn get_version_string(lune_version: impl AsRef<str>) -> String {\n    let lune_version = lune_version.as_ref();\n\n    assert!(!lune_version.is_empty(), \"Lune version string is empty\");\n    match Version::parse(lune_version) {\n        Ok(semver) => format!(\"Lune {semver}+{}\", *LUAU_VERSION),\n        Err(e) => panic!(\"Lune version string is not valid semver: {e}\"),\n    }\n}\n\nfn create_luau_version_string() -> Arc<String> {\n    // Extract the current Luau version from a fresh Lua state / VM that can't be accessed externally.\n    let luau_version_full = {\n        let temp_lua = Lua::new();\n\n        let luau_version_full = temp_lua\n            .globals()\n            .get::<LuaString>(\"_VERSION\")\n            .expect(\"Missing _VERSION global\");\n\n        luau_version_full\n            .to_str()\n            .context(\"Invalid utf8 found in _VERSION global\")\n            .expect(\"Expected _VERSION global to be a string\")\n            .to_string()\n    };\n\n    // Luau version is expected to be in the format \"Luau 0.x\" and sometimes \"Luau 0.x.y\"\n    assert!(\n        luau_version_full.starts_with(\"Luau 0.\"),\n        \"_VERSION global is formatted incorrectly\\\n        \\nFound string '{luau_version_full}'\"\n    );\n    let luau_version_noprefix = luau_version_full.strip_prefix(\"Luau 0.\").unwrap().trim();\n\n    // We make some guarantees about the format of the _VERSION global,\n    // so make sure that the luau version also follows those rules.\n    if luau_version_noprefix.is_empty() {\n        panic!(\n            \"_VERSION global is missing version number\\\n            \\nFound string '{luau_version_full}'\"\n        )\n    } else if !luau_version_noprefix.chars().all(is_valid_version_char) {\n        panic!(\n            \"_VERSION global contains invalid characters\\\n            \\nFound string '{luau_version_full}'\"\n        )\n    }\n\n    luau_version_noprefix.to_string().into()\n}\n\nfn is_valid_version_char(c: char) -> bool {\n    matches!(c, '0'..='9' | '.')\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/Cargo.toml",
    "content": "[package]\nname = \"mlua-luau-scheduler\"\nversion = \"0.2.3\"\nedition = \"2024\"\nlicense = \"MPL-2.0\"\nrepository = \"https://github.com/lune-org/lune\"\ndescription = \"Luau-based async scheduler, using mlua and async-executor\"\nreadme = \"README.md\"\nkeywords = [\"async\", \"luau\", \"scheduler\"]\ncategories = [\"asynchronous\"]\n\n[lib]\npath = \"src/lib.rs\"\n\n[lints]\nworkspace = true\n\n[dependencies]\nasync-executor = \"1.13\"\nblocking = \"1.6\"\nfutures-lite = \"2.6\"\nrustc-hash = \"2.1\"\ntracing = \"0.1\"\n\nmlua = { version = \"0.11.4\", features = [\n    \"luau\",\n    \"luau-jit\",\n    \"async\",\n    \"serialize\",\n] }\n\n[dev-dependencies]\nasync-fs = \"2.1\"\nasync-io = \"2.4\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\ntracing-tracy = \"0.11\"\n\n[[example]]\nname = \"basic_sleep\"\ntest = true\n\n[[example]]\nname = \"basic_spawn\"\ntest = true\n\n[[example]]\nname = \"callbacks\"\ntest = true\n\n[[example]]\nname = \"exit_code\"\ntest = true\n\n[[example]]\nname = \"lots_of_threads\"\ntest = true\n\n[[example]]\nname = \"scheduler_ordering\"\ntest = true\n\n[[example]]\nname = \"tracy\"\ntest = false\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/README.md",
    "content": "<!-- markdownlint-disable MD033 -->\n<!-- markdownlint-disable MD041 -->\n\n# `mlua-luau-scheduler`\n\nAn async scheduler for Luau, using [`mlua`][mlua] and built on top of [`async-executor`][async-executor].\n\nThis crate is runtime-agnostic and is compatible with any async runtime, including [Tokio][tokio], [smol][smol], [async-std][async-std], and others. </br>\nHowever, since many dependencies are shared with [smol][smol], depending on it over other runtimes may be preferred.\n\n[async-executor]: https://crates.io/crates/async-executor\n[async-std]: https://async.rs\n[mlua]: https://crates.io/crates/mlua\n[smol]: https://github.com/smol-rs/smol\n[tokio]: https://tokio.rs\n\n## Example Usage\n\n### 1. Import dependencies\n\n```rs\nuse std::time::{Duration, Instant};\nuse std::io::ErrorKind;\n\nuse async_io::{block_on, Timer};\nuse async_fs::read_to_string;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::*;\n```\n\n### 2. Set up Lua environment\n\n```rs\nlet lua = Lua::new();\n\nlua.globals().set(\n    \"sleep\",\n    lua.create_async_function(|_, duration: f64| async move {\n        let before = Instant::now();\n        let after = Timer::after(Duration::from_secs_f64(duration)).await;\n        Ok((after - before).as_secs_f64())\n    })?,\n)?;\n\nlua.globals().set(\n    \"readFile\",\n    lua.create_async_function(|lua, path: String| async move {\n        // Spawn background task that does not take up resources on the lua thread\n        let task = lua.spawn(async move {\n            match read_to_string(path).await {\n                Ok(s) => Ok(Some(s)),\n                Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),\n                Err(e) => Err(e),\n            }\n        });\n        task.await.into_lua_err()\n    })?,\n)?;\n```\n\n### 3. Set up scheduler, run threads\n\n```rs\nlet sched = Scheduler::new(&lua)?;\n\n// We can create multiple lua threads ...\nlet sleepThread = lua.load(\"sleep(0.1)\");\nlet fileThread = lua.load(\"readFile(\\\"Cargo.toml\\\")\");\n\n// ... spawn them both onto the scheduler ...\nsched.push_thread_front(sleepThread, ());\nsched.push_thread_front(fileThread, ());\n\n// ... and run until they finish\nblock_on(sched.run());\n```\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/basic_sleep.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse std::time::{Duration, Instant};\n\nuse async_io::{Timer, block_on};\nuse futures_lite::future::yield_now;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::Scheduler;\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/basic_sleep.luau\");\n\npub fn main() -> LuaResult<()> {\n    tracing_subscriber::fmt()\n        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n        .with_target(false)\n        .without_time()\n        .init();\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n    lua.globals().set(\n        \"sleep\",\n        lua.create_async_function(|_, duration: f64| async move {\n            // Guarantee that the coroutine that calls this sleep function\n            // always yields, even if the timer completes without doing so\n            yield_now().await;\n            // We may then sleep as normal\n            let before = Instant::now();\n            let after = Timer::after(Duration::from_secs_f64(duration)).await;\n            Ok((after - before).as_secs_f64())\n        })?,\n    )?;\n\n    // Load the main script into a scheduler\n    let sched = Scheduler::new(lua.clone());\n    let main = lua.load(MAIN_SCRIPT);\n    sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    Ok(())\n}\n\n#[test]\nfn test_basic_sleep() -> LuaResult<()> {\n    main()\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/basic_spawn.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse std::io::ErrorKind;\n\nuse async_fs::read_to_string;\nuse async_io::block_on;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{LuaSpawnExt, Scheduler};\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/basic_spawn.luau\");\n\npub fn main() -> LuaResult<()> {\n    tracing_subscriber::fmt()\n        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n        .with_target(false)\n        .without_time()\n        .init();\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n    lua.globals().set(\n        \"readFile\",\n        lua.create_async_function(|lua, path: String| async move {\n            // Spawn background task that does not take up resources on the Lua thread\n            let task = lua.spawn(async move {\n                match read_to_string(path).await {\n                    Ok(s) => Ok(Some(s)),\n                    Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),\n                    Err(e) => Err(e),\n                }\n            });\n\n            // Wait for it to complete\n            let result = task.await.into_lua_err();\n\n            // We can also spawn local tasks that do take up resources\n            // on the Lua thread, but that do not have the Send bound\n            if result.is_ok() {\n                lua.spawn_local(async move {\n                    println!(\"File read successfully!\");\n                });\n            }\n\n            result\n        })?,\n    )?;\n\n    // Load the main script into a scheduler\n    let sched = Scheduler::new(lua.clone());\n    let main = lua.load(MAIN_SCRIPT);\n    sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    Ok(())\n}\n\n#[test]\nfn test_basic_spawn() -> LuaResult<()> {\n    main()\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/callbacks.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::missing_panics_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::Scheduler;\n\nuse async_io::block_on;\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/callbacks.luau\");\n\npub fn main() -> LuaResult<()> {\n    tracing_subscriber::fmt()\n        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n        .with_target(false)\n        .without_time()\n        .init();\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n\n    // Create a new scheduler with custom callbacks\n    let sched = Scheduler::new(lua.clone());\n    sched.set_error_callback(|e| {\n        println!(\n            \"Captured error from Lua!\\n{}\\n{e}\\n{}\",\n            \"-\".repeat(15),\n            \"-\".repeat(15)\n        );\n    });\n\n    // Load the main script into the scheduler, and keep track of the thread we spawn\n    let main = lua.load(MAIN_SCRIPT);\n    let id = sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    // We should have gotten the error back from our script\n    assert!(sched.get_thread_result(id).unwrap().is_err());\n\n    Ok(())\n}\n\n#[test]\nfn test_callbacks() -> LuaResult<()> {\n    main()\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/exit_code.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::missing_panics_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse async_io::block_on;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{Functions, Scheduler};\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/exit_code.luau\");\n\npub fn main() -> LuaResult<()> {\n    tracing_subscriber::fmt()\n        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n        .with_target(false)\n        .without_time()\n        .init();\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n    let sched = Scheduler::new(lua.clone());\n    let fns = Functions::new(lua.clone())?;\n\n    lua.globals().set(\"exit\", fns.exit)?;\n\n    // Load the main script into the scheduler\n    let main = lua.load(MAIN_SCRIPT);\n    sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    // Verify that we got a correct exit code\n    let code = sched.get_exit_code().unwrap_or_default();\n    assert_eq!(code, 1);\n\n    Ok(())\n}\n\n#[test]\nfn test_exit_code() -> LuaResult<()> {\n    main()\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lots_of_threads.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse std::time::Duration;\n\nuse async_io::{Timer, block_on};\nuse futures_lite::future::yield_now;\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{Functions, Scheduler};\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/lots_of_threads.luau\");\n\nconst ONE_NANOSECOND: Duration = Duration::from_nanos(1);\n\npub fn main() -> LuaResult<()> {\n    tracing_subscriber::fmt()\n        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n        .with_target(false)\n        .without_time()\n        .init();\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n    let sched = Scheduler::new(lua.clone());\n    let fns = Functions::new(lua.clone())?;\n\n    lua.globals().set(\"spawn\", fns.spawn)?;\n    lua.globals().set(\n        \"sleep\",\n        lua.create_async_function(|_, ()| async move {\n            // Guarantee that the coroutine that calls this sleep function\n            // always yields, even if the timer completes without doing so\n            yield_now().await;\n            // Obviously we can't sleep for a single nanosecond since\n            // this uses OS scheduling under the hood, but we can try\n            Timer::after(ONE_NANOSECOND).await;\n            Ok(())\n        })?,\n    )?;\n\n    // Load the main script into the scheduler\n    let main = lua.load(MAIN_SCRIPT);\n    sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    Ok(())\n}\n\n#[test]\nfn test_lots_of_threads() -> LuaResult<()> {\n    main()\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lua/basic_sleep.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\nprint(\"Sleeping for 3 seconds...\")\n\nsleep(1)\nprint(\"1 second passed\")\n\nsleep(1)\nprint(\"2 seconds passed\")\n\nsleep(1)\nprint(\"3 seconds passed\")\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lua/basic_spawn.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\nlocal _, err = pcall(function()\n\tlocal file = readFile(\"Cargo.toml\")\n\tif file ~= nil then\n\t\tprint(\"Cargo.toml found!\")\n\t\tprint(\"Contents:\")\n\t\tprint(file)\n\telse\n\t\tprint(\"Cargo.toml not found!\")\n\tend\nend)\n\nif err ~= nil then\n\tprint(\"Error while reading file: \" .. err)\nend\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lua/callbacks.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\nerror(\"Oh no! Something went very very wrong!\")\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lua/exit_code.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\nprint(\"Setting exit code manually\")\n\nexit(1)\n\nerror(\"unreachable\")\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lua/lots_of_threads.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\nlocal NUM_BATCHES = 10\nlocal NUM_THREADS = 100_000\n\nprint(`Spawning {NUM_BATCHES * NUM_THREADS} threads split into {NUM_BATCHES} batches\\n`)\n\nlocal before = os.clock()\nfor i = 1, NUM_BATCHES do\n\tprint(`Batch {i} of {NUM_BATCHES}`)\n\tlocal thread = coroutine.running()\n\n\tlocal counter = 0\n\tfor j = 1, NUM_THREADS do\n\t\tspawn(function()\n\t\t\tsleep(0.1)\n\t\t\tcounter += 1\n\t\t\tif counter == NUM_THREADS then\n\t\t\t\tspawn(thread)\n\t\t\tend\n\t\tend)\n\tend\n\n\tcoroutine.yield()\nend\nlocal after = os.clock()\n\nprint(`\\nSpawned {NUM_BATCHES * NUM_THREADS} sleeping threads in {after - before}s`)\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/lua/scheduler_ordering.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\nlocal nums = {}\nlocal function insert(n: number)\n\ttable.insert(nums, n)\n\tprint(n)\nend\n\ninsert(1)\n\n-- Defer will run at the end of the resumption cycle, but without yielding\ndefer(function()\n\tinsert(5)\nend)\n\n-- Spawn will instantly run up until the first yield, and must then be resumed manually ...\nspawn(function()\n\tinsert(2)\n\tcoroutine.yield()\n\terror(\"unreachable code\")\nend)\n\n-- ... unless calling functions created using `lua.create_async_function(...)`,\n-- which will resume their calling thread with their result automatically\nspawn(function()\n\tinsert(3)\n\tsleep(1)\n\tinsert(6)\nend)\n\ninsert(4)\n\nreturn nums\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/scheduler_ordering.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::missing_panics_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse std::time::{Duration, Instant};\n\nuse async_io::{Timer, block_on};\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{Functions, Scheduler};\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/scheduler_ordering.luau\");\n\npub fn main() -> LuaResult<()> {\n    tracing_subscriber::fmt()\n        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n        .with_target(false)\n        .without_time()\n        .init();\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n    let sched = Scheduler::new(lua.clone());\n    let fns = Functions::new(lua.clone())?;\n\n    lua.globals().set(\"spawn\", fns.spawn)?;\n    lua.globals().set(\"defer\", fns.defer)?;\n    lua.globals().set(\n        \"sleep\",\n        lua.create_async_function(|_, duration: Option<f64>| async move {\n            let duration = duration.unwrap_or_default().max(1.0 / 250.0);\n            let before = Instant::now();\n            let after = Timer::after(Duration::from_secs_f64(duration)).await;\n            Ok((after - before).as_secs_f64())\n        })?,\n    )?;\n\n    // Load the main script into the scheduler, and keep track of the thread we spawn\n    let main = lua.load(MAIN_SCRIPT);\n    let id = sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    // We should have gotten proper values back from our script\n    let res = sched.get_thread_result(id).unwrap().unwrap();\n    let nums = Vec::<usize>::from_lua_multi(res, &lua)?;\n    assert_eq!(nums, vec![1, 2, 3, 4, 5, 6]);\n\n    Ok(())\n}\n\n#[test]\nfn test_scheduler_ordering() -> LuaResult<()> {\n    main()\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/examples/tracy.rs",
    "content": "/*\n    NOTE: This example is the same as \"lots_of_threads\", but with tracy set up for performance profiling.\n\n    How to run:\n\n    1. Install tracy\n       - Follow the instructions at https://github.com/wolfpld/tracy\n       - Or install via something like homebrew: `brew install tracy`\n    2. Run the server (`tracy`) in a terminal\n    3. Run the example in another terminal\n       - `export RUST_LOG=trace`\n       - `cargo run --example tracy`\n*/\n\n#![allow(clippy::missing_errors_doc)]\n#![allow(clippy::cargo_common_metadata)]\n\nuse std::time::Duration;\n\nuse async_io::{Timer, block_on};\nuse tracing_subscriber::layer::SubscriberExt;\nuse tracing_tracy::{TracyLayer, client::Client as TracyClient};\n\nuse mlua::prelude::*;\nuse mlua_luau_scheduler::{Functions, Scheduler};\n\nconst MAIN_SCRIPT: &str = include_str!(\"./lua/lots_of_threads.luau\");\n\nconst ONE_NANOSECOND: Duration = Duration::from_nanos(1);\n\npub fn main() -> LuaResult<()> {\n    let _client = TracyClient::start();\n    let _ = tracing::subscriber::set_global_default(\n        tracing_subscriber::registry().with(TracyLayer::default()),\n    );\n\n    // Set up persistent Lua environment\n    let lua = Lua::new();\n    let sched = Scheduler::new(lua.clone());\n    let fns = Functions::new(lua.clone())?;\n\n    lua.globals().set(\"spawn\", fns.spawn)?;\n    lua.globals().set(\n        \"sleep\",\n        lua.create_async_function(|_, ()| async move {\n            // Obviously we can't sleep for a single nanosecond since\n            // this uses OS scheduling under the hood, but we can try\n            Timer::after(ONE_NANOSECOND).await;\n            Ok(())\n        })?,\n    )?;\n\n    // Load the main script into the scheduler\n    let main = lua.load(MAIN_SCRIPT);\n    sched.push_thread_front(main, ())?;\n\n    // Run until completion\n    block_on(sched.run());\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/error_callback.rs",
    "content": "use std::{cell::RefCell, rc::Rc};\n\nuse mlua::prelude::*;\n\ntype ErrorCallback = Box<dyn Fn(LuaError) + Send + 'static>;\n\n#[derive(Clone)]\npub(crate) struct ThreadErrorCallback {\n    inner: Rc<RefCell<Option<ErrorCallback>>>,\n}\n\nimpl ThreadErrorCallback {\n    pub fn new() -> Self {\n        Self {\n            inner: Rc::new(RefCell::new(None)),\n        }\n    }\n\n    pub fn replace(&self, callback: impl Fn(LuaError) + Send + 'static) {\n        self.inner.borrow_mut().replace(Box::new(callback));\n    }\n\n    pub fn clear(&self) {\n        self.inner.borrow_mut().take();\n    }\n\n    pub fn call(&self, error: &LuaError) {\n        if let Some(cb) = &*self.inner.borrow() {\n            cb(error.clone());\n        }\n    }\n}\n\n#[allow(clippy::needless_pass_by_value)]\nfn default_error_callback(e: LuaError) {\n    eprintln!(\"{e}\");\n}\n\nimpl Default for ThreadErrorCallback {\n    fn default() -> Self {\n        let this = Self::new();\n        this.replace(default_error_callback);\n        this\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/events/mod.rs",
    "content": "#![allow(unused_imports)]\n\nmod multi;\nmod once;\n\npub(crate) use self::multi::{MultiEvent, MultiListener};\npub(crate) use self::once::{OnceEvent, OnceListener};\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/events/multi.rs",
    "content": "use std::{\n    cell::{Cell, RefCell},\n    future::Future,\n    mem,\n    pin::Pin,\n    rc::Rc,\n    task::{Context, Poll, Waker},\n};\n\n/**\n    Internal state for events.\n*/\n#[derive(Debug, Default)]\nstruct MultiEventState {\n    generation: Cell<u64>,\n    wakers: RefCell<Vec<Waker>>,\n}\n\n/**\n    A single-threaded event signal that can be notified multiple times.\n*/\n#[derive(Debug, Clone, Default)]\npub(crate) struct MultiEvent {\n    state: Rc<MultiEventState>,\n}\n\nimpl MultiEvent {\n    /**\n        Creates a new event.\n    */\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /**\n        Notifies all waiting listeners.\n    */\n    pub fn notify(&self) {\n        self.state.generation.set(self.state.generation.get() + 1);\n\n        let wakers = {\n            let mut wakers = self.state.wakers.borrow_mut();\n            mem::take(&mut *wakers)\n        };\n\n        for waker in wakers {\n            waker.wake();\n        }\n    }\n\n    /**\n        Creates a listener that implements `Future` and resolves when `notify` is called.\n    */\n    pub fn listen(&self) -> MultiListener {\n        MultiListener {\n            state: self.state.clone(),\n            generation: self.state.generation.get(),\n        }\n    }\n}\n\n/**\n    A listener future that resolves when the corresponding [`QueueEvent`] is notified.\n*/\n#[derive(Debug)]\npub(crate) struct MultiListener {\n    state: Rc<MultiEventState>,\n    generation: u64,\n}\n\nimpl Future for MultiListener {\n    type Output = ();\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        // Check if notify was called (generation is more recent)\n        let current = self.state.generation.get();\n        if current > self.generation {\n            self.get_mut().generation = current;\n            return Poll::Ready(());\n        }\n\n        // No notification observed yet\n        let mut wakers = self.state.wakers.borrow_mut();\n        if !wakers.iter().any(|w| w.will_wake(cx.waker())) {\n            wakers.push(cx.waker().clone());\n        }\n        Poll::Pending\n    }\n}\n\nimpl Unpin for MultiListener {}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/events/once.rs",
    "content": "use std::{\n    cell::RefCell,\n    future::Future,\n    pin::Pin,\n    rc::Rc,\n    task::{Context, Poll, Waker},\n};\n\n/**\n    State which is highly optimized for a single notification event.\n\n    `Some` means not notified yet, `None` means notified.\n*/\n#[derive(Debug, Default)]\nstruct OnceEventState {\n    wakers: RefCell<Option<Vec<Waker>>>,\n}\n\nimpl OnceEventState {\n    fn new() -> Self {\n        Self {\n            wakers: RefCell::new(Some(Vec::new())),\n        }\n    }\n}\n\n/**\n    An event that may be notified exactly once.\n\n    May be cheaply cloned.\n*/\n#[derive(Debug, Clone, Default)]\npub struct OnceEvent {\n    state: Rc<OnceEventState>,\n}\n\nimpl OnceEvent {\n    /**\n        Creates a new event that can be notified exactly once.\n    */\n    pub fn new() -> Self {\n        let initial_state = OnceEventState::new();\n        Self {\n            state: Rc::new(initial_state),\n        }\n    }\n\n    /**\n        Notifies waiting listeners.\n\n        This is idempotent; subsequent calls do nothing.\n    */\n    pub fn notify(&self) {\n        if let Some(wakers) = { self.state.wakers.borrow_mut().take() } {\n            for waker in wakers {\n                waker.wake();\n            }\n        }\n    }\n\n    /**\n        Creates a listener that implements `Future` and resolves when `notify` is called.\n\n        If `notify` has already been called, the future will resolve immediately.\n    */\n    pub fn listen(&self) -> OnceListener {\n        OnceListener {\n            state: self.state.clone(),\n        }\n    }\n}\n\n/**\n    A listener that resolves when the event is notified.\n\n    May be cheaply cloned.\n\n    See [`OnceEvent`] for more information.\n*/\n#[derive(Debug)]\npub struct OnceListener {\n    state: Rc<OnceEventState>,\n}\n\nimpl Future for OnceListener {\n    type Output = ();\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        let mut wakers_guard = self.state.wakers.borrow_mut();\n        match &mut *wakers_guard {\n            Some(wakers) => {\n                // Not yet notified\n                if !wakers.iter().any(|w| w.will_wake(cx.waker())) {\n                    wakers.push(cx.waker().clone());\n                }\n                Poll::Pending\n            }\n            None => {\n                // Already notified\n                Poll::Ready(())\n            }\n        }\n    }\n}\n\nimpl Unpin for OnceListener {}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/exit.rs",
    "content": "use std::{cell::Cell, rc::Rc};\n\nuse crate::events::OnceEvent;\n\n#[derive(Debug, Clone)]\npub(crate) struct Exit {\n    code: Rc<Cell<Option<u8>>>,\n    event: OnceEvent,\n}\n\nimpl Exit {\n    pub fn new() -> Self {\n        Self {\n            code: Rc::new(Cell::new(None)),\n            event: OnceEvent::new(),\n        }\n    }\n\n    pub fn set(&self, code: u8) {\n        self.code.set(Some(code));\n        self.event.notify();\n    }\n\n    pub fn get(&self) -> Option<u8> {\n        self.code.get()\n    }\n\n    pub async fn listen(&self) {\n        self.event.listen().await;\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/functions.rs",
    "content": "#![allow(clippy::too_many_lines)]\n\nuse mlua::prelude::*;\n\nuse crate::{\n    error_callback::ThreadErrorCallback,\n    queue::{DeferredThreadQueue, SpawnedThreadQueue},\n    threads::{ThreadId, ThreadMap},\n    traits::LuaSchedulerExt,\n    util::{LuaThreadOrFunction, is_poll_pending},\n};\n\nconst ERR_METADATA_NOT_ATTACHED: &str = \"\\\nLua state does not have scheduler metadata attached!\\\n\\nThis is most likely caused by creating functions outside of a scheduler.\\\n\\nScheduler functions must always be created from within an active scheduler.\\\n\";\n\nconst EXIT_IMPL_LUA: &str = r\"\nexit(...)\nyield()\n\";\n\nconst WRAP_IMPL_LUA: &str = r\"\nlocal t = create(...)\nreturn function(...)\n    local r = { resume(t, ...) }\n    if r[1] then\n        return select(2, unpack(r))\n    else\n        error(r[2], 2)\n    end\nend\n\";\n\n/**\n    A collection of lua functions that may be called to interact with a [`Scheduler`].\n\n    Note that these may all be implemented using [`LuaSchedulerExt`], however, this struct\n    is implemented using internal (non-public) APIs, and generally has better performance.\n*/\npub struct Functions {\n    /**\n        Implementation of `coroutine.resume` that handles async polling properly.\n\n        Defers onto the scheduler queue if the thread calls an async function.\n    */\n    pub resume: LuaFunction,\n    /**\n        Implementation of `coroutine.wrap` that handles async polling properly.\n\n        Defers onto the scheduler queue if the thread calls an async function.\n    */\n    pub wrap: LuaFunction,\n    /**\n        Resumes a function / thread once instantly, and runs until first yield.\n\n        Spawns onto the scheduler queue if not completed.\n    */\n    pub spawn: LuaFunction,\n    /**\n        Defers a function / thread onto the scheduler queue.\n\n        Does not resume instantly, only adds to the queue.\n    */\n    pub defer: LuaFunction,\n    /**\n        Cancels a function / thread, removing it from the queue.\n    */\n    pub cancel: LuaFunction,\n    /**\n        Exits the scheduler, stopping all other threads and closing the scheduler.\n\n        Yields the calling thread to ensure that it does not continue.\n    */\n    pub exit: LuaFunction,\n}\n\nimpl Functions {\n    /**\n        Creates a new collection of Lua functions that may be called to interact with a [`Scheduler`].\n\n        # Errors\n\n        Errors when out of memory, or if default Lua globals are missing.\n\n        # Panics\n\n        Panics when the given [`Lua`] instance does not have an attached [`Scheduler`].\n    */\n    pub fn new(lua: Lua) -> LuaResult<Self> {\n        let spawn_queue = lua\n            .app_data_ref::<SpawnedThreadQueue>()\n            .expect(ERR_METADATA_NOT_ATTACHED)\n            .clone();\n        let defer_queue = lua\n            .app_data_ref::<DeferredThreadQueue>()\n            .expect(ERR_METADATA_NOT_ATTACHED)\n            .clone();\n        let error_callback = lua\n            .app_data_ref::<ThreadErrorCallback>()\n            .expect(ERR_METADATA_NOT_ATTACHED)\n            .clone();\n        let thread_map = lua\n            .app_data_ref::<ThreadMap>()\n            .expect(ERR_METADATA_NOT_ATTACHED)\n            .clone();\n\n        let resume_queue = defer_queue.clone();\n        let resume_map = thread_map.clone();\n        let resume =\n            lua.create_function(move |lua, (thread, args): (LuaThread, LuaMultiValue)| {\n                let _span = tracing::trace_span!(\"Scheduler::fn_resume\").entered();\n                match thread.resume::<LuaMultiValue>(args.clone()) {\n                    Ok(v) => {\n                        if v.front().is_some_and(is_poll_pending) {\n                            // Pending, defer to scheduler and return nil\n                            resume_queue.push_item(lua, &thread, args)?;\n                            (true, LuaValue::Nil).into_lua_multi(lua)\n                        } else {\n                            // Not pending, store the value if thread is done\n                            if thread.status() != LuaThreadStatus::Resumable {\n                                let id = ThreadId::from(&thread);\n                                if resume_map.is_tracked(id) {\n                                    resume_map.insert(id, Ok(v.clone()));\n                                }\n                            }\n                            (true, v).into_lua_multi(lua)\n                        }\n                    }\n                    Err(e) => {\n                        // Not pending, store the error\n                        let id = ThreadId::from(&thread);\n                        if resume_map.is_tracked(id) {\n                            resume_map.insert(id, Err(e.clone()));\n                        }\n                        (false, e.to_string()).into_lua_multi(lua)\n                    }\n                }\n            })?;\n\n        let wrap_env = lua.create_table_from(vec![\n            (\"resume\", resume.clone()),\n            (\"error\", lua.globals().get::<LuaFunction>(\"error\")?),\n            (\"select\", lua.globals().get::<LuaFunction>(\"select\")?),\n            (\"unpack\", lua.globals().get::<LuaFunction>(\"unpack\")?),\n            (\n                \"create\",\n                lua.globals()\n                    .get::<LuaTable>(\"coroutine\")?\n                    .get::<LuaFunction>(\"create\")?,\n            ),\n        ])?;\n        let wrap = lua\n            .load(WRAP_IMPL_LUA)\n            .set_name(\"=__scheduler_wrap\")\n            .set_environment(wrap_env)\n            .into_function()?;\n\n        let spawn_map = thread_map.clone();\n        let spawn = lua.create_function(\n            move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {\n                let _span = tracing::trace_span!(\"Scheduler::fn_spawn\").entered();\n                let thread = tof.into_thread(lua)?;\n                if thread.status() == LuaThreadStatus::Resumable {\n                    // NOTE: We need to resume the thread once instantly for correct behavior,\n                    // and only if we get the pending value back we can spawn to async executor\n                    match thread.resume::<LuaMultiValue>(args.clone()) {\n                        Ok(v) => {\n                            if v.front().is_some_and(is_poll_pending) {\n                                spawn_queue.push_item(lua, &thread, args)?;\n                            } else {\n                                // Not pending, store the value if thread is done\n                                if thread.status() != LuaThreadStatus::Resumable {\n                                    let id = ThreadId::from(&thread);\n                                    if spawn_map.is_tracked(id) {\n                                        spawn_map.insert(id, Ok(v));\n                                    }\n                                }\n                            }\n                        }\n                        Err(e) => {\n                            error_callback.call(&e);\n                            // Not pending, store the error\n                            let id = ThreadId::from(&thread);\n                            if spawn_map.is_tracked(id) {\n                                spawn_map.insert(id, Err(e));\n                            }\n                        }\n                    }\n                }\n                Ok(thread)\n            },\n        )?;\n\n        let defer = lua.create_function(\n            move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {\n                let _span = tracing::trace_span!(\"Scheduler::fn_defer\").entered();\n                let thread = tof.into_thread(lua)?;\n                if thread.status() == LuaThreadStatus::Resumable {\n                    defer_queue.push_item(lua, &thread, args)?;\n                }\n                Ok(thread)\n            },\n        )?;\n\n        let close = lua\n            .globals()\n            .get::<LuaTable>(\"coroutine\")?\n            .get::<LuaFunction>(\"close\")?;\n        let close_key = lua.create_registry_value(close)?;\n        let cancel = lua.create_function(move |lua, thread: LuaThread| {\n            let _span = tracing::trace_span!(\"Scheduler::fn_cancel\").entered();\n            let close: LuaFunction = lua.registry_value(&close_key)?;\n            match close.call(thread) {\n                Err(LuaError::CoroutineUnresumable) | Ok(()) => Ok(()),\n                Err(e) => Err(e),\n            }\n        })?;\n\n        let exit_env = lua.create_table_from(vec![\n            (\n                \"exit\",\n                lua.create_function(|lua, code: Option<u8>| {\n                    let _span = tracing::trace_span!(\"Scheduler::fn_exit\").entered();\n                    let code = code.unwrap_or_default();\n                    lua.set_exit_code(code);\n                    Ok(())\n                })?,\n            ),\n            (\n                \"yield\",\n                lua.globals()\n                    .get::<LuaTable>(\"coroutine\")?\n                    .get::<LuaFunction>(\"yield\")?,\n            ),\n        ])?;\n        let exit = lua\n            .load(EXIT_IMPL_LUA)\n            .set_name(\"=__scheduler_exit\")\n            .set_environment(exit_env)\n            .into_function()?;\n\n        Ok(Self {\n            resume,\n            wrap,\n            spawn,\n            defer,\n            cancel,\n            exit,\n        })\n    }\n}\n\nimpl Functions {\n    /**\n        Injects [`Scheduler`]-compatible functions into the given [`Lua`] instance.\n\n        This will overwrite the following functions:\n\n        - `coroutine.resume`\n        - `coroutine.wrap`\n\n        # Errors\n\n        Errors when out of memory, or if default Lua globals are missing.\n    */\n    pub fn inject_compat(&self, lua: &Lua) -> LuaResult<()> {\n        let co: LuaTable = lua.globals().get(\"coroutine\")?;\n        co.set(\"resume\", self.resume.clone())?;\n        co.set(\"wrap\", self.wrap.clone())?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/lib.rs",
    "content": "#![allow(clippy::cargo_common_metadata)]\n\nmod error_callback;\nmod events;\nmod exit;\nmod functions;\nmod queue;\nmod scheduler;\nmod status;\nmod threads;\nmod traits;\nmod util;\n\npub use functions::Functions;\npub use scheduler::Scheduler;\npub use status::Status;\npub use threads::ThreadId;\npub use traits::{IntoLuaThread, LuaSchedulerExt, LuaSpawnExt};\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/queue/deferred.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse super::threads::ThreadQueue;\n\n/**\n    Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.\n*/\n#[derive(Debug, Clone)]\npub(crate) struct DeferredThreadQueue(ThreadQueue);\n\nimpl DeferredThreadQueue {\n    pub fn new() -> Self {\n        Self(ThreadQueue::new())\n    }\n}\n\nimpl Deref for DeferredThreadQueue {\n    type Target = ThreadQueue;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for DeferredThreadQueue {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/queue/futures.rs",
    "content": "use std::{cell::RefCell, mem, pin::Pin, rc::Rc};\n\nuse futures_lite::prelude::*;\n\nuse crate::events::MultiEvent;\n\npub type LocalBoxFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;\n\nstruct FuturesQueueInner<'fut> {\n    queue: RefCell<Vec<LocalBoxFuture<'fut>>>,\n    event: MultiEvent,\n}\n\nimpl FuturesQueueInner<'_> {\n    pub fn new() -> Self {\n        Self {\n            queue: RefCell::new(Vec::new()),\n            event: MultiEvent::new(),\n        }\n    }\n}\n\n/**\n    Queue for storing local futures.\n\n    Provides methods for pushing and draining the queue, as\n    well as listening for new items being pushed to the queue.\n*/\n#[derive(Clone)]\npub(crate) struct FuturesQueue<'fut> {\n    inner: Rc<FuturesQueueInner<'fut>>,\n}\n\nimpl<'fut> FuturesQueue<'fut> {\n    pub fn new() -> Self {\n        let inner = Rc::new(FuturesQueueInner::new());\n        Self { inner }\n    }\n\n    pub fn push_item(&self, fut: impl Future<Output = ()> + 'fut) {\n        self.inner.queue.borrow_mut().push(fut.boxed_local());\n        self.inner.event.notify();\n    }\n\n    pub fn take_items(&self) -> Vec<LocalBoxFuture<'fut>> {\n        let mut queue = self.inner.queue.borrow_mut();\n        mem::take(&mut *queue)\n    }\n\n    pub async fn wait_for_item(&self) {\n        if self.inner.queue.borrow().is_empty() {\n            self.inner.event.listen().await;\n        }\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/queue/mod.rs",
    "content": "mod deferred;\nmod futures;\nmod spawned;\nmod threads;\n\npub(crate) use self::deferred::DeferredThreadQueue;\npub(crate) use self::futures::FuturesQueue;\npub(crate) use self::spawned::SpawnedThreadQueue;\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/queue/spawned.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse super::threads::ThreadQueue;\n\n/**\n    Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.\n*/\n#[derive(Debug, Clone)]\npub(crate) struct SpawnedThreadQueue(ThreadQueue);\n\nimpl SpawnedThreadQueue {\n    pub fn new() -> Self {\n        Self(ThreadQueue::new())\n    }\n}\n\nimpl Deref for SpawnedThreadQueue {\n    type Target = ThreadQueue;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for SpawnedThreadQueue {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/queue/threads.rs",
    "content": "#![allow(clippy::inline_always)]\n\nuse std::{cell::RefCell, mem, rc::Rc};\n\nuse mlua::prelude::*;\n\nuse crate::{threads::ThreadId, traits::IntoLuaThread};\n\nuse crate::events::MultiEvent;\n\n#[derive(Debug)]\nstruct ThreadQueueInner {\n    queue: RefCell<Vec<(LuaThread, LuaMultiValue)>>,\n    event: MultiEvent,\n}\n\nimpl ThreadQueueInner {\n    fn new() -> Self {\n        Self {\n            queue: RefCell::new(Vec::new()),\n            event: MultiEvent::new(),\n        }\n    }\n}\n\n/**\n    Queue for storing [`LuaThread`]s with associated arguments.\n\n    Provides methods for pushing and draining the queue, as\n    well as listening for new items being pushed to the queue.\n*/\n#[derive(Debug, Clone)]\npub(crate) struct ThreadQueue {\n    inner: Rc<ThreadQueueInner>,\n}\n\nimpl ThreadQueue {\n    pub fn new() -> Self {\n        let inner = Rc::new(ThreadQueueInner::new());\n        Self { inner }\n    }\n\n    pub fn push_item(\n        &self,\n        lua: &Lua,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId> {\n        let thread = thread.into_lua_thread(lua)?;\n        let args = args.into_lua_multi(lua)?;\n\n        tracing::trace!(\"pushing item to queue with {} args\", args.len());\n        let id = ThreadId::from(&thread);\n\n        self.inner.queue.borrow_mut().push((thread, args));\n        self.inner.event.notify();\n\n        Ok(id)\n    }\n\n    #[inline(always)]\n    pub fn take_items(&self) -> Vec<(LuaThread, LuaMultiValue)> {\n        let mut queue = self.inner.queue.borrow_mut();\n        mem::take(&mut *queue)\n    }\n\n    #[inline(always)]\n    pub async fn wait_for_item(&self) {\n        if self.inner.queue.borrow().is_empty() {\n            self.inner.event.listen().await;\n        }\n    }\n\n    #[inline(always)]\n    pub fn is_empty(&self) -> bool {\n        self.inner.queue.borrow().is_empty()\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/scheduler.rs",
    "content": "#![allow(clippy::module_name_repetitions)]\n\nuse std::{\n    cell::Cell,\n    rc::Rc,\n    sync::{Arc, Weak as WeakArc},\n    thread::panicking,\n};\n\nuse futures_lite::prelude::*;\nuse mlua::prelude::*;\n\nuse async_executor::{Executor, LocalExecutor};\nuse tracing::{Instrument, debug, instrument, trace, trace_span};\n\nuse crate::{\n    error_callback::ThreadErrorCallback,\n    exit::Exit,\n    queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},\n    status::Status,\n    threads::{ThreadId, ThreadMap},\n    traits::IntoLuaThread,\n    util::run_until_yield,\n};\n\nconst ERR_METADATA_ALREADY_ATTACHED: &str = \"\\\nLua state already has scheduler metadata attached!\\\n\\nThis may be caused by running multiple schedulers on the same Lua state, or a call to Scheduler::run being cancelled.\\\n\\nOnly one scheduler can be used per Lua state at once, and schedulers must always run until completion.\\\n\";\n\nconst ERR_METADATA_REMOVED: &str = \"\\\nLua state scheduler metadata was unexpectedly removed!\\\n\\nThis should never happen, and is likely a bug in the scheduler.\\\n\";\n\nconst ERR_SET_CALLBACK_WHEN_RUNNING: &str = \"\\\nCannot set error callback when scheduler is running!\\\n\";\n\n/**\n    A scheduler for running Lua threads and async tasks.\n*/\n#[derive(Clone)]\npub struct Scheduler {\n    lua: Lua,\n    queue_spawn: SpawnedThreadQueue,\n    queue_defer: DeferredThreadQueue,\n    error_callback: ThreadErrorCallback,\n    thread_map: ThreadMap,\n    status: Rc<Cell<Status>>,\n    exit: Exit,\n}\n\nimpl Scheduler {\n    /**\n        Creates a new scheduler for the given Lua state.\n\n        This scheduler will have a default error callback that prints errors to stderr.\n\n        # Panics\n\n        Panics if the given Lua state already has a scheduler attached to it.\n    */\n    #[must_use]\n    pub fn new(lua: Lua) -> Scheduler {\n        let queue_spawn = SpawnedThreadQueue::new();\n        let queue_defer = DeferredThreadQueue::new();\n        let error_callback = ThreadErrorCallback::default();\n        let result_map = ThreadMap::new();\n        let exit = Exit::new();\n\n        assert!(\n            lua.app_data_ref::<SpawnedThreadQueue>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n        assert!(\n            lua.app_data_ref::<DeferredThreadQueue>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n        assert!(\n            lua.app_data_ref::<ThreadErrorCallback>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n        assert!(\n            lua.app_data_ref::<ThreadMap>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n        assert!(\n            lua.app_data_ref::<Exit>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n\n        lua.set_app_data(queue_spawn.clone());\n        lua.set_app_data(queue_defer.clone());\n        lua.set_app_data(error_callback.clone());\n        lua.set_app_data(result_map.clone());\n        lua.set_app_data(exit.clone());\n\n        let status = Rc::new(Cell::new(Status::NotStarted));\n\n        Scheduler {\n            lua,\n            queue_spawn,\n            queue_defer,\n            error_callback,\n            thread_map: result_map,\n            status,\n            exit,\n        }\n    }\n\n    /**\n        Sets the current status of this scheduler and emits relevant tracing events.\n    */\n    fn set_status(&self, status: Status) {\n        debug!(status = ?status, \"status\");\n        self.status.set(status);\n    }\n\n    /**\n        Returns the current status of this scheduler.\n    */\n    #[must_use]\n    pub fn status(&self) -> Status {\n        self.status.get()\n    }\n\n    /**\n        Sets the error callback for this scheduler.\n\n        This callback will be called whenever a Lua thread errors.\n\n        Overwrites any previous error callback.\n\n        # Panics\n\n        Panics if the scheduler is currently running.\n    */\n    pub fn set_error_callback(&self, callback: impl Fn(LuaError) + Send + 'static) {\n        assert!(\n            !self.status().is_running(),\n            \"{ERR_SET_CALLBACK_WHEN_RUNNING}\"\n        );\n        self.error_callback.replace(callback);\n    }\n\n    /**\n        Clears the error callback for this scheduler.\n\n        This will remove any current error callback, including default(s).\n\n        # Panics\n\n        Panics if the scheduler is currently running.\n    */\n    pub fn remove_error_callback(&self) {\n        assert!(\n            !self.status().is_running(),\n            \"{ERR_SET_CALLBACK_WHEN_RUNNING}\"\n        );\n        self.error_callback.clear();\n    }\n\n    /**\n        Gets the exit code for this scheduler, if one has been set.\n    */\n    #[must_use]\n    pub fn get_exit_code(&self) -> Option<u8> {\n        self.exit.get()\n    }\n\n    /**\n        Sets the exit code for this scheduler.\n\n        This will cause [`Scheduler::run`] to exit immediately.\n    */\n    pub fn set_exit_code(&self, code: u8) {\n        self.exit.set(code);\n    }\n\n    /**\n        Spawns a chunk / function / thread onto the scheduler queue.\n\n        Threads are guaranteed to be resumed in the order that they were pushed to the queue.\n\n        # Returns\n\n        Returns a [`ThreadId`] that can be used to retrieve the result of the thread.\n\n        Note that the result may not be available until [`Scheduler::run`] completes.\n\n        # Errors\n\n        Errors when out of memory.\n    */\n    pub fn push_thread_front(\n        &self,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId> {\n        let id = self.queue_spawn.push_item(&self.lua, thread, args)?;\n        self.thread_map.track(id);\n        Ok(id)\n    }\n\n    /**\n        Defers a chunk / function / thread onto the scheduler queue.\n\n        Deferred threads are guaranteed to run after all spawned threads either yield or complete.\n\n        Threads are guaranteed to be resumed in the order that they were pushed to the queue.\n\n        # Returns\n\n        Returns a [`ThreadId`] that can be used to retrieve the result of the thread.\n\n        Note that the result may not be available until [`Scheduler::run`] completes.\n\n        # Errors\n\n        Errors when out of memory.\n    */\n    pub fn push_thread_back(\n        &self,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId> {\n        let id = self.queue_defer.push_item(&self.lua, thread, args)?;\n        self.thread_map.track(id);\n        Ok(id)\n    }\n\n    /**\n        Gets the tracked result for the [`LuaThread`] with the given [`ThreadId`].\n\n        Depending on the current [`Scheduler::status`], this method will return:\n\n        - [`Status::NotStarted`]: returns `None`.\n        - [`Status::Running`]: may return `Some(Ok(v))` or `Some(Err(e))`, but it is not guaranteed.\n        - [`Status::Completed`]: returns `Some(Ok(v))` or `Some(Err(e))`.\n\n        Note that this method also takes the value out of the scheduler and\n        stops tracking the given thread, so it may only be called once.\n\n        Any subsequent calls after this method returns `Some` will return `None`.\n    */\n    #[must_use]\n    pub fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {\n        self.thread_map.remove(id)\n    }\n\n    /**\n        Waits for the [`LuaThread`] with the given [`ThreadId`] to complete.\n\n        This will return instantly if the thread has already completed.\n    */\n    pub async fn wait_for_thread(&self, id: ThreadId) {\n        self.thread_map.listen(id).await;\n    }\n\n    /**\n        Runs the scheduler until all Lua threads have completed.\n\n        This will return instantly if no threads have been scheduled.\n\n        Note that the given Lua state must be the same one that was\n        used to create this scheduler, otherwise this method will panic.\n\n        # Panics\n\n        Panics if the given Lua state already has a scheduler attached to it.\n    */\n    #[allow(clippy::too_many_lines)]\n    #[instrument(level = \"debug\", name = \"Scheduler::run\", skip(self))]\n    pub async fn run(&self) {\n        if self.queue_spawn.is_empty() && self.queue_defer.is_empty() {\n            return;\n        }\n\n        /*\n            Create new executors to use - note that we do not need to create multiple executors\n            for work stealing, the user may do that themselves if they want to and it will work\n            just fine, as long as anything async is .await-ed from within a Lua async function.\n\n            The main purpose of the two executors here is just to have one with\n            the Send bound, and another (local) one without it, for Lua scheduling.\n\n            We also use the main executor to drive the main loop below forward,\n            saving a tiny bit of processing from going on the Lua executor itself.\n        */\n        let local_exec = LocalExecutor::new();\n        let main_exec = Arc::new(Executor::new());\n        let fut_queue = FuturesQueue::new();\n\n        /*\n            Store the main executor and queue in Lua, so that they may be used with LuaSchedulerExt.\n\n            Also ensure we do not already have an executor or queues - these are definite user errors\n            and may happen if the user tries to run multiple schedulers on the same Lua state at once.\n        */\n        assert!(\n            self.lua.app_data_ref::<WeakArc<Executor>>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n        assert!(\n            self.lua.app_data_ref::<FuturesQueue>().is_none(),\n            \"{ERR_METADATA_ALREADY_ATTACHED}\"\n        );\n\n        self.lua.set_app_data(Arc::downgrade(&main_exec));\n        self.lua.set_app_data(fut_queue.clone());\n\n        /*\n            Manually tick the Lua executor, while running under the main executor.\n            Each tick we wait for the next action to perform, in prioritized order:\n\n            1. The exit event is triggered by setting an exit code\n            2. A Lua thread is available to run on the spawned queue\n            3. A Lua thread is available to run on the deferred queue\n            4. A new thread-local future is available to run on the local executor\n            5. Task(s) scheduled on the Lua executor have made progress and should be polled again\n\n            This ordering is vital to ensure that we don't accidentally exit the main loop\n            when there are new Lua threads to enqueue and potentially more work to be done.\n        */\n        let fut = async {\n            let result_map = self.thread_map.clone();\n            let process_thread = |thread: LuaThread, args| {\n                // NOTE: Thread may have been cancelled from Lua\n                // before we got here, so we need to check it again\n                if thread.status() == LuaThreadStatus::Resumable {\n                    // Check if we should be tracking this thread\n                    let id = ThreadId::from(&thread);\n                    let id_tracked = result_map.is_tracked(id);\n                    let result_map_inner = if id_tracked {\n                        Some(result_map.clone())\n                    } else {\n                        None\n                    };\n                    // Create our future which will run the thread and store its final result\n                    let fut = async move {\n                        if id_tracked {\n                            // Run until yield and check if we got a final result\n                            if let Some(res) = run_until_yield(thread.clone(), args).await {\n                                if let Err(e) = res.as_ref() {\n                                    self.error_callback.call(e);\n                                }\n                                if thread.status() != LuaThreadStatus::Resumable {\n                                    result_map_inner.unwrap().insert(id, res);\n                                }\n                            }\n                        } else {\n                            // Just run until yield\n                            if let Some(res) = run_until_yield(thread, args).await\n                                && let Err(e) = res.as_ref()\n                            {\n                                self.error_callback.call(e);\n                            }\n                        }\n                    };\n                    // Spawn it on the executor\n                    local_exec.spawn(fut).detach();\n                }\n            };\n\n            loop {\n                let fut_exit = self.exit.listen(); // 1\n                let fut_spawn = self.queue_spawn.wait_for_item(); // 2\n                let fut_defer = self.queue_defer.wait_for_item(); // 3\n                let fut_futs = fut_queue.wait_for_item(); // 4\n\n                // 5\n                let mut num_processed = 0;\n                let span_tick = trace_span!(\"Scheduler::tick\");\n                let fut_tick = async {\n                    local_exec.tick().await;\n                    // NOTE: Try to do as much work as possible instead of just a single tick()\n                    num_processed += 1;\n                    while local_exec.try_tick() {\n                        num_processed += 1;\n                    }\n                };\n\n                // 1 + 2 + 3 + 4 + 5\n                fut_exit\n                    .or(fut_spawn)\n                    .or(fut_defer)\n                    .or(fut_futs)\n                    .or(fut_tick.instrument(span_tick.or_current()))\n                    .await;\n\n                // Check if we should exit\n                if self.exit.get().is_some() {\n                    debug!(\"exit signal received\");\n                    break;\n                }\n\n                // Process spawned threads first, then deferred threads, then futures\n                let mut num_spawned = 0;\n                let mut num_deferred = 0;\n                let mut num_futures = 0;\n                {\n                    let _span = trace_span!(\"Scheduler::drain_spawned\").entered();\n                    for (thread, args) in self.queue_spawn.take_items() {\n                        process_thread(thread, args);\n                        num_spawned += 1;\n                    }\n                }\n                {\n                    let _span = trace_span!(\"Scheduler::drain_deferred\").entered();\n                    for (thread, args) in self.queue_defer.take_items() {\n                        process_thread(thread, args);\n                        num_deferred += 1;\n                    }\n                }\n                {\n                    let _span = trace_span!(\"Scheduler::drain_futures\").entered();\n                    for fut in fut_queue.take_items() {\n                        local_exec.spawn(fut).detach();\n                        num_futures += 1;\n                    }\n                }\n\n                // Empty executor = we didn't spawn any new Lua tasks\n                // above, and there are no remaining tasks to run later\n                let completed = local_exec.is_empty()\n                    && self.queue_spawn.is_empty()\n                    && self.queue_defer.is_empty();\n                trace!(\n                    futures_spawned = num_futures,\n                    futures_processed = num_processed,\n                    lua_threads_spawned = num_spawned,\n                    lua_threads_deferred = num_deferred,\n                    \"loop\"\n                );\n                if completed {\n                    break;\n                }\n            }\n        };\n\n        // Run the executor inside a span until all lua threads complete\n        self.set_status(Status::Running);\n        main_exec.run(fut).await;\n        self.set_status(Status::Completed);\n\n        // Clean up\n        self.lua\n            .remove_app_data::<WeakArc<Executor>>()\n            .expect(ERR_METADATA_REMOVED);\n        self.lua\n            .remove_app_data::<FuturesQueue>()\n            .expect(ERR_METADATA_REMOVED);\n    }\n}\n\nimpl Drop for Scheduler {\n    fn drop(&mut self) {\n        if panicking() {\n            // Do not cause further panics if already panicking, as\n            // this may abort the program instead of safely unwinding\n            self.lua.remove_app_data::<SpawnedThreadQueue>();\n            self.lua.remove_app_data::<DeferredThreadQueue>();\n            self.lua.remove_app_data::<ThreadErrorCallback>();\n            self.lua.remove_app_data::<ThreadMap>();\n            self.lua.remove_app_data::<Exit>();\n        } else {\n            // In any other case we panic if metadata was removed incorrectly\n            self.lua\n                .remove_app_data::<SpawnedThreadQueue>()\n                .expect(ERR_METADATA_REMOVED);\n            self.lua\n                .remove_app_data::<DeferredThreadQueue>()\n                .expect(ERR_METADATA_REMOVED);\n            self.lua\n                .remove_app_data::<ThreadErrorCallback>()\n                .expect(ERR_METADATA_REMOVED);\n            self.lua\n                .remove_app_data::<ThreadMap>()\n                .expect(ERR_METADATA_REMOVED);\n            self.lua\n                .remove_app_data::<Exit>()\n                .expect(ERR_METADATA_REMOVED);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/status.rs",
    "content": "#![allow(clippy::module_name_repetitions)]\n\n/**\n    The current status of a scheduler.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]\npub enum Status {\n    /// The scheduler has not yet started running.\n    NotStarted,\n    /// The scheduler is currently running.\n    Running,\n    /// The scheduler has completed.\n    Completed,\n}\n\nimpl Status {\n    #[must_use]\n    pub const fn is_not_started(self) -> bool {\n        matches!(self, Self::NotStarted)\n    }\n\n    #[must_use]\n    pub const fn is_running(self) -> bool {\n        matches!(self, Self::Running)\n    }\n\n    #[must_use]\n    pub const fn is_completed(self) -> bool {\n        matches!(self, Self::Completed)\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/threads/id.rs",
    "content": "use std::{\n    ffi::c_void,\n    hash::{Hash, Hasher},\n};\n\nuse mlua::prelude::*;\n\n/**\n    Opaque and unique ID representing a [`LuaThread`].\n\n    Typically used for associating metadata with a thread in a structure such as a `HashMap<ThreadId, ...>`.\n\n    Note that holding a `ThreadId` does not prevent the thread from being garbage collected.\n    The actual thread may or may not still exist and be active at any given point in time.\n*/\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct ThreadId {\n    inner: *const c_void,\n}\n\nimpl From<&LuaThread> for ThreadId {\n    fn from(thread: &LuaThread) -> Self {\n        Self {\n            inner: thread.to_pointer(),\n        }\n    }\n}\n\nimpl Hash for ThreadId {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        self.inner.hash(state);\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/threads/map.rs",
    "content": "#![allow(clippy::inline_always)]\n\nuse std::{cell::RefCell, rc::Rc};\n\nuse mlua::prelude::*;\nuse rustc_hash::FxHashMap;\n\nuse crate::events::{OnceEvent, OnceListener};\n\nuse super::id::ThreadId;\n\nstruct ThreadEvent {\n    result: Option<LuaResult<LuaMultiValue>>,\n    event: OnceEvent,\n}\n\nimpl ThreadEvent {\n    fn new() -> Self {\n        Self {\n            result: None,\n            event: OnceEvent::new(),\n        }\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct ThreadMap {\n    inner: Rc<RefCell<FxHashMap<ThreadId, ThreadEvent>>>,\n}\n\nimpl ThreadMap {\n    pub fn new() -> Self {\n        let inner = Rc::new(RefCell::new(FxHashMap::default()));\n        Self { inner }\n    }\n\n    #[inline(always)]\n    pub fn track(&self, id: ThreadId) {\n        self.inner.borrow_mut().insert(id, ThreadEvent::new());\n    }\n\n    #[inline(always)]\n    pub fn is_tracked(&self, id: ThreadId) -> bool {\n        self.inner.borrow().contains_key(&id)\n    }\n\n    #[inline(always)]\n    pub fn insert(&self, id: ThreadId, result: LuaResult<LuaMultiValue>) {\n        if let Some(tracker) = self.inner.borrow_mut().get_mut(&id) {\n            tracker.result.replace(result);\n            tracker.event.notify();\n        } else {\n            panic!(\"Thread must be tracked\");\n        }\n    }\n\n    #[inline(always)]\n    pub fn listen(&self, id: ThreadId) -> OnceListener {\n        if let Some(tracker) = self.inner.borrow().get(&id) {\n            tracker.event.listen()\n        } else {\n            panic!(\"Thread must be tracked\");\n        }\n    }\n\n    #[inline(always)]\n    pub fn remove(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {\n        if let Some(mut tracker) = self.inner.borrow_mut().remove(&id) {\n            tracker.result.take()\n        } else {\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/threads/mod.rs",
    "content": "mod id;\nmod map;\n\npub use id::ThreadId;\npub(crate) use map::ThreadMap;\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/traits.rs",
    "content": "#![allow(unused_imports)]\n#![allow(clippy::missing_errors_doc)]\n\nuse std::{\n    cell::Cell, future::Future, process::ExitCode, rc::Weak as WeakRc, sync::Weak as WeakArc,\n};\n\nuse async_executor::{Executor, Task};\nuse mlua::prelude::*;\nuse tracing::trace;\n\nuse crate::{\n    exit::Exit,\n    queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},\n    scheduler::Scheduler,\n    threads::{ThreadId, ThreadMap},\n};\n\n/**\n    Trait for any struct that can be turned into an [`LuaThread`]\n    and passed to the scheduler, implemented for the following types:\n\n    - Lua threads ([`LuaThread`])\n    - Lua functions ([`LuaFunction`])\n    - Lua chunks ([`LuaChunk`])\n*/\npub trait IntoLuaThread {\n    /**\n        Converts the value into a Lua thread.\n\n        # Errors\n\n        Errors when out of memory.\n    */\n    fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread>;\n}\n\nimpl IntoLuaThread for LuaThread {\n    fn into_lua_thread(self, _: &Lua) -> LuaResult<LuaThread> {\n        Ok(self)\n    }\n}\n\nimpl IntoLuaThread for LuaFunction {\n    fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread> {\n        lua.create_thread(self)\n    }\n}\n\nimpl IntoLuaThread for LuaChunk<'_> {\n    fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread> {\n        lua.create_thread(self.into_function()?)\n    }\n}\n\nimpl<T> IntoLuaThread for &T\nwhere\n    T: IntoLuaThread + Clone,\n{\n    fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread> {\n        self.clone().into_lua_thread(lua)\n    }\n}\n\n/**\n    Trait for interacting with the current [`Scheduler`].\n\n    Provides extra methods on the [`Lua`] struct for:\n\n    - Setting the exit code and forcibly stopping the scheduler\n    - Pushing (spawning) and deferring (pushing to the back) lua threads\n    - Tracking and getting the result of lua threads\n*/\npub trait LuaSchedulerExt {\n    /**\n        Sets the exit code of the current scheduler.\n\n        See [`Scheduler::set_exit_code`] for more information.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n    */\n    fn set_exit_code(&self, code: u8);\n\n    /**\n        Pushes (spawns) a lua thread to the **front** of the current scheduler.\n\n        See [`Scheduler::push_thread_front`] for more information.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n    */\n    fn push_thread_front(\n        &self,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId>;\n\n    /**\n        Pushes (defers) a lua thread to the **back** of the current scheduler.\n\n        See [`Scheduler::push_thread_back`] for more information.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n    */\n    fn push_thread_back(\n        &self,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId>;\n\n    /**\n        Registers the given thread to be tracked within the current scheduler.\n\n        Must be called before waiting for a thread to complete or getting its result.\n    */\n    fn track_thread(&self, id: ThreadId);\n\n    /**\n        Gets the result of the given thread.\n\n        See [`Scheduler::get_thread_result`] for more information.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n    */\n    fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>>;\n\n    /**\n        Waits for the given thread to complete.\n\n        See [`Scheduler::wait_for_thread`] for more information.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n    */\n    fn wait_for_thread(&self, id: ThreadId) -> impl Future<Output = ()>;\n}\n\n/**\n    Trait for interacting with the [`Executor`] for the current [`Scheduler`].\n\n    Provides extra methods on the [`Lua`] struct for:\n\n    - Spawning thread-local (`!Send`) futures on the current executor\n    - Spawning background (`Send`) futures on the current executor\n    - Spawning blocking tasks on a separate thread pool\n*/\npub trait LuaSpawnExt {\n    /**\n        Spawns the given future on the current executor and returns its [`Task`].\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n\n        # Example usage\n\n        ```rust\n        use async_io::block_on;\n\n        use mlua::prelude::*;\n        use mlua_luau_scheduler::*;\n\n        fn main() -> LuaResult<()> {\n            let lua = Lua::new();\n\n            lua.globals().set(\n                \"spawnBackgroundTask\",\n                lua.create_async_function(|lua, ()| async move {\n                    lua.spawn(async move {\n                        println!(\"Hello from background task!\");\n                    }).await;\n                    Ok(())\n                })?\n            )?;\n\n            let sched = Scheduler::new(lua.clone());\n            sched.push_thread_front(lua.load(\"spawnBackgroundTask()\"), ());\n            block_on(sched.run());\n\n            Ok(())\n        }\n        ```\n    */\n    fn spawn<F, T>(&self, fut: F) -> Task<T>\n    where\n        F: Future<Output = T> + Send + 'static,\n        T: Send + 'static;\n\n    /**\n        Spawns the given thread-local future on the current executor.\n\n        Note that this future will run detached and always to completion,\n        preventing the [`Scheduler`] was spawned on from completing until done.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n\n        # Example usage\n\n        ```rust\n        use async_io::block_on;\n\n        use mlua::prelude::*;\n        use mlua_luau_scheduler::*;\n\n        fn main() -> LuaResult<()> {\n            let lua = Lua::new();\n\n            lua.globals().set(\n                \"spawnLocalTask\",\n                lua.create_async_function(|lua, ()| async move {\n                    lua.spawn_local(async move {\n                        println!(\"Hello from local task!\");\n                    });\n                    Ok(())\n                })?\n            )?;\n\n            let sched = Scheduler::new(lua.clone());\n            sched.push_thread_front(lua.load(\"spawnLocalTask()\"), ());\n            block_on(sched.run());\n\n            Ok(())\n        }\n        ```\n    */\n    fn spawn_local<F>(&self, fut: F)\n    where\n        F: Future<Output = ()> + 'static;\n\n    /**\n        Spawns the given blocking function and returns its [`Task`].\n\n        This function will run on a separate thread pool and not block the current executor.\n\n        # Panics\n\n        Panics if called outside of a running [`Scheduler`].\n\n        # Example usage\n\n        ```rust\n        use async_io::block_on;\n\n        use mlua::prelude::*;\n        use mlua_luau_scheduler::*;\n\n        fn main() -> LuaResult<()> {\n            let lua = Lua::new();\n\n            lua.globals().set(\n                \"spawnBlockingTask\",\n                lua.create_async_function(|lua, ()| async move {\n                    lua.spawn_blocking(|| {\n                        println!(\"Hello from blocking task!\");\n                    }).await;\n                    Ok(())\n                })?\n            )?;\n\n            let sched = Scheduler::new(lua.clone());\n            sched.push_thread_front(lua.load(\"spawnBlockingTask()\"), ());\n            block_on(sched.run());\n\n            Ok(())\n        }\n        ```\n    */\n    fn spawn_blocking<F, T>(&self, f: F) -> Task<T>\n    where\n        F: FnOnce() -> T + Send + 'static,\n        T: Send + 'static;\n}\n\nimpl LuaSchedulerExt for Lua {\n    fn set_exit_code(&self, code: u8) {\n        let exit = self\n            .app_data_ref::<Exit>()\n            .expect(\"exit code can only be set from within an active scheduler\");\n        exit.set(code);\n    }\n\n    fn push_thread_front(\n        &self,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId> {\n        let queue = self\n            .app_data_ref::<SpawnedThreadQueue>()\n            .expect(\"lua threads can only be pushed from within an active scheduler\");\n        queue.push_item(self, thread, args)\n    }\n\n    fn push_thread_back(\n        &self,\n        thread: impl IntoLuaThread,\n        args: impl IntoLuaMulti,\n    ) -> LuaResult<ThreadId> {\n        let queue = self\n            .app_data_ref::<DeferredThreadQueue>()\n            .expect(\"lua threads can only be pushed from within an active scheduler\");\n        queue.push_item(self, thread, args)\n    }\n\n    fn track_thread(&self, id: ThreadId) {\n        let map = self\n            .app_data_ref::<ThreadMap>()\n            .expect(\"lua threads can only be tracked from within an active scheduler\");\n        map.track(id);\n    }\n\n    fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {\n        let map = self\n            .app_data_ref::<ThreadMap>()\n            .expect(\"lua threads results can only be retrieved from within an active scheduler\");\n        map.remove(id)\n    }\n\n    fn wait_for_thread(&self, id: ThreadId) -> impl Future<Output = ()> {\n        let map = self\n            .app_data_ref::<ThreadMap>()\n            .expect(\"lua threads results can only be retrieved from within an active scheduler\");\n        map.listen(id)\n    }\n}\n\nimpl LuaSpawnExt for Lua {\n    fn spawn<F, T>(&self, fut: F) -> Task<T>\n    where\n        F: Future<Output = T> + Send + 'static,\n        T: Send + 'static,\n    {\n        let exec = self\n            .app_data_ref::<WeakArc<Executor>>()\n            .expect(\"tasks can only be spawned within an active scheduler\")\n            .upgrade()\n            .expect(\"executor was dropped\");\n        trace!(\"spawning future on executor\");\n        exec.spawn(fut)\n    }\n\n    fn spawn_local<F>(&self, fut: F)\n    where\n        F: Future<Output = ()> + 'static,\n    {\n        let queue = self\n            .app_data_ref::<FuturesQueue>()\n            .expect(\"tasks can only be spawned within an active scheduler\");\n        trace!(\"spawning local task on executor\");\n        queue.push_item(fut);\n    }\n\n    fn spawn_blocking<F, T>(&self, f: F) -> Task<T>\n    where\n        F: FnOnce() -> T + Send + 'static,\n        T: Send + 'static,\n    {\n        let exec = self\n            .app_data_ref::<WeakArc<Executor>>()\n            .expect(\"tasks can only be spawned within an active scheduler\")\n            .upgrade()\n            .expect(\"executor was dropped\");\n        trace!(\"spawning blocking task on executor\");\n        exec.spawn(blocking::unblock(f))\n    }\n}\n"
  },
  {
    "path": "crates/mlua-luau-scheduler/src/util.rs",
    "content": "use futures_lite::StreamExt;\nuse mlua::prelude::*;\nuse tracing::instrument;\n\n/**\n    Runs a Lua thread until it manually yields (using coroutine.yield), errors, or completes.\n\n    May return `None` if the thread was cancelled.\n\n    Otherwise returns the values yielded by the thread, or the error that caused it to stop.\n*/\n#[instrument(level = \"trace\", name = \"Scheduler::run_until_yield\", skip_all)]\npub(crate) async fn run_until_yield(\n    thread: LuaThread,\n    args: LuaMultiValue,\n) -> Option<LuaResult<LuaMultiValue>> {\n    let mut stream = thread.into_async(args).expect(\"thread must be resumable\");\n    /*\n        NOTE: It is very important that we drop the thread/stream as\n        soon as we are done, it takes up valuable Lua registry space\n        and detached tasks will not drop until the executor does\n\n        https://github.com/smol-rs/smol/issues/294\n\n        We also do not unwrap here since returning `None` is expected behavior for cancellation.\n\n        Even though we are converting into a stream, and then immediately running it,\n        the future may still be cancelled before it is polled, which gives us None.\n    */\n    stream.next().await\n}\n\n/**\n    Checks if the given [`LuaValue`] is the async `POLL_PENDING` constant.\n*/\n#[inline]\npub(crate) fn is_poll_pending(value: &LuaValue) -> bool {\n    value\n        .as_light_userdata()\n        .is_some_and(|l| l == Lua::poll_pending())\n}\n\n/**\n    Wrapper struct to accept either a Lua thread or a Lua function as function argument.\n\n    [`LuaThreadOrFunction::into_thread`] may be used to convert the value into a Lua thread.\n*/\n#[derive(Clone)]\npub(crate) enum LuaThreadOrFunction {\n    Thread(LuaThread),\n    Function(LuaFunction),\n}\n\nimpl LuaThreadOrFunction {\n    pub(super) fn into_thread(self, lua: &Lua) -> LuaResult<LuaThread> {\n        match self {\n            Self::Thread(t) => Ok(t),\n            Self::Function(f) => lua.create_thread(f),\n        }\n    }\n}\n\nimpl FromLua for LuaThreadOrFunction {\n    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {\n        match value {\n            LuaValue::Thread(t) => Ok(Self::Thread(t)),\n            LuaValue::Function(f) => Ok(Self::Function(f)),\n            value => Err(LuaError::FromLuaConversionError {\n                from: value.type_name(),\n                to: \"LuaThreadOrFunction\".to_string(),\n                message: Some(\"Expected thread or function\".to_string()),\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "rokit.toml",
    "content": "[tools]\nluau-lsp = \"JohnnyMorganz/luau-lsp@1.54.0\"\nlune = \"lune-org/lune@0.10.4\"\nstylua = \"JohnnyMorganz/StyLua@2.3.0\"\n"
  },
  {
    "path": "scripts/analyze.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nlune run scripts/analyze_copy_typedefs\n\nluau-lsp analyze \\\n\t--platform=standard \\\n\t--settings=\".vscode/settings.json\" \\\n\t--ignore=\"tests/roblox/rbx-test-files/**\" \\\n\t.lune crates scripts tests\n"
  },
  {
    "path": "scripts/analyze_copy_typedefs.luau",
    "content": "local fs = require(\"@lune/fs\")\n\nfs.writeDir(\"./types\")\n\nfor _, dir in fs.readDir(\"./crates\") do\n\tlocal std = string.match(dir, \"^lune%-std%-(%w+)$\")\n\tif std ~= nil then\n\t\tlocal from = `./crates/{dir}/types.d.luau`\n\t\tif fs.isFile(from) then\n\t\t\tlocal to = `./types/{std}.luau`\n\t\t\tfs.copy(from, to, true)\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "scripts/brick_color.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal net = require(\"@lune/net\")\nlocal serde = require(\"@lune/serde\")\n\nlocal URL =\n\t\"https://gist.githubusercontent.com/Anaminus/49ac255a68e7a7bc3cdd72b602d5071f/raw/f1534dcae312dbfda716b7677f8ac338b565afc3/BrickColor.json\"\n\nlocal json = serde.decode(\"json\", net.request(URL).body)\n\nlocal contents = \"\"\n\ncontents ..= \"const BRICK_COLOR_DEFAULT: u16 = \"\ncontents ..= tostring(json.Default)\ncontents ..= \";\\n\"\n\ncontents ..= \"\\nconst BRICK_COLOR_VALUES: &[(u16, &str, (u8, u8, u8))] = &[\\n\"\nfor _, color in json.BrickColors do\n\tcontents ..= string.format(\n\t\t'    (%d, \"%s\", (%d, %d, %d)),\\n',\n\t\tcolor.Number,\n\t\tcolor.Name,\n\t\tcolor.Color8[1],\n\t\tcolor.Color8[2],\n\t\tcolor.Color8[3]\n\t)\nend\ncontents ..= \"];\\n\"\n\ncontents ..= \"\\nconst BRICK_COLOR_PALETTE: &[u16] = &[\"\ncontents ..= table.concat(json.Palette, \", \")\ncontents ..= \"];\\n\"\n\ncontents ..= \"\\nconst BRICK_COLOR_CONSTRUCTORS: &[(&str, u16)] = &[\"\nfor key, number in json.Constructors do\n\tcontents ..= string.format('    (\"%s\", %d),\\n', key, number)\nend\ncontents ..= \"];\\n\"\n\nfs.writeFile(\"packages/lib-roblox/scripts/brick_color.rs\", contents)\n"
  },
  {
    "path": "scripts/font_enum_map.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\n-- NOTE: This must be ran in Roblox Studio to get up-to-date font values\n\nlocal contents = \"\"\n\ncontents ..= \"\\ntype FontData = (&'static str, FontWeight, FontStyle);\\n\"\n\nlocal ENUM_IMPLEMENTATION = [[\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub(crate) enum <<ENUM_NAME>> {\n\t<<ENUM_NAMES>>\n}\n\nimpl <<ENUM_NAME>> {\n\tpub(crate) fn as_<<NUMBER_TYPE>>(&self) -> <<NUMBER_TYPE>> {\n\t\tmatch self {\n\t\t\t<<ENUM_TO_NUMBERS>>\n\t\t}\n\t}\n\n\tpub(crate) fn from_<<NUMBER_TYPE>>(n: <<NUMBER_TYPE>>) -> Option<Self> {\n\t\tmatch n {\n\t\t\t<<NUMBERS_TO_ENUM>>\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\nimpl Default for <<ENUM_NAME>> {\n\tfn default() -> Self {\n\t\tSelf::<<DEFAULT_NAME>>\n\t}\n}\n\nimpl std::str::FromStr for <<ENUM_NAME>> {\n\ttype Err = &'static str;\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tmatch s {\n\t\t\t<<STRINGS_TO_ENUM>>\n\t\t\t_ => Err(\"Unknown <<ENUM_NAME>>\"),\n\t\t}\n\t}\n}\n\nimpl std::fmt::Display for <<ENUM_NAME>> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(\n\t\t\tf,\n\t\t\t\"{}\",\n\t\t\tmatch self {\n\t\t\t\t<<ENUM_TO_STRINGS>>\n\t\t\t}\n\t\t)\n\t}\n}\n\nimpl<'lua> FromLua<'lua> for <<ENUM_NAME>> {\n    fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {\n        let mut message = None;\n        if let LuaValue::UserData(ud) = &lua_value {\n            let value = ud.borrow::<EnumItem>()?;\n            if value.parent.desc.name == \"<<ENUM_NAME>>\" {\n                if let Ok(value) = <<ENUM_NAME>>::from_str(&value.name) {\n                    return Ok(value);\n                } else {\n                    message = Some(format!(\n                        \"Found unknown Enum.<<ENUM_NAME>> value '{}'\",\n                        value.name\n                    ));\n                }\n            } else {\n                message = Some(format!(\n                    \"Expected Enum.<<ENUM_NAME>>, got Enum.{}\",\n                    value.parent.desc.name\n                ));\n            }\n        }\n        Err(LuaError::FromLuaConversionError {\n            from: lua_value.type_name(),\n            to: \"Enum.<<ENUM_NAME>>\",\n            message,\n        })\n    }\n}\n\nimpl<'lua> ToLua<'lua> for <<ENUM_NAME>> {\n    fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {\n        match EnumItem::from_enum_name_and_name(\"<<ENUM_NAME>>\", self.to_string()) {\n            Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),\n            None => Err(LuaError::ToLuaConversionError {\n                from: \"<<ENUM_NAME>>\",\n                to: \"EnumItem\",\n                message: Some(format!(\"Found unknown Enum.<<ENUM_NAME>> value '{}'\", self)),\n            }),\n        }\n    }\n}\n\n]]\n\n-- FontWeight enum and implementation\n\nlocal function makeRustEnum(enum, default, numType: string)\n\tlocal name = tostring(enum)\n\tname = string.gsub(name, \"^Enum.\", \"\")\n\n\tlocal defaultName = tostring(default)\n\tdefaultName = string.gsub(defaultName, \"^Enum.\", \"\")\n\tdefaultName = string.gsub(defaultName, \"^\" .. name .. \".\", \"\")\n\n\tlocal enumNames = \"\"\n\tlocal enumToNumbers = \"\"\n\tlocal numbersToEnum = \"\"\n\tlocal stringsToEnum = \"\"\n\tlocal enumToStrings = \"\"\n\n\tfor _, enum in enum:GetEnumItems() do\n\t\tenumNames ..= `\\n{enum.Name},`\n\t\tenumToNumbers ..= `\\nSelf::{enum.Name} => {enum.Value},`\n\t\tnumbersToEnum ..= `\\n{enum.Value} => Some(Self::{enum.Name}),`\n\t\tstringsToEnum ..= `\\n\"{enum.Name}\" => Ok(Self::{enum.Name}),`\n\t\tenumToStrings ..= `\\nSelf::{enum.Name} => \"{enum.Name}\",`\n\tend\n\n\tlocal mappings: { [string]: string } = {\n\t\t[\"<<ENUM_NAME>>\"] = name,\n\t\t[\"<<DEFAULT_NAME>>\"] = defaultName,\n\t\t[\"<<NUMBER_TYPE>>\"] = numType,\n\t\t[\"<<ENUM_NAMES>>\"] = enumNames,\n\t\t[\"<<ENUM_TO_NUMBERS>>\"] = enumToNumbers,\n\t\t[\"<<ENUM_TO_STRINGS>>\"] = enumToStrings,\n\t\t[\"<<NUMBERS_TO_ENUM>>\"] = numbersToEnum,\n\t\t[\"<<STRINGS_TO_ENUM>>\"] = stringsToEnum,\n\t}\n\n\tlocal result = ENUM_IMPLEMENTATION\n\tfor key, replacement in mappings do\n\t\tresult = string.gsub(result, \"(\\t*)\" .. key, function(tabbing)\n\t\t\tlocal spacing = string.gsub(tabbing, \"\\t\", \"    \")\n\t\t\tlocal inner = string.gsub(replacement, \"\\n\", \"\\n\" .. spacing)\n\t\t\tinner = string.gsub(inner, \"^\\n+\", \"\")\n\t\t\treturn inner\n\t\tend)\n\tend\n\treturn result\nend\n\ncontents ..= makeRustEnum(Enum.FontWeight, Enum.FontWeight.Regular, \"u16\")\ncontents ..= \"\\n\"\n\ncontents ..= makeRustEnum(Enum.FontStyle, Enum.FontStyle.Normal, \"u8\")\ncontents ..= \"\\n\"\n\n-- Font constant map from enum to font data\n\nlocal longestNameLen = 0\nlocal longestFamilyLen = 0\nlocal longestWeightLen = 0\nfor _, enum in Enum.Font:GetEnumItems() do\n\tlongestNameLen = math.max(longestNameLen, #enum.Name)\n\tif enum ~= Enum.Font.Unknown then\n\t\tlocal font = Font.fromEnum(enum)\n\t\tlongestFamilyLen = math.max(longestFamilyLen, #font.Family)\n\t\tlongestWeightLen = math.max(longestWeightLen, #font.Weight.Name)\n\tend\nend\n\ncontents ..= \"\\n#[rustfmt::skip]\\nconst FONT_ENUM_MAP: &[(&str, Option<FontData>)] = &[\\n\"\nfor _, enum in Enum.Font:GetEnumItems() do\n\tif enum == Enum.Font.Unknown then\n\t\tcontents ..= string.format(\n\t\t\t'    (\"Unknown\",%s None),\\n',\n\t\t\tstring.rep(\" \", longestNameLen - #enum.Name)\n\t\t)\n\telse\n\t\tlocal font = Font.fromEnum(enum)\n\t\tcontents ..= string.format(\n\t\t\t'    (\"%s\",%s Some((\"%s\",%s FontWeight::%s,%s FontStyle::%s))),\\n',\n\t\t\tenum.Name,\n\t\t\tstring.rep(\" \", longestNameLen - #enum.Name),\n\t\t\tfont.Family,\n\t\t\tstring.rep(\" \", longestFamilyLen - #font.Family),\n\t\t\tfont.Weight.Name,\n\t\t\tstring.rep(\" \", longestWeightLen - #font.Weight.Name),\n\t\t\tfont.Style.Name\n\t\t)\n\tend\nend\ncontents ..= \"];\\n\"\n\nprint(contents)\n"
  },
  {
    "path": "scripts/format-check.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nstylua .lune crates scripts tests \\\n\t--glob \"tests/**/*.luau\" \\\n\t--glob \"!tests/roblox/rbx-test-files/**\" \\\n\t--check\n\ncargo fmt --check\n"
  },
  {
    "path": "scripts/format.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nstylua .lune crates scripts tests \\\n\t--glob \"tests/**/*.luau\" \\\n\t--glob \"!tests/roblox/rbx-test-files/**\"\n\ncargo fmt\n"
  },
  {
    "path": "scripts/generate_compression_test_files.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal process = require(\"@lune/process\")\nlocal serde = require(\"@lune/serde\")\nlocal stdio = require(\"@lune/stdio\")\n\nlocal TEST_FILES_DIR = process.cwd .. \"tests/serde/test-files\"\nlocal INPUT_FILE = TEST_FILES_DIR .. \"/loremipsum.txt\"\nlocal TEMP_FILE = TEST_FILES_DIR .. \"/loremipsum.temp\"\n\nlocal INPUT_FILE_CONTENTS = fs.readFile(INPUT_FILE)\n\n-- Make some utility functions for viewing unexpected differences in files easier\n\nlocal function stringAsHex(str: string): string\n\tlocal hex = {}\n\tfor i = 1, #str do\n\t\ttable.insert(hex, string.format(\"%02x\", string.byte(str, i)))\n\tend\n\treturn table.concat(hex)\nend\n\nlocal function hexDiff(a: string, b: string): string\n\tlocal diff = {}\n\tfor i = 1, math.max(#a, #b) do\n\t\tlocal aByte = if #a >= i then string.byte(a, i) else nil\n\t\tlocal bByte = if #b >= i then string.byte(b, i) else nil\n\t\tif aByte == nil then\n\t\t\ttable.insert(\n\t\t\t\tdiff,\n\t\t\t\tstring.format(\n\t\t\t\t\t\"%s%02x%s\",\n\t\t\t\t\tstdio.color(\"green\"),\n\t\t\t\t\tassert(bByte, \"unreachable\"),\n\t\t\t\t\tstdio.color(\"reset\")\n\t\t\t\t)\n\t\t\t)\n\t\telseif bByte == nil then\n\t\t\ttable.insert(\n\t\t\t\tdiff,\n\t\t\t\tstring.format(\n\t\t\t\t\t\"%s%02x%s\",\n\t\t\t\t\tstdio.color(\"red\"),\n\t\t\t\t\tassert(aByte, \"unreachable\"),\n\t\t\t\t\tstdio.color(\"reset\")\n\t\t\t\t)\n\t\t\t)\n\t\telse\n\t\t\tif aByte == bByte then\n\t\t\t\ttable.insert(diff, string.format(\"%02x\", aByte))\n\t\t\telse\n\t\t\t\ttable.insert(\n\t\t\t\t\tdiff,\n\t\t\t\t\tstring.format(\n\t\t\t\t\t\t\"%s%02x%s\",\n\t\t\t\t\t\tstdio.color(\"yellow\"),\n\t\t\t\t\t\tassert(bByte, \"unreachable\"),\n\t\t\t\t\t\tstdio.color(\"reset\")\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\tend\n\t\tend\n\tend\n\treturn table.concat(diff)\nend\n\nlocal function stripCwdIfPresent(path: string): string\n\tif string.sub(path, 1, #process.cwd) == process.cwd then\n\t\treturn string.sub(path, #process.cwd + 1)\n\telse\n\t\treturn path\n\tend\nend\n\n-- Make some processing functions for manipulating output of certain commands\n\nlocal function processNoop(output: string): string\n\treturn output\nend\n\nlocal function processGzipSetOsUnknown(output: string): string\n\t-- This will set the os bits to be \"unknown\" so that the\n\t-- output is deterministic and consistent with serde lib\n\t-- https://www.rfc-editor.org/rfc/rfc1952#section-2.3.1\n\tlocal buf = buffer.fromstring(output)\n\tbuffer.writeu8(buf, 9, 0xFF)\n\treturn buffer.tostring(buf)\nend\n\nlocal function processLz4PrependSize(output: string): string\n\t-- Lune supports only lz4 with the decompressed size\n\t-- prepended to it, but the lz4 command line tool\n\t-- doesn't add this automatically, so we have to\n\t-- TODO: Remove this in the future when no longer needed\n\tlocal buf = buffer.create(4 + #output)\n\tbuffer.writeu32(buf, 0, #INPUT_FILE_CONTENTS)\n\tbuffer.writestring(buf, 4, output)\n\treturn buffer.tostring(buf)\nend\n\n-- Make sure we have all of the different compression tools installed,\n-- note that on macos we do not use the system-installed compression\n-- tools, instead preferring to use homebrew-installed (gnu) ones\n\nlocal BIN_BROTLI = if process.os == \"macos\" then \"/opt/homebrew/bin/brotli\" else \"brotli\"\nlocal BIN_GZIP = if process.os == \"macos\" then \"/opt/homebrew/bin/gzip\" else \"gzip\"\nlocal BIN_LZ4 = if process.os == \"macos\" then \"/opt/homebrew/bin/lz4\" else \"lz4\"\nlocal BIN_ZLIB = if process.os == \"macos\" then \"/opt/homebrew/bin/pigz\" else \"pigz\"\nlocal BIN_ZSTD = if process.os == \"macos\" then \"/opt/homebrew/bin/zstd\" else \"zstd\"\n\nlocal function checkInstalled(program: string, args: { string }?)\n\tprint(\"Checking if\", program, \"is installed\")\n\tlocal result = process.exec(program, args)\n\tif not result.ok then\n\t\tstdio.ewrite(string.format(\"Program '%s' is not installed\\n\", program))\n\t\tprocess.exit(1)\n\tend\nend\n\ncheckInstalled(BIN_BROTLI, { \"--version\" })\ncheckInstalled(BIN_GZIP, { \"--version\" })\ncheckInstalled(BIN_LZ4, { \"--version\" })\ncheckInstalled(BIN_ZLIB, { \"--version\" })\ncheckInstalled(BIN_ZSTD, { \"--version\" })\n\n-- Run them to generate files\n\nlocal function run(program: string, args: { string }): string\n\tlocal result = process.exec(program, args)\n\tif not result.ok then\n\t\tstdio.ewrite(string.format(\"Command '%s' failed\\n\", program))\n\t\tif #result.stdout > 0 then\n\t\t\tstdio.ewrite(\"stdout:\\n\")\n\t\t\tstdio.ewrite(result.stdout)\n\t\t\tstdio.ewrite(\"\\n\")\n\t\tend\n\t\tif #result.stderr > 0 then\n\t\t\tstdio.ewrite(\"stderr:\\n\")\n\t\t\tstdio.ewrite(result.stderr)\n\t\t\tstdio.ewrite(\"\\n\")\n\t\tend\n\t\tprocess.exit(1)\n\telse\n\t\tif #result.stdout > 0 then\n\t\t\tstdio.ewrite(\"stdout:\\n\")\n\t\t\tstdio.ewrite(result.stdout)\n\t\t\tstdio.ewrite(\"\\n\")\n\t\tend\n\tend\n\treturn result.stdout\nend\n\nlocal OUTPUT_FILES = {\n\t{\n\t\tcommand = BIN_BROTLI,\n\t\tformat = \"brotli\" :: serde.CompressDecompressFormat,\n\t\targs = { \"--best\", \"-w\", \"22\", TEMP_FILE },\n\t\toutput = TEMP_FILE .. \".br\",\n\t\tprocess = processNoop,\n\t\tfinal = INPUT_FILE .. \".br\",\n\t},\n\t{\n\t\tcommand = BIN_GZIP,\n\t\tformat = \"gzip\" :: serde.CompressDecompressFormat,\n\t\targs = { \"--best\", \"--no-name\", \"--synchronous\", TEMP_FILE },\n\t\toutput = TEMP_FILE .. \".gz\",\n\t\tprocess = processGzipSetOsUnknown,\n\t\tfinal = INPUT_FILE .. \".gz\",\n\t},\n\t{\n\t\tcommand = BIN_LZ4,\n\t\tformat = \"lz4\" :: serde.CompressDecompressFormat,\n\t\targs = { \"--best\", TEMP_FILE, TEMP_FILE .. \".lz4\" },\n\t\toutput = TEMP_FILE .. \".lz4\",\n\t\tprocess = processLz4PrependSize,\n\t\tfinal = INPUT_FILE .. \".lz4\",\n\t},\n\t{\n\t\tcommand = BIN_ZLIB,\n\t\tformat = \"zlib\" :: serde.CompressDecompressFormat,\n\t\targs = { \"--best\", \"--zlib\", TEMP_FILE },\n\t\toutput = TEMP_FILE .. \".zz\",\n\t\tprocess = processNoop,\n\t\tfinal = INPUT_FILE .. \".z\",\n\t},\n\t{\n\t\tcommand = BIN_ZSTD,\n\t\tformat = \"zstd\" :: serde.CompressDecompressFormat,\n\t\targs = { \"-c\", \"--ultra\", \"-22\", TEMP_FILE },\n\t\toutput = TEMP_FILE .. \".zst\",\n\t\tprocess = processNoop,\n\t\tfinal = INPUT_FILE .. \".zst\",\n\t},\n}\n\nfor _, spec in OUTPUT_FILES do\n\t-- Write the temp file for the compression tool to read and use, then\n\t-- remove it, some tools may remove it on their own, so we ignore errors\n\tfs.writeFile(TEMP_FILE, INPUT_FILE_CONTENTS)\n\tlocal argsToDisplay = {}\n\tfor _, arg in spec.args do\n\t\ttable.insert(argsToDisplay, stripCwdIfPresent(arg))\n\tend\n\tprint(\n\t\t\"\\nRunning compression\\n  Cmd:  \",\n\t\tspec.command,\n\t\t\"\\n  Args: \",\n\t\tstdio.format(table.unpack(argsToDisplay))\n\t)\n\tlocal output = run(spec.command, spec.args)\n\tif #output > 0 then\n\t\tprint(\"Output:\", output)\n\tend\n\tpcall(fs.removeFile, TEMP_FILE)\n\n\t-- Read the compressed output file that is now supposed to exist\n\tlocal compressedContents\n\tpcall(function()\n\t\tcompressedContents = fs.readFile(spec.output)\n\t\tcompressedContents = spec.process(compressedContents)\n\t\tfs.removeFile(spec.output)\n\tend)\n\tif not compressedContents then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Nothing was written to output file while running %s:\\n%s\",\n\t\t\t\tspec.command,\n\t\t\t\tspec.output\n\t\t\t)\n\t\t)\n\tend\n\n\t-- If the newly compressed contents do not match the existing contents,\n\t-- warn the user about this and ask if they want to overwrite the file\n\tlocal existingContents = fs.readFile(spec.final)\n\tif compressedContents ~= existingContents then\n\t\tstdio.ewrite(\"\\nCompressed file does not match existing contents!\")\n\t\tstdio.ewrite(\"\\n\\nExisting:\\n\")\n\t\tstdio.ewrite(stringAsHex(existingContents))\n\t\tstdio.ewrite(\"\\n\\nCompressed:\\n\")\n\t\tstdio.ewrite(hexDiff(existingContents, compressedContents))\n\t\tstdio.ewrite(\"\\n\\n\")\n\t\tlocal confirm = stdio.prompt(\"confirm\", \"Do you want to continue?\")\n\t\tif confirm == true then\n\t\t\tprint(\"Overwriting file!\")\n\t\telse\n\t\t\tstdio.ewrite(\"\\n\\nAborting...\\n\")\n\t\t\tprocess.exit(1)\n\t\t\treturn\n\t\tend\n\tend\n\n\t-- Check if the compressed contents can be decompressed using serde\n\tlocal decompressSuccess, decompressedContents =\n\t\tpcall(serde.decompress, spec.format, compressedContents)\n\tif not decompressSuccess then\n\t\tstdio.ewrite(\"\\nCompressed contents could not be decompressed using serde!\")\n\t\tstdio.ewrite(\"\\n\\nCompressed:\\n\")\n\t\tstdio.ewrite(stringAsHex(compressedContents))\n\t\tstdio.ewrite(\"\\n\\nError:\\n\")\n\t\tstdio.ewrite(tostring(decompressedContents))\n\t\tstdio.ewrite(\"\\n\\n\")\n\t\tlocal confirm = stdio.prompt(\"confirm\", \"Do you want to continue?\")\n\t\tif confirm == true then\n\t\t\tprint(\"Ignoring decompression error!\")\n\t\telse\n\t\t\tstdio.ewrite(\"\\n\\nAborting...\\n\")\n\t\t\tprocess.exit(1)\n\t\t\treturn\n\t\tend\n\tend\n\tif decompressedContents ~= INPUT_FILE_CONTENTS then\n\t\tstdio.ewrite(\"\\nCompressed contents were not decompressable properly using serde!\")\n\t\tstdio.ewrite(\"\\n\\nOriginal:\\n\")\n\t\tstdio.ewrite(INPUT_FILE_CONTENTS)\n\t\tstdio.ewrite(\"\\n\\nDecompressed:\\n\")\n\t\tstdio.ewrite(decompressedContents)\n\t\tstdio.ewrite(\"\\n\\n\")\n\t\tlocal confirm = stdio.prompt(\"confirm\", \"Do you want to continue?\")\n\t\tif confirm == true then\n\t\t\tprint(\"Ignoring decompression mismatch!\")\n\t\telse\n\t\t\tstdio.ewrite(\"\\n\\nAborting...\\n\")\n\t\t\tprocess.exit(1)\n\t\t\treturn\n\t\tend\n\tend\n\n\t-- Check if the compressed contents match the serde compressed contents,\n\t-- if they don't this will 100% make the tests fail, but maybe we are doing\n\t-- it because we are updating the serde library and need to update test files\n\tlocal serdeContents = serde.compress(spec.format, INPUT_FILE_CONTENTS)\n\tif compressedContents ~= serdeContents then\n\t\tstdio.ewrite(\"\\nTemp file does not match contents compressed with serde!\")\n\t\tstdio.ewrite(\"\\nThis will caused the new compressed file to fail tests.\")\n\t\tstdio.ewrite(\"\\n\\nSerde:\\n\")\n\t\tstdio.ewrite(stringAsHex(serdeContents))\n\t\tstdio.ewrite(\"\\n\\nCompressed:\\n\")\n\t\tstdio.ewrite(hexDiff(serdeContents, compressedContents))\n\t\tstdio.ewrite(\"\\n\\n\")\n\t\tlocal confirm = stdio.prompt(\"confirm\", \"Do you want to continue?\")\n\t\tif confirm == true then\n\t\t\tprint(\"Writing new file!\")\n\t\telse\n\t\t\tstdio.ewrite(\"\\n\\nAborting...\\n\")\n\t\t\tprocess.exit(1)\n\t\t\treturn\n\t\tend\n\tend\n\n\t-- Finally, write the new compressed file\n\tfs.writeFile(spec.final, compressedContents)\n\tprint(\"Wrote new file successfully to\", stripCwdIfPresent(spec.final))\nend\n"
  },
  {
    "path": "scripts/get-version.sh",
    "content": "#!/usr/bin/env bash\n\n# Since we are using cargo workspaces, reading the actual version\n# of the CLI is slightly more complicated - which is why this exists\n\nset -euo pipefail\n\nCLI_MANIFEST=$(cargo read-manifest --manifest-path crates/lune/Cargo.toml)\nCLI_VERSION=$(echo $CLI_MANIFEST | jq -r .version)\n\necho $CLI_VERSION\n"
  },
  {
    "path": "scripts/physical_properties_enum_map.luau",
    "content": "--!nocheck\n--!nolint UnknownGlobal\n\n-- NOTE: This must be ran in Roblox Studio to get up-to-date enum values\n\nlocal contents = \"\"\n\nlocal longestNameLen = 0\nfor _, enum in Enum.Material:GetEnumItems() do\n\tlongestNameLen = math.max(longestNameLen, #enum.Name)\nend\n\ncontents ..= \"\\n#[rustfmt::skip]\\nconst MATERIAL_ENUM_MAP: &[(&str, f32, f32, f32, f32, f32, f32)] = &[\\n\"\nfor _, enum in Enum.Material:GetEnumItems() do\n\tlocal props = PhysicalProperties.new(enum)\n\tcontents ..= string.format(\n\t\t'    (\"%s\",%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f),\\n',\n\t\tenum.Name,\n\t\tstring.rep(\" \", longestNameLen - #enum.Name),\n\t\tprops.Density,\n\t\tprops.Friction,\n\t\tprops.Elasticity,\n\t\tprops.FrictionWeight,\n\t\tprops.ElasticityWeight,\n\t\tprops.AcousticAbsorption\n\t)\nend\ncontents ..= \"];\\n\"\n\nprint(contents)\n"
  },
  {
    "path": "scripts/unpack-releases.sh",
    "content": "#!/usr/bin/env bash\n\n# This script is used to move a group of zipped and nested\n# release artifacts, and is used in the GitHub workflow so\n# that we can upload all artifacts to the release easier\n\nCWD=\"$PWD\"\n\n# We should have gotten RELEASES_DIR as the first arg to this script\nRELEASES_DIR=\"$1\"\nif [ -z \"$RELEASES_DIR\" ]; then\n    echo \"Usage: $0 <RELEASES_DIR>\"\n    exit 1\nfi\nif [ ! -d \"$RELEASES_DIR\" ]; then\n    echo \"Releases directory '$RELEASES_DIR' does not exist\"\n    exit 1\nfi\n\n# Navigate into the releases dir and print out verbose info about it\ncd \"$RELEASES_DIR\"\necho \"\"\necho \"Releases dir:\"\nls -lhrt\n\n# Look for and move out zip files into a common directory\necho \"\"\necho \"Searching for zipped releases...\"\nfor DIR in * ; do\n\tif [ -d \"$DIR\" ]; then\n\t\tcd \"$DIR\"\n\t\tfor FILE in * ; do\n\t\t\tif [ ! -d \"$FILE\" ]; then\n\t\t\t\tif [ \"$FILE\" = \"release.zip\" ]; then\n\t\t\t\t\techo \"Found zipped release '$DIR'\"\n\t\t\t\t\tmv \"$FILE\" \"../$DIR.zip\"\n\t\t\t\t\trm -rf \"../$DIR/\"\n\t\t\t\tfi\n\t\t\tfi\n\t\tdone\n\t\tcd ..\n\tfi\ndone\n\n# Finally, print out verbose info about the releases dir again,\n# so that anyone inspecting the script output can see that the\n# zipped releases have been moved out successfully\necho \"\"\necho \"Releases dir:\"\nls -lhrt\n\n# Go back to cwd\ncd \"$CWD\"\n"
  },
  {
    "path": "scripts/zip-release.sh",
    "content": "#!/usr/bin/env bash\n\n# This script is used to zip up the built binary for release,\n# and is used in the GitHub workflow to create a release artifact\n\nBIN_NAME=\"lune\"\nBIN_EXT=\"\"\nCWD=\"$PWD\"\n\n# We should have gotten TARGET_TRIPLE as the first arg to this script\nTARGET_TRIPLE=\"$1\"\nif [ -z \"$TARGET_TRIPLE\" ]; then\n    echo \"Usage: $0 <TARGET_TRIPLE>\"\n    exit 1\nfi\nTARGET_DIR=\"target/$TARGET_TRIPLE/release\"\nif [ ! -d \"$TARGET_DIR\" ]; then\n    echo \"Target directory '$TARGET_DIR' does not exist\"\n    exit 1\nfi\n\n# Use exe extension on windows\nOS=\"$(uname -s | tr '[:upper:]' '[:lower:]')\"\ncase \"$OS\" in\n    darwin) OS=\"macos\" ;;\n    linux) OS=\"linux\" ;;\n    cygwin*|mingw*|msys*) OS=\"windows\" ;;\n    *)\n        echo \"Unsupported OS: $OS\" >&2\n        exit 1 ;;\nesac\nif [ \"$OS\" = \"windows\" ]; then\n    BIN_EXT=\".exe\"\nfi\n\n# Clean up any previous artifacts and dirs\nrm -rf staging\nrm -rf release.zip\n\n# Create new staging dir to work in and copy the binary into that\nmkdir -p staging\ncp \"$TARGET_DIR/$BIN_NAME$BIN_EXT\" staging/\ncd staging\n\n# Zip the staging dir up\nif [ \"$OS\" = \"windows\" ]; then\n\t7z a ../release.zip *\nelse\n\tchmod +x \"$BIN_NAME\"\n\tzip ../release.zip *\nfi\n\n# Go back to cwd and clean up staging dir\ncd \"$CWD\"\nrm -rf staging\n"
  },
  {
    "path": "stylua.toml",
    "content": "column_width = 100\nline_endings = \"Unix\"\nindent_type = \"Tabs\"\nindent_width = 4\nquote_style = \"AutoPreferDouble\"\ncall_parentheses = \"Always\"\n\n[sort_requires]\nenabled = true\n"
  },
  {
    "path": "tests/datetime/formatLocalTime.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\nlocal process = require(\"@lune/process\")\n\nlocal expectedTimeString = os.date(\"%Y-%m-%dT%H:%M:%S\", 1694078954)\n\nassert(\n\tDateTime.fromUnixTimestamp(1694078954):formatLocalTime(\"%Y-%m-%dT%H:%M:%S\", \"en\")\n\t\t== expectedTimeString,\n\t\"invalid ISO 8601 formatting for DateTime.formatLocalTime()\"\n)\n\n--[[\n\tThe rest of this test requires 'fr_FR.UTF-8 UTF-8' to be in /etc/locale.gen to pass\n\n\tLocale should be set up by a script, or by the user,\n\tor in CI, test runner takes no responsibility for this\n\n\tTo run tests related to locales, one must\n\texplicitly provide the `--test-locales` flag\n]]\nlocal runLocaleTests = false\n\nfor _, arg in process.args do\n\tif arg == \"--test-locales\" then\n\t\trunLocaleTests = true\n\t\tbreak\n\tend\nend\n\nif not runLocaleTests then\n\treturn\nend\n\nlocal dateCmd = process.exec(\"bash\", { \"-c\", \"date +\\\"%A, %d %B %Y\\\" --date='@1693068988'\" }, {\n\tenv = {\n\t\tLC_ALL = \"fr_FR.UTF-8 \",\n\t},\n})\nassert(dateCmd.ok, \"Failed to execute date command\")\n\nlocal expectedLocalizedString = string.gsub(dateCmd.stdout, \"\\n\", \"\")\n\nassert(\n\tDateTime.fromUnixTimestamp(1693068988):formatLocalTime(\"%A, %d %B %Y\", \"fr\")\n\t\t== expectedLocalizedString,\n\t`expected format specifier '%A, %d %B %Y' to return '{expectedLocalizedString}' for locale 'fr' (local)`\n)\n\nassert(\n\tDateTime.fromUnixTimestamp(1693068988):formatUniversalTime(\"%A, %d %B %Y\", \"fr\")\n\t\t== \"samedi, 26 août 2023\",\n\t\"expected format specifier '%A, %d %B %Y' to return 'samedi, 26 août 2023' for locale 'fr' (UTC)\"\n)\n"
  },
  {
    "path": "tests/datetime/formatUniversalTime.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nassert(\n\tDateTime.fromUnixTimestamp(1693068988):formatUniversalTime(\"%Y-%m-%dT%H:%M:%SZ\", \"en\")\n\t\t== \"2023-08-26T16:56:28Z\",\n\t\"invalid ISO 8601 formatting for DateTime.formatTime() (UTC)\"\n)\n\nlocal expectedTimeString = os.date(\"%Y-%m-%dT%H:%M:%SZ\", 1694078954)\n\nassert(\n\tDateTime.fromUnixTimestamp(1694078954):formatLocalTime(\"%Y-%m-%dT%H:%M:%SZ\", \"en\")\n\t\t== expectedTimeString,\n\t\"invalid ISO 8601 formatting for DateTime.formatTime()\"\n)\n"
  },
  {
    "path": "tests/datetime/fromLocalTime.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nlocal timeValues1 = os.date(\"*t\", 1693049188)\n\nassert(\n\tDateTime.fromLocalTime({\n\t\tyear = timeValues1.year,\n\t\tmonth = timeValues1.month,\n\t\tday = timeValues1.day,\n\t\thour = timeValues1.hour,\n\t\tminute = timeValues1.min,\n\t\tsecond = timeValues1.sec,\n\t\tmillisecond = 0,\n\t}).unixTimestamp == 1693049188,\n\t\"expected DateTime.fromLocalTime() with DateTimeValues arg to return 1693049188s\"\n)\n\nprint(DateTime.fromLocalTime({\n\tyear = 2023,\n\tmonth = 8,\n\tday = 26,\n\thour = 16,\n\tminute = 56,\n\tsecond = 28,\n\tmillisecond = 892,\n}).unixTimestamp)\n\nlocal timeValues2 = os.date(\"*t\", 1693049188.892)\n\nassert(\n\tDateTime.fromLocalTime({\n\t\tyear = timeValues2.year,\n\t\tmonth = timeValues2.month,\n\t\tday = timeValues2.day,\n\t\thour = timeValues2.hour,\n\t\tminute = timeValues2.min,\n\t\tsecond = timeValues2.sec,\n\t\tmillisecond = 892,\n\t}).unixTimestampMillis == 1693049188892,\n\t\"expected DateTime.fromLocalTime() with DateTimeValues arg with millis to return 1693049188892ms\"\n)\n"
  },
  {
    "path": "tests/datetime/fromRfc2822.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nassert(\n\tDateTime.fromRfc2822(\"Fri, 21 Nov 1997 09:55:06 -0600\") ~= nil,\n\t\"expected DateTime.fromRfcDate() to return DateTime, got nil\"\n)\n\nassert(\n\tDateTime.fromRfc2822(\"Tue, 1 Jul 2003 10:52:37 +0200\") ~= nil,\n\t\"expected DateTime.fromRfcDate() to return DateTime, got nil\"\n)\n"
  },
  {
    "path": "tests/datetime/fromRfc3339.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nassert(\n\tDateTime.fromRfc3339(\"2023-08-26T16:56:28Z\") ~= nil,\n\t\"expected DateTime.fromRfc3339() to return DateTime, got nil\"\n)\n\nassert(\n\tDateTime.fromRfc3339(\"1929-12-05T23:18:23Z\") ~= nil,\n\t\"expected DateTime.fromRfc3339() to return DateTime, got nil\"\n)\n"
  },
  {
    "path": "tests/datetime/fromUniversalTime.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nassert(\n\tDateTime.fromUniversalTime({\n\t\tyear = 2023,\n\t\tmonth = 8,\n\t\tday = 26,\n\t\thour = 16,\n\t\tminute = 56,\n\t\tsecond = 28,\n\t\tmillisecond = 0,\n\t}).unixTimestamp == 1693068988,\n\t\"expected DateTime.fromUniversalTime() with DateTimeValues arg to return 1693068988s\"\n)\n\nassert(\n\tDateTime.fromUniversalTime({\n\t\tyear = 2023,\n\t\tmonth = 8,\n\t\tday = 26,\n\t\thour = 16,\n\t\tminute = 56,\n\t\tsecond = 28,\n\t\tmillisecond = 892,\n\t}).unixTimestampMillis == 1693068988892,\n\t\"expected DateTime.fromUniversalTime() with DateTimeValues arg with millis to return 1693068988892ms\"\n)\n"
  },
  {
    "path": "tests/datetime/fromUnixTimestamp.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\n-- Bug in rust side implementation for fromUnixTimestamp, calculation for conversion there is wonky,\n-- a difference of few millis causes differences as whole seconds for some reason\n\nassert(\n\tDateTime.fromUnixTimestamp(0000.892).unixTimestampMillis == (0 * 1000) + 892,\n\t\"expected DateTime.fromUnixTimestamp() with millis float to return correct millis timestamp\"\n)\n\n-- We subtract one due to the floating point accuracy... Need to fix later\nassert(\n\tDateTime.fromUnixTimestamp(1693114921.632).unixTimestampMillis\n\t\t== ((1693114921 * 1000) + 632) - 1,\n\t\"expected DateTime.fromUnixTimestamp() with millis and seconds float to return correct millis timestamp\"\n)\n"
  },
  {
    "path": "tests/datetime/now.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nlocal TYPE = \"DateTime\"\n\nlocal now = DateTime.now()\n\nassert(typeof(now) == TYPE, `dateTime.now() should return a {TYPE}, returned {typeof(now)}`)\n"
  },
  {
    "path": "tests/datetime/toLocalTime.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nlocal values = DateTime.fromRfc3339(\"2023-08-27T05:54:19Z\"):toLocalTime()\n\nlocal expectedDateTimeValues = os.date(\"*t\", 1693115659)\n\nassert(\n\tvalues.year == expectedDateTimeValues.year,\n\t`expected {values.year} == {expectedDateTimeValues.year}`\n)\nassert(\n\tvalues.month == expectedDateTimeValues.month,\n\t`expected {values.month} == {expectedDateTimeValues.month}`\n)\nassert(\n\tvalues.day == expectedDateTimeValues.day,\n\t`expected {values.day} == {expectedDateTimeValues.day}`\n)\nassert(\n\tvalues.hour == expectedDateTimeValues.hour,\n\t`expected {values.hour} == {expectedDateTimeValues.hour}`\n)\nassert(\n\tvalues.minute == expectedDateTimeValues.min,\n\t`expected {values.minute} == {expectedDateTimeValues.min}`\n)\nassert(\n\tvalues.second == expectedDateTimeValues.sec,\n\t`expected {values.second} == {expectedDateTimeValues.sec}`\n)\n"
  },
  {
    "path": "tests/datetime/toRfc2822.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nlocal now = DateTime.now()\nlocal nowRfc = now:toRfc2822()\n\nassert(type(nowRfc) == \"string\", \"toRfcDate should return a string\")\nassert(\n\tstring.match(nowRfc, \"^%a%a%a, %d%d? %a%a%a %d%d%d%d %d%d:%d%d:%d%d [+-]%d%d%d%d$\"),\n\t\"RFC 2822 date string does not match expected format\"\n)\n\n-- Extract components of the RFC 2822 string\nlocal day, date, month, year, time, timezone =\n\tnowRfc:match(\"^(%a%a%a), (%d%d?) (%a%a%a) (%d%d%d%d) (%d%d:%d%d:%d%d) ([+-]%d%d%d%d)$\")\n\nif not day or not date or not month or not year or not time or not timezone then\n\terror(\"Failed to extract components from RFC 2822 date string\")\nend\n\n-- Validate month\nlocal validMonths = {\n\tJan = true,\n\tFeb = true,\n\tMar = true,\n\tApr = true,\n\tMay = true,\n\tJun = true,\n\tJul = true,\n\tAug = true,\n\tSep = true,\n\tOct = true,\n\tNov = true,\n\tDec = true,\n}\nassert(validMonths[month], \"Month must be a valid RFC 2822 month abbreviation\")\n\n-- Validate year\nassert(string.match(year, \"^%d%d%d%d$\"), \"Year must be a 4-digit number\")\n\n-- Validate date\nlocal dayNum = tonumber(date)\nassert(dayNum >= 1 and dayNum <= 31, \"Date must be between 1 and 31\")\n\n-- Validate time\nlocal hour, minute, second = time:match(\"^(%d%d):(%d%d):(%d%d)$\")\nif not hour or not minute or not second then\n\terror(\"Failed to extract time components from RFC 2822 date string\")\nend\n\nassert(hour and tonumber(hour) >= 0 and tonumber(hour) < 24, \"Hour must be between 0 and 23\")\nassert(\n\tminute and tonumber(minute) >= 0 and tonumber(minute) < 60,\n\t\"Minute must be between 0 and 59\"\n)\nassert(\n\tsecond and tonumber(second) >= 0 and tonumber(second) < 60,\n\t\"Second must be between 0 and 59\"\n)\n\n-- Validate timezone\nlocal tzHour, tzMinute = timezone:match(\"^([+-]%d%d)(%d%d)$\")\nif not tzHour or not tzMinute then\n\terror(\"Failed to extract timezone components from RFC 2822 date string\")\nend\n\nassert(\n\ttzHour and tonumber(tzHour) >= -14 and tonumber(tzHour) <= 14,\n\t\"Timezone hour offset must be between -14 and +14\"\n)\nassert(\n\ttzMinute and tonumber(tzMinute) >= 0 and tonumber(tzMinute) < 60,\n\t\"Timezone minute offset must be between 0 and 59\"\n)\n"
  },
  {
    "path": "tests/datetime/toRfc3339.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nlocal now = DateTime.now()\nlocal nowRfc = now:toRfc3339()\n\n-- Make sure we have separator characters, T to separate date & time, + or Z to separate timezone\n\nlocal dateTimeSplitIdx = string.find(nowRfc, \"T\")\nlocal timezoneSplitIdx = string.find(nowRfc, \"+\")\nlocal timezoneZeroedIdx = string.find(nowRfc, \"Z\")\n\nassert(dateTimeSplitIdx ~= nil, \"Missing date & time separator 'T' in RFC 3339 string\")\nassert(\n\ttimezoneSplitIdx ~= nil or timezoneZeroedIdx ~= nil,\n\t\"Missing timezone separator '+' or 'Z' in RFC 3339 string\"\n)\n\n-- Split date (before T) by dashes, split time (after T, before + or Z)\n-- by colons, we should then get 3 substrings for each of date & time\n\nlocal dateParts = string.split(string.sub(nowRfc, 1, dateTimeSplitIdx - 1), \"-\")\nlocal timeParts = string.split(\n\tstring.sub(\n\t\tnowRfc,\n\t\tdateTimeSplitIdx + 1,\n\t\t((timezoneSplitIdx or timezoneZeroedIdx) :: number) - 1\n\t),\n\t\":\"\n)\n\nassert(#dateParts == 3, \"Date partial of RFC 3339 should consist of 3 substrings, separated by '-'\")\nassert(#timeParts == 3, \"Time partial of RFC 3339 should consist of 3 substrings, separated by ':'\")\n\n-- date should be in format YYYY:MM::DD\n-- time should be in format HH:MM:SS with optional fraction for seconds\n\nassert(string.match(dateParts[1], \"^%d%d%d%d$\"), \"Date partial should have 4 digits for year\")\nassert(string.match(dateParts[2], \"^%d%d$\"), \"Date partial should have 2 digits for month\")\nassert(string.match(dateParts[3], \"^%d%d$\"), \"Date partial should have 2 digits for day\")\n\nassert(string.match(timeParts[1], \"^%d%d$\"), \"Time partial should have 2 digits for hour\")\nassert(string.match(timeParts[2], \"^%d%d$\"), \"Time partial should have 2 digits for minute\")\nassert(\n\tstring.match(timeParts[3], \"^%d%d%.?%d*$\") and tonumber(timeParts[3]) ~= nil,\n\t\"Time partial should have minimum 2 digits with optional fraction for seconds\"\n)\n\n-- Timezone specifier is either 'Z' for zeroed out timezone (no offset),\n-- in which case we don't need to check anything other than it being the\n-- last character, or it can be a timezone offset in the format HH::MM\n\nif timezoneZeroedIdx ~= nil then\n\t-- No timezone offset\n\tassert(\n\t\ttimezoneZeroedIdx == #nowRfc,\n\t\t\"Timezone specifier 'Z' must be at the last character in RFC 3339 string\"\n\t)\nelseif timezoneSplitIdx ~= nil then\n\t-- Timezone offset\n\tlocal timezoneParts = string.split(string.sub(nowRfc, timezoneSplitIdx + 1), \":\")\n\tassert(#timezoneParts == 2, \"Timezone partial should consist of 2 substings, separated by ':'\")\n\tassert(\n\t\tstring.match(timezoneParts[1], \"^%d%d$\"),\n\t\t\"Timezone partial should have 2 digits for hour\"\n\t)\n\tassert(\n\t\tstring.match(timezoneParts[2], \"^%d%d$\"),\n\t\t\"Timezone partial should have 2 digits for minute\"\n\t)\nelse\n\terror(\"unreachable\")\nend\n"
  },
  {
    "path": "tests/datetime/toUniversalTime.luau",
    "content": "local DateTime = require(\"@lune/datetime\")\n\nlocal values = DateTime.fromRfc3339(\"2023-08-27T05:54:19Z\"):toLocalTime()\n\nlocal expectedDateTimeValues = os.date(\"*t\", 1693115659)\n\nassert(\n\tvalues.year == expectedDateTimeValues.year,\n\t`expected {values.year} == {expectedDateTimeValues.year}`\n)\nassert(\n\tvalues.month == expectedDateTimeValues.month,\n\t`expected {values.month} == {expectedDateTimeValues.month}`\n)\nassert(\n\tvalues.day == expectedDateTimeValues.day,\n\t`expected {values.day} == {expectedDateTimeValues.day}`\n)\nassert(\n\tvalues.hour == expectedDateTimeValues.hour,\n\t`expected {values.hour} == {expectedDateTimeValues.hour}`\n)\nassert(\n\tvalues.minute == expectedDateTimeValues.min,\n\t`expected {values.minute} == {expectedDateTimeValues.min}`\n)\nassert(\n\tvalues.second == expectedDateTimeValues.sec,\n\t`expected {values.second} == {expectedDateTimeValues.sec}`\n)\n"
  },
  {
    "path": "tests/fs/copy.luau",
    "content": "local TEMP_DIR_PATH = \"bin/\"\nlocal TEMP_ROOT_PATH = TEMP_DIR_PATH .. \"fs_copy_test\"\nlocal TEMP_ROOT_PATH_2 = TEMP_DIR_PATH .. \"fs_copy_test_2\"\n\nlocal fs = require(\"@lune/fs\")\nlocal utils = require(\"./utils\")\n\n-- Make sure our bin dir exists\n\nfs.writeDir(TEMP_DIR_PATH)\nif fs.isDir(TEMP_ROOT_PATH) then\n\tfs.removeDir(TEMP_ROOT_PATH)\nend\nif fs.isDir(TEMP_ROOT_PATH_2) then\n\tfs.removeDir(TEMP_ROOT_PATH_2)\nend\n\n--[[\n\tCreate a file structure like this:\n\n\t-> fs_copy_test\n\t-- -> foo (dir)\n\t-- -- -> bar (dir)\n\t-- -- -- -> baz (file)\n\t-- -- -> fizz (file)\n\t-- -- -> buzz (file)\n\n]]\n\nfs.writeDir(TEMP_ROOT_PATH)\nfs.writeDir(TEMP_ROOT_PATH .. \"/foo\")\nfs.writeDir(TEMP_ROOT_PATH .. \"/foo/bar\")\nfs.writeFile(TEMP_ROOT_PATH .. \"/foo/bar/baz\", utils.binaryBlob)\nfs.writeFile(TEMP_ROOT_PATH .. \"/foo/fizz\", utils.binaryBlob)\nfs.writeFile(TEMP_ROOT_PATH .. \"/foo/buzz\", utils.binaryBlob)\n\n-- Copy the entire structure\n\nfs.copy(TEMP_ROOT_PATH, TEMP_ROOT_PATH_2)\n\n-- Verify the copied structure\n\nassert(fs.isDir(TEMP_ROOT_PATH_2), \"Missing copied dir - root/\")\nassert(fs.isDir(TEMP_ROOT_PATH_2 .. \"/foo\"), \"Missing copied dir - root/foo/\")\nassert(fs.isDir(TEMP_ROOT_PATH_2 .. \"/foo/bar\"), \"Missing copied dir - root/foo/bar/\")\nassert(fs.isFile(TEMP_ROOT_PATH_2 .. \"/foo/bar/baz\"), \"Missing copied file - root/foo/bar/baz\")\nassert(fs.isFile(TEMP_ROOT_PATH_2 .. \"/foo/fizz\"), \"Missing copied file - root/foo/fizz\")\nassert(fs.isFile(TEMP_ROOT_PATH_2 .. \"/foo/buzz\"), \"Missing copied file - root/foo/buzz\")\n\n-- Make sure the copied files are correct\n\nassert(\n\tfs.readFile(TEMP_ROOT_PATH_2 .. \"/foo/bar/baz\") == buffer.tostring(utils.binaryBlob),\n\t\"Invalid copied file - root/foo/bar/baz\"\n)\nassert(\n\tfs.readFile(TEMP_ROOT_PATH_2 .. \"/foo/fizz\") == buffer.tostring(utils.binaryBlob),\n\t\"Invalid copied file - root/foo/fizz\"\n)\nassert(\n\tfs.readFile(TEMP_ROOT_PATH_2 .. \"/foo/buzz\") == buffer.tostring(utils.binaryBlob),\n\t\"Invalid copied file - root/foo/buzz\"\n)\n\n-- Finally, clean up after us for any subsequent tests\n\nfs.removeDir(TEMP_ROOT_PATH)\nfs.removeDir(TEMP_ROOT_PATH_2)\n"
  },
  {
    "path": "tests/fs/dirs.luau",
    "content": "local TEMP_DIR_PATH = \"bin/\"\nlocal TEMP_ROOT_PATH = TEMP_DIR_PATH .. \"fs_dirs_test\"\n\nlocal fs = require(\"@lune/fs\")\n\n-- Write two inner dirs in the bin dir, a parent and a child\n\nfs.writeDir(TEMP_ROOT_PATH .. \"/test_inner\")\n\n-- Make sure dir checks succeed but file\n-- checks fail for all levels of dirs\n\nassert(fs.isDir(TEMP_DIR_PATH), \"Dir root isDir check failed\")\nassert(fs.isDir(TEMP_ROOT_PATH), \"Dir outer isDir check failed\")\nassert(fs.isDir(TEMP_ROOT_PATH .. \"/test_inner\"), \"Dir inner isDir check failed\")\n\nassert(not fs.isFile(TEMP_DIR_PATH), \"Dir root isFile check failed\")\nassert(not fs.isFile(TEMP_ROOT_PATH), \"Dir outer isFile check failed\")\nassert(not fs.isFile(TEMP_ROOT_PATH .. \"/test_inner\"), \"Dir inner isFile check failed\")\n\n-- Remove the created parent and child dirs and\n-- make sure the APIs say they no longer exist\n\nfs.removeDir(TEMP_ROOT_PATH)\n\nassert(not fs.isDir(TEMP_ROOT_PATH), \"After removal isDir check failed\")\nassert(not fs.isFile(TEMP_ROOT_PATH), \"After removal isFile check failed\")\n"
  },
  {
    "path": "tests/fs/files.luau",
    "content": "local TEMP_DIR_PATH = \"bin/\"\nlocal TEMP_ROOT_PATH = TEMP_DIR_PATH .. \"fs_files_test\"\n\nlocal fs = require(\"@lune/fs\")\nlocal utils = require(\"./utils\")\n\n-- Make sure our bin dir exists\n\nfs.writeDir(TEMP_DIR_PATH)\nfs.writeDir(TEMP_ROOT_PATH)\n\n-- Write both of our files\n\n-- binaryBlob is of type buffer to make sure fs.writeFile\n-- works with both strings and buffers\nfs.writeFile(TEMP_ROOT_PATH .. \"/test_binary\", utils.binaryBlob)\nfs.writeFile(TEMP_ROOT_PATH .. \"/test_json.json\", utils.jsonBlob)\n\n-- Make sure reading the file we just\n-- wrote gets us back the original strings\n\nassert(\n\tfs.readFile(TEMP_ROOT_PATH .. \"/test_binary\") == buffer.tostring(utils.binaryBlob),\n\t\"Binary file round-trip resulted in different strings\"\n)\n\nassert(\n\tfs.readFile(TEMP_ROOT_PATH .. \"/test_json.json\") == utils.jsonBlob,\n\t\"JSON file round-trip resulted in different strings\"\n)\n\n-- Make sure file checks succeed but dir checks fail\n\nassert(fs.isFile(TEMP_ROOT_PATH .. \"/test_binary\"), \"Binary file isFile check failed\")\nassert(fs.isFile(TEMP_ROOT_PATH .. \"/test_json.json\"), \"JSON file isFile check failed\")\n\nassert(not fs.isDir(TEMP_ROOT_PATH .. \"/test_binary\"), \"Binary file isDir check failed\")\nassert(not fs.isDir(TEMP_ROOT_PATH .. \"/test_json.json\"), \"JSON file isDir check failed\")\n\n-- Remove the files and make sure\n-- the APIs say they no longer exist\n\nfs.removeFile(TEMP_ROOT_PATH .. \"/test_binary\")\nfs.removeFile(TEMP_ROOT_PATH .. \"/test_json.json\")\n\nassert(not fs.isDir(TEMP_ROOT_PATH .. \"/test_binary\"), \"Binary after removal isDir check failed\")\nassert(not fs.isFile(TEMP_ROOT_PATH .. \"/test_binary\"), \"Binary after removal isFile check failed\")\n\nassert(not fs.isDir(TEMP_ROOT_PATH .. \"/test_json.json\"), \"JSON after removal isDir check failed\")\nassert(not fs.isFile(TEMP_ROOT_PATH .. \"/test_json.json\"), \"JSON after removal isFile check failed\")\n\n-- Remove the testing dir specific to this test\n\nfs.removeDir(TEMP_ROOT_PATH)\n"
  },
  {
    "path": "tests/fs/metadata.luau",
    "content": "--!nolint UnknownType\n\nlocal TEMP_DIR_PATH = \"bin/\"\nlocal TEMP_FILE_PATH = TEMP_DIR_PATH .. \"metadata_test\"\n\nlocal fs = require(\"@lune/fs\")\nlocal task = require(\"@lune/task\")\nlocal utils = require(\"./utils\")\n\n-- Make sure our bin dir exists\n\nfs.writeDir(TEMP_DIR_PATH)\nif fs.isFile(TEMP_FILE_PATH) then\n\tfs.removeFile(TEMP_FILE_PATH)\nend\n\n--[[\n\t1. File should initially not exist\n\t2. Write the file\n\t3. File should now exist\n]]\n\nassert(not fs.metadata(TEMP_FILE_PATH).exists, \"File metadata not exists failed\")\nfs.writeFile(TEMP_FILE_PATH, utils.binaryBlob)\nassert(fs.metadata(TEMP_FILE_PATH).exists, \"File metadata exists failed\")\n\n--[[\n\t1. Kind should be `dir` for our temp directory\n\t2. Kind should be `file` for our temp file\n]]\n\nlocal metaDir = fs.metadata(TEMP_DIR_PATH)\nlocal metaFile = fs.metadata(TEMP_FILE_PATH)\nassert(metaDir.kind == \"dir\", \"Dir metadata kind was invalid\")\nassert(metaFile.kind == \"file\", \"File metadata kind was invalid\")\n\n--[[\n\t1. Capture initial metadata\n\t2. Wait for a bit so that timestamps can change\n\t3. Write the file, with an extra newline\n\t4. Metadata changed timestamp should be different\n\t5. Metadata created timestamp should be the same\n\t6. Timestamps should be DateTime structs\n]]\n\nlocal metaBefore = fs.metadata(TEMP_FILE_PATH)\ntask.wait(1)\nfs.writeFile(TEMP_FILE_PATH, buffer.tostring(utils.binaryBlob) .. \"\\n\")\nlocal metaAfter = fs.metadata(TEMP_FILE_PATH)\n\nassert(\n\tmetaAfter.modifiedAt ~= metaBefore.modifiedAt,\n\t\"File metadata change timestamp did not change\"\n)\nassert(\n\tmetaAfter.createdAt == metaBefore.createdAt,\n\t\"File metadata creation timestamp changed from modification\"\n)\n\nassert(typeof(metaAfter.modifiedAt) == \"DateTime\", \"File metadata modifiedAt is not a DateTime\")\nassert(typeof(metaAfter.createdAt) == \"DateTime\", \"File metadata createdAt is not a DateTime\")\n\n--[[\n\t1. Permissions should exist\n\t2. Our newly created file should not be readonly\n]]\nassert(metaAfter.permissions ~= nil, \"File metadata permissions are missing\")\nassert(not metaAfter.permissions.readOnly, \"File metadata permissions are readonly\")\n\n-- Finally, clean up after us for any subsequent tests\n\nfs.removeFile(TEMP_FILE_PATH)\n"
  },
  {
    "path": "tests/fs/move.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal utils = require(\"./utils\")\n\n-- Make sure our bin dir exists\n\nfs.writeDir(\"bin\")\n\n-- Write both of our files\n\nfs.writeFile(\"bin/move_test_binary\", utils.binaryBlob)\nfs.writeFile(\"bin/move_test_json.json\", utils.jsonBlob)\n\n-- Move / rename them to something else, to test we\n-- change the prefix in the names from \"move\" to \"moved\"\n\nfs.move(\"bin/move_test_binary\", \"bin/moved_test_binary\")\nfs.move(\"bin/move_test_json.json\", \"bin/moved_test_json.json\")\n\n-- Make sure reading the files we just\n-- wrote gets us back the original strings\n\nassert(\n\tfs.readFile(\"bin/moved_test_binary\") == buffer.tostring(utils.binaryBlob),\n\t\"Binary file round-trip resulted in different strings\"\n)\n\nassert(\n\tfs.readFile(\"bin/moved_test_json.json\") == utils.jsonBlob,\n\t\"JSON file round-trip resulted in different strings\"\n)\n\n-- Remove the files and make sure\n-- the APIs say they no longer exist\n\nfs.removeFile(\"bin/moved_test_binary\")\nfs.removeFile(\"bin/moved_test_json.json\")\n\nassert(not fs.isDir(\"bin/moved_test_binary\"), \"Binary after removal isDir check failed\")\nassert(not fs.isFile(\"bin/moved_test_binary\"), \"Binary after removal isFile check failed\")\n\nassert(not fs.isDir(\"bin/moved_test_json.json\"), \"JSON after removal isDir check failed\")\nassert(not fs.isFile(\"bin/moved_test_json.json\"), \"JSON after removal isFile check failed\")\n\n-- Also make sure files and dirs at the paths before moving do not exist\n\nassert(not fs.isDir(\"bin/moved_test_binary\"), \"Binary file path still existed after moving\")\nassert(not fs.isFile(\"bin/moved_test_binary\"), \"Binary file path still existed after moving\")\n\nassert(not fs.isDir(\"bin/moved_test_json.json\"), \"JSON file path still existed after moving\")\nassert(not fs.isFile(\"bin/moved_test_json.json\"), \"JSON file path still existed after moving\")\n"
  },
  {
    "path": "tests/fs/utils.luau",
    "content": "local serde = require(\"@lune/serde\")\n\n-- Generate testing data\n\nlocal binaryBlob = \"\"\nfor _ = 1, 1024 do\n\tbinaryBlob ..= string.char(math.random(1, 127))\nend\n\nlocal jsonBlob = serde.encode(\"json\", {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n\tInner = { Array = { 1, 2, 3 } },\n}, true)\n\n-- Return testing data and utils\n\nreturn {\n\tbinaryBlob = buffer.fromstring(binaryBlob),\n\tjsonBlob = jsonBlob,\n}\n"
  },
  {
    "path": "tests/globals/_G.luau",
    "content": "assert(_G ~= nil, \"Missing _G\")\n\nassert(type(_G) == \"table\", \"Invalid type for _G\")\n\nassert(_G.require == nil, \"Built-in global value was found in _G\")\nassert(_G.print == nil, \"Built-in global value was found in _G\")\nassert(_G.warn == nil, \"Built-in global value was found in _G\")\nassert(_G.error == nil, \"Built-in global value was found in _G\")\nassert(_G.coroutine == nil, \"Built-in global value was found in _G\")\nassert(_G.typeof == nil, \"Built-in global value was found in _G\")\nassert(_G.type == nil, \"Built-in global value was found in _G\")\n\nassert(next(_G) == nil, \"_G contained value but should be empty\")\n\n_G.Hello = \"World\"\nassert(_G.Hello == \"World\", \"Failed to set value in _G\")\n\nlocal bar = {}\n_G.Foo = bar\nassert(_G.Foo == bar, \"Failed to set reference in _G\")\n"
  },
  {
    "path": "tests/globals/_VERSION.luau",
    "content": "-- _VERSION global should follow the following format:\n--[[\n\t_VERSION global must have the following format:\n\n\tLune LUNE_MAJOR.LUNE_MINOR.LUNE_PATCH+LUAU_VERSION\n\n\tExamples:\n\n\tLune 0.0.0+0\n\tLune 1.0.0+500\n\tLune 0.11.22+9999\n]]\n\nassert(_VERSION ~= nil, \"_VERSION global is missing\")\nassert(type(_VERSION) == \"string\", \"_VERSION global must be a string\")\n\nassert(string.sub(_VERSION, 1, 5) == \"Lune \", \"_VERSION global must start with 'Lune '\")\n\nlocal withoutPrefix = string.sub(_VERSION, 6)\nlocal versions = string.split(withoutPrefix, \"+\")\nassert(versions[1] ~= nil, \"_VERSION global does not contain lune version\")\nassert(versions[2] ~= nil, \"_VERSION global does not contain luau version\")\n\nlocal luneVersion = string.split(versions[1], \".\")\nassert(luneVersion[1] ~= nil, \"_VERSION global is missing lune major version\")\nassert(luneVersion[2] ~= nil, \"_VERSION global is missing lune minor version\")\nassert(luneVersion[3] ~= nil, \"_VERSION global is missing lune patch version\")\n\nassert(\n\tstring.find(versions[2], \".\", 1, true) == nil,\n\t\"_VERSION global contains more than one semver partial for luau version\"\n)\n\nassert(tonumber(luneVersion[1]) ~= nil, \"_VERSION global lune major version is not a number\")\nassert(tonumber(luneVersion[2]) ~= nil, \"_VERSION global lune minor version is not a number\")\nassert(tonumber(luneVersion[3]) ~= nil, \"_VERSION global lune patch version is not a number\")\nassert(tonumber(versions[2]) ~= nil, \"_VERSION global luau version is not a number\")\n\nlocal rebuilt = string.format(\n\t\"Lune %d.%d.%d+%d\",\n\ttonumber(luneVersion[1]) :: number,\n\ttonumber(luneVersion[2]) :: number,\n\ttonumber(luneVersion[3]) :: number,\n\ttonumber(versions[2]) :: number\n)\n\nprint(\"_VERSION:\", _VERSION)\nprint(\"Rebuilt:\", rebuilt)\n\nassert(rebuilt == _VERSION)\n"
  },
  {
    "path": "tests/globals/coroutine.luau",
    "content": "-- Coroutines should return true, ret values OR false, error\n\nlocal function pass()\n\tcoroutine.yield(1, 2, 3)\n\tcoroutine.yield(4, 5, 6)\nend\n\nlocal function fail()\n\terror(\"Error message\")\nend\n\nlocal thread1 = coroutine.create(pass)\nlocal t10, t11, t12, t13 = coroutine.resume(thread1)\nassert(t10 == true, \"Coroutine resume should return true as first value unless errored\")\nassert(t11 == 1, \"Coroutine resume should return values yielded to it (1)\")\nassert(t12 == 2, \"Coroutine resume should return values yielded to it (2)\")\nassert(t13 == 3, \"Coroutine resume should return values yielded to it (3)\")\n\nlocal thread2 = coroutine.create(fail)\nlocal t20, t21 = coroutine.resume(thread2)\nassert(t20 == false, \"Coroutine resume should return false as first value when errored\")\nassert(#tostring(t21) > 0, \"Coroutine resume should return error as second if it errors\")\n\n-- Coroutine suspended status should be correct\n\nassert(\n\tcoroutine.status(thread1) == \"suspended\",\n\t\"Coroutine status should return suspended properly\"\n)\nassert(coroutine.status(thread2) == \"dead\", \"Coroutine status should return dead properly\")\n\n-- Coroutines should return values yielded after the first\n\nlocal t30, t31, t32, t33 = coroutine.resume(thread1)\nassert(t30 == true, \"Coroutine resume should return true as first value unless errored\")\nassert(t31 == 4, \"Coroutine resume should return values yielded to it (4)\")\nassert(t32 == 5, \"Coroutine resume should return values yielded to it (5)\")\nassert(t33 == 6, \"Coroutine resume should return values yielded to it (6)\")\n\nlocal t40, t41 = coroutine.resume(thread1)\nassert(t40 == true, \"Coroutine resume should return true as first value unless errored\")\nassert(t41 == nil, \"Coroutine resume should return values yielded to it (7)\")\n\n-- Coroutine dead status should be correct after first yielding\n\nassert(coroutine.status(thread1) == \"dead\", \"Coroutine status should return dead properly\")\n\n-- Resume should error for dead coroutines\n\nlocal success1 = coroutine.resume(thread1)\nlocal success2 = coroutine.resume(thread2)\n\nassert(success1 == false, \"Coroutine resume on dead coroutines should return false\")\nassert(success2 == false, \"Coroutine resume on dead coroutines should return false\")\n\n-- Task library wait should work inside native lua coroutines\n\nlocal task = require(\"@lune/task\")\n\nlocal flag: boolean = false\ncoroutine.resume(coroutine.create(function()\n\ttask.wait(0.1)\n\tflag = true\nend))\nassert(not flag, \"Wait failed while inside coroutine (1)\")\ntask.wait(0.2)\nassert(flag, \"Wait failed while inside coroutine (2)\")\n\nlocal flag2: boolean = false\ncoroutine.wrap(function()\n\ttask.wait(0.1)\n\tflag2 = true\nend)()\nassert(not flag2, \"Wait failed while inside wrap (1)\")\ntask.wait(0.2)\nassert(flag2, \"Wait failed while inside wrap (2)\")\n\n-- Coroutines should be passed arguments on initial resume\n\nlocal co = coroutine.create(function(a, b, c)\n\tassert(a == 1)\n\tassert(b == \"Hello, world!\")\n\tassert(c == true)\nend)\n\ncoroutine.resume(co, 1, \"Hello, world!\", true)\n"
  },
  {
    "path": "tests/globals/error.luau",
    "content": "local errValue = newproxy(false)\n\nlocal success, result = pcall(function()\n\terror({\n\t\tInner = errValue,\n\t})\nend)\n\nassert(not success, \"Pcall succeeded when erorred\")\n\nassert(result ~= nil, \"Pcall did not return error\")\nassert(type(result) == \"table\", \"Pcall error value should have been a table\")\n\nassert(result.Inner ~= nil, \"Pcall error table should contain inner value\")\nassert(result.Inner == errValue, \"Pcall error table should have correct inner value\")\n"
  },
  {
    "path": "tests/globals/pcall.luau",
    "content": "local net = require(\"@lune/net\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = 9090 -- NOTE: This must be different from\n-- net tests to let them run in parallel with this file\n\nlocal function test(f, ...)\n\tlocal success, message = pcall(f, ...)\n\tassert(not success, \"Function did not throw an error\")\n\tassert(\n\t\ttype(message) == \"string\" or type(message) == \"userdata\",\n\t\t\"Pcall did not return a proper error\"\n\t)\nend\n\n-- These are not async but should be pcallable\n\ntest(error, \"Test error\", 2)\n\n-- Net request is async and will throw a DNS error here for the weird address\n\ntest(net.request, \"https://wxyz.google.com\")\n\n-- Net serve is async and will throw an OS error when trying to serve twice on the same port\n\nlocal handle = net.serve(PORT, function()\n\treturn \"\"\nend)\n\ntask.delay(0.25, function()\n\thandle.stop()\nend)\n\ntest(net.serve, PORT, function() end)\n"
  },
  {
    "path": "tests/globals/type.luau",
    "content": "local task = require(\"@lune/task\")\n\nlocal function f() end\n\nlocal thread1 = coroutine.create(f)\nlocal thread2 = task.spawn(f)\nlocal thread3 = task.defer(f)\nlocal thread4 = task.delay(0, f)\n\nassert(type(thread1) == \"thread\", \"Calling type() did not return 'thread' after coroutine.create\")\nassert(type(thread2) == \"thread\", \"Calling type() did not return 'thread' after task.spawn\")\nassert(type(thread3) == \"thread\", \"Calling type() did not return 'thread' after task.defer\")\nassert(type(thread4) == \"thread\", \"Calling type() did not return 'thread' after delay\")\n"
  },
  {
    "path": "tests/globals/typeof.luau",
    "content": "-- NOTE: luau-lsp warns without this for the roblox types below\n--!nolint UnknownType\n\nlocal task = require(\"@lune/task\")\n\nlocal function f() end\n\nlocal thread1 = coroutine.create(f)\nlocal thread2 = task.spawn(f)\nlocal thread3 = task.defer(f)\nlocal thread4 = task.delay(0, f)\n\nassert(\n\ttypeof(thread1) == \"thread\",\n\t\"Calling typeof() did not return 'thread' after coroutine.create\"\n)\nassert(typeof(thread2) == \"thread\", \"Calling typeof() did not return 'thread' after task.spawn\")\nassert(typeof(thread3) == \"thread\", \"Calling typeof() did not return 'thread' after task.defer\")\nassert(typeof(thread4) == \"thread\", \"Calling typeof() did not return 'thread' after delay\")\n\nlocal roblox = require(\"@lune/roblox\")\n\nlocal rbx1 = roblox.Instance.new(\"Part\")\nlocal rbx2 = (roblox :: any).Vector3.zero\nlocal rbx3 = (roblox :: any).Enum.KeyCode.A\n\nassert(typeof(rbx1) == \"Instance\", \"Calling typeof() did not return 'Instance' for Instance.new\")\nassert(typeof(rbx2) == \"Vector3\", \"Calling typeof() did not return 'Vector3' for Vector3.zero\")\nassert(typeof(rbx3) == \"EnumItem\", \"Calling typeof() did not return 'EnumItem' for Enum.KeyCode.A\")\n"
  },
  {
    "path": "tests/globals/warn.luau",
    "content": "assert(warn ~= nil, \"Missing 'warn' global\")\nassert(\n\ttype(warn) == \"function\",\n\tstring.format(\"Global 'warn' should be a function, got '%s'\", tostring(type(warn)))\n)\n"
  },
  {
    "path": "tests/luau/compile.luau",
    "content": "local luau = require(\"@lune/luau\")\n\nlocal EMPTY_LUAU_CODE_BLOCK = \"do end\"\nlocal BROKEN_LUAU_CODE_BLOCK = \"do\"\n\nassert(type(luau.compile) == \"function\", \"expected `luau.compile` to be a function\")\n\nassert(\n\ttype(luau.compile(EMPTY_LUAU_CODE_BLOCK)) == \"string\",\n\t\"expected `luau.compile` to return bytecode string\"\n)\n\nlocal success = pcall(function()\n\tluau.compile(BROKEN_LUAU_CODE_BLOCK)\nend)\n\nassert(success == false, \"expected 'BROKEN_LUAU_CODE_BLOCK' to fail to compile into bytecode.\")\n"
  },
  {
    "path": "tests/luau/load.luau",
    "content": "local luau = require(\"@lune/luau\")\n\nlocal RETURN_VALUE = 1\n\nlocal EMPTY_LUAU_CODE_BLOCK = \"do end\"\nlocal RETURN_LUAU_CODE_BLOCK = \"return \" .. tostring(RETURN_VALUE)\n\nlocal CUSTOM_SOURCE_BLOCK_NAME = \"test\"\n\nassert(type(luau.load) == \"function\", \"expected `luau.compile` to be a function\")\n\nassert(\n\ttype(luau.load(EMPTY_LUAU_CODE_BLOCK)) == \"function\",\n\t\"expected 'luau.load' to return a function\"\n)\nassert(\n\tluau.load(RETURN_LUAU_CODE_BLOCK)() == RETURN_VALUE,\n\t\"expected 'luau.load' to return a value\"\n)\n\nlocal sourceFunction = luau.load(EMPTY_LUAU_CODE_BLOCK, { debugName = CUSTOM_SOURCE_BLOCK_NAME })\nlocal sourceFunctionDebugName = debug.info(sourceFunction, \"s\")\n\nassert(\n\tstring.find(sourceFunctionDebugName, CUSTOM_SOURCE_BLOCK_NAME),\n\t\"expected source block name for 'luau.load' to return a custom debug name\"\n)\n\nlocal loadSuccess = pcall(function()\n\tluau.load(luau.compile(RETURN_LUAU_CODE_BLOCK))\nend)\n\nassert(loadSuccess, \"expected `luau.load` to be able to process the result of `luau.compile`\")\n\nlocal CUSTOM_SOURCE_WITH_FOO_FN = \"return foo()\"\n\n-- NOTE: We use newproxy here to make a userdata to ensure\n-- we get the *exact* same value sent back, not some copy\nlocal fooValue = newproxy(false)\nlocal fooFn = luau.load(CUSTOM_SOURCE_WITH_FOO_FN, {\n\tenvironment = {\n\t\tfoo = function()\n\t\t\treturn fooValue\n\t\tend,\n\t},\n})\n\nlocal fooFnRet = fooFn()\nassert(fooFnRet == fooValue, \"expected `luau.load` with custom environment to return proper values\")\n\nlocal fooValue2 = newproxy(false)\nlocal fooFn2 = luau.load(CUSTOM_SOURCE_WITH_FOO_FN, {\n\tenvironment = {\n\t\tfoo = function()\n\t\t\treturn fooValue2\n\t\tend,\n\t},\n\tenableGlobals = false,\n})\n\nlocal fooFn2Ret = fooFn2()\nassert(\n\tfooFn2Ret == fooValue2,\n\t\"expected `luau.load` with custom environment and no default globals to still return proper values\"\n)\n\nlocal CUSTOM_SOURCE_WITH_PRINT_FN = \"return print()\"\n\n-- NOTE: Testing overriding the print function\nlocal overriddenPrintValue1 = newproxy(false)\nlocal overriddenPrintFn1 = luau.load(CUSTOM_SOURCE_WITH_PRINT_FN, {\n\tenvironment = {\n\t\tprint = function()\n\t\t\treturn overriddenPrintValue1\n\t\tend,\n\t},\n\tenableGlobals = true,\n})\n\nlocal overriddenPrintFnRet1 = overriddenPrintFn1()\nassert(\n\toverriddenPrintFnRet1 == overriddenPrintValue1,\n\t\"expected `luau.load` with overridden environment to return proper values\"\n)\n\nlocal overriddenPrintValue2 = newproxy(false)\nlocal overriddenPrintFn2 = luau.load(CUSTOM_SOURCE_WITH_PRINT_FN, {\n\tenvironment = {\n\t\tprint = function()\n\t\t\treturn overriddenPrintValue2\n\t\tend,\n\t},\n\tenableGlobals = false,\n})\n\nlocal overriddenPrintFnRet2 = overriddenPrintFn2()\nassert(\n\toverriddenPrintFnRet2 == overriddenPrintValue2,\n\t\"expected `luau.load` with overridden environment and disabled default globals to return proper values\"\n)\n\n-- NOTE: Testing whether injectGlobals works\nlocal CUSTOM_SOURCE_WITH_DEFAULT_FN = \"return string.lower(...)\"\n\nlocal lowerFn1 = luau.load(CUSTOM_SOURCE_WITH_DEFAULT_FN, {\n\tenvironment = {},\n\tinjectGlobals = false,\n})\n\nlocal lowerFn1Success = pcall(lowerFn1, \"LOWERCASE\")\n\nassert(\n\tnot lowerFn1Success,\n\t\"expected `luau.load` with injectGlobals = false and empty custom environment to not contain default globals\"\n)\n\nlocal lowerFn2 = luau.load(CUSTOM_SOURCE_WITH_DEFAULT_FN, {\n\tenvironment = { string = string },\n\tinjectGlobals = false,\n})\n\nlocal lowerFn2Success, lowerFn2Result = pcall(lowerFn2, \"LOWERCASE\")\n\nassert(\n\tlowerFn2Success and lowerFn2Result == \"lowercase\",\n\t\"expected `luau.load` with injectGlobals = false and valid custom environment to return proper values\"\n)\n\nlocal lowerFn3 = luau.load(CUSTOM_SOURCE_WITH_DEFAULT_FN, {\n\tenvironment = {},\n\tinjectGlobals = true,\n})\n\nlocal lowerFn3Success, lowerFn3Result = pcall(lowerFn3, \"LOWERCASE\")\n\nassert(\n\tlowerFn3Success and lowerFn3Result == \"lowercase\",\n\t\"expected `luau.load` with injectGlobals = true and empty custom environment to return proper values\"\n)\n"
  },
  {
    "path": "tests/luau/options.luau",
    "content": "local luau = require(\"@lune/luau\")\n\nlocal EMPTY_LUAU_CODE_BLOCK = \"do end\"\n\nlocal MIN_OPTION_VALUE = 0\nlocal MAX_OPTION_VALUE = 2\nlocal OPTION_NAMES = {\n\t\"optimizationLevel\",\n\t\"coverageLevel\",\n\t\"debugLevel\",\n}\n\nfor _, optionName in OPTION_NAMES do\n\t-- In range should work\n\tfor optionValue = MIN_OPTION_VALUE, MAX_OPTION_VALUE, 1 do\n\t\tlocal options = { [optionName] = optionValue }\n\t\tlocal success2 = pcall(function()\n\t\t\tluau.compile(EMPTY_LUAU_CODE_BLOCK, options)\n\t\tend)\n\t\tassert(success2, \"expected `luau.compile` to accept options within range\")\n\tend\n\t-- Lower than min range should error\n\tfor optionValue = MIN_OPTION_VALUE - 16, MIN_OPTION_VALUE - 1, 1 do\n\t\tlocal options = { [optionName] = optionValue }\n\t\tlocal success2 = pcall(function()\n\t\t\tluau.compile(EMPTY_LUAU_CODE_BLOCK, options)\n\t\tend)\n\t\tassert(not success2, \"expected `luau.compile` to not accept options outside of range\")\n\tend\n\t-- Higher than max range should error\n\tfor optionValue = MAX_OPTION_VALUE + 1, MAX_OPTION_VALUE + 16, 1 do\n\t\tlocal options = { [optionName] = optionValue }\n\t\tlocal success2 = pcall(function()\n\t\t\tluau.compile(EMPTY_LUAU_CODE_BLOCK, options)\n\t\tend)\n\t\tassert(not success2, \"expected `luau.compile` to not accept options outside of range\")\n\tend\nend\n"
  },
  {
    "path": "tests/luau/safeenv.luau",
    "content": "local luau = require(\"@lune/luau\")\n\nlocal TEST_SCRIPT = [[\n    local start = os.clock()\n    local x\n    for i = 1, 1e6 do\n        x = math.sqrt(i)\n    end\n    local finish = os.clock()\n\n    return finish - start\n]]\n\nlocal TEST_BYTECODE = luau.compile(TEST_SCRIPT, {\n\toptimizationLevel = 2,\n\tcoverageLevel = 0,\n\tdebugLevel = 0,\n})\n\n-- Load the bytecode with different configurations\nlocal safeCodegenFunction = luau.load(TEST_BYTECODE, {\n\tdebugName = \"safeCodegenFunction\",\n\tcodegenEnabled = true,\n})\nlocal unsafeCodegenFunction = luau.load(TEST_BYTECODE, {\n\tdebugName = \"unsafeCodegenFunction\",\n\tenvironment = {},\n\tinjectGlobals = true,\n\tcodegenEnabled = true,\n})\nlocal safeFunction = luau.load(TEST_BYTECODE, {\n\tdebugName = \"safeFunction\",\n\tcodegenEnabled = false,\n})\nlocal unsafeFunction = luau.load(TEST_BYTECODE, {\n\tdebugName = \"unsafeFunction\",\n\tenvironment = {},\n\tinjectGlobals = true,\n\tcodegenEnabled = false,\n})\n\n-- Run the functions to get the timings\nlocal safeCodegenTime = safeCodegenFunction()\nlocal unsafeCodegenTime = unsafeCodegenFunction()\nlocal safeTime = safeFunction()\nlocal unsafeTime = unsafeFunction()\n\n-- Assert that safeCodegenTime is always twice as fast as both unsafe functions\nlocal safeCodegenUpperBound = safeCodegenTime * 2\nassert(\n\tunsafeCodegenTime > safeCodegenUpperBound and unsafeTime > safeCodegenUpperBound,\n\t\"expected luau.load with codegenEnabled = true and no custom environment to use codegen\"\n)\n\n-- Assert that safeTime is always atleast twice as fast as both unsafe functions\nlocal safeUpperBound = safeTime * 2\nassert(\n\tunsafeCodegenTime > safeUpperBound and unsafeTime > safeUpperBound,\n\t\"expected luau.load with codegenEnabled = false and no custom environment to have safeenv enabled\"\n)\n\n-- Normally we'd also want to check whether codegen is actually being enabled by\n-- comparing timings of safe_codegen_fn and safe_fn but since we don't have a way of\n-- checking whether the current device even supports codegen, we can't safely test this.\n"
  },
  {
    "path": "tests/net/request/codes.luau",
    "content": "local util = require(\"./util\")\nlocal pass, fail = util.pass, util.fail\n\npass(\"GET\", \"https://httpbingo.org/status/200\", \"Request status code - 200\")\nfail(\"GET\", \"https://httpbingo.org/status/400\", \"Request status code - 400\")\nfail(\"GET\", \"https://httpbingo.org/status/500\", \"Request status code - 500\")\n"
  },
  {
    "path": "tests/net/request/compression.luau",
    "content": "local net = require(\"@lune/net\")\nlocal serde = require(\"@lune/serde\")\n\n-- Should decompress automatically by default\n\nlocal response = net.request({\n\turl = \"https://httpbingo.org/gzip\",\n\theaders = {\n\t\t[\"Accept-Encoding\"] = \"gzip\",\n\t} :: { [string]: string },\n})\n\nassert(\n\tresponse.ok,\n\t\"Request failed with status \"\n\t\t.. tostring(response.statusCode)\n\t\t.. \" \"\n\t\t.. tostring(response.statusMessage)\n)\n\nlocal success, json = pcall(serde.decode, \"json\" :: \"json\", response.body)\nassert(success, \"Failed to decode json response\\n\" .. tostring(json))\n\n-- Content encoding header should no longer exist when automatically decompressed\n\nassert(\n\tresponse.headers[\"content-encoding\"] == nil,\n\t\"Content encoding header still exists after automatic decompression\"\n)\n\n-- Should do nothing when explicitly disabled\n\nlocal response2 = net.request({\n\turl = \"https://httpbingo.org/gzip\",\n\theaders = {\n\t\t[\"Accept-Encoding\"] = \"gzip\",\n\t} :: { [string]: string },\n\toptions = { decompress = false },\n})\n\nassert(\n\tresponse2.ok,\n\t\"Request failed with status \"\n\t\t.. tostring(response2.statusCode)\n\t\t.. \" \"\n\t\t.. tostring(response2.statusMessage)\n)\n\nlocal success2 = pcall(serde.decode, \"json\" :: \"json\", response2.body)\nassert(not success2, \"Decompression disabled still returned json response\")\n\n-- Content encoding header should still exist when not automatically decompressed\n\nassert(\n\tresponse2.headers[\"content-encoding\"] ~= nil,\n\t\"Content encoding header is missing when automatic decompression is disabled\"\n)\n"
  },
  {
    "path": "tests/net/request/https.luau",
    "content": "local task = require(\"@lune/task\")\n\nlocal util = require(\"./util\")\nlocal pass = util.pass\n\n-- These are some public APIs that have, or most likely have, different\n-- certificate authorities (CAs), plus are both free to use and stable.\n-- This should be enough to ensure that rustls is configured correctly.\nlocal servers = {\n\t\"https://www.googleapis.com/discovery/v1/apis\",\n\t\"https://api.cloudflare.com/client/v4/ips\",\n\t\"https://azure.microsoft.com/en-us/updates/feed/\",\n\t\"https://acme-v02.api.letsencrypt.org/directory\",\n\t\"https://ip-ranges.amazonaws.com/ip-ranges.json\",\n\t\"https://en.wikipedia.org/w/api.php\",\n\t\"https://status.godaddy.com/api/v2/summary.json\",\n}\n\nfor _, server in servers do\n\ttask.spawn(function()\n\t\tpass(\"GET\", server, server)\n\tend)\nend\n"
  },
  {
    "path": "tests/net/request/methods.luau",
    "content": "local util = require(\"./util\")\nlocal pass = util.pass\n\n-- stylua: ignore start\npass(\"GET\",    \"https://httpbingo.org/get\",    \"Request method - GET\")\npass(\"POST\",   \"https://httpbingo.org/post\",   \"Request method - POST\")\npass(\"PATCH\",  \"https://httpbingo.org/patch\",  \"Request method - PATCH\")\npass(\"PUT\",    \"https://httpbingo.org/put\",    \"Request method - PUT\")\npass(\"DELETE\", \"https://httpbingo.org/delete\", \"Request method - DELETE\")\n"
  },
  {
    "path": "tests/net/request/query.luau",
    "content": "local net = require(\"@lune/net\")\nlocal serde = require(\"@lune/serde\")\n\nlocal QUERY: { [string]: string } = {\n\tKey = \"Value\",\n\tHello = \"World\",\n\tSpaceEmoji = \" 🚀 \",\n}\n\n-- Make a request with some basic query params as well\n-- as a special non-ascii one that needs url encoding\n\nlocal response = net.request({\n\turl = \"https://httpbingo.org/anything\",\n\tquery = QUERY,\n})\n\nassert(\n\tresponse.ok,\n\t\"Request failed with status \"\n\t\t.. tostring(response.statusCode)\n\t\t.. \" \"\n\t\t.. tostring(response.statusMessage)\n)\n\n-- We should get a json response here with an \"args\" table which is our query\n\nlocal success, json = pcall(serde.decode, \"json\" :: \"json\", response.body)\nassert(success, \"Failed to decode json response\\n\" .. tostring(json))\n\nlocal args = if type(json.args) == \"table\" then json.args else nil\nassert(args ~= nil, \"Response body did not contain an args table\")\n\n-- The args table should then have the same contents as our query,\n-- these will however be returned as arrays of strings and not a\n-- single string, presumably because http query params support\n-- multiple values of the same name, so we just grab the first\n\nfor key, value in QUERY do\n\tlocal received = args[key][1]\n\tif received == nil then\n\t\terror(string.format(\"Response body did not contain query parameter '%s'\", key))\n\telseif typeof(received) ~= typeof(value) then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Response body contained query parameter '%s' but it was of type '%s', expected '%s'\",\n\t\t\t\tkey,\n\t\t\t\ttypeof(received),\n\t\t\t\ttypeof(value)\n\t\t\t)\n\t\t)\n\telseif received ~= value then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Response body contained query parameter '%s' but it had the value '%s', expected '%s'\",\n\t\t\t\tkey,\n\t\t\t\treceived,\n\t\t\t\tvalue\n\t\t\t)\n\t\t)\n\tend\nend\n"
  },
  {
    "path": "tests/net/request/redirect.luau",
    "content": "local util = require(\"./util\")\nlocal pass = util.pass\n\npass(\"GET\", \"https://httpbingo.org/absolute-redirect/3\", \"Redirect 3 times\")\n"
  },
  {
    "path": "tests/net/request/user_agent.luau",
    "content": "local net = require(\"@lune/net\")\nlocal serde = require(\"@lune/serde\")\n\nlocal runtime, version = table.unpack(_VERSION:split(\" \"))\nlocal expectedUserAgent = runtime:lower() .. \"/\" .. version\n\nlocal userAgent: string =\n\tserde.decode(\"json\", net.request(\"https://www.whatsmyua.info/api/v1/ua\").body)[1].ua.rawUa\n\nassert(userAgent == expectedUserAgent, \"Expected user agent to be \" .. expectedUserAgent)\n"
  },
  {
    "path": "tests/net/request/util.luau",
    "content": "local net = require(\"@lune/net\")\nlocal stdio = require(\"@lune/stdio\")\n\nlocal util = {}\n\nfunction util.pass(method, url, message)\n\tlocal success, response = pcall(net.request, {\n\t\tmethod = method,\n\t\turl = url,\n\t})\n\tif not success then\n\t\terror(`{message} errored!\\nError message: {tostring(response)}`)\n\telseif not response.ok then\n\t\terror(\n\t\t\t`{message} failed, but should have passed!`\n\t\t\t\t.. `\\nStatus code: {response.statusCode}`\n\t\t\t\t.. `\\nStatus message: {response.statusMessage}`\n\t\t\t\t.. `\\nResponse headers: {stdio.format(response.headers)}`\n\t\t\t\t.. `\\nResponse body: {response.body}`\n\t\t)\n\tend\nend\n\nfunction util.fail(method, url, message)\n\tlocal success, response = pcall(net.request, {\n\t\tmethod = method,\n\t\turl = url,\n\t})\n\tif not success then\n\t\terror(`{message} errored!\\nError message: {tostring(response)}`)\n\telseif response.ok then\n\t\terror(\n\t\t\t`{message} passed, but should have failed!`\n\t\t\t\t.. `\\nStatus code: {response.statusCode}`\n\t\t\t\t.. `\\nStatus message: {response.statusMessage}`\n\t\t\t\t.. `\\nResponse headers: {stdio.format(response.headers)}`\n\t\t\t\t.. `\\nResponse body: {response.body}`\n\t\t)\n\tend\nend\n\nreturn util\n"
  },
  {
    "path": "tests/net/serve/addresses.luau",
    "content": "local net = require(\"@lune/net\")\n\nlocal PORT = 8811\nlocal LOCALHOST = \"http://localhost\"\nlocal BROADCAST = `http://0.0.0.0`\nlocal RESPONSE = \"Hello, lune!\"\n\n-- Serve should be able to bind to broadcast IP addresse\n\nlocal handle = net.serve(PORT, {\n\taddress = BROADCAST,\n\thandleRequest = function(request)\n\t\treturn `Response from {BROADCAST}:{PORT}`\n\tend,\n})\n\n-- And any requests to localhost should then succeed\n\nlocal response = net.request(`{LOCALHOST}:{PORT}`).body\nassert(response ~= nil, \"Invalid response from server\")\n\nhandle.stop()\n\n-- Attempting to serve with a malformed IP address should throw an error\n\nlocal success = pcall(function()\n\tnet.serve(8080, {\n\t\taddress = \"a.b.c.d\",\n\t\thandleRequest = function()\n\t\t\treturn RESPONSE\n\t\tend,\n\t})\nend)\n\nassert(not success, \"Server was created with malformed address\")\n"
  },
  {
    "path": "tests/net/serve/handles.luau",
    "content": "local net = require(\"@lune/net\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = 8822\nlocal URL = `http://127.0.0.1:{PORT}`\nlocal RESPONSE = \"Hello, lune!\"\n\nlocal handle = net.serve(PORT, function(request)\n\treturn RESPONSE\nend)\n\n-- Stopping is not guaranteed to happen instantly since it is async, but\n-- it should happen on the next yield, so we wait the minimum amount here\n\nhandle.stop()\ntask.wait()\n\n-- Sending a request to the stopped server should now error\n\nlocal success, response2 = pcall(net.request, URL)\nif not success then\n\tlocal message = tostring(response2)\n\tassert(\n\t\tstring.find(message, \"Connection reset\")\n\t\t\tor string.find(message, \"Connection closed\")\n\t\t\tor string.find(message, \"Connection refused\")\n\t\t\tor string.find(message, \"No connection could be made\"), -- Windows Request Error\n\t\t\"Server did not stop responding to requests\"\n\t)\nelse\n\tassert(not response2.ok, \"Server did not stop responding to requests\")\nend\n\n--[[\n\tTrying to *stop* the server again should error, and\n\talso mention that the server has already been stopped\n\n\tNote that we cast pcall to any because of a\n\tLuau limitation where it throws a type error for\n\t`err` because handle.stop doesn't return any value\n]]\n\nlocal success2, err = (pcall :: any)(handle.stop)\nassert(not success2, \"Calling stop twice on the net serve handle should error\")\nlocal message = tostring(err)\nassert(\n\tstring.find(message, \"stop\")\n\t\tor string.find(message, \"shutdown\")\n\t\tor string.find(message, \"shut down\"),\n\t\"The error message for calling stop twice on the net serve handle should be descriptive\"\n)\n"
  },
  {
    "path": "tests/net/serve/non_blocking.luau",
    "content": "local net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = 8833\nlocal RESPONSE = \"Hello, lune!\"\n\n-- Serve should not yield the entire main thread forever, only\n-- for the initial binding to socket which should be very fast\n\nlocal thread = task.delay(1, function()\n\tstdio.ewrite(\"Serve must not yield the current thread for too long\\n\")\n\ttask.wait(1)\n\tprocess.exit(1)\nend)\n\nlocal handle = net.serve(PORT, function(request)\n\treturn RESPONSE\nend)\n\ntask.cancel(thread)\n\nhandle.stop()\n"
  },
  {
    "path": "tests/net/serve/requests.luau",
    "content": "local net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = 8844\nlocal URL = `http://127.0.0.1:{PORT}`\nlocal RESPONSE = \"Hello, lune!\"\n\n-- Serve should get proper path, query, and other request information\n\nlocal handle = net.serve(PORT, function(request)\n\t-- print(\"Got a request from\", request.ip, \"on port\", request.port)\n\n\tassert(type(request.path) == \"string\")\n\tassert(type(request.query) == \"table\")\n\tassert(type(request.query.key) == \"table\")\n\tassert(type(request.query.key2) == \"string\")\n\n\tassert(request.path == \"/some/path\")\n\tassert(request.query.key[1] == \"param1\")\n\tassert(request.query.key[2] == \"param2\")\n\tassert(request.query.key2 == \"param3\")\n\n\treturn RESPONSE\nend)\n\n-- Serve should be able to handle at least 100 requests per second with a basic handler such as the above\n\nlocal thread = task.delay(1, function()\n\tstdio.ewrite(\"Serve should respond to requests in a reasonable amount of time\\n\")\n\ttask.wait(1)\n\tprocess.exit(1)\nend)\n\n-- Serve should respond to requests we send, and keep responding until we stop it\n\nfor _ = 1, 100 do\n\tlocal response = net.request(URL .. \"/some/path?key=param1&key=param2&key2=param3\").body\n\tassert(response == RESPONSE, \"Invalid response from server\")\nend\n\ntask.cancel(thread)\n\nhandle.stop()\n"
  },
  {
    "path": "tests/net/serve/websockets.luau",
    "content": "local net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\nlocal PORT = 8855\nlocal WS_URL = `ws://127.0.0.1:{PORT}`\nlocal REQUEST = \"Hello from client!\"\nlocal RESPONSE = \"Hello, lune!\"\n\n-- Serve should not block the thread from continuing\n\nlocal thread = task.delay(1, function()\n\tstdio.ewrite(\"Serve must not block the current thread\\n\")\n\ttask.wait(1)\n\tprocess.exit(1)\nend)\n\nlocal handle = net.serve(PORT, {\n\thandleRequest = function()\n\t\tstdio.ewrite(\"Web socket should upgrade automatically, not pass to the request handler\\n\")\n\t\ttask.wait(1)\n\t\tprocess.exit(1)\n\t\treturn \"unreachable\"\n\tend,\n\thandleWebSocket = function(socket)\n\t\tlocal socketMessage = socket:next()\n\t\tassert(socketMessage == REQUEST, \"Invalid web socket request from client\")\n\t\tsocket:send(RESPONSE)\n\t\tsocket:close()\n\tend,\n})\n\ntask.cancel(thread)\n\n-- Web socket responses should also be responded to\n\nlocal thread2 = task.delay(1, function()\n\tstdio.ewrite(\"Serve should respond to websockets in a reasonable amount of time\\n\")\n\ttask.wait(1)\n\tprocess.exit(1)\nend)\n\nlocal socket = net.socket(WS_URL)\n\nsocket:send(REQUEST)\n\nlocal socketMessage = socket:next()\nassert(socketMessage ~= nil, \"Got no web socket response from server\")\nassert(socketMessage == RESPONSE, \"Invalid web socket response from server\")\n\nsocket:close()\n\ntask.cancel(thread2)\n\n-- Wait for the socket to close and make sure we can't send messages afterwards\ntask.wait()\nlocal success3, err2 = (pcall :: any)(socket.send, socket, \"\")\nassert(not success3, \"Sending messages after the socket has been closed should error\")\nlocal message2 = tostring(err2)\nassert(\n\tstring.find(message2, \"close\") or string.find(message2, \"closing\"),\n\t\"The error message for sending messages on a closed web socket should be descriptive\"\n)\n\n-- Stop the server to end the test\nhandle.stop()\n"
  },
  {
    "path": "tests/net/socket/basic.luau",
    "content": "local net = require(\"@lune/net\")\n\n-- We're going to use Discord's WebSocket gateway server for testing\nlocal socket = net.socket(\"wss://gateway.discord.gg/?v=10&encoding=json\")\n\nassert(type(socket.next) == \"function\", \"next must be a function\")\nassert(type(socket.send) == \"function\", \"send must be a function\")\nassert(type(socket.close) == \"function\", \"close must be a function\")\n\n-- Request to close the socket\nsocket:close()\n\n-- Drain remaining messages, until we got our close message\nrepeat\n\tlocal message = socket:next()\n\tif message ~= nil then\n\t\tassert(type(message) == \"string\", \"Message should be a string\")\n\t\tassert(#message > 0, \"Message should not be empty\")\n\t\tassert(string.sub(message, 1, 1) == \"{\", \"Message should be a JSON object\")\n\tend\nuntil message == nil\n\nassert(type(socket.closeCode) == \"number\", \"closeCode should exist after closing\")\nassert(socket.closeCode == 1000, \"closeCode should be 1000 after closing\")\n\nlocal success, errorMessage = pcall(function()\n\tsocket:send(\"Hello, world!\")\nend)\n\nassert(not success, \"send should fail after closing\")\nassert(\n\tstring.find(tostring(errorMessage), \"closed\") or string.find(tostring(errorMessage), \"closing\"),\n\t\"send should fail with a message that the socket was closed\"\n)\n"
  },
  {
    "path": "tests/net/socket/wss.luau",
    "content": "local net = require(\"@lune/net\")\nlocal task = require(\"@lune/task\")\n\n-- We're going to use Discord's WebSocket gateway server\n-- for testing wss - it does not require auth, and this test\n-- only exists to ensure wss (WebSockets with TLS) works correctly\nlocal socket = net.socket(\"wss://gateway.discord.gg/?v=10&encoding=json\")\n\nwhile not socket.closeCode do\n\tlocal message = socket:next()\n\n\tif message ~= nil then\n\t\t-- Make sure we got some JSON object, which Discord should always give us\n\t\tassert(type(message) == \"string\", \"Message should be a string\")\n\t\tassert(#message > 0, \"Message should not be empty\")\n\t\tassert(string.sub(message, 1, 1) == \"{\", \"Message should be a JSON object\")\n\n\t\t-- Close the connection after a second with the success close code\n\t\ttask.wait(1)\n\t\tsocket:close(1000)\n\tend\nend\n"
  },
  {
    "path": "tests/net/socket/wss_rw.luau",
    "content": "local net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\n-- We're going to use Discord's WebSocket gateway server\n-- for testing that we can both read from a stream,\n-- as well as write to the same stream concurrently\nlocal socket = net.socket(\"wss://gateway.discord.gg/?v=10&encoding=json\")\n\nlocal spawnedThread = task.spawn(function()\n\twhile not socket.closeCode do\n\t\tlocal message = socket:next()\n\t\tif message ~= nil then\n\t\t\tassert(type(message) == \"string\", \"Message should be a string\")\n\t\t\tassert(#message > 0, \"Message should not be empty\")\n\t\t\tassert(string.sub(message, 1, 1) == \"{\", \"Message should be a JSON object\")\n\t\tend\n\tend\nend)\n\nlocal delayedThread = task.delay(10, function()\n\tstdio.ewrite(\"`socket.send` halted, failed to write to socket\")\n\tprocess.exit(1)\nend)\n\nlocal payload = '{\"op\":1,\"d\":null}'\nsocket:send(payload)\nsocket:send(buffer.fromstring(payload))\nsocket:close(1000)\n\ntask.cancel(delayedThread)\ntask.cancel(spawnedThread)\n\nprocess.exit(0)\n"
  },
  {
    "path": "tests/net/tcp/basic.luau",
    "content": "local net = require(\"@lune/net\")\n\n-- NOTE: We do not use httpbingo in these TCP tests, since they do not accept\n-- very basic HTTP requests such as the below, and it is not known which headers\n-- are missing for them to work. All we need is a bit of HTML and a server that\n-- properly closes the connection, which example.com handles just fine.\nlocal stream = net.tcp.connect(\"example.com\", 80)\n\nlocal request = \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\nConnection: close\\r\\n\\r\\n\"\nstream:write(request)\n\nlocal responseChunks = {}\nwhile true do\n\tlocal chunk = stream:read()\n\tif chunk == nil or #chunk <= 0 then\n\t\tbreak\n\tend\n\ttable.insert(responseChunks, chunk)\nend\n\nlocal response = table.concat(responseChunks)\nlocal function contains(substring: string): boolean\n\treturn string.find(response, substring, 1, true) ~= nil\nend\n\nstream:close()\n\nassert(contains(\"HTTP/1.1 200\"), \"Should receive HTTP 200 response\")\nassert(contains(\"\\nContent-Length: \"), \"Response should have content length header\")\nassert(contains(\"<html\"), \"Response should contain some HTML\")\nassert(contains(\"/html>\"), \"Response should contain some HTML\")\n"
  },
  {
    "path": "tests/net/tcp/info.luau",
    "content": "local net = require(\"@lune/net\")\n\nlocal stream = net.tcp.connect(\"httpbingo.org\", 80)\n\nassert(type(stream.localIp) == \"string\", \"localIp should be a string\")\nassert(type(stream.localPort) == \"number\", \"localPort should be a number\")\nassert(type(stream.remoteIp) == \"string\", \"remoteIp should be a string\")\nassert(stream.remotePort == 80, \"remotePort should be 80\")\n\nassert(\n\tstring.match(stream.remoteIp, \"^%d+%.%d+%.%d+%.%d+$\"),\n\t\"remoteIp should be a valid IP address\"\n)\n\nassert(stream.localPort > 0 and stream.localPort <= 65535, \"localPort should be in valid range\")\n\nstream:close()\n"
  },
  {
    "path": "tests/net/tcp/tls.luau",
    "content": "local net = require(\"@lune/net\")\n\n-- NOTE: We do not use httpbingo in these TCP tests, since they do not accept\n-- very basic HTTP requests such as the below, and it is not known which headers\n-- are missing for them to work. All we need is a bit of HTML and a server that\n-- properly closes the connection, which example.com handles just fine.\nlocal stream = net.tcp.connect(\"example.com\", 443, { tls = true })\n\nlocal request = \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\nConnection: close\\r\\n\\r\\n\"\nstream:write(request)\n\nlocal responseChunks = {}\nwhile true do\n\tlocal chunk = stream:read()\n\tif chunk == nil or #chunk <= 0 then\n\t\tbreak\n\tend\n\ttable.insert(responseChunks, chunk)\nend\n\nlocal response = table.concat(responseChunks)\nlocal function contains(substring: string): boolean\n\treturn string.find(response, substring, 1, true) ~= nil\nend\n\nstream:close()\n\nassert(contains(\"HTTP/1.1 200\"), \"Should receive HTTP 200 response\")\nassert(contains(\"\\nContent-Length: \"), \"Response should have content length header\")\nassert(contains(\"<html\"), \"Response should contain some HTML\")\nassert(contains(\"/html>\"), \"Response should contain some HTML\")\n"
  },
  {
    "path": "tests/net/url/decode.luau",
    "content": "local net = require(\"@lune/net\")\n\nlocal decoded = net.urlDecode(\"%F0%9F%9A%80%20This%20string%20will%20be%20decoded.\")\nassert(decoded == \"🚀 This string will be decoded.\")\n"
  },
  {
    "path": "tests/net/url/encode.luau",
    "content": "local net = require(\"@lune/net\")\n\nlocal encoded = net.urlEncode(\"🚀 This string will be encoded.\")\nassert(encoded == \"%F0%9F%9A%80%20This%20string%20will%20be%20encoded.\")\n"
  },
  {
    "path": "tests/process/args.luau",
    "content": "local process = require(\"@lune/process\")\n\nassert(type(process.args) == \"table\", \"Process args should be a plain table\")\n\nassert(#process.args > 0, \"No process arguments found\")\n\nassert(process.args[1] == \"Foo\", \"Invalid first argument to process\")\nassert(process.args[2] == \"Bar\", \"Invalid second argument to process\")\n\nlocal success, err = pcall(function()\n\tprocess.args[1] = \"abc\"\nend)\nlocal message = if err == nil then nil else tostring(err)\nassert(\n\tsuccess == false and type(message) == \"string\" and #message > 0,\n\t\"Trying to set process arguments should throw an error with a message\"\n)\nassert(\n\tstring.find(message, \"read\") ~= nil,\n\t\"Setting process args error message should mention that they are read-only\"\n)\nassert(\n\tstring.find(message, \"only\") ~= nil,\n\t\"Setting process args error message should mention that they are read-only\"\n)\n"
  },
  {
    "path": "tests/process/create/kill.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal expected = \"Hello, world!\"\n\nlocal catChild = process.create(\"cat\")\ncatChild.stdin:write(expected)\ncatChild:kill()\nlocal catStatus = catChild:status()\nlocal catStdout = catChild.stdout:readToEnd()\n\nassert(catStatus.code == 9, \"Child process should have an exit code of 9 (SIGKILL)\")\nassert(catStdout == expected, \"Reading from stdout of child process should work even after kill\")\n\nlocal stdinWriteOk = pcall(function()\n\tcatChild.stdin:write(expected)\nend)\nassert(not stdinWriteOk, \"Writing to stdin of child process should not work after kill\")\n"
  },
  {
    "path": "tests/process/create/non_blocking.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal childThread = coroutine.create(process.create)\n\nlocal ok, err = coroutine.resume(childThread, \"echo\", { \"hello, world\" })\nassert(ok, err)\n\nassert(\n\tcoroutine.status(childThread) == \"dead\",\n\t\"Child process should not yield the thread it is created on\"\n)\n"
  },
  {
    "path": "tests/process/create/status.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal testCode = math.random(0, 255)\nlocal testOk = testCode == 0\n\nlocal exitChild = process.create(\"exit\", { tostring(testCode) }, { shell = true })\nlocal exitStatus = exitChild:status()\n\nassert(type(exitStatus) == \"table\", \"Child status should be a table\")\nassert(type(exitStatus.ok) == \"boolean\", \"Child status.ok should be a boolean\")\nassert(type(exitStatus.code) == \"number\", \"Child status.code should be a number\")\n\nassert(\n\texitStatus.ok == testOk,\n\t\"Child status should be \"\n\t\t.. (if exitStatus.ok then \"ok\" else \"not ok\")\n\t\t.. \", was \"\n\t\t.. (if exitStatus.ok then \"not ok\" else \"ok\")\n)\nassert(\n\texitStatus.code == testCode,\n\t\"Child process exited with an unexpected exit code!\"\n\t\t.. `\\nExpected: ${testCode}`\n\t\t.. `\\nReceived: ${exitStatus.code}`\n)\n"
  },
  {
    "path": "tests/process/create/stream.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal expected = \"hello, world\"\n\n-- Stdout test\n\nlocal catChild = process.create(\"cat\")\ncatChild.stdin:write(expected)\ncatChild.stdin:close()\nlocal catOutput = catChild.stdout:read(#expected)\n\nassert(\n\texpected == catOutput,\n\t\"Failed to write to stdin or read from stdout of child process!\"\n\t\t.. `\\nExpected: \"{expected}\"`\n\t\t.. `\\nReceived: \"{catOutput}\"`\n)\n\n-- Stderr test, needs to run in shell because there is no\n-- other good cross-platform way to simply write to stdout\n\nlocal echoChild = if process.os == \"windows\"\n\tthen process.create(\"/c\", { \"echo\", expected, \"1>&2\" }, { shell = \"cmd\" })\n\telse process.create(\"echo\", { expected, \">>/dev/stderr\" }, { shell = true })\nlocal echoOutput = echoChild.stderr:read(#expected)\n\nassert(\n\texpected == echoOutput,\n\t\"Failed to write to stdin or read from stderr of child process!\"\n\t\t.. `\\nExpected: \"{expected}\"`\n\t\t.. `\\nReceived: \"{echoOutput}\"`\n)\n"
  },
  {
    "path": "tests/process/cwd.luau",
    "content": "local process = require(\"@lune/process\")\n\nassert(process.cwd ~= nil, \"Process cwd is missing\")\n\nassert(type(process.cwd) == \"string\", \"Process cwd is not a string\")\n\nassert(#process.cwd > 0, \"Process cwd is an empty string\")\n\nif process.os == \"windows\" then\n\tassert(string.sub(process.cwd, -1) == \"\\\\\", \"Process cwd does not end with '\\\\'\")\nelse\n\tassert(string.sub(process.cwd, -1) == \"/\", \"Process cwd does not end with '/'\")\nend\n"
  },
  {
    "path": "tests/process/env.luau",
    "content": "local process = require(\"@lune/process\")\n\nassert(type(process.env) == \"table\", \"Process env should be a plain table\")\n\nlocal randomKey = string.format(\"LUNE_TEST_%d\", math.random(1, 999_999))\n\nassert(process.env[randomKey] == nil, \"Unset variable returned a non-nil value\")\n\nprocess.env[randomKey] = \"abc\"\nassert(process.env[randomKey] == \"abc\", \"Failed to set environment variable\")\n\nprocess.env[randomKey] = nil\nassert(process.env[randomKey] == nil, \"Failed to set environment variable\")\n"
  },
  {
    "path": "tests/process/exec/async.luau",
    "content": "local process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\nlocal IS_WINDOWS = process.os == \"windows\"\n\n-- Executing a command should not block any lua thread(s)\n\nlocal SLEEP_DURATION = 1 / 4\nlocal SLEEP_SAMPLES = 2\n\nlocal thread2 = task.delay(\n\tif IS_WINDOWS then 30 else (SLEEP_DURATION * SLEEP_SAMPLES * 2),\n\tfunction()\n\t\tstdio.ewrite(\"Spawning a sleep process should take a reasonable amount of time\\n\")\n\t\ttask.wait(1)\n\t\tprocess.exit(1)\n\tend\n)\n\nlocal sleepStart = os.clock()\nlocal sleepCounter = 0\nfor i = 1, SLEEP_SAMPLES, 1 do\n\ttask.spawn(function()\n\t\tlocal args = {\n\t\t\t-- Sleep command on Windows in Seconds has some weird behavior with decimals ...\n\t\t\ttostring(SLEEP_DURATION * (IS_WINDOWS and 1000 or 1)),\n\t\t}\n\t\tif IS_WINDOWS then\n\t\t\t-- ... so we use milliseconds instead.\n\t\t\ttable.insert(args, 1, \"-Milliseconds\")\n\t\tend\n\t\t-- Windows does not have `sleep` as a process, so we use powershell instead.\n\t\tprocess.exec(\"sleep\", args, if IS_WINDOWS then { shell = true } else nil)\n\t\tsleepCounter += 1\n\tend)\nend\nwhile sleepCounter < SLEEP_SAMPLES do\n\ttask.wait()\nend\n\ntask.cancel(thread2)\n\nassert(\n\t(os.clock() - sleepStart) >= SLEEP_DURATION,\n\t\"Spawning a process that does blocking sleep did not sleep enough\"\n)\n"
  },
  {
    "path": "tests/process/exec/basic.luau",
    "content": "local process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\n-- Executing a command should work, with options\n\nlocal thread = task.delay(1, function()\n\tstdio.ewrite(\"Spawning a process should take a reasonable amount of time\\n\")\n\ttask.wait(1)\n\tprocess.exit(1)\nend)\n\nlocal IS_WINDOWS = process.os == \"windows\"\n\nlocal result = process.exec(\n\tif IS_WINDOWS then \"cmd\" else \"ls\",\n\tif IS_WINDOWS then { \"/c\", \"dir\" } else { \"-a\" }\n)\n\ntask.cancel(thread)\n\nassert(result.ok, \"Failed to spawn child process\")\n\nassert(result.stderr == \"\", \"Stderr was not empty\")\nassert(result.stdout ~= \"\", \"Stdout was empty\")\n\nassert(string.find(result.stdout, \"Cargo.toml\") ~= nil, \"Missing Cargo.toml in output\")\nassert(string.find(result.stdout, \".gitignore\") ~= nil, \"Missing .gitignore in output\")\n"
  },
  {
    "path": "tests/process/exec/cwd.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal IS_WINDOWS = process.os == \"windows\"\n\nlocal pwdCommand = if IS_WINDOWS then \"cmd\" else \"pwd\"\nlocal pwdArgs = if IS_WINDOWS then { \"/c\", \"cd\" } else {}\n\n-- Make sure the cwd option actually uses the directory we want\nlocal rootPwd = process.exec(pwdCommand, pwdArgs, {\n\tcwd = \"/\",\n}).stdout\nrootPwd = string.gsub(rootPwd, \"^%s+\", \"\")\nrootPwd = string.gsub(rootPwd, \"%s+$\", \"\")\n\n-- Windows: <Drive Letter>:\\, Unix: /\nlocal expectedRootPwd = if IS_WINDOWS then string.sub(rootPwd, 1, 1) .. \":\\\\\" else \"/\"\nif rootPwd ~= expectedRootPwd then\n\terror(\n\t\tstring.format(\n\t\t\t\"Current working directory for child process was not set correctly!\"\n\t\t\t\t.. \"\\nExpected '%s', got '%s'\",\n\t\t\texpectedRootPwd,\n\t\t\trootPwd\n\t\t)\n\t)\nend\n\n-- Setting cwd should not change the cwd of this process\n\nlocal pwdBefore = process.exec(pwdCommand, pwdArgs).stdout\nprocess.exec(\"ls\", {}, {\n\tcwd = \"/\",\n\tshell = true,\n})\nlocal pwdAfter = process.exec(pwdCommand, pwdArgs).stdout\nassert(pwdBefore == pwdAfter, \"Current working directory changed after running child process\")\n\n-- Setting the cwd on a child process should properly\n-- replace any leading ~ with the users real home dir\n\nlocal homeDir1 = process.exec(\"echo $HOME\", nil, {\n\tshell = true,\n}).stdout\n\n-- NOTE: Powershell for windows uses `$pwd.Path` instead of `pwd` as pwd would return\n-- a PathInfo object, using $pwd.Path gets the Path property of the PathInfo object\nlocal homeDir2 = process.exec(if IS_WINDOWS then \"$pwd.Path\" else \"pwd\", nil, {\n\tshell = true,\n\tcwd = \"~\",\n}).stdout\n\nassert(#homeDir1 > 0, \"Home dir from echo was empty\")\nassert(#homeDir2 > 0, \"Home dir from pwd was empty\")\nassert(homeDir1 == homeDir2, \"Home dirs did not match when performing tilde substitution\")\n"
  },
  {
    "path": "tests/process/exec/no_panic.luau",
    "content": "local process = require(\"@lune/process\")\n\n-- Executing a non existent command as a child process\n-- should not panic, but should error\n\nlocal success = pcall(process.exec, \"someProgramThatDoesNotExist\")\nassert(not success, \"Spawned a non-existent program\")\n"
  },
  {
    "path": "tests/process/exec/shell.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal IS_WINDOWS = process.os == \"windows\"\n\n-- Default shell should be /bin/sh on unix and powershell on Windows,\n-- note that powershell needs slightly different command flags for ls\n\nlocal shellResult = process.exec(\"ls\", {\n\tif IS_WINDOWS then \"-Force\" else \"-a\",\n}, {\n\tshell = true,\n})\n\nassert(shellResult.ok, \"Failed to spawn child process (shell)\")\n\nassert(shellResult.stderr == \"\", \"Stderr was not empty (shell)\")\nassert(shellResult.stdout ~= \"\", \"Stdout was empty (shell)\")\n\nassert(string.find(shellResult.stdout, \"Cargo.toml\") ~= nil, \"Missing Cargo.toml in output (shell)\")\nassert(string.find(shellResult.stdout, \".gitignore\") ~= nil, \"Missing .gitignore in output (shell)\")\n"
  },
  {
    "path": "tests/process/exec/stdin.luau",
    "content": "--[[\n\tUnfortunately, this test is, for some reason, very flaky on Windows.\n\n\tThe behavior of stdin itself works as expected, but a simple test\n\tthat passes a string to stdin, and needs that exact string back out,\n\tis near impossible to write using PowerShell on Windows.\n\n\tFor now, we will disable this test entirely on that platform to\n\tnot disturb the rest of the test suite and contribution process.\n]]\n\nlocal DISABLE_WINDOWS_TESTS = true\n\nlocal process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\nlocal expected = \"Hello from child process!\"\n\n-- Writing to stdin should succeed and we should be able\n-- to read the exact message we wrote to it from stdout\n\nif DISABLE_WINDOWS_TESTS and process.os == \"windows\" then\n\tprocess.exit(0)\nend\n\nlocal options = { stdio = { stdin = expected } }\nlocal result = if process.os == \"windows\"\n\tthen process.exec(\"powershell\", {\n\t\t\"-NonInteractive\",\n\t\t\"-NoProfile\",\n\t\t\"-Command\",\n\t\t\"[System.Console]::Write([System.Console]::In.ReadToEnd())\",\n\t}, options)\n\telse process.exec(\"bash\", { \"-c\", \"cat\" }, options)\n\nlocal resultStdout = result.stdout\nresultStdout = string.gsub(resultStdout, \"^%s+\", \"\") -- Trim leading whitespace\nresultStdout = string.gsub(resultStdout, \"%s+$\", \"\") -- Trim trailing whitespace\nassert(\n\tresultStdout == expected,\n\t\"Passing a string to stdin did not return the expected output!\"\n\t\t.. `\\nExpected: {expected}`\n\t\t.. `\\nReceived: {resultStdout}`\n)\n\n-- Not passing anything as the stdin option should guarantee\n-- that stdin is completely closed, and not waiting for input\n\nlocal thread = task.delay(5, function()\n\tstdio.ewrite(\"Not passing stdin to process.exec should not hang\\n\")\n\tprocess.exit(1)\nend)\n\nif process.os == \"windows\" then\n\tprocess.exec(\"powershell\", {\n\t\t-- \"-NonInteractive\", -- if interactive (we have stdin) we *should* hang!\n\t\t\"-NoProfile\",\n\t\t\"-Command\",\n\t\t\"[System.Console]::In.ReadToEnd()\",\n\t})\nelse\n\tprocess.exec(\"bash\", { \"-c\", \"cat\" })\nend\n\ntask.cancel(thread)\nprocess.exit(0)\n"
  },
  {
    "path": "tests/process/exec/stdio.luau",
    "content": "local process = require(\"@lune/process\")\n\nlocal IS_WINDOWS = process.os == \"windows\"\n\n-- Inheriting stdio & environment variables should work\n\nlocal echoMessage = \"Hello from child process!\"\nlocal echoResult = process.exec(\"echo\", {\n\tif IS_WINDOWS then '\"$Env:TEST_VAR\"' else '\"$TEST_VAR\"',\n}, {\n\tenv = { TEST_VAR = echoMessage },\n\tshell = if IS_WINDOWS then \"powershell\" else \"bash\",\n\tstdio = \"inherit\",\n})\n\n-- Windows uses \\r\\n (CRLF) and unix uses \\n (LF)\n\nlocal echoTrail = if IS_WINDOWS then \"\\r\\n\" else \"\\n\"\nassert(\n\techoResult.stdout == (echoMessage .. echoTrail),\n\t\"Inheriting stdio did not return proper output\"\n)\n"
  },
  {
    "path": "tests/process/exit.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal process = require(\"@lune/process\")\nlocal task = require(\"@lune/task\")\n\nlocal function assert(condition, err)\n\tif not condition then\n\t\ttask.spawn(error, err)\n\t\tprocess.exit(0)\n\tend\nend\n\nlocal path = process.cwd .. \"asdfghjkl\"\n\nassert(fs.isDir(path), \"Process should exit with success\")\nassert(fs.isDir(path), \"Process should exit with success\")\nassert(fs.isDir(path), \"Process should exit with success\")\n\nerror(\"Process should have exited with success...\")\n"
  },
  {
    "path": "tests/regex/general.luau",
    "content": "--!nocheck\n\nlocal regex = require(\"@lune/regex\")\n\nlocal re = regex.new(\"[0-9]+\")\n\nassert(re:isMatch(\"look, a number: 1241425\") == true)\n\nlocal mtch = re:find(\"1337 wow\")\nassert(mtch ~= nil)\nassert(mtch.start == 1)\nassert(mtch.finish == 4)\nassert(mtch.len == 4)\nassert(mtch.text == \"1337\")\nassert(#mtch == mtch.len)\n\nre = regex.new([[([0-9]+) (\\d+) \\D+ \\d+ (\\d)]])\nlocal captures = re:captures(\"1337 125600 wow! 1984 0\")\nassert(captures ~= nil)\nassert(#captures == 3)\nassert(captures:get(0).text == \"1337 125600 wow! 1984 0\")\nassert(captures:get(1).text == \"1337\")\nassert(captures:get(2).text == \"125600\")\nassert(captures:get(3).text == \"0\")\nassert(captures:get(4) == nil)\n\nassert(captures:format(\"$0\") == \"1337 125600 wow! 1984 0\")\nassert(captures:format(\"$3 $2 $1\") == \"0 125600 1337\")\n\nre = regex.new(\"(?P<first>[^ ]+)[ ]+(?P<last>[^ ]+)(?P<space>[ ]*)\")\ncaptures = re:captures(\"w1 w2 w3 w4\")\nassert(captures:format(\"$last $first$space\") == \"w2 w1 \")\n\nlocal split = regex.new([[,]]):split(\"1,2,3,4\")\nassert(#split == 4)\nassert(split[1] == \"1\")\nassert(split[2] == \"2\")\nassert(split[3] == \"3\")\nassert(split[4] == \"4\")\n"
  },
  {
    "path": "tests/regex/metamethods.luau",
    "content": "--!nolint\n\nlocal regex = require(\"@lune/regex\")\n\nlocal re = regex.new(\"[0-9]+\")\nassert(tostring(re) == \"[0-9]+\")\nassert(typeof(re) == \"Regex\")\n\nlocal mtch = re:find(\"1337 wow\")\nassert(tostring(mtch) == \"1337\")\nassert(typeof(mtch) == \"RegexMatch\")\n\nlocal re2 = regex.new(\"([0-9]+) ([0-9]+) wow! ([0-9]+) ([0-9]+)\")\nlocal captures = re2:captures(\"1337 125600 wow! 1984 0\")\nassert(tostring(captures) == \"4\")\nassert(typeof(captures) == \"RegexCaptures\")\n"
  },
  {
    "path": "tests/regex/replace.luau",
    "content": "local regex = require(\"@lune/regex\")\n\n-- Tests taken from the Regex crate\n\nlocal function replace(\n\tname: string,\n\tpattern: string,\n\tfind: string,\n\treplace: string,\n\texpected: string\n)\n\tlocal re = regex.new(pattern)\n\tlocal replaced = re:replace(find, replace)\n\tif replaced ~= expected then\n\t\terror(`test '{name}' did not return expected result (expected {expected} got {replaced})`)\n\tend\nend\n\nlocal function replaceAll(\n\tname: string,\n\tpattern: string,\n\tfind: string,\n\treplace: string,\n\texpected: string\n)\n\tlocal re = regex.new(pattern)\n\tlocal replaced = re:replaceAll(find, replace)\n\tif replaced ~= expected then\n\t\terror(`test '{name}' did not return expected result (expected {expected} got {replaced})`)\n\tend\nend\n\nreplace(\"first\", \"[0-9]\", \"age: 26\", \"Z\", \"age: Z6\")\nreplace(\"plus\", \"[0-9]+\", \"age: 26\", \"Z\", \"age: Z\")\nreplaceAll(\"all\", \"[0-9]\", \"age: 26\", \"Z\", \"age: ZZ\")\nreplace(\"groups\", \"([^ ]+)[ ]+([^ ]+)\", \"w1 w2\", \"$2 $1\", \"w2 w1\")\nreplace(\"double dollar\", \"([^ ]+)[ ]+([^ ]+)\", \"w1 w2\", \"$2 $$1\", \"w2 $1\")\n\nreplaceAll(\n\t\"named\",\n\t\"(?P<first>[^ ]+)[ ]+(?P<last>[^ ]+)(?P<space>[ ]*)\",\n\t\"w1 w2 w3 w4\",\n\t\"$last $first$space\",\n\t\"w2 w1 w4 w3\"\n)\nreplaceAll(\"trim\", \"^[ \\t]+|[ \\t]+$\", \" \\t  trim me\\t   \\t\", \"\", \"trim me\")\nreplace(\"number hypen\", \"(.)(.)\", \"ab\", \"$1-$2\", \"a-b\")\nreplaceAll(\"simple expand\", \"([a-z]) ([a-z])\", \"a b\", \"$2 $1\", \"b a\")\nreplaceAll(\"literal dollar 1\", \"([a-z]+) ([a-z]+)\", \"a b\", \"$$1\", \"$1\")\nreplaceAll(\"literal dollar 2\", \"([a-z]+) ([a-z]+)\", \"a b\", \"$2 $$c $1\", \"b $c a\")\n\nreplaceAll(\"match at start replace with empty\", \"foo\", \"foobar\", \"\", \"bar\")\nreplace(\"single empty match\", \"^\", \"bar\", \"foo\", \"foobar\")\n"
  },
  {
    "path": "tests/require/modules/init.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/modules/module.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/aliases.luau",
    "content": "local module = require(\"@tests/require/tests/module\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nlocal module2 = require(\"@require-tests/module\")\n\nassert(type(module2) == \"table\", \"Required module did not return a table\")\nassert(module2.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module2.Hello == \"World\", \"Required module did not contain correct values\")\n\nassert(module == module2, \"Require did not return the same table for the same module\")\n"
  },
  {
    "path": "tests/require/tests/async.luau",
    "content": "local module = require(\"./modules/async\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nmodule = require(\"./modules/async\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n"
  },
  {
    "path": "tests/require/tests/async_concurrent.luau",
    "content": "local task = require(\"@lune/task\")\n\nlocal module1\nlocal module2\n\ntask.defer(function()\n\tmodule2 = require(\"./modules/async\")\nend)\n\ntask.spawn(function()\n\tmodule1 = require(\"./modules/async\")\nend)\n\ntask.wait(1)\n\nassert(type(module1) == \"table\", \"Required module1 did not return a table\")\nassert(module1.Foo == \"Bar\", \"Required module1 did not contain correct values\")\nassert(module1.Hello == \"World\", \"Required module1 did not contain correct values\")\n\nassert(type(module2) == \"table\", \"Required module2 did not return a table\")\nassert(module2.Foo == \"Bar\", \"Required module2 did not contain correct values\")\nassert(module2.Hello == \"World\", \"Required module2 did not contain correct values\")\n\nassert(module1 == module2, \"Required modules should point to the same return value\")\n"
  },
  {
    "path": "tests/require/tests/async_sequential.luau",
    "content": "local task = require(\"@lune/task\")\n\nlocal module1 = require(\"./modules/async\")\nlocal module2 = require(\"./modules/async\")\n\ntask.wait(1)\n\nassert(type(module1) == \"table\", \"Required module1 did not return a table\")\nassert(module1.Foo == \"Bar\", \"Required module1 did not contain correct values\")\nassert(module1.Hello == \"World\", \"Required module1 did not contain correct values\")\n\nassert(type(module2) == \"table\", \"Required module2 did not return a table\")\nassert(module2.Foo == \"Bar\", \"Required module2 did not contain correct values\")\nassert(module2.Hello == \"World\", \"Required module2 did not contain correct values\")\n\nassert(module1 == module2, \"Required modules should point to the same return value\")\n"
  },
  {
    "path": "tests/require/tests/builtins.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal net = require(\"@lune/net\")\nlocal process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\nassert(type(fs.move) == \"function\")\nassert(type(net.request) == \"function\")\nassert(type(process.cwd) == \"string\")\nassert(type(stdio.format(\"\")) == \"string\")\nassert(type(task.spawn(function() end)) == \"thread\")\n\nassert(not pcall(function()\n\treturn require(\"@\") :: any\nend))\n\nassert(not pcall(function()\n\treturn require(\"@lune\") :: any\nend))\n\nassert(not pcall(function()\n\treturn require(\"@lune/\") :: any\nend))\n\nassert(not pcall(function()\n\treturn require(\"@src\") :: any\nend))\n"
  },
  {
    "path": "tests/require/tests/children.luau",
    "content": "local module = require(\"./modules/module\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nmodule = require(\"./modules/module\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nreturn true\n"
  },
  {
    "path": "tests/require/tests/init_files.luau",
    "content": "local module = require(\"./modules\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nmodule = require(\"./modules/modules\")\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nmodule = require(\"./modules/self_alias\")\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nreturn true\n"
  },
  {
    "path": "tests/require/tests/invalid.luau",
    "content": "local function test(path: string)\n\tlocal success, message = pcall(function()\n\t\tlocal _ = require(\"./\" .. path) :: any\n\tend)\n\tif success then\n\t\terror(string.format(\"Invalid require at path '%s' succeeded\", path))\n\telse\n\t\tmessage = tostring(message)\n\t\tif string.find(message, string.format(\"%s\", path)) == nil then\n\t\t\terror(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Invalid require did not mention path '%s' in its error message!\\nMessage: %s\",\n\t\t\t\t\tpath,\n\t\t\t\t\ttostring(message)\n\t\t\t\t)\n\t\t\t)\n\t\tend\n\tend\nend\n\ntest(\"fooo\")\ntest(\"baar\")\ntest(\"moduuuuule\")\ntest(\"modules.nested\")\ntest(\" modules \")\ntest(\"mod\" .. string.char(127) .. \"ules\")\n"
  },
  {
    "path": "tests/require/tests/module.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/modules/async.luau",
    "content": "local task = require(\"@lune/task\")\n\ntask.wait(0.25)\n\nreturn {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/modules/init.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/modules/module.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/modules/modules/init.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/modules/modules/module.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/modules/nested.luau",
    "content": "return require(\"./modules/module\")\n"
  },
  {
    "path": "tests/require/tests/modules/self_alias/init.luau",
    "content": "local inner = require(\"@self/module\")\nlocal outer = require(\"./module\")\n\nassert(type(outer) == \"table\", \"Outer module is not a table\")\nassert(type(inner) == \"table\", \"Inner module is not a table\")\n\nassert(outer.Foo == inner.Foo, \"Outer and inner modules have different Foo values\")\nassert(inner.Hello == outer.Hello, \"Outer and inner modules have different Hello values\")\n\nreturn inner\n"
  },
  {
    "path": "tests/require/tests/modules/self_alias/module.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/multi.ext.file.luau",
    "content": "return {\n\tFoo = \"Bar\",\n\tHello = \"World\",\n}\n"
  },
  {
    "path": "tests/require/tests/multi_ext.luau",
    "content": "require(\"./multi.ext.file\")\n"
  },
  {
    "path": "tests/require/tests/nested.luau",
    "content": "local module = require(\"./modules/nested\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nmodule = require(\"./modules/nested\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n"
  },
  {
    "path": "tests/require/tests/parents.luau",
    "content": "local module = require(\"../modules/module\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nreturn true\n"
  },
  {
    "path": "tests/require/tests/siblings.luau",
    "content": "local module = require(\"./module\")\n\nassert(type(module) == \"table\", \"Required module did not return a table\")\nassert(module.Foo == \"Bar\", \"Required module did not contain correct values\")\nassert(module.Hello == \"World\", \"Required module did not contain correct values\")\n\nrequire(\"./children\")\nrequire(\"./parents\")\n\nrequire(\"./children\")\nrequire(\"./parents\")\n"
  },
  {
    "path": "tests/require/tests/state.luau",
    "content": "-- the idea of this test is that state_module stores some state in one of its local\n-- variable\nlocal state_module = require(\"./state_module\")\n\n-- we confirm that without anything happening, the initial value is what we expect\nassert(state_module.state == 10)\n\n-- this second file also requires state_module and calls a function that changes the local\n-- state to 11\nrequire(\"./state_second\")\n\n-- with correct module caching, we should see the change done in state_secone reflected\n-- here\nassert(state_module.state == 11)\n"
  },
  {
    "path": "tests/require/tests/state_module.luau",
    "content": "local M = {}\n\nM.state = 10\n\nfunction M.set_state(n: number)\n\tM.state = n\nend\n\nreturn M\n"
  },
  {
    "path": "tests/require/tests/state_second.luau",
    "content": "local state_module = require(\"./state_module\")\n\nstate_module.set_state(11)\n\nreturn {}\n"
  },
  {
    "path": "tests/roblox/datatypes/Axes.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Axes = roblox.Axes\nlocal Enum = roblox.Enum\n\n-- Constructors & properties\n\nAxes.new()\nAxes.new(Enum.Axis.X)\nAxes.new(Enum.Axis.X, Enum.NormalId.Top)\n\nassert(not pcall(function()\n\treturn Axes.new(false)\nend))\nassert(not pcall(function()\n\treturn Axes.new({})\nend))\nassert(not pcall(function()\n\treturn Axes.new(newproxy(true))\nend))\n\nassert(Axes.new().X == false)\nassert(Axes.new().Y == false)\nassert(Axes.new().Z == false)\n\nassert(Axes.new(Enum.Axis.X, Enum.NormalId.Top).X == true)\nassert(Axes.new(Enum.Axis.X, Enum.NormalId.Top).Y == true)\nassert(Axes.new(Enum.Axis.X, Enum.NormalId.Top).Z == false)\n\nassert(Axes.new(Enum.Axis.X, Enum.NormalId.Left).X == true)\nassert(Axes.new(Enum.Axis.X, Enum.NormalId.Right).X == true)\n\nassert(Axes.new(Enum.NormalId.Front, Enum.NormalId.Back).X == false)\nassert(Axes.new(Enum.NormalId.Front, Enum.NormalId.Back).Y == false)\nassert(Axes.new(Enum.NormalId.Front, Enum.NormalId.Back).Z == true)\n\n-- Ops\n\nassert(not pcall(function()\n\treturn Axes.new() + Axes.new()\nend))\nassert(not pcall(function()\n\treturn Axes.new() / Axes.new()\nend))\n"
  },
  {
    "path": "tests/roblox/datatypes/BrickColor.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal BrickColor = roblox.BrickColor\nlocal Color3 = roblox.Color3\n\n-- Constructors & properties\n\nBrickColor.new(1)\nBrickColor.new(\"Medium stone grey\")\n\nassert(not pcall(function()\n\treturn BrickColor.new(false)\nend))\nassert(not pcall(function()\n\treturn BrickColor.new({})\nend))\nassert(not pcall(function()\n\treturn BrickColor.new(newproxy(true))\nend))\n\nassert(BrickColor.new(\"Really red\").R == 1)\nassert(BrickColor.new(\"Really red\").G == 0)\nassert(BrickColor.new(\"Really red\").B == 0)\n\nassert(BrickColor.new(\"Really red\").Number == 1004)\nassert(BrickColor.new(\"Really red\").Name == \"Really red\")\nassert(BrickColor.new(\"Really red\").Color == Color3.new(1, 0, 0))\n\n-- Ops\n\nassert(not pcall(function()\n\treturn BrickColor.new(1) + BrickColor.new(2)\nend))\nassert(not pcall(function()\n\treturn BrickColor.new(1) / BrickColor.new(2)\nend))\n"
  },
  {
    "path": "tests/roblox/datatypes/CFrame.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal CFrame = roblox.CFrame\nlocal Vector3 = roblox.Vector3\nlocal Instance = roblox.Instance\n\nlocal COMPONENT_NAMES =\n\t{ \"X\", \"Y\", \"Z\", \"R00\", \"R01\", \"R02\", \"R10\", \"R11\", \"R12\", \"R20\", \"R21\", \"R22\" }\n\nlocal function formatCFrame(cf)\n\tlocal rot = Vector3.new(cf:ToOrientation())\n\treturn string.format(\n\t\t\"%.2f, %.2f, %.2f | %.2f, %.2f, %.2f\",\n\t\tcf.Position.X,\n\t\tcf.Position.Y,\n\t\tcf.Position.Z,\n\t\tmath.deg(rot.X),\n\t\tmath.deg(rot.Y),\n\t\tmath.deg(rot.Z)\n\t)\nend\n\nlocal function assertEq(actual, expected)\n\tlocal actComps: { number } = { actual:GetComponents() }\n\tlocal expComps: { number } = { expected:GetComponents() }\n\tfor index, actComp in actComps do\n\t\tlocal expComp = expComps[index]\n\t\tif math.abs(expComp - actComp) >= (1 / 512) then\n\t\t\terror(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Expected component '%s' to be %.4f, got %.4f\"\n\t\t\t\t\t\t.. \"\\nActual:   %s\"\n\t\t\t\t\t\t.. \"\\nExpected: %s\",\n\t\t\t\t\tCOMPONENT_NAMES[index],\n\t\t\t\t\texpComp,\n\t\t\t\t\tactComp,\n\t\t\t\t\tformatCFrame(actual),\n\t\t\t\t\tformatCFrame(expected)\n\t\t\t\t)\n\t\t\t)\n\t\tend\n\tend\nend\n\n-- Constructors & properties\n\nCFrame.new()\nCFrame.new(0, 0, 0)\nCFrame.new(0 / 0, 0 / 0, 0 / 0)\nCFrame.new(0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1)\n\nassert(not pcall(function()\n\treturn CFrame.new(false)\nend))\nassert(not pcall(function()\n\treturn CFrame.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn CFrame.new(newproxy(true))\nend))\n\nassert(CFrame.new(1, 2, 3).X == 1)\nassert(CFrame.new(1, 2, 3).Y == 2)\nassert(CFrame.new(1, 2, 3).Z == 3)\n\nassertEq(\n\tCFrame.fromMatrix(\n\t\tVector3.new(1, 2, 3),\n\t\tVector3.new(1, 0, 0),\n\t\tVector3.new(0, 1, 0),\n\t\tVector3.new(0, 0, 1)\n\t),\n\tCFrame.new(1, 2, 3)\n)\n\nassertEq(CFrame.new(1, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 1), CFrame.new(1, 2, 3))\n\n-- Constants\n\nassertEq(CFrame.identity, CFrame.new())\nassertEq(CFrame.identity, CFrame.new(0, 0, 0))\nassertEq(CFrame.identity, CFrame.Angles(0, 0, 0))\nassertEq(CFrame.identity, CFrame.fromOrientation(0, 0, 0))\n\n-- Ops\n\nassertEq(CFrame.new(2, 4, 8) + Vector3.new(1, 1, 2), CFrame.new(3, 5, 10))\nassertEq(CFrame.new(2, 4, 8) - Vector3.new(1, 1, 2), CFrame.new(1, 3, 6))\nassertEq(CFrame.new(2, 4, 8) * CFrame.new(1, 1, 2), CFrame.new(3, 5, 10))\nassert(CFrame.new(2, 4, 8) * Vector3.new(1, 1, 2) == Vector3.new(3, 5, 10))\n\n-- Mult ops with rotated CFrames\n\nassertEq(\n\tCFrame.fromOrientation(0, math.rad(90), 0) * CFrame.fromOrientation(math.rad(5), 0, 0),\n\tCFrame.fromOrientation(math.rad(5), math.rad(90), 0)\n)\nassertEq(\n\tCFrame.fromOrientation(0, math.rad(90), 0) * CFrame.new(0, 0, -5),\n\tCFrame.new(-5, 0, 0) * CFrame.fromOrientation(0, math.rad(90), 0)\n)\n\n-- World & object space conversions\n\nlocal offset = CFrame.new(0, 0, -5)\nassert(offset:ToWorldSpace(offset).Z == offset.Z * 2)\nassert(offset:ToObjectSpace(offset).Z == 0)\n\nassert(select(\"#\", offset:ToWorldSpace(offset, offset, offset)) == 3)\nassert(select(\"#\", offset:ToObjectSpace(offset, offset, offset)) == 3)\n\nlocal world = CFrame.fromOrientation(0, math.rad(90), 0) * CFrame.new(0, 0, -5)\nlocal world2 = CFrame.fromOrientation(0, -math.rad(90), 0) * CFrame.new(0, 0, -5)\nassertEq(CFrame.identity:ToObjectSpace(world), world)\nassertEq(\n\tworld:ToObjectSpace(world2),\n\tCFrame.fromOrientation(0, math.rad(180), 0) * CFrame.new(0, 0, -10)\n)\n\n-- Look\n\nassertEq(CFrame.fromOrientation(0, math.rad(90), 0), CFrame.lookAt(Vector3.zero, -Vector3.xAxis))\nassertEq(CFrame.fromOrientation(0, -math.rad(90), 0), CFrame.lookAt(Vector3.zero, Vector3.xAxis))\nassertEq(\n\tCFrame.new(0, 0, -5) * CFrame.fromOrientation(0, math.rad(90), 0),\n\tCFrame.lookAt(Vector3.new(0, 0, -5), Vector3.new(0, 0, -5) - Vector3.xAxis)\n)\n\n-- Angles\n\n-- stylua: ignore start\nassertEq(\n\tCFrame.Angles(math.pi / 2, math.pi / 4, math.pi / 4),\n\tCFrame.new(\n\t\t0,           0,           0,\n\t\t0.49999997, -0.49999997,  0.707106769,\n\t\t0.49999994, -0.5,        -0.707106769,\n\t\t0.707106769, 0.707106769, 0\n\t)\n)\n-- stylua: ignore end\n\n-- TODO: More methods\n\n-- CFrames on instances\n\nlocal part0 = Instance.new(\"Part\")\nlocal part1 = Instance.new(\"MeshPart\")\n\npart0.CFrame = CFrame.fromOrientation(-math.rad(45), math.rad(180), 0)\npart1.CFrame = CFrame.new(0, 0, -5) * CFrame.fromOrientation(0, math.rad(180), 0)\n\nlocal weld = Instance.new(\"Weld\")\nweld.C0 = part0.CFrame:ToObjectSpace(part1.CFrame)\nweld.Part0 = part0\nweld.Part1 = part1\nweld.Parent = part1\n\nassertEq(weld.C0, CFrame.new(0, -3.5355, 3.5355) * CFrame.fromOrientation(math.rad(45), 0, 0))\n"
  },
  {
    "path": "tests/roblox/datatypes/Color3.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Color3 = roblox.Color3\n\n-- Constructors & properties\n\nColor3.new()\nColor3.new(0)\nColor3.new(0, 0)\nColor3.new(0, 0, 0)\nColor3.new(0 / 0, 0 / 0)\nColor3.new(0 / 0, 0 / 0, 0 / 0)\n\nassert(not pcall(function()\n\treturn Color3.new(false)\nend))\nassert(not pcall(function()\n\treturn Color3.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Color3.new(newproxy(true))\nend))\n\nassert(Color3.new(0.25, 0.5, 1).R == 0.25)\nassert(Color3.new(0.25, 0.5, 1).G == 0.5)\nassert(Color3.new(0.25, 0.5, 1).B == 1)\n\nassert(Color3.fromRGB(0, 0, 0) == Color3.new(0, 0, 0))\nassert(Color3.fromRGB(255, 255, 255) == Color3.new(1, 1, 1))\n\nassert(Color3.fromHex(\"00F\") == Color3.new(0, 0, 1))\nassert(Color3.fromHex(\"0000FF\") == Color3.new(0, 0, 1))\n\nassert(Color3.fromHSV(0, 1, 1) == Color3.new(1, 0, 0))\nassert(Color3.fromHSV(0, 1, 0) == Color3.new(0, 0, 0))\nassert(Color3.fromHSV(0, 0, 1) == Color3.new(1, 1, 1))\n\n-- Ops\n\nassert(Color3.new(0.25, 0.5, 1) + Color3.new(0.25, 0.25, 0.25) == Color3.new(0.5, 0.75, 1.25))\nassert(Color3.new(0.25, 0.5, 1) - Color3.new(0.25, 0.25, 0.25) == Color3.new(0, 0.25, 0.75))\nassert(Color3.new(0.25, 0.5, 1) * Color3.new(0.25, 0.25, 0.5) == Color3.new(0.0625, 0.125, 0.5))\nassert(Color3.new(0.25, 0.5, 1) / Color3.new(0.25, 0.25, 0.5) == Color3.new(1, 2, 2))\n\nassert(Color3.new(0.25, 0.5, 1) * 2 == Color3.new(0.5, 1, 2))\nassert(Color3.new(0.25, 0.5, 1) / 2 == Color3.new(0.125, 0.25, 0.5))\n\n-- Methods\n\nlocal h, s, v\n\nh, s, v = Color3.fromHSV(0, 0.25, 0.75):ToHSV()\nassert(h == 0 and s == 0.25 and v == 0.75)\n\nh, s, v = Color3.fromHSV(0.5, 1, 0.125):ToHSV()\nassert(h == 0.5 and s == 1 and v == 0.125)\n\nassert(Color3.fromHex(\"FFF\"):ToHex() == \"FFFFFF\")\nassert(Color3.fromHex(\"FA0\"):ToHex() == \"FFAA00\")\nassert(Color3.fromHex(\"FFFFFF\"):ToHex() == \"FFFFFF\")\nassert(Color3.fromHex(\"FFAA00\"):ToHex() == \"FFAA00\")\n"
  },
  {
    "path": "tests/roblox/datatypes/ColorSequence.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Color3 = roblox.Color3\nlocal ColorSequence = roblox.ColorSequence\nlocal ColorSequenceKeypoint = roblox.ColorSequenceKeypoint\n\n-- Constructors & properties\n\nColorSequence.new(Color3.new())\nColorSequence.new(Color3.new(), Color3.new())\nlocal sequence = ColorSequence.new({\n\tColorSequenceKeypoint.new(0, Color3.new(1, 0, 0)),\n\tColorSequenceKeypoint.new(0.5, Color3.new(0, 1, 0)),\n\tColorSequenceKeypoint.new(1, Color3.new(0, 0, 1)),\n})\n\nassert(not pcall(function()\n\treturn ColorSequence.new()\nend))\nassert(not pcall(function()\n\treturn ColorSequence.new(0.5)\nend))\nassert(not pcall(function()\n\treturn ColorSequence.new(false)\nend))\nassert(not pcall(function()\n\treturn ColorSequence.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn ColorSequence.new(newproxy(true))\nend))\n\nassert(#sequence.Keypoints == 3)\nassert(sequence.Keypoints[1] == ColorSequenceKeypoint.new(0, Color3.new(1, 0, 0)))\nassert(sequence.Keypoints[2] == ColorSequenceKeypoint.new(0.5, Color3.new(0, 1, 0)))\nassert(sequence.Keypoints[3] == ColorSequenceKeypoint.new(1, Color3.new(0, 0, 1)))\n"
  },
  {
    "path": "tests/roblox/datatypes/ColorSequenceKeypoint.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Color3 = roblox.Color3\nlocal ColorSequenceKeypoint = roblox.ColorSequenceKeypoint\n\n-- Constructors & properties\n\nColorSequenceKeypoint.new(0, Color3.new())\nColorSequenceKeypoint.new(1, Color3.new())\n\nassert(not pcall(function()\n\treturn ColorSequenceKeypoint.new()\nend))\nassert(not pcall(function()\n\treturn ColorSequenceKeypoint.new(0.5)\nend))\nassert(not pcall(function()\n\treturn ColorSequenceKeypoint.new(false)\nend))\nassert(not pcall(function()\n\treturn ColorSequenceKeypoint.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn ColorSequenceKeypoint.new(newproxy(true))\nend))\n\nassert(ColorSequenceKeypoint.new(0, Color3.new()).Time == 0)\nassert(ColorSequenceKeypoint.new(1, Color3.new()).Time == 1)\nassert(ColorSequenceKeypoint.new(0, Color3.new()).Value == Color3.new())\nassert(ColorSequenceKeypoint.new(1, Color3.new(1)).Value == Color3.new(1))\n"
  },
  {
    "path": "tests/roblox/datatypes/Content.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Content = roblox.Content\nlocal Instance = roblox.Instance\nlocal Enum = roblox.Enum\n\nassert(Content.none, \"Content.none did not exist\")\nassert(\n\tContent.none.SourceType == Enum.ContentSourceType.None,\n\t\"Content.none's SourceType was wrong\"\n)\nassert(Content.none.Uri == nil, \"Content.none's Uri field was wrong\")\nassert(Content.none.Object == nil, \"Content.none's Object field was wrong\")\n\nlocal uri = Content.fromUri(\"test uri\")\nassert(uri.SourceType == Enum.ContentSourceType.Uri, \"URI Content's SourceType was wrong\")\nassert(uri.Uri == \"test uri\", \"URI Content's Uri field was wrong\")\nassert(uri.Object == nil, \"URI Content's Object field was wrong\")\n\nassert(not pcall(Content.fromUri), \"Content.fromUri accepted no argument\")\nassert(not pcall(Content.fromUri, false), \"Content.fromUri accepted a boolean argument\")\nassert(not pcall(Content.fromUri, Enum), \"Content.fromUri accepted a UserData as an argument\")\nassert(\n\tnot pcall(Content.fromUri, buffer.create(0)),\n\t\"Content.fromUri accepted a buffer as an argument\"\n)\n\n-- It feels weird that this is allowed because `EditableImage` is very much\n-- not an Instance. But what can you do?\nlocal target = Instance.new(\"EditableImage\")\nlocal object = Content.fromObject(target)\nassert(object.SourceType == Enum.ContentSourceType.Object, \"Object Content's SourceType was wrong\")\nassert(object.Uri == nil, \"Object Content's Uri field was wrong\")\nassert(object.Object == target, \"Object Content's Object field was wrong\")\n\nassert(not pcall(Content.fromObject), \"Content.fromObject accepted no argument\")\nassert(not pcall(Content.fromObject, false), \"Content.fromObject accepted a boolean argument\")\nassert(\n\tnot pcall(Content.fromObject, Enum),\n\t\"Content.fromObject accepted a non-Instance/Object UserData as an argument\"\n)\nassert(\n\tnot pcall(Content.fromObject, buffer.create(0)),\n\t\"Content.fromObject accepted a buffer as an argument\"\n)\n\nassert(\n\tnot pcall(Content.fromObject, Instance.new(\"Folder\")),\n\t\"Content.fromObject accepted an Instance as an argument\"\n)\n\nassert(\n\ttostring(Content.none) == \"Content(None)\",\n\t`expected tostring(Content.none) to be Content(None), it was actually {Content.none}`\n)\nassert(\n\ttostring(uri) == \"Content(Uri=test uri)\",\n\t`expected tostring(URI Content) to be Content(Uri=...), it was actually {uri}`\n)\nassert(\n\ttostring(object) == \"Content(Object)\",\n\t`expected tostring(Object Content) to be Content(Object), it was actually {object}`\n)\n"
  },
  {
    "path": "tests/roblox/datatypes/Enum.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Enum = roblox.Enum\n\n-- Constructors & properties\n\nassert(tostring(Enum) == \"Enum\")\nassert(tostring(Enum.KeyCode) == \"Enum.KeyCode\")\nassert(tostring(Enum.KeyCode.X) == \"Enum.KeyCode.X\")\n\n-- NOTE: We use the axis enum here since it is unlikely\n-- any more will be added to it and change the value\nassert(Enum.Axis.X.Name == \"X\")\nassert(Enum.Axis.X.Value == 0)\n\n-- Methods\n\nlocal foundKeyCode = false\nfor _, enum in Enum:GetEnums() do\n\tif enum == Enum.KeyCode then\n\t\tfoundKeyCode = true\n\t\tbreak\n\tend\nend\nassert(foundKeyCode, \"GetEnums did not contain Enum.KeyCode\")\n\nlocal foundKeyCodeX = false\nfor _, keyCode in Enum.KeyCode:GetEnumItems() do\n\tif keyCode == Enum.KeyCode.X then\n\t\tfoundKeyCodeX = true\n\t\tbreak\n\tend\nend\nassert(foundKeyCodeX, \"GetEnumItems did not contain X for Enum.KeyCode\")\n"
  },
  {
    "path": "tests/roblox/datatypes/Faces.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Faces = roblox.Faces\nlocal Enum = roblox.Enum\n\n-- Constructors & properties\n\nFaces.new()\nFaces.new(Enum.NormalId.Top)\nFaces.new(Enum.NormalId.Left, Enum.NormalId.Top)\n\nassert(not pcall(function()\n\treturn Faces.new(false)\nend))\nassert(not pcall(function()\n\treturn Faces.new({})\nend))\nassert(not pcall(function()\n\treturn Faces.new(newproxy(true))\nend))\n\nassert(Faces.new().Left == false)\nassert(Faces.new().Right == false)\nassert(Faces.new().Top == false)\nassert(Faces.new().Bottom == false)\nassert(Faces.new().Front == false)\nassert(Faces.new().Back == false)\n\nassert(Faces.new(Enum.NormalId.Left).Left == true)\nassert(Faces.new(Enum.NormalId.Right).Right == true)\n\nlocal f = Faces.new(\n\tEnum.NormalId.Left,\n\tEnum.NormalId.Right,\n\tEnum.NormalId.Top,\n\tEnum.NormalId.Bottom,\n\tEnum.NormalId.Back\n)\nassert(f.Left == true)\nassert(f.Right == true)\nassert(f.Top == true)\nassert(f.Bottom == true)\nassert(f.Front == false)\nassert(f.Back == true)\n\n-- Ops\n\nassert(not pcall(function()\n\treturn Faces.new() + Faces.new()\nend))\nassert(not pcall(function()\n\treturn Faces.new() / Faces.new()\nend))\n"
  },
  {
    "path": "tests/roblox/datatypes/Font.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Enum = roblox.Enum\nlocal Font = roblox.Font\n\n-- Constructors\n\nFont.new(\"\")\nFont.new(\"\", Enum.FontWeight.Bold)\nFont.new(\"\", Enum.FontWeight.Bold, Enum.FontStyle.Italic)\n\nassert(not pcall(function()\n\tFont.new(\"\", Enum.FontStyle.Italic, Enum.FontWeight.Bold)\nend))\nassert(not pcall(function()\n\treturn Font.new()\nend))\nassert(not pcall(function()\n\treturn Font.new(false)\nend))\nassert(not pcall(function()\n\treturn Font.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Font.new(newproxy(true))\nend))\n\nFont.fromEnum(Enum.Font.Gotham)\nFont.fromEnum(Enum.Font.GothamMedium)\nFont.fromEnum(Enum.Font.GothamBold)\n\nFont.fromName(\"file-name\")\nFont.fromName(\"file-name\", Enum.FontWeight.Bold)\nFont.fromName(\"file-name\", Enum.FontWeight.Bold, Enum.FontStyle.Italic)\n\nFont.fromId(1234567890)\nFont.fromId(1234567890, Enum.FontWeight.Bold)\nFont.fromId(1234567890, Enum.FontWeight.Bold, Enum.FontStyle.Italic)\n\n-- Properties\n\nlocal arial = \"rbxasset://fonts/families/Arial.json\"\nassert(Font.new(arial).Family == arial)\nassert(Font.fromName(\"Arial\").Family == arial)\nassert(Font.fromEnum(Enum.Font.Arial).Family == arial)\nassert(Font.fromId(1234567890).Family == \"rbxassetid://1234567890\")\n\nfor _, weight in Enum.FontWeight:GetEnumItems() do\n\tassert(Font.new(arial, weight).Weight == weight)\nend\n\nfor _, style in Enum.FontStyle:GetEnumItems() do\n\tassert(Font.new(arial, Enum.FontWeight.Regular, style).Style == style)\nend\n"
  },
  {
    "path": "tests/roblox/datatypes/NumberRange.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal NumberRange = roblox.NumberRange\n\n-- Constructors & properties\n\nNumberRange.new(0)\nNumberRange.new(0, 0)\n\nassert(not pcall(function()\n\treturn NumberRange.new()\nend))\nassert(not pcall(function()\n\treturn NumberRange.new(false)\nend))\nassert(not pcall(function()\n\treturn NumberRange.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn NumberRange.new(newproxy(true))\nend))\n\nassert(NumberRange.new(0, 1).Min == 0)\nassert(NumberRange.new(1, 1).Min == 1)\n\nassert(NumberRange.new(0, 0).Max == 0)\nassert(NumberRange.new(0, 1).Max == 1)\n\n-- Swapped args should still set proper min/max\n\nassert(NumberRange.new(1, 0).Min == 0)\nassert(NumberRange.new(1, 0).Max == 1)\n"
  },
  {
    "path": "tests/roblox/datatypes/NumberSequence.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal NumberSequence = roblox.NumberSequence\nlocal NumberSequenceKeypoint = roblox.NumberSequenceKeypoint\n\n-- Constructors & properties\n\nNumberSequence.new(0)\nNumberSequence.new(0, 0)\nlocal sequence = NumberSequence.new({\n\tNumberSequenceKeypoint.new(0, 1),\n\tNumberSequenceKeypoint.new(0.5, 0.5),\n\tNumberSequenceKeypoint.new(1, 0),\n})\n\nassert(not pcall(function()\n\treturn NumberSequence.new()\nend))\nassert(not pcall(function()\n\treturn NumberSequence.new(false)\nend))\nassert(not pcall(function()\n\treturn NumberSequence.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn NumberSequence.new(newproxy(true))\nend))\n\nassert(#sequence.Keypoints == 3)\nassert(sequence.Keypoints[1] == NumberSequenceKeypoint.new(0, 1))\nassert(sequence.Keypoints[2] == NumberSequenceKeypoint.new(0.5, 0.5))\nassert(sequence.Keypoints[3] == NumberSequenceKeypoint.new(1, 0))\n"
  },
  {
    "path": "tests/roblox/datatypes/NumberSequenceKeypoint.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal NumberSequenceKeypoint = roblox.NumberSequenceKeypoint\n\n-- Constructors & properties\n\nNumberSequenceKeypoint.new(0, 0)\nNumberSequenceKeypoint.new(1, 0)\nNumberSequenceKeypoint.new(0.5, 0.5, 0.5)\n\nassert(not pcall(function()\n\treturn NumberSequenceKeypoint.new()\nend))\nassert(not pcall(function()\n\treturn NumberSequenceKeypoint.new(false)\nend))\nassert(not pcall(function()\n\treturn NumberSequenceKeypoint.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn NumberSequenceKeypoint.new(newproxy(true))\nend))\n\nassert(NumberSequenceKeypoint.new(0, 0, 0).Time == 0)\nassert(NumberSequenceKeypoint.new(1, 0, 1).Time == 1)\nassert(NumberSequenceKeypoint.new(0, 0, 0).Value == 0)\nassert(NumberSequenceKeypoint.new(1, 1, 1).Value == 1)\nassert(NumberSequenceKeypoint.new(0, 0, 0).Envelope == 0)\nassert(NumberSequenceKeypoint.new(1, 1, 1).Envelope == 1)\n"
  },
  {
    "path": "tests/roblox/datatypes/PhysicalProperties.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal PhysicalProperties = roblox.PhysicalProperties\nlocal Enum = roblox.Enum\n\n-- Constructors & properties\n\nPhysicalProperties.new(Enum.Material.SmoothPlastic)\nPhysicalProperties.new(0, 0, 0)\nPhysicalProperties.new(0, 0, 0, 0, 0)\n\nassert(not pcall(function()\n\treturn PhysicalProperties.new()\nend))\nassert(not pcall(function()\n\treturn PhysicalProperties.new(false)\nend))\nassert(not pcall(function()\n\treturn PhysicalProperties.new({})\nend))\nassert(not pcall(function()\n\treturn PhysicalProperties.new(newproxy(true))\nend))\nassert(not pcall(function()\n\treturn PhysicalProperties.new(Enum.Axis.X)\nend))\n\nassert(PhysicalProperties.new(1, 2, 3).FrictionWeight == 1)\nassert(PhysicalProperties.new(1, 2, 3).ElasticityWeight == 1)\nassert(PhysicalProperties.new(1, 2, 3).AcousticAbsorption == 1)\n\nassert(PhysicalProperties.new(1, 2, 3, 4, 5, 6).Density == 1)\nassert(PhysicalProperties.new(1, 2, 3, 4, 5, 6).Friction == 2)\nassert(PhysicalProperties.new(1, 2, 3, 4, 5, 6).Elasticity == 3)\nassert(PhysicalProperties.new(1, 2, 3, 4, 5, 6).FrictionWeight == 4)\nassert(PhysicalProperties.new(1, 2, 3, 4, 5, 6).ElasticityWeight == 5)\nassert(PhysicalProperties.new(1, 2, 3, 4, 5, 6).AcousticAbsorption == 6)\n\nlocal function fuzzyEq(n0: number, n1: number)\n\treturn math.abs(n1 - n0) <= 0.0001\nend\n\nlocal plastic = PhysicalProperties.new(Enum.Material.Plastic)\nassert(fuzzyEq(plastic.Density, 0.7))\nassert(fuzzyEq(plastic.Friction, 0.3))\nassert(fuzzyEq(plastic.Elasticity, 0.5))\n\nlocal splastic = PhysicalProperties.new(Enum.Material.SmoothPlastic)\nassert(fuzzyEq(splastic.Density, 0.7))\nassert(fuzzyEq(splastic.Friction, 0.2))\nassert(fuzzyEq(splastic.Elasticity, 0.5))\n\nlocal sand = PhysicalProperties.new(Enum.Material.Sand)\nassert(fuzzyEq(sand.Density, 1.6))\nassert(fuzzyEq(sand.Friction, 0.5))\nassert(fuzzyEq(sand.Elasticity, 0.05))\nassert(fuzzyEq(sand.FrictionWeight, 5))\nassert(fuzzyEq(sand.ElasticityWeight, 2.5))\nassert(fuzzyEq(sand.AcousticAbsorption, 0.55))\n\n-- Ops\n\nassert(not pcall(function()\n\treturn PhysicalProperties.new() + PhysicalProperties.new()\nend))\nassert(not pcall(function()\n\treturn PhysicalProperties.new() / PhysicalProperties.new()\nend))\n"
  },
  {
    "path": "tests/roblox/datatypes/Ray.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Ray = roblox.Ray\nlocal Vector3 = roblox.Vector3\n\nlocal origin = Vector3.zero\nlocal direction = Vector3.zAxis * 10\n\n-- Constructors & properties\n\nRay.new(origin, direction)\n\nassert(not pcall(function()\n\treturn Ray.new(false)\nend))\nassert(not pcall(function()\n\treturn Ray.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Ray.new(newproxy(true))\nend))\n\nassert(Ray.new(origin, direction).Origin == origin)\nassert(Ray.new(origin, direction).Direction == direction)\n\nassert(Ray.new(origin, direction).Unit.Origin == origin)\nassert(Ray.new(origin, direction).Unit.Direction == direction.Unit)\n\n-- Ops\n\nassert(not pcall(function()\n\treturn Ray.new(origin, direction) + Ray.new(origin, direction)\nend))\nassert(not pcall(function()\n\treturn Ray.new(origin, direction) / Ray.new(origin, direction)\nend))\n\n-- Methods\n\nassert(Ray.new(origin, direction):ClosestPoint(origin) == origin)\nassert(Ray.new(origin, direction):Distance(origin) == 0)\n\nfor z = 0, 10, 1 do\n\tlocal x = if z % 2 == 0 then 2.5 else 7.5\n\tassert(\n\t\tRay.new(origin, direction):ClosestPoint(Vector3.new(x, 0, z))\n\t\t\t== Vector3.zero + Vector3.zAxis * z\n\t)\n\tassert(Ray.new(origin, direction):Distance(Vector3.new(x, 0, z)) == x)\nend\n"
  },
  {
    "path": "tests/roblox/datatypes/Rect.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Vector2 = roblox.Vector2\nlocal Rect = roblox.Rect\n\n-- Constructors & properties\n\nRect.new()\nRect.new(0)\nRect.new(0, 0)\nRect.new(0, 0, 0)\nRect.new(0, 0, 0, 0)\nRect.new(0 / 0, 0, 0 / 0, 0)\n\nRect.new(Vector2.zero)\nRect.new(Vector2.zero, Vector2.zero)\n\nassert(not pcall(function()\n\treturn Rect.new(false)\nend))\nassert(not pcall(function()\n\treturn Rect.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Rect.new(newproxy(true))\nend))\n\nassert(Rect.new(1, 0, 2, 4).Min == Vector2.new(1, 0))\nassert(Rect.new(1, 0, 2, 4).Max == Vector2.new(2, 4))\nassert(Rect.new(0, 0, 1, 2).Width == 1)\nassert(Rect.new(0, 0, 1, 2).Height == 2)\n\nassert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Min == Vector2.new(1, 0))\nassert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Max == Vector2.new(2, 4))\nassert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Width == 1)\nassert(Rect.new(Vector2.new(1, 0), Vector2.new(2, 4)).Height == 4)\n\n-- Ops\n\nassert(Rect.new(2, 4, 6, 8) + Rect.new(1, 1, 1, 1) == Rect.new(3, 5, 7, 9))\nassert(Rect.new(2, 4, 6, 8) - -Rect.new(1, 1, 1, 1) == Rect.new(3, 5, 7, 9))\nassert(Rect.new(2, 4, 6, 8) - Rect.new(1, 1, 1, 1) == Rect.new(1, 3, 5, 7))\n"
  },
  {
    "path": "tests/roblox/datatypes/Region3.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Region3 = roblox.Region3\nlocal Vector3 = roblox.Vector3\nlocal CFrame = roblox.CFrame\n\nlocal min = Vector3.new(-2, -2, -2)\nlocal max = Vector3.new(2, 2, 2)\n\n-- Constructors & properties\n\nRegion3.new(min, max)\n\nassert(not pcall(function()\n\treturn Region3.new(false)\nend))\nassert(not pcall(function()\n\treturn Region3.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Region3.new(newproxy(true))\nend))\n\nassert(Region3.new(min, max).CFrame == CFrame.new(0, 0, 0))\nassert(Region3.new(min, max).Size == Vector3.new(4, 4, 4))\n\n-- Ops\n\nassert(not pcall(function()\n\treturn Region3.new(min, max) + Region3.new(min, max)\nend))\nassert(not pcall(function()\n\treturn Region3.new(min, max) / Region3.new(min, max)\nend))\n\n-- Methods\n\nassert(Region3.new(min, max):ExpandToGrid(1) == Region3.new(min, max))\n\nassert(\n\tRegion3.new(min, max):ExpandToGrid(3)\n\t\t== Region3.new(Vector3.new(-3, -3, -3), Vector3.new(3, 3, 3))\n)\n\nassert(\n\tRegion3.new(min, max):ExpandToGrid(4)\n\t\t== Region3.new(Vector3.new(-4, -4, -4), Vector3.new(4, 4, 4))\n)\n\nassert(\n\tRegion3.new(min, max):ExpandToGrid(7.5)\n\t\t== Region3.new(Vector3.new(-7.5, -7.5, -7.5), Vector3.new(7.5, 7.5, 7.5))\n)\n"
  },
  {
    "path": "tests/roblox/datatypes/Region3int16.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Region3int16 = roblox.Region3int16\nlocal Vector3int16 = roblox.Vector3int16\nlocal Vector3 = roblox.Vector3\n\nlocal min = Vector3int16.new(0, 0, 0)\nlocal max = Vector3int16.new(2, 2, 2)\n\n-- Constructors & properties\n\nRegion3int16.new(min, max)\n\nassert(not pcall(function()\n\treturn Region3int16.new(false)\nend))\nassert(not pcall(function()\n\treturn Region3int16.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Region3int16.new(newproxy(true))\nend))\nassert(not pcall(function()\n\treturn Region3int16.new(Vector3.new(), Vector3.new())\nend))\n\nassert(Region3int16.new(min, max).Min == min)\nassert(Region3int16.new(min, max).Max == max)\n\n-- Ops\n\nassert(not pcall(function()\n\treturn Region3int16.new(min, max) + Region3int16.new(min, max)\nend))\nassert(not pcall(function()\n\treturn Region3int16.new(min, max) / Region3int16.new(min, max)\nend))\n"
  },
  {
    "path": "tests/roblox/datatypes/UDim.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal UDim = roblox.UDim\n\n-- Constructors & properties\n\nUDim.new()\nUDim.new(0)\nUDim.new(0, 0)\nUDim.new(0 / 0, 0)\n\nassert(not pcall(function()\n\treturn UDim.new(false)\nend))\nassert(not pcall(function()\n\treturn UDim.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn UDim.new(newproxy(true))\nend))\n\nassert(UDim.new(1, 2).Scale == 1)\nassert(UDim.new(1, 2).Offset == 2)\n\n-- Ops\n\nassert(UDim.new(2, 4) + UDim.new(1, 1) == UDim.new(3, 5))\nassert(UDim.new(2, 4) - UDim.new(1, 1) == UDim.new(1, 3))\n"
  },
  {
    "path": "tests/roblox/datatypes/UDim2.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal UDim = roblox.UDim\nlocal UDim2 = roblox.UDim2\n\n-- Constructors & properties\n\nUDim2.new()\nUDim2.new(0)\nUDim2.new(0, 0)\nUDim2.new(0, 0, 0)\nUDim2.new(0, 0, 0, 0)\nUDim2.new(0 / 0, 0, 0 / 0, 0)\n\nassert(not pcall(function()\n\treturn UDim2.new(false)\nend))\nassert(not pcall(function()\n\treturn UDim2.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn UDim2.new(newproxy(true))\nend))\n\nUDim2.fromScale()\nUDim2.fromScale(0)\nUDim2.fromScale(0, 0)\n\nUDim2.fromOffset()\nUDim2.fromOffset(0)\nUDim2.fromOffset(0, 0)\n\nassert(UDim2.fromScale(1, 1).X == UDim.new(1, 0))\nassert(UDim2.fromScale(1, 1).Y == UDim.new(1, 0))\nassert(UDim2.fromScale(1, 1).Width == UDim.new(1, 0))\nassert(UDim2.fromScale(1, 1).Height == UDim.new(1, 0))\n\nassert(UDim2.fromOffset(1, 1).X == UDim.new(0, 1))\nassert(UDim2.fromOffset(1, 1).Y == UDim.new(0, 1))\nassert(UDim2.fromOffset(1, 1).Width == UDim.new(0, 1))\nassert(UDim2.fromOffset(1, 1).Height == UDim.new(0, 1))\n\n-- Ops\n\nassert(UDim2.new(2, 4, 6, 8) + UDim2.new(1, 1, 1, 1) == UDim2.new(3, 5, 7, 9))\nassert(UDim2.new(2, 4, 6, 8) - UDim2.new(1, 1, 1, 1) == UDim2.new(1, 3, 5, 7))\n\n-- Methods\n\nassert(UDim2.new(2, 4, 6, 8):Lerp(UDim2.new(1, 2, 3, 4), 0.0) == UDim2.new(2, 4, 6, 8))\nassert(UDim2.new(2, 4, 6, 8):Lerp(UDim2.new(1, 2, 3, 4), 0.5) == UDim2.new(1.5, 3, 4.5, 6))\nassert(UDim2.new(2, 4, 6, 8):Lerp(UDim2.new(1, 2, 3, 4), 1.0) == UDim2.new(1, 2, 3, 4))\n"
  },
  {
    "path": "tests/roblox/datatypes/UniqueId.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal UniqueId = roblox.UniqueId\n\nUniqueId.new()\nUniqueId.fromString(\"1234567890123456\")\nUniqueId.fromString(buffer.fromstring(\"1234567890123456\"))\n\nassert(not pcall(function()\n\treturn UniqueId.fromString(false)\nend))\nassert(not pcall(function()\n\treturn UniqueId.fromString(1, 2)\nend))\nassert(not pcall(function()\n\treturn UniqueId.fromString(\"This string is not 16 characters long\")\nend))\n\nassert(typeof(UniqueId.null) :: any == \"UniqueId\")\n\nassert(UniqueId.null == UniqueId.null)\nassert(UniqueId.fromString(\"1234567890123456\") == UniqueId.fromString(\"1234567890123456\"))\nassert(\n\tUniqueId.fromString(\"1234567890123456\")\n\t\t== UniqueId.fromString(buffer.fromstring(\"1234567890123456\"))\n)\n\nassert(tostring(UniqueId.null) == \"00000000000000000000000000000000\")\nassert(\n\ttostring(\n\t\tUniqueId.fromString(\"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x10\\x11\\x12\\x13\\x14\\x15\\x16\")\n\t) == \"01020304050607080910111213141516\"\n)\n\n-- Check that it reads it correctly from files\nlocal fs = require(\"@lune/fs\")\nlocal dom = roblox.deserializePlace(\n\tfs.readFile(\"tests/roblox/rbx-test-files/places/baseplate-566/binary.rbxl\")\n)\nlocal workspace = assert(dom:FindFirstChild(\"Workspace\")) :: any\nlocal expected = \"44b188dace632b4702e9c68d004815fc\"\nassert(tostring(workspace.UniqueId) == expected)\n"
  },
  {
    "path": "tests/roblox/datatypes/Vector2.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Vector2 = roblox.Vector2\n\n-- Constructors & properties\n\nVector2.new()\nVector2.new(0)\nVector2.new(0, 0)\nVector2.new(0 / 0, 0 / 0)\n\nassert(not pcall(function()\n\treturn Vector2.new(false)\nend))\nassert(not pcall(function()\n\treturn Vector2.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Vector2.new(newproxy(true))\nend))\n\nassert(Vector2.new(1, 2).X == 1)\nassert(Vector2.new(1, 2).Y == 2)\n\n-- Constants\n\nassert(Vector2.one == Vector2.new(1, 1))\nassert(Vector2.zero == Vector2.new(0, 0))\nassert(Vector2.xAxis == Vector2.new(1, 0))\nassert(Vector2.yAxis == Vector2.new(0, 1))\nassert(Vector2.zAxis == nil)\n\n-- Ops\n\nassert(Vector2.new(2, 4) + Vector2.new(1, 1) == Vector2.new(3, 5))\nassert(Vector2.new(2, 4) - Vector2.new(1, 1) == Vector2.new(1, 3))\nassert(Vector2.new(2, 4) * Vector2.new(1, 2) == Vector2.new(2, 8))\nassert(Vector2.new(2, 4) / Vector2.new(1, 2) == Vector2.new(2, 2))\n\nassert(Vector2.new(2, 4) * 2 == Vector2.new(4, 8))\nassert(Vector2.new(2, 4) / 2 == Vector2.new(1, 2))\n\nassert(Vector2.new(7, 15) // Vector2.new(3, 7) == Vector2.new(2, 2))\nassert(Vector2.new(3, 7) // 2 == Vector2.new(1, 3))\n\n-- Vector math methods\nassert(Vector2.new(-1, -2):Abs() == Vector2.new(1, 2))\nassert(Vector2.new(-1.7, 2):Sign() == Vector2.new(-1, 1))\nassert(Vector2.new(-1.9, 2.1):Ceil() == Vector2.new(-1, 3))\nassert(Vector2.new(-1.1, 2.99):Floor() == Vector2.new(-2, 2))\n\nassert(Vector2.new(1, 2):FuzzyEq(Vector2.new(1 - 1e-6, 2 + 1e-6), 1e-5))\nassert(not Vector2.new(1, 2):FuzzyEq(Vector2.new(1.2, 2), 0.1))\n\nlocal angle = Vector2.new(1, 1):Angle(Vector2.new(-1, 1))\nassert(math.abs(angle - (math.pi / 2)) < 1e-5)\n"
  },
  {
    "path": "tests/roblox/datatypes/Vector2int16.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Vector2int16 = roblox.Vector2int16\n\n-- Constructors & properties\n\nVector2int16.new()\nVector2int16.new(0)\nVector2int16.new(0, 0)\n\nassert(not pcall(function()\n\treturn Vector2int16.new(999_999, 999_999)\nend))\nassert(not pcall(function()\n\treturn Vector2int16.new(false)\nend))\nassert(not pcall(function()\n\treturn Vector2int16.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Vector2int16.new(newproxy(true))\nend))\n\nassert(Vector2int16.new(1, 2).X == 1)\nassert(Vector2int16.new(1, 2).Y == 2)\n\n-- Ops\n\nassert(Vector2int16.new(2, 4) + Vector2int16.new(1, 1) == Vector2int16.new(3, 5))\nassert(Vector2int16.new(2, 4) - Vector2int16.new(1, 1) == Vector2int16.new(1, 3))\nassert(Vector2int16.new(2, 4) * Vector2int16.new(1, 2) == Vector2int16.new(2, 8))\nassert(Vector2int16.new(2, 4) / Vector2int16.new(1, 2) == Vector2int16.new(2, 2))\n\nassert(Vector2int16.new(2, 4) * 2 == Vector2int16.new(4, 8))\nassert(Vector2int16.new(2, 4) / 2 == Vector2int16.new(1, 2))\n"
  },
  {
    "path": "tests/roblox/datatypes/Vector3.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Vector3 = roblox.Vector3\n\n-- Constructors & properties\n\nVector3.new()\nVector3.new(0)\nVector3.new(0, 0)\nVector3.new(0, 0, 0)\nVector3.new(0 / 0, 0 / 0)\nVector3.new(0 / 0, 0 / 0, 0 / 0)\n\nassert(not pcall(function()\n\treturn Vector3.new(false)\nend))\nassert(not pcall(function()\n\treturn Vector3.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Vector3.new(newproxy(true))\nend))\n\nassert(Vector3.new(1, 2, 3).X == 1)\nassert(Vector3.new(1, 2, 3).Y == 2)\nassert(Vector3.new(1, 2, 3).Z == 3)\n\n-- Constants\n\nassert(Vector3.one == Vector3.new(1, 1, 1))\nassert(Vector3.zero == Vector3.new(0, 0, 0))\nassert(Vector3.xAxis == Vector3.new(1, 0, 0))\nassert(Vector3.yAxis == Vector3.new(0, 1, 0))\nassert(Vector3.zAxis == Vector3.new(0, 0, 1))\n\n-- Ops\n\nassert(Vector3.new(2, 4, 8) + Vector3.new(1, 1, 1) == Vector3.new(3, 5, 9))\nassert(Vector3.new(2, 4, 8) - Vector3.new(1, 1, 1) == Vector3.new(1, 3, 7))\nassert(Vector3.new(2, 4, 8) * Vector3.new(1, 1, 2) == Vector3.new(2, 4, 16))\nassert(Vector3.new(2, 4, 8) / Vector3.new(1, 1, 2) == Vector3.new(2, 4, 4))\n\nassert(Vector3.new(2, 4, 8) * 2 == Vector3.new(4, 8, 16))\nassert(Vector3.new(2, 4, 8) / 2 == Vector3.new(1, 2, 4))\n\nassert(Vector3.new(7, 11, 15) // Vector3.new(3, 5, 7) == Vector3.new(2, 2, 2))\nassert(Vector3.new(3, 5, 7) // 2 == Vector3.new(1, 2, 3))\n\n-- Vector math methods\nassert(Vector3.new(-1, -2, -3):Abs() == Vector3.new(1, 2, 3))\nassert(Vector3.new(-1.7, 2, -3):Sign() == Vector3.new(-1, 1, -1))\nassert(Vector3.new(-1.9, 2.1, 3.5):Ceil() == Vector3.new(-1, 3, 4))\nassert(Vector3.new(-1.1, 2.99, 3.5):Floor() == Vector3.new(-2, 2, 3))\n\nassert(Vector3.new(1, 2, 3):FuzzyEq(Vector3.new(1 - 1e-6, 2 + 1e-6, 3 + 1e-6), 1e-5))\n"
  },
  {
    "path": "tests/roblox/datatypes/Vector3int16.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Vector3int16 = roblox.Vector3int16\n\n-- Constructors & properties\n\nVector3int16.new()\nVector3int16.new(0)\nVector3int16.new(0, 0)\nVector3int16.new(0, 0, 0)\n\nassert(not pcall(function()\n\treturn Vector3int16.new(999_999, 999_999, 999_999)\nend))\nassert(not pcall(function()\n\treturn Vector3int16.new(false)\nend))\nassert(not pcall(function()\n\treturn Vector3int16.new(\"\", \"\")\nend))\nassert(not pcall(function()\n\treturn Vector3int16.new(newproxy(true))\nend))\n\nassert(Vector3int16.new(1, 2, 3).X == 1)\nassert(Vector3int16.new(1, 2, 3).Y == 2)\nassert(Vector3int16.new(1, 2, 3).Z == 3)\n\n-- Ops\n\nassert(Vector3int16.new(2, 4, 8) + Vector3int16.new(1, 1, 1) == Vector3int16.new(3, 5, 9))\nassert(Vector3int16.new(2, 4, 8) - Vector3int16.new(1, 1, 1) == Vector3int16.new(1, 3, 7))\nassert(Vector3int16.new(2, 4, 8) * Vector3int16.new(1, 1, 2) == Vector3int16.new(2, 4, 16))\nassert(Vector3int16.new(2, 4, 8) / Vector3int16.new(1, 1, 2) == Vector3int16.new(2, 4, 4))\n\nassert(Vector3int16.new(2, 4, 8) * 2 == Vector3int16.new(4, 8, 16))\nassert(Vector3int16.new(2, 4, 8) / 2 == Vector3int16.new(1, 2, 4))\n"
  },
  {
    "path": "tests/roblox/files/deserializeModel.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\n\nlocal modelDirs = {}\nfor _, dirName in fs.readDir(\"tests/roblox/rbx-test-files/places\") do\n\ttable.insert(modelDirs, \"tests/roblox/rbx-test-files/places/\" .. dirName)\nend\n\nfor _, modelDir in modelDirs do\n\tlocal modelFileBinary = fs.readFile(modelDir .. \"/binary.rbxl\")\n\tlocal modelFileXml = fs.readFile(modelDir .. \"/xml.rbxlx\")\n\n\tlocal modelBinary = roblox.deserializeModel(modelFileBinary)\n\tlocal modelXml = roblox.deserializeModel(modelFileXml)\n\n\tfor _, modelInstance in modelBinary do\n\t\tassert(modelInstance:IsA(\"Instance\"))\n\tend\n\n\tfor _, modelInstance in modelXml do\n\t\tassert(modelInstance:IsA(\"Instance\"))\n\tend\nend\n"
  },
  {
    "path": "tests/roblox/files/deserializePlace.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\n\nlocal placeDirs = {}\nfor _, dirName in fs.readDir(\"tests/roblox/rbx-test-files/places\") do\n\ttable.insert(placeDirs, \"tests/roblox/rbx-test-files/places/\" .. dirName)\nend\n\nfor _, placeDir in placeDirs do\n\tlocal placeFileBinary = fs.readFile(placeDir .. \"/binary.rbxl\")\n\tlocal placeFileXml = fs.readFile(placeDir .. \"/xml.rbxlx\")\n\n\tlocal placeBinary = roblox.deserializePlace(placeFileBinary)\n\tlocal placeXml = roblox.deserializePlace(placeFileXml)\n\n\tassert(placeBinary.ClassName == \"DataModel\")\n\tassert(placeXml.ClassName == \"DataModel\")\n\n\tassert(placeBinary:IsA(\"ServiceProvider\"))\n\tassert(placeXml:IsA(\"ServiceProvider\"))\nend\n"
  },
  {
    "path": "tests/roblox/files/serializeModel.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\n-- Smoke tests\ndo\n\tlocal instances = {\n\t\tInstance.new(\"Model\"),\n\t\tInstance.new(\"Part\"),\n\t}\n\n\tlocal modelAsBinary = roblox.serializeModel(instances)\n\tlocal modelAsXml = roblox.serializeModel(instances, true)\n\n\tfs.writeFile(\"bin/temp-model.rbxm\", modelAsBinary)\n\tfs.writeFile(\"bin/temp-model.rbxmx\", modelAsXml)\n\n\tlocal savedFileBinary = fs.readFile(\"bin/temp-model.rbxm\")\n\tlocal savedFileXml = fs.readFile(\"bin/temp-model.rbxmx\")\n\n\tlocal savedBinary = roblox.deserializeModel(savedFileBinary)\n\tlocal savedXml = roblox.deserializeModel(savedFileXml)\n\n\tassert(savedBinary[1].Name ~= \"ROOT\")\n\tassert(savedXml[1].Name ~= \"ROOT\")\n\n\tassert(savedBinary[1].Name ~= \"DataModel\")\n\tassert(savedXml[1].Name ~= \"DataModel\")\n\n\tassert(savedBinary[1].ClassName == \"Model\")\n\tassert(savedBinary[2].ClassName == \"Part\")\n\n\tassert(savedXml[1].ClassName == \"Model\")\n\tassert(savedXml[2].ClassName == \"Part\")\nend\n\n-- Ensure Ref properties are preserved across descendants of multi-root model siblings\ndo\n\tlocal part = Instance.new(\"Part\")\n\n\tlocal particleEmitter = Instance.new(\"ParticleEmitter\")\n\tparticleEmitter.Parent = part\n\n\tlocal folder = Instance.new(\"Folder\")\n\n\tlocal objectValue = Instance.new(\"ObjectValue\") :: any\n\tobjectValue.Value = particleEmitter\n\tobjectValue.Parent = folder\n\n\tlocal serialized = roblox.serializeModel({ part, folder })\n\tlocal deserialized = roblox.deserializeModel(serialized) :: any\n\n\tassert(deserialized[2].ObjectValue.Value == deserialized[1].ParticleEmitter)\nend\n"
  },
  {
    "path": "tests/roblox/files/serializePlace.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\n-- Smoke tests\ndo\n\tlocal game = Instance.new(\"DataModel\")\n\n\tlocal workspace = game:GetService(\"Workspace\")\n\n\tlocal model = Instance.new(\"Model\")\n\tlocal part = Instance.new(\"Part\")\n\n\tpart.Parent = model\n\tmodel.Parent = workspace\n\n\tlocal placeAsBinary = roblox.serializePlace(game)\n\tlocal placeAsXml = roblox.serializePlace(game, true)\n\n\tfs.writeFile(\"bin/temp-place.rbxl\", placeAsBinary)\n\tfs.writeFile(\"bin/temp-place.rbxlx\", placeAsXml)\n\n\tlocal savedFileBinary = fs.readFile(\"bin/temp-place.rbxl\")\n\tlocal savedFileXml = fs.readFile(\"bin/temp-place.rbxlx\")\n\n\tlocal savedBinary = roblox.deserializePlace(savedFileBinary)\n\tlocal savedXml = roblox.deserializePlace(savedFileXml)\n\n\tassert(savedBinary.Name ~= \"ROOT\")\n\tassert(savedXml.Name ~= \"ROOT\")\n\n\tassert(savedBinary.ClassName == \"DataModel\")\n\tassert(savedXml.ClassName == \"DataModel\")\nend\n\n-- Ensure Ref properties are preserved across services\ndo\n\tlocal game = Instance.new(\"DataModel\")\n\tlocal ReplicatedStorage = Instance.new(\"ReplicatedStorage\")\n\tlocal Workspace = Instance.new(\"Workspace\")\n\n\tWorkspace.Parent = game\n\tReplicatedStorage.Parent = game\n\n\tlocal part = Instance.new(\"Part\")\n\tpart.Parent = ReplicatedStorage\n\n\tlocal objectValue = Instance.new(\"ObjectValue\") :: any\n\tobjectValue.Value = part\n\tobjectValue.Parent = Workspace\n\n\tlocal serialized = roblox.serializePlace(game)\n\tlocal deserialized = roblox.deserializePlace(serialized) :: any\n\n\tassert(deserialized.Workspace.ObjectValue.Value == deserialized.ReplicatedStorage.Part)\nend\n"
  },
  {
    "path": "tests/roblox/instance/attributes.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\") :: any\n\nlocal BrickColor = roblox.BrickColor\nlocal CFrame = roblox.CFrame\nlocal Color3 = roblox.Color3\nlocal ColorSequence = roblox.ColorSequence\nlocal ColorSequenceKeypoint = roblox.ColorSequenceKeypoint\nlocal Font = roblox.Font\nlocal NumberRange = roblox.NumberRange\nlocal NumberSequence = roblox.NumberSequence\nlocal NumberSequenceKeypoint = roblox.NumberSequenceKeypoint\nlocal Rect = roblox.Rect\nlocal UDim = roblox.UDim\nlocal UDim2 = roblox.UDim2\nlocal Vector2 = roblox.Vector2\nlocal Vector3 = roblox.Vector3\nlocal Instance = roblox.Instance\nlocal Enum = roblox.Enum\n\nlocal modelFile = fs.readFile(\"tests/roblox/rbx-test-files/models/attributes/binary.rbxm\")\nlocal model = roblox.deserializeModel(modelFile)[1]\n\nmodel:SetAttribute(\"Foo\", \"Bar\")\nmodel:SetAttribute(\"CFrame\", CFrame.identity)\nmodel:SetAttribute(\"Font\", Font.new(\"Arial\"))\n\nlocal ATTRS_ACTUAL = model:GetAttributes()\nlocal ATTRS_EXPECTED: { [string]: any } = {\n\t-- From the file\n\tBoolean = true,\n\tBrickColor = BrickColor.new(\"Really red\"),\n\tColor3 = Color3.fromRGB(162, 0, 255),\n\tColorSequence = ColorSequence.new({\n\t\tColorSequenceKeypoint.new(0, Color3.new(1, 0, 0)),\n\t\tColorSequenceKeypoint.new(0.5, Color3.new(0, 1, 0)),\n\t\tColorSequenceKeypoint.new(1, Color3.new(0, 0, 1)),\n\t}),\n\tNumber = 12345,\n\tNumberRange = NumberRange.new(5, 10),\n\tNumberSequence = NumberSequence.new({\n\t\tNumberSequenceKeypoint.new(0, 1),\n\t\tNumberSequenceKeypoint.new(0.5, 0),\n\t\tNumberSequenceKeypoint.new(1, 1),\n\t}),\n\tRect = Rect.new(1, 2, 3, 4),\n\tString = \"Hello, world!\",\n\tUDim = UDim.new(0.5, 100),\n\tUDim2 = UDim2.new(0.5, 10, 0.7, 30),\n\tVector2 = Vector2.new(10, 50),\n\tVector3 = Vector3.new(1, 2, 3),\n\tInfinity = math.huge,\n\tNaN = 0 / 0,\n\t-- Extras we set\n\tFoo = \"Bar\",\n\tCFrame = CFrame.identity,\n\tFont = Font.new(\"Arial\"),\n}\n\nfor name, value in ATTRS_EXPECTED do\n\tlocal actual = ATTRS_ACTUAL[name]\n\tif actual ~= value then\n\t\tif value ~= value and actual ~= actual then\n\t\t\tcontinue -- NaN\n\t\tend\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Expected attribute '%s' to have value '%s', got value '%s'\",\n\t\t\t\tname,\n\t\t\t\ttostring(value),\n\t\t\t\ttostring(actual)\n\t\t\t)\n\t\t)\n\tend\nend\n\n-- Cloning instances should also clone attributes\n\nlocal cloned = model:Clone()\nATTRS_ACTUAL = cloned:GetAttributes()\n\nfor name, value in ATTRS_EXPECTED do\n\tlocal actual = ATTRS_ACTUAL[name]\n\tif actual ~= value then\n\t\tif value ~= value and actual ~= actual then\n\t\t\tcontinue -- NaN\n\t\tend\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Expected cloned attribute '%s' to have value '%s', got value '%s'\",\n\t\t\t\tname,\n\t\t\t\ttostring(value),\n\t\t\t\ttostring(actual)\n\t\t\t)\n\t\t)\n\tend\nend\n\n-- Setting attributes on a new empty instance should work\n\nlocal folder = Instance.new(\"Folder\")\nfolder:SetAttribute(\"Foo\", \"Bar\")\nassert(folder:GetAttribute(\"Foo\") == \"Bar\")\n\n-- Setting attributes to nil should work\n\nfolder:SetAttribute(\"Foo\", nil)\nassert(folder:GetAttribute(\"Foo\") == nil)\n\n-- Attribute names should allow certain special characters\n\nfolder:SetAttribute(\"test-hyphens\", nil)\nfolder:SetAttribute(\"test_underscores\", nil)\nfolder:SetAttribute(\"test/slashes\", nil)\nfolder:SetAttribute(\"test.periods\", nil)\n\n-- Writing files with modified attributes should work\n\nlocal game = Instance.new(\"DataModel\")\nmodel.Parent = game\n\nlocal placeFile = roblox.serializePlace(game)\nfs.writeDir(\"bin/roblox\")\nfs.writeFile(\"bin/roblox/attributes.rbxl\", placeFile)\n\nlocal enum_attr = Instance.new(\"Folder\")\nenum_attr:SetAttribute(\"Foo\", Enum.NormalId.Front)\nassert(enum_attr:GetAttribute(\"Foo\") == Enum.NormalId.Front)\n\nlocal enum_attr_ser = roblox.serializeModel({ enum_attr })\nlocal enum_attr_de = roblox.deserializeModel(enum_attr_ser)\n\nassert(enum_attr_de[1]:GetAttribute(\"Foo\") == Enum.NormalId.Front)\n"
  },
  {
    "path": "tests/roblox/instance/classes/DataModel.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal game = Instance.new(\"DataModel\")\n\n-- Workspace should always exist as a \"Workspace\" property, or be created when accessed\n\nlocal workspace = (game :: any).Workspace\nassert(workspace ~= nil)\nassert(workspace:IsA(\"Workspace\"))\nassert(workspace == game:FindFirstChildOfClass(\"Workspace\"))\n\n-- GetService and FindService should work, GetService should create services that don't exist\n\nassert(game:FindService(\"CSGDictionaryService\") == nil)\nassert(game:GetService(\"CSGDictionaryService\"))\nassert(game:FindService(\"CSGDictionaryService\") ~= nil)\n\n-- Service names should be strict and not allow weird characters or substrings\n\nassert(not pcall(function()\n\tgame:GetService(\"wrorokspacey\")\nend))\n\nassert(not pcall(function()\n\tgame:GetService(\"Work-space\")\nend))\n\nassert(not pcall(function()\n\tgame:GetService(\"workspac\")\nend))\n"
  },
  {
    "path": "tests/roblox/instance/classes/Terrain.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal Instance = roblox.Instance\nlocal Color3 = roblox.Color3\nlocal Enum = roblox.Enum\n\nlocal game = Instance.new(\"DataModel\")\nlocal workspace = game:GetService(\"Workspace\")\nlocal terrain = (workspace :: any).Terrain\n\nassert(terrain:GetMaterialColor(Enum.Material.Grass) == Color3.fromRGB(106, 127, 63))\n\nterrain:SetMaterialColor(Enum.Material.Sand, Color3.new(1, 1, 1))\nassert(terrain:GetMaterialColor(Enum.Material.Sand) == Color3.new(1, 1, 1))\n"
  },
  {
    "path": "tests/roblox/instance/classes/Workspace.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal game = Instance.new(\"DataModel\")\nlocal workspace = game:GetService(\"Workspace\")\n\n-- Terrain should always exist as a \"Terrain\" property, or be created when accessed\n\nlocal terrain = (workspace :: any).Terrain\nassert(terrain ~= nil)\nassert(terrain:IsA(\"Terrain\"))\nassert(terrain == workspace:FindFirstChildOfClass(\"Terrain\"))\n\n-- Camera should always exist as a \"CurrentCamera\" property, or be created when accessed\n\nlocal camera = (workspace :: any).CurrentCamera\nassert(camera ~= nil)\nassert(camera:IsA(\"Camera\"))\nassert(camera == workspace:FindFirstChildOfClass(\"Camera\"))\n"
  },
  {
    "path": "tests/roblox/instance/custom/async.luau",
    "content": "local net = require(\"@lune/net\")\nlocal roblox = require(\"@lune/roblox\")\nlocal serde = require(\"@lune/serde\")\n\nroblox.implementMethod(\"HttpService\", \"GetAsync\", function(_, url: string)\n\tlocal response = net.request({\n\t\tmethod = \"GET\",\n\t\turl = url,\n\t})\n\treturn response.body\nend)\n\nroblox.implementMethod(\"HttpService\", \"JSONDecode\", function(_, value)\n\treturn serde.decode(\"json\", value)\nend)\n\n-- Reference: https://create.roblox.com/docs/reference/engine/classes/HttpService#GetAsync\n\nlocal game = roblox.Instance.new(\"DataModel\")\nlocal HttpService = game:GetService(\"HttpService\") :: any\n\nlocal response = HttpService:GetAsync(\"https://httpbingo.org/json\")\nlocal data = HttpService:JSONDecode(response)\n\nassert(type(data) == \"table\", \"Returned JSON data should decode to a table\")\nassert(type(data.slideshow) == \"table\", \"Returned JSON data should contain 'slideshow'\")\n"
  },
  {
    "path": "tests/roblox/instance/custom/methods.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal inst = roblox.Instance.new(\"Instance\") :: any\nlocal part = roblox.Instance.new(\"Part\") :: any\n\n-- Basic sanity checks for callbacks\n\nlocal success = pcall(function()\n\tinst:Wat()\nend)\nassert(not success, \"Nonexistent methods should error\")\n\nroblox.implementMethod(\"Instance\", \"Wat\", function() end)\n\nlocal success2 = pcall(function()\n\tinst:Wat()\nend)\nassert(success2, \"Nonexistent methods should error, unless implemented\")\n\n-- Instance should be passed to callback\n\nroblox.implementMethod(\"Instance\", \"PassingInstanceTest\", function(instance)\n\tassert(instance == inst, \"Invalid instance was passed to callback\")\nend)\nroblox.implementMethod(\"Part\", \"PassingPartTest\", function(instance)\n\tassert(instance == part, \"Invalid instance was passed to callback\")\nend)\ninst:PassingInstanceTest()\npart:PassingPartTest()\n\n-- Any number of args passed & returned should work\n\nroblox.implementMethod(\"Instance\", \"Echo\", function(_, ...)\n\treturn ...\nend)\n\nlocal one, two, three = inst:Echo(\"one\", \"two\", \"three\")\nassert(one == \"one\", \"implementMethod callback should return proper values\")\nassert(two == \"two\", \"implementMethod callback should return proper values\")\nassert(three == \"three\", \"implementMethod callback should return proper values\")\n\n-- Methods implemented by Lune should take precedence\n\nroblox.implementMethod(\"Instance\", \"FindFirstChild\", function()\n\terror(\"unreachable\")\nend)\ninst:FindFirstChild(\"Test\")\npart:FindFirstChild(\"Test\")\n"
  },
  {
    "path": "tests/roblox/instance/custom/properties.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal inst = roblox.Instance.new(\"Instance\") :: any\nlocal part = roblox.Instance.new(\"Part\") :: any\n\n-- Basic sanity checks for callbacks\n\nlocal success = pcall(function()\n\tlocal _ = inst.Wat\nend)\nassert(not success, \"Nonexistent properties should error\")\n\nroblox.implementProperty(\"Instance\", \"Wat\", function()\n\treturn nil\nend)\n\nlocal success2 = pcall(function()\n\tlocal _ = inst.Wat\nend)\nassert(success2, \"Nonexistent properties should error, unless implemented\")\n\n-- Instance should be passed to callback\n\nroblox.implementProperty(\"Instance\", \"PassingInstanceTest\", function(instance)\n\tassert(instance == inst, \"Invalid instance was passed to callback\")\n\treturn nil\nend)\nroblox.implementProperty(\"Part\", \"PassingPartTest\", function(instance)\n\tassert(instance == part, \"Invalid instance was passed to callback\")\n\treturn nil\nend)\nlocal _ = inst.PassingInstanceTest\nlocal _ = part.PassingPartTest\n\n-- Any number of args passed & returned should work\n\nlocal counters = {}\nroblox.implementProperty(\"Instance\", \"Counter\", function(instance)\n\tlocal value = counters[instance:GetDebugId()] or 0\n\tvalue += 1\n\tcounters[instance:GetDebugId()] = value\n\treturn value\nend, function(instance, value)\n\tcounters[instance:GetDebugId()] = value\nend)\n\nassert(inst.Counter == 1, \"implementProperty callback should return proper values\")\nassert(inst.Counter == 2, \"implementProperty callback should return proper values\")\nassert(inst.Counter == 3, \"implementProperty callback should return proper values\")\n\ninst.Counter = 10\n\nassert(inst.Counter == 11, \"implementProperty callback should set proper values\")\nassert(inst.Counter == 12, \"implementProperty callback should return proper values\")\nassert(inst.Counter == 13, \"implementProperty callback should return proper values\")\n\n-- Properties implemented by Lune should take precedence\n\nroblox.implementProperty(\"Instance\", \"Parent\", function()\n\terror(\"unreachable\")\nend)\nlocal _ = inst.Parent\nlocal _ = part.Parent\n"
  },
  {
    "path": "tests/roblox/instance/methods/ClearAllChildren.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Model\")\nlocal child1 = Instance.new(\"Part\")\nlocal child2 = Instance.new(\"Part\")\n\nchild1.Parent = root\nchild2.Parent = root\n\nassert(#root:GetChildren() == 2)\nassert(root:GetChildren()[1] == child1)\nassert(root:GetChildren()[2] == child2)\n\nroot:ClearAllChildren()\n\nassert(#root:GetChildren() == 0)\n\nassert(not pcall(function()\n\treturn child1.Name\nend))\n\nassert(not child1.Parent)\n\nassert(not pcall(function()\n\treturn child2.Name\nend))\n\nassert(not child2.Parent)\n"
  },
  {
    "path": "tests/roblox/instance/methods/Clone.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Model\")\nlocal child = Instance.new(\"Part\")\nlocal objValue1 = Instance.new(\"ObjectValue\")\nlocal objValue2 = Instance.new(\"ObjectValue\")\n\nobjValue1.Name = \"ObjectValue1\"\nobjValue2.Name = \"ObjectValue2\"\n(objValue1 :: any).Value = root;\n(objValue2 :: any).Value = child\nobjValue1.Parent = child\nobjValue2.Parent = child\nchild.Parent = root\n\nlocal clonedChild = child:Clone()\nassert(clonedChild ~= child)\nassert(clonedChild.Parent == nil)\n\nlocal clonedObjValue1 = clonedChild[objValue1.Name]\nlocal clonedObjValue2 = clonedChild[objValue2.Name]\n\nassert(clonedObjValue1 ~= objValue1)\nassert(clonedObjValue2 ~= objValue2)\n\nassert(clonedObjValue1.Value == root, \"ObjectValue1.Value should still point to original root\")\nassert(clonedObjValue2.Value == clonedChild, \"ObjectValue2.Value should point to cloned child\")\n"
  },
  {
    "path": "tests/roblox/instance/methods/Destroy.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal descendant = Instance.new(\"Part\")\n\nchild.Parent = root\ndescendant.Parent = child\n\nroot:Destroy()\n\nassert(not pcall(function()\n\treturn root.Name\nend))\n\nassert(not root.Parent)\n\nassert(not pcall(function()\n\treturn child.Name\nend))\n\nassert(not child.Parent)\n\nassert(not pcall(function()\n\treturn descendant.Name\nend))\n\nassert(not descendant.Parent)\n"
  },
  {
    "path": "tests/roblox/instance/methods/FindFirstAncestor.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal nested = Instance.new(\"Tool\")\nlocal descendant = Instance.new(\"Part\")\n\ndescendant.Parent = nested\nnested.Parent = child\nchild.Parent = root\n\nassert(descendant:FindFirstAncestor(\"Part\") == nil)\nassert(descendant:FindFirstAncestor(\"Tool\") == nested)\nassert(descendant:FindFirstAncestor(\"Model\") == child)\nassert(descendant:FindFirstAncestor(\"Folder\") == root)\n"
  },
  {
    "path": "tests/roblox/instance/methods/FindFirstAncestorOfClass.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal nested = Instance.new(\"Tool\")\nlocal descendant = Instance.new(\"Part\")\n\ndescendant.Parent = nested\nnested.Parent = child\nchild.Parent = root\n\nassert(descendant:FindFirstAncestorOfClass(\"Part\") == nil)\nassert(descendant:FindFirstAncestorOfClass(\"Tool\") == nested)\nassert(descendant:FindFirstAncestorOfClass(\"Model\") == child)\nassert(descendant:FindFirstAncestorOfClass(\"Folder\") == root)\n"
  },
  {
    "path": "tests/roblox/instance/methods/FindFirstAncestorWhichIsA.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal nested = Instance.new(\"Tool\")\nlocal descendant = Instance.new(\"Part\")\n\ndescendant.Parent = nested\nnested.Parent = child\nchild.Parent = root\n\nassert(descendant:FindFirstAncestorWhichIsA(\"Part\") == nil)\nassert(descendant:FindFirstAncestorWhichIsA(\"Tool\") == nested)\nassert(descendant:FindFirstAncestorWhichIsA(\"Model\") == nested)\nassert(descendant:FindFirstAncestorWhichIsA(\"Folder\") == root)\n"
  },
  {
    "path": "tests/roblox/instance/methods/FindFirstChild.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal nested = Instance.new(\"Tool\")\nlocal adjacent = Instance.new(\"Model\")\nlocal descendant = Instance.new(\"Part\")\n\ndescendant.Parent = nested\nnested.Parent = child\nadjacent.Parent = child\nchild.Parent = root\n\nassert(child:FindFirstChild(\"Folder\") == nil)\nassert(child:FindFirstChild(\"Model\") == adjacent)\nassert(child:FindFirstChild(\"Tool\") == nested)\nassert(child:FindFirstChild(\"Part\") == nil)\n"
  },
  {
    "path": "tests/roblox/instance/methods/FindFirstChildOfClass.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal nested = Instance.new(\"Tool\")\nlocal adjacent = Instance.new(\"Model\")\nlocal descendant = Instance.new(\"Part\")\n\ndescendant.Parent = nested\nnested.Parent = child\nadjacent.Parent = child\nchild.Parent = root\n\nassert(child:FindFirstChildOfClass(\"Folder\") == nil)\nassert(child:FindFirstChildOfClass(\"Tool\") == nested)\nassert(child:FindFirstChildOfClass(\"Model\") == adjacent)\nassert(child:FindFirstChildOfClass(\"Part\") == nil)\n"
  },
  {
    "path": "tests/roblox/instance/methods/FindFirstChildWhichIsA.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal nested = Instance.new(\"Tool\")\nlocal adjacent = Instance.new(\"Model\")\nlocal descendant = Instance.new(\"Part\")\n\ndescendant.Parent = nested\nnested.Parent = child\nadjacent.Parent = child\nchild.Parent = root\n\nassert(child:FindFirstChildWhichIsA(\"Folder\") == nil)\nassert(child:FindFirstChildWhichIsA(\"Tool\") == nested)\nassert(child:FindFirstChildWhichIsA(\"Model\") == nested)\nassert(child:FindFirstChildWhichIsA(\"Part\") == nil)\n"
  },
  {
    "path": "tests/roblox/instance/methods/GetChildren.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal modelFile = fs.readFile(\"tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm\")\nlocal model = roblox.deserializeModel(modelFile)[1]\n\nassert(#model:GetChildren() == 1)\n\nlocal newChild = Instance.new(\"Model\")\nnewChild.Parent = model\n\nassert(#model:GetChildren() == 2)\nassert(table.find(model:GetChildren(), newChild) ~= nil)\n\nnewChild:Destroy()\n\nassert(#model:GetChildren() == 1)\nassert(table.find(model:GetChildren(), newChild) == nil)\n"
  },
  {
    "path": "tests/roblox/instance/methods/GetDebugId.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal part = roblox.Instance.new(\"Part\")\n\nlocal id = part:GetDebugId()\nassert(type(id) == \"string\", \"GetDebugId should return a string\")\nassert(#id == 32, \"GetDebugId should return a string with length 32\")\nassert(\n\tstring.match(id, \"^[0-9A-Fa-f]+$\"),\n\t\"GetDebugId should return a string with only hexadecimal characters\"\n)\n"
  },
  {
    "path": "tests/roblox/instance/methods/GetDescendants.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal modelFile = fs.readFile(\"tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm\")\nlocal model = roblox.deserializeModel(modelFile)[1]\n\nassert(#model:GetDescendants() == 2)\n\nlocal newChild = Instance.new(\"Model\")\nnewChild.Parent = model\n\nassert(#model:GetDescendants() == 3)\nassert(table.find(model:GetDescendants(), newChild) == 2)\n\nnewChild:Destroy()\n\nassert(#model:GetDescendants() == 2)\nassert(table.find(model:GetDescendants(), newChild) == nil)\n"
  },
  {
    "path": "tests/roblox/instance/methods/GetFullName.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal roblox = require(\"@lune/roblox\")\n\nlocal modelFile = fs.readFile(\"tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm\")\nlocal model = roblox.deserializeModel(modelFile)[1]\n\nlocal child = model:FindFirstChild(\"Parent\")\nassert(child ~= nil)\nlocal descendant = child:FindFirstChild(\"Child\")\nassert(descendant ~= nil)\n\nassert(descendant:GetFullName() == \"Grandparent.Parent.Child\")\nassert(child:GetFullName() == \"Grandparent.Parent\")\nassert(model:GetFullName() == \"Grandparent\")\n"
  },
  {
    "path": "tests/roblox/instance/methods/IsA.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal part = Instance.new(\"Part\")\nlocal workspace = Instance.new(\"Workspace\")\n\n-- Valid\n\nassert(part:IsA(\"Part\") == true)\nassert(part:IsA(\"BasePart\") == true)\nassert(part:IsA(\"PVInstance\") == true)\nassert(part:IsA(\"Instance\") == true)\n\nassert(workspace:IsA(\"Workspace\") == true)\nassert(workspace:IsA(\"Model\") == true)\nassert(workspace:IsA(\"Instance\") == true)\n\n-- Invalid\n\nassert(part:IsA(\"part\") == false)\nassert(part:IsA(\"Base-Part\") == false)\nassert(part:IsA(\"Model\") == false)\nassert(part:IsA(\"Paart\") == false)\n\nassert(workspace:IsA(\"Service\") == false)\nassert(workspace:IsA(\".\") == false)\nassert(workspace:IsA(\"\") == false)\n"
  },
  {
    "path": "tests/roblox/instance/methods/IsAncestorOf.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal descendant = Instance.new(\"Part\")\n\nchild.Parent = root\ndescendant.Parent = child\n\nassert(not root:IsAncestorOf(root))\nassert(not child:IsAncestorOf(root))\nassert(not descendant:IsAncestorOf(root))\n\nassert(root:IsAncestorOf(child))\nassert(not child:IsAncestorOf(child))\nassert(not descendant:IsAncestorOf(child))\n\nassert(root:IsAncestorOf(descendant))\nassert(child:IsAncestorOf(descendant))\nassert(not descendant:IsAncestorOf(descendant))\n"
  },
  {
    "path": "tests/roblox/instance/methods/IsDescendantOf.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal root = Instance.new(\"Folder\")\nlocal child = Instance.new(\"Model\")\nlocal descendant = Instance.new(\"Part\")\n\nchild.Parent = root\ndescendant.Parent = child\n\nassert(not root:IsDescendantOf(root))\nassert(child:IsDescendantOf(root))\nassert(descendant:IsDescendantOf(root))\n\nassert(not root:IsDescendantOf(child))\nassert(not child:IsDescendantOf(child))\nassert(descendant:IsDescendantOf(child))\n\nassert(not root:IsDescendantOf(descendant))\nassert(not child:IsDescendantOf(descendant))\nassert(not descendant:IsDescendantOf(descendant))\n"
  },
  {
    "path": "tests/roblox/instance/new.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\n-- Should not allow creating unknown classes\nassert(not pcall(function()\n\tInstance.new(\"asdf\")\nend))\n\n-- Should be case sensitive\nassert(not pcall(function()\n\tInstance.new(\"part\")\nend))\n\n-- Should allow \"not creatable\" tagged classes to be created\nInstance.new(\"BasePart\")\n\n-- Should have correct classnames\nassert(Instance.new(\"Part\").ClassName == \"Part\")\nassert(Instance.new(\"Folder\").ClassName == \"Folder\")\nassert(Instance.new(\"ReplicatedStorage\").ClassName == \"ReplicatedStorage\")\n\n-- Should have initial names that are the same as the class name\nassert(Instance.new(\"Part\").Name == \"Part\")\nassert(Instance.new(\"Folder\").Name == \"Folder\")\nassert(Instance.new(\"ReplicatedStorage\").Name == \"ReplicatedStorage\")\n\n-- Parent should be nil until parented\nlocal folder = Instance.new(\"Folder\")\nlocal model = Instance.new(\"Model\")\nassert(folder.Parent == nil)\nassert(model.Parent == nil)\n\n-- Parenting and indexing should work\nmodel.Parent = folder\nassert(model.Parent == folder)\nassert((folder :: any).Model == model)\n\n-- Parenting to nil should work\nmodel.Parent = nil\nassert(model.Parent == nil)\n\n-- Name should be able to be set, and should not be nillable\nmodel.Name = \"MyCoolModel\"\nassert(model.Name == \"MyCoolModel\")\nassert(not pcall(function()\n\tmodel.Name = nil :: any\nend))\nassert(model.Name == \"MyCoolModel\")\n"
  },
  {
    "path": "tests/roblox/instance/properties.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\nlocal BrickColor = roblox.BrickColor\nlocal Instance = roblox.Instance\nlocal Vector3 = roblox.Vector3\nlocal CFrame = roblox.CFrame\nlocal Enum = roblox.Enum\n\nlocal part = Instance.new(\"Part\")\n\n-- Primitive type properties should work (note that these are inherited from BasePart)\n\npart.Anchored = true\npart.CanCollide = true\npart.CanQuery = false\n\nassert(part.Anchored == true)\nassert(part.CanCollide == true)\nassert(part.CanQuery == false)\n\n-- More complex types like Vector3 should work\n\npart.Size = Vector3.one\npart.CFrame = CFrame.identity\npart.BrickColor = BrickColor.Red()\n\nassert(part.Size == Vector3.one)\nassert(part.CFrame == CFrame.identity)\nassert(part.BrickColor == BrickColor.Red())\n\n-- Enums should work (note that these are specific to Part and not on BasePart)\n\npart.Shape = Enum.PartType.Ball\n\nassert(part.Shape == Enum.PartType.Ball)\n\n-- Enums should roundtrip through serde without problem\n\nlocal decal = Instance.new(\"Decal\")\ndecal.Face = Enum.NormalId.Top\n\nlocal decal_ser = roblox.serializeModel({ decal })\nlocal decal_de = roblox.deserializeModel(decal_ser)\n\nassert(decal_de[1].Face == Enum.NormalId.Top)\n\n-- Properties that don't exist for a class should error\n\nlocal meshPart = Instance.new(\"MeshPart\")\n\nassert(not pcall(function()\n\tmeshPart.Shape = Enum.PartType.Ball\nend))\n\n-- We should be able to access properties without first setting them\n\nassert(meshPart.Anchored == false)\nassert(meshPart.Material == Enum.Material.Plastic)\nassert(meshPart.Size == Vector3.new(4, 1.2, 2))\nassert(meshPart.CustomPhysicalProperties == nil)\n\n-- Instance reference properties should work\n\nlocal objectValue = Instance.new(\"ObjectValue\")\n\nassert(objectValue.Value == nil)\nobjectValue.Value = meshPart\nassert(objectValue.Value == meshPart)\n"
  },
  {
    "path": "tests/roblox/instance/tags.luau",
    "content": "local roblox = require(\"@lune/roblox\")\nlocal Instance = roblox.Instance\n\nlocal model = Instance.new(\"Model\")\nlocal part = Instance.new(\"Part\")\npart.Parent = model\n\nlocal TAG_NAME = \"InstanceTagName\"\n\nassert(model:HasTag(TAG_NAME) == false)\nassert(part:HasTag(TAG_NAME) == false)\n\npart:AddTag(TAG_NAME)\n\nassert(model:HasTag(TAG_NAME) == false)\nassert(part:HasTag(TAG_NAME) == true)\n\npart:RemoveTag(TAG_NAME)\n\nassert(model:HasTag(TAG_NAME) == false)\nassert(part:HasTag(TAG_NAME) == false)\n\nassert(#model:GetTags() == 0)\nassert(#part:GetTags() == 0)\n\nmodel:AddTag(TAG_NAME)\npart:AddTag(TAG_NAME)\n\nassert(#model:GetTags() == 1)\nassert(#part:GetTags() == 1)\nassert(model:GetTags()[1] == TAG_NAME)\nassert(part:GetTags()[1] == TAG_NAME)\n"
  },
  {
    "path": "tests/roblox/misc/typeof.luau",
    "content": "local roblox = require(\"@lune/roblox\") :: any\n\nlocal TYPES_AND_VALUES = {\n\tAxes = roblox.Axes.new(),\n\tBrickColor = roblox.BrickColor.new(\"Really red\"),\n\tCFrame = roblox.CFrame.new(),\n\tColor3 = roblox.Color3.new(0, 0, 0),\n\tColorSequence = roblox.ColorSequence.new(roblox.Color3.new(0, 0, 0)),\n\tColorSequenceKeypoint = roblox.ColorSequenceKeypoint.new(0, roblox.Color3.new(0, 0, 0)),\n\tEnums = roblox.Enum,\n\tEnum = roblox.Enum.KeyCode,\n\tEnumItem = roblox.Enum.KeyCode.Unknown,\n\tFaces = roblox.Faces.new(),\n\tFont = roblox.Font.new(\"Gotham\"),\n\tNumberRange = roblox.NumberRange.new(0, 1),\n\tNumberSequence = roblox.NumberSequence.new(0, 1),\n\tNumberSequenceKeypoint = roblox.NumberSequenceKeypoint.new(0, 1),\n\tPhysicalProperties = roblox.PhysicalProperties.new(1, 1, 1),\n\tRay = roblox.Ray.new(roblox.Vector3.zero, roblox.Vector3.one),\n\tRect = roblox.Rect.new(0, 0, 0, 0),\n\tRegion3 = roblox.Region3.new(roblox.Vector3.zero, roblox.Vector3.one),\n\tRegion3int16 = roblox.Region3int16.new(\n\t\troblox.Vector3int16.new(0, 0, 0),\n\t\troblox.Vector3int16.new(1, 1, 1)\n\t),\n\tUDim = roblox.UDim.new(0, 0),\n\tUDim2 = roblox.UDim2.new(0, 0, 0, 0),\n\tVector2 = roblox.Vector2.new(0, 0),\n\tVector2int16 = roblox.Vector2int16.new(0, 0),\n\tVector3 = roblox.Vector3.new(0, 0),\n\tVector3int16 = roblox.Vector3int16.new(0, 0),\n}\n\nfor name, value in TYPES_AND_VALUES :: { [string]: any } do\n\tif typeof(value) ~= name then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"typeof() did not return correct value!\\nExpected: %s\\nActual: %s\",\n\t\t\t\tname,\n\t\t\t\ttypeof(value)\n\t\t\t)\n\t\t)\n\tend\nend\n"
  },
  {
    "path": "tests/roblox/reflection/class.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal db = roblox.getReflectionDatabase()\n\n-- Make sure database classes exist + fields / properties are correct types\n\nfor _, className in db:GetClassNames() do\n\tlocal class = db:GetClass(className)\n\tassert(class ~= nil, \"Missing \" .. className .. \" class in database\")\n\tassert(type(class.Name) == \"string\", \"Name property must be a string\")\n\tassert(\n\t\tclass.Superclass == nil or type(class.Superclass) == \"string\",\n\t\t\"Superclass property must be nil or a string\"\n\t)\n\tassert(type(class.Properties) == \"table\", \"Properties property must be a table\")\n\tassert(type(class.DefaultProperties) == \"table\", \"DefaultProperties property must be a table\")\n\tassert(type(class.Tags) == \"table\", \"Tags property must be a table\")\nend\n\n-- Any property present in default properties must also\n-- be in properties *or* the properties of a superclass\n\nfor _, className in db:GetClassNames() do\n\tlocal class = db:GetClass(className)\n\tassert(class ~= nil)\n\tfor name, value in class.DefaultProperties do\n\t\tlocal found = false\n\t\tlocal current: roblox.DatabaseClass? = class\n\t\twhile current ~= nil do\n\t\t\tif current.Properties[name] ~= nil then\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\telseif current.Superclass ~= nil then\n\t\t\t\tcurrent = db:GetClass(current.Superclass)\n\t\t\telse\n\t\t\t\tbreak\n\t\t\tend\n\t\tend\n\t\tassert(found, \"Missing default property \" .. name .. \" in properties table\")\n\tend\nend\n"
  },
  {
    "path": "tests/roblox/reflection/database.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal db = roblox.getReflectionDatabase()\nlocal db2 = roblox.getReflectionDatabase()\n\n-- Subsequent calls to getReflectionDatabase should return the same database\nassert(db == db2, \"Database should always compare as equal to other database\")\n\n-- Database should not be empty\nassert(#db:GetClassNames() > 0, \"Database should not be empty (no class names)\")\nassert(#db:GetEnumNames() > 0, \"Database should not be empty (no enum names)\")\n\n-- Make sure our database finds classes correctly\n\nlocal class = db:GetClass(\"Instance\")\nassert(class ~= nil, \"Missing Instance class in database\")\nlocal prop = class.Properties.Parent\nassert(prop ~= nil, \"Missing Parent property on Instance class in database\")\n\nlocal class2 = db:FindClass(\"    instance \")\nassert(class2 ~= nil, \"Missing Instance class in database (2)\")\nlocal prop2 = class2.Properties.Parent\nassert(prop2 ~= nil, \"Missing Parent property on Instance class in database (2)\")\n\nassert(class == class2, \"Class userdatas from the database should compare as equal\")\nassert(prop == prop2, \"Property userdatas from the database should compare as equal\")\n\nassert(db:GetClass(\"PVInstance\") ~= nil, \"Missing PVInstance class in database\")\nassert(db:GetClass(\"BasePart\") ~= nil, \"Missing BasePart class in database\")\nassert(db:GetClass(\"Part\") ~= nil, \"Missing Part class in database\")\n\n-- Make sure our database finds enums correctly\n\nlocal enum = db:GetEnum(\"PartType\")\nassert(enum ~= nil, \"Missing PartType enum in database\")\n\nlocal enum2 = db:FindEnum(\"   parttype \")\nassert(enum2 ~= nil, \"Missing PartType enum in database (2)\")\n\nassert(enum == enum2, \"Enum userdatas from the database should compare as equal\")\n\nassert(db:GetEnum(\"UserInputType\") ~= nil, \"Missing UserInputType enum in database\")\nassert(db:GetEnum(\"NormalId\") ~= nil, \"Missing NormalId enum in database\")\nassert(db:GetEnum(\"Font\") ~= nil, \"Missing Font enum in database\")\n\n-- All the class and enum names gotten from the database should be accessible\n\nfor _, className in db:GetClassNames() do\n\tassert(db:GetClass(className) ~= nil, \"Missing \" .. className .. \" class in database (3)\")\n\tassert(db:FindClass(className) ~= nil, \"Missing \" .. className .. \" class in database (4)\")\nend\nfor _, enumName in db:GetEnumNames() do\n\tassert(db:GetEnum(enumName) ~= nil, \"Missing \" .. enumName .. \" enum in database (3)\")\n\tassert(db:FindEnum(enumName) ~= nil, \"Missing \" .. enumName .. \" enum in database (4)\")\nend\n"
  },
  {
    "path": "tests/roblox/reflection/enums.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal db = roblox.getReflectionDatabase()\n\n-- Make sure database enums exist + fields / properties are correct types\n\nfor _, enumName in db:GetEnumNames() do\n\tlocal enum = db:GetEnum(enumName)\n\tassert(enum ~= nil, \"Missing \" .. enumName .. \" enum in database\")\n\tassert(type(enum.Name) == \"string\", \"Name property must be a string\")\n\tassert(type(enum.Items) == \"table\", \"Items property must be a table\")\nend\n\n-- Enum items should be a non-empty map of string -> positive integer values\n\nfor _, enumName in db:GetEnumNames() do\n\tlocal enum = db:GetEnum(enumName)\n\tassert(enum ~= nil)\n\tlocal empty = true\n\tfor name, value in enum.Items do\n\t\tassert(\n\t\t\ttype(name) == \"string\" and #name > 0,\n\t\t\t\"Enum items map must only contain non-empty string keys\"\n\t\t)\n\t\tassert(\n\t\t\ttype(value) == \"number\" and value >= 0 and math.floor(value) == value,\n\t\t\t\"Enum items map must only contain positive integer values\"\n\t\t)\n\t\tempty = false\n\tend\n\t-- As of October 12 2025, the enum AudioCaptureMode is empty in Roblox API dumps.\n\t-- TODO: Remove this if statement when AudioCaptureMode becomes non-empty.\n\tif enumName ~= \"AudioCaptureMode\" then\n\t\tassert(not empty, \"Enum items map must not be empty\")\n\tend\nend\n"
  },
  {
    "path": "tests/roblox/reflection/property.luau",
    "content": "local roblox = require(\"@lune/roblox\")\n\nlocal db = roblox.getReflectionDatabase()\n\n-- Make sure database class properties exist + their fields / properties are correct types\n\nfor _, className in db:GetClassNames() do\n\tlocal class = db:GetClass(className)\n\tassert(class ~= nil)\n\n\tfor name, prop in class.Properties do\n\t\tassert(type(prop.Name) == \"string\", \"Name property must be a string\")\n\t\tassert(type(prop.Datatype) == \"string\", \"Datatype property must be a string\")\n\t\tassert(type(prop.Scriptability) == \"string\", \"Scriptability property must be a string\")\n\t\tassert(type(prop.Tags) == \"table\", \"Tags property must be a table\")\n\tend\nend\n"
  },
  {
    "path": "tests/serde/compression/files.luau",
    "content": "--[[\n\tThe \"compress\" tests are currently disabled, and may not ever be enabled again.\n\n\tUnit testing that our compression implementation produces the exact result saved\n\tin test files is very fragile and prone to breakage whenever one of the crates\n\twe depend on for compression algorithms updates, be it to improve compression\n\tratio, speed, memory usage, or any other metric.\n\n\tInstead, we should focus on the correctness of the implementation, ensuring that\n\tany files we know are good from other sources can be decompressed without issues.\n\n\tIn the future, it is probably a good idea to rewrite these compression tests so\n\tthat they do not rely on any exact output, but instead call some external program\n\tto verify that others can decompress without issues. This is a challenge in and of\n\titself since finding CLI programs for testing compression is near-impossible, but alas.\n]]\nlocal TEST_COMPRESS = false\nlocal TEST_DECOMPRESS = true\n\nlocal fs = require(\"@lune/fs\")\nlocal process = require(\"@lune/process\")\nlocal serde = require(\"@lune/serde\")\nlocal stdio = require(\"@lune/stdio\")\n\ntype Test = {\n\tFormat: serde.CompressDecompressFormat,\n\tSource: string,\n\tTarget: string,\n}\n\nlocal TESTS: { Test } = {\n\t{\n\t\tFormat = \"brotli\",\n\t\tSource = \"tests/serde/test-files/loremipsum.txt\",\n\t\tTarget = \"tests/serde/test-files/loremipsum.txt.br\",\n\t},\n\t{\n\t\tFormat = \"gzip\",\n\t\tSource = \"tests/serde/test-files/loremipsum.txt\",\n\t\tTarget = \"tests/serde/test-files/loremipsum.txt.gz\",\n\t},\n\t{\n\t\tFormat = \"lz4\",\n\t\tSource = \"tests/serde/test-files/loremipsum.txt\",\n\t\tTarget = \"tests/serde/test-files/loremipsum.txt.lz4\",\n\t},\n\t{\n\t\tFormat = \"zlib\",\n\t\tSource = \"tests/serde/test-files/loremipsum.txt\",\n\t\tTarget = \"tests/serde/test-files/loremipsum.txt.z\",\n\t},\n\t{\n\t\tFormat = \"zstd\",\n\t\tSource = \"tests/serde/test-files/loremipsum.txt\",\n\t\tTarget = \"tests/serde/test-files/loremipsum.txt.zst\",\n\t},\n}\n\nlocal failed = false\nlocal function testOperation(\n\toperationName: \"Compress\" | \"Decompress\",\n\toperation: (\n\t\tformat: serde.CompressDecompressFormat,\n\t\ts: buffer | string\n\t) -> string,\n\tformat: serde.CompressDecompressFormat,\n\tsource: string | buffer,\n\ttarget: string\n)\n\tlocal success, res = pcall(operation, format, source)\n\tif not success then\n\t\tstdio.ewrite(\n\t\t\tstring.format(\n\t\t\t\t\"%sing source using '%s' format threw an error!\\n%s\",\n\t\t\t\toperationName,\n\t\t\t\ttostring(format),\n\t\t\t\ttostring(res)\n\t\t\t)\n\t\t)\n\t\tfailed = true\n\telseif res ~= target then\n\t\tstdio.ewrite(\n\t\t\tstring.format(\n\t\t\t\t\"%sing source using '%s' format did not produce target!\\n\",\n\t\t\t\toperationName,\n\t\t\t\ttostring(format)\n\t\t\t)\n\t\t)\n\t\tstdio.ewrite(\n\t\t\tstring.format(\n\t\t\t\t\"%sed (%d chars long):\\n%s\\nTarget (%d chars long):\\n%s\\n\\n\",\n\t\t\t\toperationName,\n\t\t\t\t#res,\n\t\t\t\ttostring(res),\n\t\t\t\t#target,\n\t\t\t\ttostring(target)\n\t\t\t)\n\t\t)\n\t\tfailed = true\n\tend\nend\n\nfor _, test in TESTS do\n\tlocal source = fs.readFile(test.Source)\n\tlocal target = fs.readFile(test.Target)\n\n\t-- Compression\n\tif TEST_COMPRESS then\n\t\ttestOperation(\"Compress\", serde.compress, test.Format, source, target)\n\t\ttestOperation(\"Compress\", serde.compress, test.Format, buffer.fromstring(source), target)\n\tend\n\n\t-- Decompression\n\tif TEST_DECOMPRESS then\n\t\ttestOperation(\"Decompress\", serde.decompress, test.Format, target, source)\n\t\ttestOperation(\n\t\t\t\"Decompress\",\n\t\t\tserde.decompress,\n\t\t\ttest.Format,\n\t\t\tbuffer.fromstring(target),\n\t\t\tsource\n\t\t)\n\tend\nend\n\nif failed then\n\tprocess.exit(1)\nend\n"
  },
  {
    "path": "tests/serde/compression/roundtrip.luau",
    "content": "local fs = require(\"@lune/fs\")\nlocal process = require(\"@lune/process\")\nlocal serde = require(\"@lune/serde\")\nlocal stdio = require(\"@lune/stdio\")\n\nlocal FORMATS: { serde.CompressDecompressFormat } = { \"brotli\", \"gzip\", \"lz4\", \"zlib\", \"zstd\" }\nlocal FILES: { string } = {\n\t\"tests/serde/test-files/loremipsum.txt\",\n\t\"tests/serde/test-files/uncompressed.csv\",\n\t\"tests/serde/test-files/uncompressed.json\",\n\t\"tests/serde/test-files/uncompressed.yaml\",\n}\n\nlocal failed = false\nfor _, filePath in FILES do\n\tlocal source = fs.readFile(filePath)\n\tfor _, format: serde.CompressDecompressFormat in FORMATS do\n\t\tlocal compressed = serde.compress(format, source)\n\t\tlocal decompressed = serde.decompress(format, compressed)\n\n\t\t-- Compressing something should return something else\n\t\tif #compressed <= 0 then\n\t\t\tstdio.ewrite(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Compressing source using '%s' returned an empty string!\\n\",\n\t\t\t\t\ttostring(format)\n\t\t\t\t)\n\t\t\t)\n\t\t\tstdio.ewrite(string.format(\"Source (%d chars long):\\n%s\\n\", #source, tostring(source)))\n\t\t\tfailed = true\n\t\t\tcontinue\n\t\tend\n\t\tif compressed == source then\n\t\t\tstdio.ewrite(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Compressing source using '%s' format did not change contents!\\n\",\n\t\t\t\t\ttostring(format)\n\t\t\t\t)\n\t\t\t)\n\t\t\tstdio.ewrite(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Source (%d chars long):\\n%s\\nCompressed (%d chars long):\\n%s\\n\",\n\t\t\t\t\t#source,\n\t\t\t\t\ttostring(source),\n\t\t\t\t\t#compressed,\n\t\t\t\t\ttostring(compressed)\n\t\t\t\t)\n\t\t\t)\n\t\t\tfailed = true\n\t\t\tcontinue\n\t\tend\n\n\t\t-- Decompressing that something else should return the original source\n\t\tif decompressed ~= source then\n\t\t\tstdio.ewrite(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Decompressing using '%s' format did not return the source!\\n\",\n\t\t\t\t\ttostring(format)\n\t\t\t\t)\n\t\t\t)\n\t\t\tstdio.ewrite(\n\t\t\t\tstring.format(\n\t\t\t\t\t\"Source (%d chars long):\\n%s\\nCompressed (%d chars long):\\n%s\\nDecompressed (%d chars long):\\n%s\\n\",\n\t\t\t\t\t#source,\n\t\t\t\t\ttostring(source),\n\t\t\t\t\t#compressed,\n\t\t\t\t\ttostring(compressed),\n\t\t\t\t\t#decompressed,\n\t\t\t\t\ttostring(decompressed)\n\t\t\t\t)\n\t\t\t)\n\t\t\tfailed = true\n\t\t\tcontinue\n\t\tend\n\tend\nend\n\nif failed then\n\tprocess.exit(1)\nend\n"
  },
  {
    "path": "tests/serde/hashing/hash.luau",
    "content": "local serde = require(\"@lune/serde\")\n\nlocal TEST_INPUT =\n\t\"Luau is a fast, small, safe, gradually typed embeddable scripting language derived from Lua.\"\n\nlocal function test_case_hash(algorithm: serde.HashAlgorithm, expected: string)\n\tassert(\n\t\tserde.hash(algorithm, TEST_INPUT) == expected,\n\t\t`hashing algorithm '{algorithm}' did not hash test string correctly`\n\t)\n\tassert(\n\t\tserde.hash(algorithm, buffer.fromstring(TEST_INPUT)) == expected,\n\t\t`hashing algorithm '{algorithm}' did not hash test buffer correctly`\n\t)\nend\n\ntest_case_hash(\"blake3\", \"eccfe3a6696b2a1861c64cc78663cff51301058e5dc22bb6249e7e1e0173d7fe\")\ntest_case_hash(\"md5\", \"2aed9e020b49d219dc383884c5bd7acd\")\ntest_case_hash(\"sha1\", \"9dce74190857f36e6d3f5e8eb7fe704a74060726\")\ntest_case_hash(\"sha224\", \"f7ccd8a5f2697df8470b66f03824e073075292a1fab40d3a2ddc2e83\")\ntest_case_hash(\"sha256\", \"f1d149bfd1ea38833ae6abf2a6fece1531532283820d719272e9cf3d9344efea\")\ntest_case_hash(\n\t\"sha384\",\n\t\"f6da4b47846c6016a9b32f01b861e45195cf1fa6fc5c9dd2257f7dc1c14092f11001839ec1223c30ab7adb7370812863\"\n)\ntest_case_hash(\n\t\"sha512\",\n\t\"49fd834fdf3d4eaf4d4aff289acfc24d649f81cee7a5a7940e5c86854e04816f0a97c53f2ca4908969a512ec5ad1dc466422e3928f5ce3da9913959315df807c\"\n)\ntest_case_hash(\"sha3-224\", \"56a4dd1ff1bd9baff7f8bbe380dbf2c75b073161693f94ebf91aeee5\")\ntest_case_hash(\"sha3-256\", \"ee01be10e0dc133cd702999e854b396f40b039d5ba6ddec9d04bf8623ba04dd7\")\ntest_case_hash(\n\t\"sha3-384\",\n\t\"e992f31e638b47802f33a4327c0a951823e32491ddcef5af9ce18cff84475c98ced23928d47ef51a8a4299dfe2ece361\"\n)\ntest_case_hash(\n\t\"sha3-512\",\n\t\"08bd02aca3052b7740de80b8e8b9969dc9059a4bfae197095430e0aa204fbd3afb11731b127559b90c2f7e295835ea844ddbb29baf2fdb1d823046052c120fc9\"\n)\n\nlocal failed = pcall(serde.hash, \"a random string\" :: any, \"input that shouldn't be hashed\")\nassert(failed == false, \"serde.hash shouldn't allow invalid algorithms passed to it!\")\n\nassert(\n\tserde.hash(\"sha256\", \"\\0oh no invalid utf-8\\127\\0\\255\")\n\t\t== \"c18ed3188f9e93f9ecd3582d7398c45120b0b30a0e26243809206228ab711b78\",\n\t\"serde.hash should hash invalid UTF-8 just fine\"\n)\n"
  },
  {
    "path": "tests/serde/hashing/hmac.luau",
    "content": "local serde = require(\"@lune/serde\")\n\nlocal INPUT_STRING = \"important data to verify the integrity of\"\n\n-- if you read this string, you're obligated to keep it a secret! :-)\nlocal SECRET_STRING = \"don't read this we operate on the honor system\"\n\nlocal function test_case_hmac(algorithm: serde.HashAlgorithm, expected: string)\n\tassert(\n\t\tserde.hmac(algorithm, INPUT_STRING, SECRET_STRING) == expected,\n\t\t`HMAC test for algorithm '{algorithm}' was not correct with string input and string secret`\n\t)\n\tassert(\n\t\tserde.hmac(algorithm, INPUT_STRING, buffer.fromstring(SECRET_STRING)) == expected,\n\t\t`HMAC test for algorithm '{algorithm}' was not correct with string input and buffer secret`\n\t)\n\tassert(\n\t\tserde.hmac(algorithm, buffer.fromstring(INPUT_STRING), SECRET_STRING) == expected,\n\t\t`HMAC test for algorithm '{algorithm}' was not correct with buffer input and string secret`\n\t)\n\tassert(\n\t\tserde.hmac(algorithm, buffer.fromstring(INPUT_STRING), buffer.fromstring(SECRET_STRING))\n\t\t\t== expected,\n\t\t`HMAC test for algorithm '{algorithm}' was not correct with buffer input and buffer secret`\n\t)\nend\n\ntest_case_hmac(\"blake3\", \"1d9c1b9405567fc565c2c3c6d6c0e170be72a2623d29911f43cb2ce42a373c01\")\ntest_case_hmac(\"md5\", \"525379669c93ab5f59d2201024145b79\")\ntest_case_hmac(\"sha1\", \"75227c11ed65133788feab0ce7eb8efc8c1f0517\")\ntest_case_hmac(\"sha224\", \"47a4857d7d7e1070f47f76558323e03471a918facaf3667037519c29\")\ntest_case_hmac(\"sha256\", \"4a4816ab8d4b780a8cf131e34a3df25e4c7bc4eba453cd86e50271aab4e95f45\")\ntest_case_hmac(\n\t\"sha384\",\n\t\"6b24aeae78d0f84ec8a4669b24bda1131205535233c344f4262c1f90f29af04c5537612c269bbab8aaca9d8293f4a280\"\n)\ntest_case_hmac(\n\t\"sha512\",\n\t\"9fffa071241e2f361f8a47a97d251c1d4aae37498efbc49745bf9916d8431f1f361080d350067ed65744d3da42956da33ec57b04901a5fd63a891381a1485ef7\"\n)\ntest_case_hmac(\"sha3-224\", \"ea102dfaa74aa285555bdba29a04429dfd4e997fa40322459094929f\")\ntest_case_hmac(\"sha3-256\", \"17bde287e4692e5b7f281e444efefe92e00696a089570bd6814fd0e03d7763d2\")\ntest_case_hmac(\n\t\"sha3-384\",\n\t\"24f68401653d25f36e7ee8635831215f8b46710d4e133c9d1e091e5972c69b0f1d0cb80f5507522fa174d5c4746963c1\"\n)\ntest_case_hmac(\n\t\"sha3-512\",\n\t\"d2566d156c254ced0101159f97187dbf48d900b8361fa5ebdd7e81409856b1b6a21d93a1fb6e8f700e75620d244ab9e894454030da12d158e9362ffe090d2669\"\n)\n\nlocal failed =\n\tpcall(serde.hmac, \"a random string\" :: any, \"input that shouldn't be hashed\", \"not a secret\")\nassert(failed == false, \"serde.hmac shouldn't allow invalid algorithms passed to it!\")\n\nassert(\n\tserde.hmac(\"sha256\", \"\\0oh no invalid utf-8\\127\\0\\255\", SECRET_STRING)\n\t\t== \"1f0d7f65016e9e4c340e3ba23da2483a7dc101ce8a9405f834c23f2e19232c3d\",\n\t\"serde.hmac should hash invalid UTF-8 just fine\"\n)\n"
  },
  {
    "path": "tests/serde/json/decode.luau",
    "content": "local serde = require(\"@lune/serde\")\nlocal source = require(\"./source\")\n\nlocal decoded = serde.decode(\"json\", source.pretty)\n\nassert(type(decoded) == \"table\", \"Decoded payload was not a table\")\nassert(decoded.Hello == \"World\", \"Decoded payload Hello was not World\")\nassert(type(decoded.Inner) == \"table\", \"Decoded payload Inner was not a table\")\nassert(type(decoded.Inner.Array) == \"table\", \"Decoded payload Inner.Array was not a table\")\nassert(type(decoded.Inner.Array[1]) == \"number\", \"Decoded payload Inner.Array[1] was not a number\")\nassert(type(decoded.Inner.Array[2]) == \"number\", \"Decoded payload Inner.Array[2] was not a number\")\nassert(type(decoded.Inner.Array[3]) == \"number\", \"Decoded payload Inner.Array[3] was not a number\")\nassert(decoded.Foo == \"Bar\", \"Decoded payload Foo was not Bar\")\n"
  },
  {
    "path": "tests/serde/json/encode.luau",
    "content": "local serde = require(\"@lune/serde\")\nlocal source = require(\"./source\")\n\nlocal decoded = serde.decode(\"json\", source.pretty)\nlocal encoded = serde.encode(\"json\", decoded, false)\nassert(encoded == source.encoded, \"JSON round-trip did not produce the same result\")\n\nlocal encodedPretty = serde.encode(\"json\", decoded, true)\nassert(encodedPretty == source.pretty, \"JSON round-trip did not produce the same result (pretty)\")\n"
  },
  {
    "path": "tests/serde/json/source.luau",
    "content": "local JSON_STRING = [[{\"Foo\":\"Bar\",\"Hello\":\"World\",\"Inner\":{\"Array\":[1,3,2]}}]]\n\nlocal JSON_STRING_PRETTY = [[{\n  \"Foo\": \"Bar\",\n  \"Hello\": \"World\",\n  \"Inner\": {\n    \"Array\": [\n      1,\n      3,\n      2\n    ]\n  }\n}]]\n\nreturn {\n\tencoded = JSON_STRING,\n\tpretty = JSON_STRING_PRETTY,\n}\n"
  },
  {
    "path": "tests/serde/jsonc/decode.luau",
    "content": "local serde = require(\"@lune/serde\")\nlocal source = require(\"./source\")\n\nlocal decoded = serde.decode(\"jsonc\", source.encoded)\n\nassert(type(decoded) == \"table\", \"Decoded payload was not a table\")\nassert(decoded.Hello == \"World\", \"Decoded payload Hello was not World\")\nassert(type(decoded.Inner) == \"table\", \"Decoded payload Inner was not a table\")\nassert(type(decoded.Inner.Array) == \"table\", \"Decoded payload Inner.Array was not a table\")\nassert(type(decoded.Inner.Array[1]) == \"number\", \"Decoded payload Inner.Array[1] was not a number\")\nassert(type(decoded.Inner.Array[2]) == \"number\", \"Decoded payload Inner.Array[2] was not a number\")\nassert(type(decoded.Inner.Array[3]) == \"number\", \"Decoded payload Inner.Array[3] was not a number\")\nassert(decoded.Foo == \"Bar\", \"Decoded payload Foo was not Bar\")\n"
  },
  {
    "path": "tests/serde/jsonc/encode.luau",
    "content": "local serde = require(\"@lune/serde\")\nlocal source = require(\"./source\")\n\n-- NOTE: Our json format (intentionally) does not round-trip or keep\n-- comments, so we will strip those for the purposes of this test\nlocal original = string.gsub(source.encoded, \"%s*//[^\\n]+\", \"\")\n\nlocal decoded = serde.decode(\"jsonc\", original)\nlocal encoded = serde.encode(\"jsonc\", decoded, true)\nassert(encoded == original, \"JSONC round-trip did not produce the same result\")\n"
  },
  {
    "path": "tests/serde/jsonc/source.luau",
    "content": "local JSON_STRING = [[{\n  \"Foo\": \"Bar\", // Baz ?!?\n  \"Hello\": \"World\",\n  \"Inner\": {\n    \"Array\": [\n      1,\n      3,\n      2\n    ]\n  }\n}]]\n\nreturn {\n\tencoded = JSON_STRING,\n}\n"
  },
  {
    "path": "tests/serde/test-files/loremipsum.txt",
    "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, sapien ut efficitur tempor, nulla dolor bibendum eros, in faucibus leo quam sit amet purus.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, sapien ut efficitur tempor, nulla dolor bibendum eros, in faucibus leo quam sit amet purus.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, sapien ut efficitur tempor, nulla dolor bibendum eros, in faucibus leo quam sit amet purus.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, sapien ut efficitur tempor, nulla dolor bibendum eros, in faucibus leo quam sit amet purus.\n"
  },
  {
    "path": "tests/serde/test-files/loremipsum.txt.z",
    "content": "x\r0\fE\u000e\u0010u1A\"KIX\u0002\u00068]\u0017;dZtTm`ȝPt\u0018c!WbE\u0003l\u001b`u\tp \u001c<O)&}J\u0018Z\u000f98+ť \u0003g\"G\u0018\u001a\u0015Wc\nn\u0007\u000e"
  },
  {
    "path": "tests/serde/test-files/uncompressed.csv",
    "content": "name,age,hobbies,friends\nJohn,30,\"reading, writing, coding, 👽\",\"Ξθής, Bob\"\nΞθής,28,\"painting, hiking, 🦛\",\"\"\nBob,35,\"fishing, gardening, 🌿\",\"\"\n"
  },
  {
    "path": "tests/serde/test-files/uncompressed.json",
    "content": "{\n    \"name\": \"John\",\n    \"age\": 30,\n    \"hobbies\": [\"reading\", \"writing\", \"coding\", \"👽\"],\n    \"friends\": [\n        {\n            \"name\": \"Ξθής\",\n            \"age\": 28,\n            \"hobbies\": [\"painting\", \"hiking\", \"🦛\"]\n        },\n        {\n            \"name\": \"Bob\",\n            \"age\": 35,\n            \"hobbies\": [\"fishing\", \"gardening\", \"🌿\"]\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/serde/test-files/uncompressed.yaml",
    "content": "- name: John\n  age: 30\n  hobbies:\n    - reading\n    - writing\n    - coding\n    - 👽\n  friends:\n    - name: Ξθής\n      age: 28\n      hobbies:\n        - painting\n        - hiking\n        - 🦛\n    - name: Bob\n      age: 35\n      hobbies:\n        - fishing\n        - gardening\n        - 🌿\n"
  },
  {
    "path": "tests/serde/toml/decode.luau",
    "content": "local serde = require(\"@lune/serde\")\nlocal source = require(\"./source\")\n\nlocal toml = serde.decode(\"toml\", source.encoded)\n\nassert(toml.package.name == \"my-cool-toml-package\")\nassert(toml.package.version == \"0.1.0\")\nassert(toml.values.epic == true)\n"
  },
  {
    "path": "tests/serde/toml/encode.luau",
    "content": "local serde = require(\"@lune/serde\")\nlocal source = require(\"./source\")\n\nlocal str = serde.encode(\"toml\", source.decoded)\nassert(str == source.encoded)\n"
  },
  {
    "path": "tests/serde/toml/source.luau",
    "content": "local TOML_LINES = {\n\t\"[package]\",\n\t'name = \"my-cool-toml-package\"',\n\t'version = \"0.1.0\"',\n\t\"\",\n\t\"[values]\",\n\t\"epic = true\",\n\t\"\",\n}\n\nlocal TOML_STRING = table.concat(TOML_LINES, \"\\n\")\n\nlocal TOML_TABLE = {\n\tpackage = {\n\t\tname = \"my-cool-toml-package\",\n\t\tversion = \"0.1.0\",\n\t},\n\tvalues = {\n\t\tepic = true,\n\t},\n}\n\nreturn {\n\tencoded = TOML_STRING,\n\tdecoded = TOML_TABLE,\n}\n"
  },
  {
    "path": "tests/stdio/color.luau",
    "content": "local stdio = require(\"@lune/stdio\")\n\nlocal COLORS_VALID =\n\t{ \"reset\", \"black\", \"red\", \"green\", \"yellow\", \"blue\", \"purple\", \"cyan\", \"white\" }\nlocal COLORS_INVALID = { \"\", \"gray\", \"grass\", \"red?\", \"super red\", \" \", \"none\" }\n\nfor _, color in COLORS_VALID do\n\tstdio.color(color :: any)\n\tstdio.color(\"reset\")\nend\n\nfor _, color in COLORS_INVALID do\n\tif pcall(stdio.color, color :: any) then\n\t\tstdio.color(\"reset\")\n\t\terror(string.format(\"Setting color should have failed for color '%s' but succeeded\", color))\n\tend\nend\n"
  },
  {
    "path": "tests/stdio/ewrite.luau",
    "content": "local process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\n\nstdio.ewrite(\"Hello, stderr!\")\n\nprocess.exit(0)\n"
  },
  {
    "path": "tests/stdio/format.luau",
    "content": "local process = require(\"@lune/process\")\nlocal regex = require(\"@lune/regex\")\nlocal roblox = require(\"@lune/roblox\")\nlocal stdio = require(\"@lune/stdio\")\n\nlocal function assertFormatting(errorMessage: string, formatted: string, expected: string)\n\tif formatted ~= expected then\n\t\tstdio.ewrite(string.format(\"%s\\nExpected: %s\\nGot: %s\", errorMessage, expected, formatted))\n\t\tprocess.exit(1)\n\tend\nend\n\nlocal function assertContains(errorMessage: string, haystack: string, needle: string)\n\tif string.find(haystack, needle) == nil then\n\t\tstdio.ewrite(string.format(\"%s\\nHaystack: %s\\nNeedle: %s\", errorMessage, needle, haystack))\n\t\tprocess.exit(1)\n\tend\nend\n\nassertFormatting(\n\t\"Should add a single space between arguments\",\n\tstdio.format(\"Hello\", \"world\", \"!\"),\n\t\"Hello world !\"\n)\n\nassertFormatting(\n\t\"Should format tables in a sorted manner\",\n\tstdio.format({ A = \"A\", B = \"B\", C = \"C\" }),\n\t'{\\n    A = \"A\",\\n    B = \"B\",\\n    C = \"C\",\\n}'\n)\n\nassertFormatting(\n\t\"Should format tables properly with single values\",\n\tstdio.format({ Hello = \"World\" }),\n\t'{\\n    Hello = \"World\",\\n}'\n)\n\nassertFormatting(\n\t\"Should format tables properly with multiple values\",\n\tstdio.format({ Hello = \"World\", Hello2 = \"Value\" }),\n\t'{\\n    Hello = \"World\",\\n    Hello2 = \"Value\",\\n}'\n)\n\nassertFormatting(\n\t\"Should simplify array-like tables and not format keys\",\n\tstdio.format({ \"Hello\", \"World\" }),\n\t'{\\n    \"Hello\",\\n    \"World\",\\n}'\n)\n\nassertFormatting(\n\t\"Should still format numeric keys for mixed tables\",\n\tstdio.format({ \"Hello\", \"World\", Hello = \"World\" }),\n\t'{\\n    [1] = \"Hello\",\\n    [2] = \"World\",\\n    Hello = \"World\",\\n}'\n)\n\nlocal userdatas = {\n\tFoo = newproxy(false),\n\tBar = regex.new(\"TEST\"),\n\tBaz = (roblox :: any).Vector3.new(1, 2, 3),\n}\n\nassertFormatting(\n\t\"Should format userdatas as generic 'userdata' if unknown\",\n\tstdio.format(userdatas.Foo),\n\t\"<userdata>\"\n)\n\nassertContains(\n\t\"Should format userdatas with their type if they have a __type metafield\",\n\tstdio.format(userdatas.Bar),\n\t\"Regex\"\n)\n\nassertContains(\n\t\"Should format userdatas with their type even if they have a __tostring metamethod\",\n\tstdio.format(userdatas.Baz),\n\t\"Vector3\"\n)\n\nassertContains(\n\t\"Should format userdatas with their tostringed value if they have a __tostring metamethod\",\n\tstdio.format(userdatas.Baz),\n\t\"1, 2, 3\"\n)\n\nassertFormatting(\n\t\"Should format userdatas properly in tables\",\n\tstdio.format(userdatas),\n\t\"{\\n    Bar = <Regex(TEST)>,\\n    Baz = <Vector3(1, 2, 3)>,\\n    Foo = <userdata>,\\n}\"\n)\n\nlocal nested = {\n\tOh = {\n\t\tNo = {\n\t\t\tTooMuch = {\n\t\t\t\tNesting = {\n\t\t\t\t\t\"Will not print\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n}\n\nassertContains(\n\t\"Should print 4 levels of nested tables before cutting off\",\n\tstdio.format(nested),\n\t\"Nesting = { ... }\"\n)\n\nlocal _, errorMessage = pcall(function()\n\tlocal function innerInnerFn()\n\t\tprocess.exec(\"PROGRAM_THAT_DOES_NOT_EXIST\")\n\tend\n\tlocal function innerFn()\n\t\tinnerInnerFn()\n\tend\n\tinnerFn()\nend)\n\nstdio.ewrite(typeof(errorMessage))\n\nassertContains(\"Should format errors similarly to userdata\", stdio.format(errorMessage), \"<LuaErr\")\nassertContains(\"Should format errors with stack begins\", stdio.format(errorMessage), \"Stack Begin\")\nassertContains(\"Should format errors with stack ends\", stdio.format(errorMessage), \"Stack End\")\n\n-- Check that calling stdio.format in a __tostring metamethod by print doesn't cause a deadlock\n\nlocal inner = {}\nsetmetatable(inner, {\n\t__tostring = function()\n\t\treturn stdio.format(5)\n\tend,\n})\n\nprint(inner)\n\nlocal outer = {}\nsetmetatable(outer, {\n\t__tostring = function()\n\t\treturn stdio.format(inner)\n\tend,\n})\n\nprint(outer)\n"
  },
  {
    "path": "tests/stdio/prompt.luau",
    "content": "local process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\n-- NOTE: This test is intentionally not included in the\n-- automated tests suite since it requires user input\n\nlocal passed = false\ntask.delay(0.2, function()\n\tif passed then\n\t\ttask.spawn(error, \"Prompt must not block other lua threads\")\n\t\tprocess.exit(1)\n\telse\n\t\t-- stdio.ewrite(\"Hello from concurrent task!\")\n\tend\nend)\n\n-- Text prompt\n\nlocal text = stdio.prompt(\"text\", \"Type some text\")\nassert(#text > 0, \"Did not get any text\")\nprint(`Got text '{text}'\\n`)\n\npassed = true\n\n-- Confirmation prompt\n\nlocal confirmed = stdio.prompt(\"confirm\", \"Please confirm\", true)\nassert(type(confirmed) == \"boolean\", \"Did not get a boolean as result\")\nprint(if confirmed then \"Confirmed\\n\" else \"Did not confirm\\n\")\n\n-- Selection prompt\n\nlocal option = stdio.prompt(\n\t\"select\",\n\t\"Please select the first option from the list\",\n\t{ \"one\", \"two\", \"three\", \"four\" }\n)\nassert(option == 1, \"Did not get the first option as result\")\nprint(`Got option #{option}\\n`)\n\n-- Multi-selection prompt\n\nlocal options = stdio.prompt(\n\t\"multiselect\",\n\t\"Please select options two and four\",\n\t{ \"one\", \"two\", \"three\", \"four\", \"five\" }\n)\nassert(\n\toptions ~= nil and table.find(options, 2) and table.find(options, 4),\n\t\"Did not get options 2 and 4 as result\"\n)\nprint(`Got option(s) {stdio.format(options)}\\n`)\n"
  },
  {
    "path": "tests/stdio/style.luau",
    "content": "local stdio = require(\"@lune/stdio\")\n\nlocal STYLES_VALID = { \"reset\", \"bold\", \"dim\" }\nlocal STYLES_INVALID = { \"\", \"*bold*\", \"dimm\", \"megabright\", \"cheerful\", \"sad\", \" \" }\n\nfor _, style in STYLES_VALID do\n\tstdio.style(style :: any)\n\tstdio.style(\"reset\")\nend\n\nfor _, style in STYLES_INVALID do\n\tif pcall(stdio.style, style :: any) then\n\t\tstdio.style(\"reset\")\n\t\terror(string.format(\"Setting style should have failed for style '%s' but succeeded\", style))\n\tend\nend\n"
  },
  {
    "path": "tests/stdio/write.luau",
    "content": "local stdio = require(\"@lune/stdio\")\n\nstdio.write(\"Hello, stdout!\")\n"
  },
  {
    "path": "tests/task/cancel.luau",
    "content": "local task = require(\"@lune/task\")\n\n-- Cancel should cancel any deferred or delayed threads\n\nlocal flag: boolean = false\nlocal thread = task.defer(function()\n\tflag = true\nend)\ntask.cancel(thread)\ntask.wait(0.1)\nassert(not flag, \"Cancel should handle deferred threads\")\n\nlocal flag2: boolean = false\nlocal thread2 = task.delay(0.1, function()\n\tflag2 = true\nend)\ntask.wait(0)\ntask.cancel(thread2)\ntask.wait(0.2)\nassert(not flag2, \"Cancel should handle delayed threads\")\n\n-- Cancellation should work with yields in spawned threads\n\nlocal flag3: number = 1\nlocal thread3 = task.spawn(function()\n\ttask.wait(0.1)\n\tflag3 = 2\n\ttask.wait(0.2)\n\tflag3 = 3\nend)\ntask.wait(0.2)\ntask.cancel(thread3)\ntask.wait(0.2)\nassert(flag3 == 2, \"Cancel should properly handle yielding threads\")\n"
  },
  {
    "path": "tests/task/defer.luau",
    "content": "local task = require(\"@lune/task\")\n\n-- Deferring a task should return the thread that can then be cancelled\n\nlocal thread = task.defer(function() end)\nassert(type(thread) == \"thread\", \"Defer should return the thread spawned\")\n\n-- Deferred functions should run after other threads\n\nlocal flag: boolean = false\ntask.defer(function()\n\tflag = true\nend)\nassert(not flag, \"Defer should not run instantly or block\")\ntask.wait(0.05)\nassert(flag, \"Defer should run\")\n\n-- Deferred functions should work with yielding\n\nlocal flag2: boolean = false\ntask.defer(function()\n\ttask.wait(0.05)\n\tflag2 = true\nend)\nassert(not flag2, \"Defer should work with yielding (1)\")\ntask.wait(0.1)\nassert(flag2, \"Defer should work with yielding (2)\")\n\n-- Deferred functions should run after other spawned threads\nlocal flag3: number = 1\ntask.defer(function()\n\tif flag3 == 2 then\n\t\tflag3 = 3\n\tend\nend)\ntask.spawn(function()\n\tif flag3 == 1 then\n\t\tflag3 = 2\n\tend\nend)\ntask.wait()\nassert(flag3 == 3, \"Defer should run after spawned threads\")\n\n-- Delay should be able to be nested\n\nlocal flag4: boolean = false\ntask.delay(0.05, function()\n\tlocal function nested3()\n\t\ttask.delay(0.05, function()\n\t\t\tflag4 = true\n\t\tend)\n\tend\n\tlocal function nested2()\n\t\ttask.delay(0.05, nested3)\n\tend\n\tlocal function nested1()\n\t\ttask.delay(0.05, nested2)\n\tend\n\tnested1()\nend)\ntask.wait(0.25)\nassert(flag4, \"Defer should work with nesting\")\n\n-- Varargs should get passed correctly\n\nlocal fcheck = require(\"./fcheck\")\n\nlocal function f(...: any)\n\tfcheck(1, \"string\", select(1, ...))\n\tfcheck(2, \"number\", select(2, ...))\n\tfcheck(3, \"function\", select(3, ...))\nend\n\ntask.defer(f, \"\", 1, f)\ntask.defer(f, \"inf\", math.huge, f)\ntask.defer(f, \"NaN\", 0 / 0, f)\n"
  },
  {
    "path": "tests/task/delay.luau",
    "content": "local task = require(\"@lune/task\")\n\n-- Delaying a task should return the thread that can then be cancelled\n\nlocal thread = task.delay(0, function() end)\nassert(type(thread) == \"thread\", \"Delay should return the thread spawned\")\n\n-- Delayed functions should never run right away\n\nlocal flag: boolean = false\ntask.delay(0, function()\n\tflag = true\nend)\nassert(not flag, \"Delay should not run instantly or block\")\ntask.wait(0.05)\nassert(flag, \"Delay should run after the wanted duration\")\n\n-- Delayed functions should work with yielding\n\nlocal flag2: boolean = false\ntask.delay(0.05, function()\n\tflag2 = true\n\ttask.wait(0.1)\n\tflag2 = false\nend)\ntask.wait(0.1)\nassert(flag, \"Delay should work with yielding (1)\")\ntask.wait(0.1)\nassert(not flag2, \"Delay should work with yielding (2)\")\n\n-- Defer should be able to be nested\n\nlocal flag4: boolean = false\ntask.defer(function()\n\tlocal function nested3()\n\t\ttask.defer(function()\n\t\t\ttask.wait(0.05)\n\t\t\tflag4 = true\n\t\tend)\n\tend\n\tlocal function nested2()\n\t\ttask.defer(function()\n\t\t\ttask.wait(0.05)\n\t\t\tnested3()\n\t\tend)\n\tend\n\tlocal function nested1()\n\t\ttask.defer(function()\n\t\t\ttask.wait(0.05)\n\t\t\tnested2()\n\t\tend)\n\tend\n\ttask.wait(0.05)\n\tnested1()\nend)\ntask.wait(0.25)\nassert(flag4, \"Defer should work with nesting\")\n\n-- Varargs should get passed correctly\n\nlocal fcheck = require(\"./fcheck\")\n\nlocal function f(...: any)\n\tfcheck(1, \"string\", select(1, ...))\n\tfcheck(2, \"number\", select(2, ...))\n\tfcheck(3, \"function\", select(3, ...))\nend\n\ntask.delay(0, f, \"\", 1, f)\ntask.delay(0, f, \"inf\", math.huge, f)\ntask.delay(0, f, \"NaN\", 0 / 0, f)\n"
  },
  {
    "path": "tests/task/fcheck.luau",
    "content": "local stdio = require(\"@lune/stdio\")\n\nreturn function(index: number, type: string, value: any)\n\tif typeof(value) ~= type then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Expected argument #%d to be of type %s, got %s\",\n\t\t\t\tindex,\n\t\t\t\ttype,\n\t\t\t\tstdio.format(value)\n\t\t\t)\n\t\t)\n\tend\nend\n"
  },
  {
    "path": "tests/task/spawn.luau",
    "content": "local task = require(\"@lune/task\")\n\n-- Spawning a task should return the thread that can then be cancelled\n\nlocal thread = task.spawn(function() end)\nassert(type(thread) == \"thread\", \"Spawn should return the thread spawned\")\n\n-- Spawned functions should run right away\n\nlocal flag: boolean = false\ntask.spawn(function()\n\tflag = true\nend)\nassert(flag, \"Spawn should run instantly\")\n\n-- Spawned functions should work with yielding\n\nlocal flag2: boolean = false\ntask.spawn(function()\n\ttask.wait(0.05)\n\tflag2 = true\nend)\nassert(not flag2, \"Spawn should work with yielding (1)\")\ntask.wait(0.1)\nassert(flag2, \"Spawn should work with yielding (2)\")\n\n-- Spawned functions should be able to run threads created with the coroutine global\n\nlocal flag3: boolean = false\nlocal thread2 = coroutine.create(function()\n\tflag3 = true\nend)\ntask.spawn(thread2)\nassert(flag3, \"Spawn should run threads made from coroutine.create\")\n\n-- Spawn should be able to be nested\n\nlocal flag4: boolean = false\ntask.spawn(function()\n\tlocal function nested3()\n\t\ttask.spawn(function()\n\t\t\ttask.wait(0.05)\n\t\t\tflag4 = true\n\t\tend)\n\tend\n\tlocal function nested2()\n\t\ttask.spawn(function()\n\t\t\ttask.wait(0.05)\n\t\t\tnested3()\n\t\tend)\n\tend\n\tlocal function nested1()\n\t\ttask.spawn(function()\n\t\t\ttask.wait(0.05)\n\t\t\tnested2()\n\t\tend)\n\tend\n\ttask.wait(0.05)\n\tnested1()\nend)\ntask.wait(0.25)\nassert(flag4, \"Spawn should work with nesting\")\n\n-- Varargs should get passed correctly\n\nlocal fcheck = require(\"./fcheck\")\n\nlocal function f(...: any)\n\tfcheck(1, \"string\", select(1, ...))\n\tfcheck(2, \"number\", select(2, ...))\n\tfcheck(3, \"function\", select(3, ...))\nend\n\ntask.spawn(f, \"\", 1, f)\ntask.spawn(f, \"inf\", math.huge, f)\ntask.spawn(f, \"NaN\", 0 / 0, f)\n"
  },
  {
    "path": "tests/task/wait.luau",
    "content": "local process = require(\"@lune/process\")\nlocal stdio = require(\"@lune/stdio\")\nlocal task = require(\"@lune/task\")\n\n-- NOTE: For now we don't test accuracy of waiting, the only thing\n-- we guarantee is that task.wait waits for _at least_ the amount\n-- of time given. Windows sleep is extremely inaccurate.\nlocal TEST_ACCURACY = false\n\nlocal EPSILON = if process.os == \"windows\" then 12 / 1_000 else 8 / 1_000\n\nlocal function test(expected: number)\n\tlocal start = os.clock()\n\tlocal returned = task.wait(expected)\n\tif typeof(returned) ~= \"number\" then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Expected task.wait to return a number, got %s %s\",\n\t\t\t\ttypeof(returned),\n\t\t\t\tstdio.format(returned)\n\t\t\t),\n\t\t\t2\n\t\t)\n\tend\n\tlocal elapsed = os.clock() - start\n\tif elapsed < expected then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Expected task.wait to yield for at least %.3f seconds, yielded for %.3f seconds\",\n\t\t\t\texpected,\n\t\t\t\telapsed\n\t\t\t)\n\t\t)\n\tend\n\tif not TEST_ACCURACY then\n\t\treturn\n\tend\n\tlocal difference = math.abs(elapsed - expected)\n\tif difference > EPSILON then\n\t\terror(\n\t\t\tstring.format(\n\t\t\t\t\"Elapsed time diverged too much from argument!\"\n\t\t\t\t\t.. \"\\nGot argument of %.3fms and elapsed time of %.3fms\"\n\t\t\t\t\t.. \"\\nGot maximum difference of %.3fms and real difference of %.3fms\",\n\t\t\t\texpected * 1_000,\n\t\t\t\telapsed * 1_000,\n\t\t\t\tEPSILON * 1_000,\n\t\t\t\tdifference * 1_000\n\t\t\t)\n\t\t)\n\tend\nend\n\nlocal function measure(duration: number)\n\tfor _ = 1, 5 do\n\t\ttest(duration)\n\tend\nend\n\n-- About 20ms is the shortest safe sleep time on Windows, but\n-- Linux and macOS can do down to about 10ms or less safely\nmeasure(if process.os == \"windows\" then 15 / 1_000 else 5 / 1_000)\n\nmeasure(1 / 60)\nmeasure(1 / 30)\nmeasure(1 / 20)\nmeasure(1 / 10)\n\n-- Wait should work in other threads\n\nlocal flag: boolean = false\ntask.spawn(function()\n\ttask.wait(0.1)\n\tflag = true\nend)\nassert(not flag, \"Wait failed while inside task-spawned thread (1)\")\ntask.wait(0.2)\nassert(flag, \"Wait failed while inside task-spawned thread (2)\")\n"
  }
]