commit 8078425b2de84bff2afd2764e2ee365cb39cf510 Author: Filip Tibell Date: Wed Jan 18 20:47:14 2023 -0500 Initial commit diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..02c62e2 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,23 @@ +name: Build + +on: + push: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + profile: minimal + + - name: Build + run: cargo build --locked --verbose diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..8f4d480 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,26 @@ +name: Lint + +on: + push: + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Rustfmt + run: cargo fmt -- --check + + - name: Clippy + run: cargo clippy diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..d9d5a2f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,117 @@ +name: Release + +on: + workflow_dispatch: + +jobs: + init: + name: Init + runs-on: ubuntu-latest + outputs: + manifest_name: ${{ steps.get_name.outputs.value }} + manifest_version: ${{ steps.get_version.outputs.value }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get name from manifest + uses: SebRollen/toml-action@0ad94c4a52c402aaa76e14e8a43551163b6cedf9 + id: get_name + with: + file: Cargo.toml + field: package.name + + - name: Get version from manifest + uses: SebRollen/toml-action@0ad94c4a52c402aaa76e14e8a43551163b6cedf9 + id: get_version + with: + file: Cargo.toml + field: package.version + + create-release: + needs: ["init"] + name: Create release + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ needs.init.outputs.manifest_version }} + release_name: ${{ needs.init.outputs.manifest_version }} + draft: true + + release: + needs: ["init", "create-release"] + strategy: + matrix: + include: + - name: Windows x86_64 + runner-os: windows-latest + artifact-name: ${{ needs.init.outputs.manifest_name }}-${{ needs.init.outputs.manifest_version }}-windows-x86_64 + cargo-target: x86_64-pc-windows-msvc + - name: Linux x86_64 + runner-os: ubuntu-latest + artifact-name: ${{ needs.init.outputs.manifest_name }}-${{ needs.init.outputs.manifest_version }}-linux-x86_64 + cargo-target: x86_64-unknown-linux-gnu + - name: macOS x86_64 + runner-os: macos-latest + artifact-name: ${{ needs.init.outputs.manifest_name }}-${{ needs.init.outputs.manifest_version }}-macos-x86_64 + cargo-target: x86_64-apple-darwin + + name: Build - ${{ matrix.name }} + runs-on: ${{ matrix.runner-os }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.cargo-target }} + override: true + profile: minimal + + - name: Build binary + run: cargo build --locked --release --all-features --target ${{ matrix.cargo-target }} + env: + CARGO_TARGET_DIR: output + OPENSSL_STATIC: 1 + + - name: Create binary archive + shell: bash + run: | + mkdir -p staging + if [ "${{ matrix.runner-os }}" = "windows-latest" ]; then + cp "output/${{ matrix.cargo-target }}/release/${{ needs.init.outputs.manifest_name }}.exe" staging/ + cd staging + 7z a ../release.zip * + else + cp "output/${{ matrix.cargo-target }}/release/${{ needs.init.outputs.manifest_name }}" staging/ + cd staging + zip ../release.zip * + fi + + - name: Upload binary artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.artifact-name }} + path: release.zip + + - name: Upload binary to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: release.zip + asset_name: ${{ matrix.artifact-name }}.zip + asset_content_type: application/octet-stream diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..943cbbe --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,23 @@ +name: Test + +on: + push: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + profile: minimal + + - name: Test + run: cargo test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2449f0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target + +.DS_Store +*/.DS_Store diff --git a/.luaurc b/.luaurc new file mode 100644 index 0000000..14268d5 --- /dev/null +++ b/.luaurc @@ -0,0 +1,7 @@ +{ + "languageMode": "strict", + "lint": { "*": true }, + "lintErrors": false, + "typeErrors": true, + "globals": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5a0cd53 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "luau-lsp.types.roblox": false, + "luau-lsp.types.definitionFiles": ["luneTypes.d.luau"], + "luau-lsp.sourcemap.enabled": false, + "editor.formatOnSave": true, + "stylua.searchParentDirectories": true, + "prettier.tabWidth": 2, + "[luau][lua]": { + "editor.defaultFormatter": "JohnnyMorganz.stylua" + }, + "[json][jsonc][markdown][yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + } +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b6e08c2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,661 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "erased-serde" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" +dependencies = [ + "serde", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "luau0-src" +version = "0.5.1+luau558" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0869ba31f9b152c28bb604ecee6deaa2103a97365bb219781ac137bd487626" +dependencies = [ + "cc", +] + +[[package]] +name = "lune" +version = "0.0.1" +dependencies = [ + "clap", + "mlua", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "mlua" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee2ad7a9aa69056b148d9d590344bc155d3ce0d2200e3b2838f7034f6ba33c1" +dependencies = [ + "bstr", + "cc", + "erased-serde", + "futures-core", + "futures-task", + "futures-util", + "luau0-src", + "num-traits", + "once_cell", + "pkg-config", + "rustc-hash", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "tokio" +version = "1.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..667a316 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lune" +version = "0.0.1" +edition = "2021" + +[dependencies] +clap = { version = "4.1.1", features = ["derive"] } +mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] } +tokio = { version = "1.24.2", features = ["full"] } +serde = { version = "1.0.152", features = ["derive"] } +serde_json = { version = "1.0.91" } diff --git a/aftman.toml b/aftman.toml new file mode 100644 index 0000000..4644129 --- /dev/null +++ b/aftman.toml @@ -0,0 +1,4 @@ +[tools] +luau-lsp = "JohnnyMorganz/luau-lsp@1.15.0" +selene = "Kampfkarren/selene@0.24.0" +stylua = "JohnnyMorganz/StyLua@0.16.0" diff --git a/lune.yml b/lune.yml new file mode 100644 index 0000000..1bc8078 --- /dev/null +++ b/lune.yml @@ -0,0 +1,55 @@ +--- +globals: + # FS (filesystem) + fs.readFile: + args: + - type: string + fs.readDir: + args: + - type: string + fs.writeFile: + args: + - type: string + - type: string + fs.writeDir: + args: + - type: string + fs.removeFile: + args: + - type: string + fs.removeDir: + args: + - type: string + fs.isFile: + args: + - type: string + fs.isDir: + args: + - type: string + # JSON + json.encode: + args: + - type: any + - required: false + type: boolean + json.decode: + args: + - type: string + # Process + process.getEnvVars: + process.getEnvVar: + args: + - type: string + process.setEnvVar: + args: + - type: string + - type: string + process.exit: + args: + - required: false + type: number + process.spawn: + args: + - type: string + - required: false + type: table diff --git a/luneTypes.d.luau b/luneTypes.d.luau new file mode 100644 index 0000000..04abf5d --- /dev/null +++ b/luneTypes.d.luau @@ -0,0 +1,28 @@ +declare fs: { + readFile: (path: string) -> string, + readDir: (path: string) -> { string }, + writeFile: (path: string, contents: string) -> (), + writeDir: (path: string) -> (), + removeFile: (path: string) -> (), + removeDir: (path: string) -> (), + isFile: (path: string) -> boolean, + isDir: (path: string) -> boolean, +} + +declare json: { + encode: (value: any, pretty: boolean?) -> string, + decode: (encoded: string) -> any, +} + +declare process: { + getEnvVars: () -> { string }, + getEnvVar: (key: string) -> string?, + setEnvVar: (key: string, value: string) -> (), + exit: (code: number?) -> (), + spawn: (program: string, params: { string }?) -> { + ok: boolean, + code: number, + stdout: string, + stderr: string, + }, +} diff --git a/scripts/hello_lune.luau b/scripts/hello_lune.luau new file mode 100644 index 0000000..5a64e36 --- /dev/null +++ b/scripts/hello_lune.luau @@ -0,0 +1,71 @@ +print("Hello, lune! 🌙") + +-- Use a function from another module + +local module = require("scripts/module") +module.hello() + +-- Read and print out directories & files in +-- the current directory, with fancy icons + +print("\nReading current dir...") +local entries = fs.readDir(".") + +-- NOTE: We have to do this outside of the sort function +-- to avoid yielding across the metamethod boundary, any +-- calls to fs functions may yield for any reason +local dirs = {} +for _, entry in entries do + dirs[entry] = fs.isDir(entry) +end + +-- Sort directories first, then alphabetically +table.sort(entries, function(entry0, entry1) + if dirs[entry0] ~= dirs[entry1] then + return dirs[entry0] + end + return entry0 < entry1 +end) + +assert(table.find(entries, "Cargo.toml") ~= nil, "Missing Cargo.toml") +assert(table.find(entries, "Cargo.lock") ~= nil, "Missing Cargo.lock") + +for _, entry in entries do + if fs.isDir(entry) then + print("📁 " .. entry) + else + print("📄 " .. entry) + end +end + +-- Read and print out environment variables + +print("\nReading current environment...") +local vars = process.getEnvVars() +table.sort(vars) + +assert(table.find(vars, "PATH") ~= nil, "Missing PATH") +assert(table.find(vars, "PWD") ~= nil, "Missing PWD") + +for _, key in vars do + local value = process.getEnvVar(key) + local box = if value and value ~= "" then "✅" else "❌" + print(string.format("[%s] %s", box, key)) +end + +-- Call out to another program + +print("\nSending 4 pings to google...") +local result = process.spawn("ping", { + "google.com", + "-c 4", +}) + +if result.ok then + print(result.stdout) +else + print(result.stderr) + process.exit(result.code) +end + +print("\nGoodbye, lune! 🌙") diff --git a/scripts/module.luau b/scripts/module.luau new file mode 100644 index 0000000..6c3faf0 --- /dev/null +++ b/scripts/module.luau @@ -0,0 +1,7 @@ +local module = {} + +function module.hello() + print("\nHello from a module! 🧩") +end + +return module diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..4ee41a8 --- /dev/null +++ b/selene.toml @@ -0,0 +1,6 @@ +std = "luau+lune" + +exclude = ["luneTypes.d.luau"] + +[lints] +high_cyclomatic_complexity = "warn" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..98cc2d4 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,84 @@ +use std::{fs::read_to_string, path::PathBuf}; + +use clap::Parser; +use mlua::{Lua, Result}; + +use crate::lune::{fs::LuneFs, json::LuneJson, process::LuneProcess}; + +/// Lune CLI +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// Path to the file to run + path: String, +} + +impl Cli { + #[allow(dead_code)] + pub fn from_path>(path: S) -> Self { + Self { + path: path.as_ref().to_owned(), + } + } + + fn parse_file_path(&self) -> Result { + let parsed_file_path = match &self.path { + path if path.ends_with(".luau") || path.ends_with(".lua") => Some(PathBuf::from(path)), + path => { + let temp_path = PathBuf::from(path); + if temp_path.extension().is_none() { + let as_luau_path = temp_path.with_extension("luau"); + let as_lua_path = temp_path.with_extension("lua"); + if as_luau_path.exists() { + Some(as_luau_path) + } else if as_lua_path.exists() { + Some(as_lua_path) + } else { + let as_luau_in_scripts_folder = PathBuf::from("scripts").join(as_luau_path); + let as_lua_in_scripts_folder = PathBuf::from("scripts").join(as_lua_path); + if as_luau_in_scripts_folder.exists() { + Some(as_luau_in_scripts_folder) + } else if as_lua_in_scripts_folder.exists() { + Some(as_lua_in_scripts_folder) + } else { + None + } + } + } else { + None + } + } + }; + if let Some(file_path) = parsed_file_path { + if file_path.exists() { + Ok(file_path) + } else { + Err(mlua::Error::RuntimeError(format!( + "File does not exist at path: '{}'", + self.path + ))) + } + } else { + Err(mlua::Error::RuntimeError(format!( + "Invalid file path: '{}'", + self.path + ))) + } + } + + pub async fn run(self) -> Result<()> { + // Parse and read the wanted file + let file_path = self.parse_file_path()?; + let file_contents = read_to_string(file_path)?; + // Create a new lua state and add in all lune globals + let lua = Lua::new(); + let globals = lua.globals(); + globals.set("fs", LuneFs::new())?; + globals.set("process", LuneProcess::new())?; + globals.set("json", LuneJson::new())?; + lua.sandbox(true)?; + // Run the file + lua.load(&file_contents).exec_async().await?; + Ok(()) + } +} diff --git a/src/lune/fs.rs b/src/lune/fs.rs new file mode 100644 index 0000000..608891f --- /dev/null +++ b/src/lune/fs.rs @@ -0,0 +1,109 @@ +use std::path::{PathBuf, MAIN_SEPARATOR}; + +use mlua::{Lua, Result, UserData, UserDataMethods}; +use tokio::fs; + +pub struct LuneFs(); + +impl LuneFs { + pub fn new() -> Self { + Self() + } +} + +impl UserData for LuneFs { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_async_function("readFile", fs_read_file); + methods.add_async_function("readDir", fs_read_dir); + methods.add_async_function("writeFile", fs_write_file); + methods.add_async_function("writeDir", fs_write_dir); + methods.add_async_function("removeFile", fs_remove_file); + methods.add_async_function("removeDir", fs_remove_dir); + methods.add_async_function("isFile", fs_is_file); + methods.add_async_function("isDir", fs_is_dir); + } +} + +async fn fs_read_file(_: &Lua, path: String) -> Result { + Ok(fs::read_to_string(&path) + .await + .map_err(mlua::Error::external)?) +} + +async fn fs_read_dir(_: &Lua, path: String) -> Result> { + let mut dir_strings = Vec::new(); + let mut dir = fs::read_dir(&path).await.map_err(mlua::Error::external)?; + while let Some(dir_entry) = dir.next_entry().await.map_err(mlua::Error::external)? { + if let Some(dir_path_str) = dir_entry.path().to_str() { + dir_strings.push(dir_path_str.to_owned()); + } else { + return Err(mlua::Error::RuntimeError(format!( + "File path could not be converted into a string: '{}'", + dir_entry.path().display() + ))); + } + } + let mut dir_string_prefix = path; + if !dir_string_prefix.ends_with(MAIN_SEPARATOR) { + dir_string_prefix.push(MAIN_SEPARATOR); + } + let dir_strings_no_prefix = dir_strings + .iter() + .map(|inner_path| { + inner_path + .trim() + .strip_prefix(&dir_string_prefix) + .unwrap() + .to_owned() + }) + .collect::>(); + Ok(dir_strings_no_prefix) +} + +async fn fs_write_file(_: &Lua, (path, contents): (String, String)) -> Result<()> { + Ok(fs::write(&path, &contents) + .await + .map_err(mlua::Error::external)?) +} + +async fn fs_write_dir(_: &Lua, path: String) -> Result<()> { + Ok(fs::create_dir_all(&path) + .await + .map_err(mlua::Error::external)?) +} + +async fn fs_remove_file(_: &Lua, path: String) -> Result<()> { + Ok(fs::remove_file(&path) + .await + .map_err(mlua::Error::external)?) +} + +async fn fs_remove_dir(_: &Lua, path: String) -> Result<()> { + Ok(fs::remove_dir_all(&path) + .await + .map_err(mlua::Error::external)?) +} + +async fn fs_is_file(_: &Lua, path: String) -> Result { + let path = PathBuf::from(path); + if path.exists() { + Ok(fs::metadata(path) + .await + .map_err(mlua::Error::external)? + .is_file()) + } else { + Ok(false) + } +} + +async fn fs_is_dir(_: &Lua, path: String) -> Result { + let path = PathBuf::from(path); + if path.exists() { + Ok(fs::metadata(path) + .await + .map_err(mlua::Error::external)? + .is_dir()) + } else { + Ok(false) + } +} diff --git a/src/lune/json.rs b/src/lune/json.rs new file mode 100644 index 0000000..488475e --- /dev/null +++ b/src/lune/json.rs @@ -0,0 +1,28 @@ +use mlua::{Error, Lua, LuaSerdeExt, Result, UserData, UserDataMethods, Value}; + +pub struct LuneJson(); + +impl LuneJson { + pub fn new() -> Self { + Self() + } +} + +impl UserData for LuneJson { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("encode", json_encode); + methods.add_function("decode", json_decode); + } +} + +fn json_encode(_: &Lua, (val, pretty): (Value, Option)) -> Result { + if let Some(true) = pretty { + Ok(serde_json::to_string_pretty(&val).map_err(Error::external)?) + } else { + Ok(serde_json::to_string(&val).map_err(Error::external)?) + } +} + +fn json_decode(lua: &Lua, json: String) -> Result { + Ok(lua.to_value(&json)?) +} diff --git a/src/lune/mod.rs b/src/lune/mod.rs new file mode 100644 index 0000000..e04e1b9 --- /dev/null +++ b/src/lune/mod.rs @@ -0,0 +1,3 @@ +pub mod fs; +pub mod json; +pub mod process; diff --git a/src/lune/process.rs b/src/lune/process.rs new file mode 100644 index 0000000..9b3c345 --- /dev/null +++ b/src/lune/process.rs @@ -0,0 +1,93 @@ +use std::{ + env::{self, VarError}, + process::{exit, Stdio}, +}; + +use mlua::{Error, Lua, Result, Table, UserData, UserDataMethods, Value}; +use tokio::process::Command; + +pub struct LuneProcess(); + +impl LuneProcess { + pub fn new() -> Self { + Self() + } +} + +impl UserData for LuneProcess { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("getEnvVars", process_get_env_vars); + methods.add_function("getEnvVar", process_get_env_var); + methods.add_function("setEnvVar", process_set_env_var); + methods.add_function("exit", process_exit); + methods.add_async_function("spawn", process_spawn); + } +} + +fn process_get_env_vars(_: &Lua, _: ()) -> Result> { + let mut vars = Vec::new(); + for (key, _) in env::vars() { + vars.push(key); + } + Ok(vars) +} + +fn process_get_env_var(lua: &Lua, key: String) -> Result { + match env::var(&key) { + Ok(value) => Ok(Value::String(lua.create_string(&value)?)), + Err(VarError::NotPresent) => Ok(Value::Nil), + Err(VarError::NotUnicode(_)) => Err(Error::external(format!( + "The env var '{}' contains invalid utf8", + &key + ))), + } +} + +fn process_set_env_var(_: &Lua, (key, value): (String, String)) -> Result<()> { + Ok(env::set_var(&key, &value)) +} + +fn process_exit(_: &Lua, exit_code: Option) -> Result<()> { + if let Some(code) = exit_code { + exit(code); + } else { + exit(0) + } +} + +async fn process_spawn(lua: &Lua, (program, args): (String, Option>)) -> Result { + // Create and spawn a child process, and + // wait for it to terminate with output + let mut cmd = Command::new(program); + if let Some(args) = args { + cmd.args(args); + } + let child = cmd + .current_dir(env::current_dir().map_err(mlua::Error::external)?) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(mlua::Error::external)?; + let output = child + .wait_with_output() + .await + .map_err(mlua::Error::external)?; + // NOTE: Exit code defaults to 1 if it did not exist and if there + // is any stderr, will otherwise default to 0 if it did not exist + let code = output + .status + .code() + .unwrap_or_else(|| match output.stderr.is_empty() { + true => 0, + false => 1, + }); + // Construct and return a readonly lua table with results + let table = lua.create_table()?; + table.raw_set("ok", code == 0)?; + table.raw_set("code", code)?; + table.raw_set("stdout", lua.create_string(&output.stdout)?)?; + table.raw_set("stderr", lua.create_string(&output.stderr)?)?; + table.set_readonly(true); + Ok(table) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cc8fd7e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,30 @@ +use clap::Parser; +use mlua::Result; + +mod cli; +mod lune; +mod utils; + +use cli::Cli; +use utils::pretty_print_luau_error; + +#[tokio::main] +async fn main() -> Result<()> { + let cli = Cli::parse(); + match cli.run().await { + Ok(_) => Ok(()), + Err(e) => { + eprintln!(); + eprintln!("[ERROR]"); + pretty_print_luau_error(&e); + std::process::exit(1); + } + } +} + +#[tokio::test] +async fn hello_lune() { + let cli = Cli::from_path("hello_lune"); + let result = cli.run().await; + assert!(result.is_ok()); +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..2e31705 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,33 @@ +pub fn pretty_print_luau_error(e: &mlua::Error) { + match e { + mlua::Error::RuntimeError(e) => { + eprintln!("{}", e); + } + mlua::Error::CallbackError { cause, traceback } => { + pretty_print_luau_error(cause.as_ref()); + eprintln!("Traceback:"); + eprintln!("{}", traceback.strip_prefix("stack traceback:\n").unwrap()); + } + mlua::Error::ToLuaConversionError { from, to, message } => { + let msg = message + .clone() + .map(|m| format!("\nDetails:\n\t{m}")) + .unwrap_or_else(|| "".to_string()); + eprintln!( + "Failed to convert Rust type '{}' into Luau type '{}'!{}", + from, to, msg + ) + } + mlua::Error::FromLuaConversionError { from, to, message } => { + let msg = message + .clone() + .map(|m| format!("\nDetails:\n\t{m}")) + .unwrap_or_else(|| "".to_string()); + eprintln!( + "Failed to convert Luau type '{}' into Rust type '{}'!{}", + from, to, msg + ) + } + e => eprintln!("{}", e.to_string()), + } +} diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..ef2b3cf --- /dev/null +++ b/stylua.toml @@ -0,0 +1,10 @@ +column_width = 100 + +line_endings = "Unix" + +indent_type = "Tabs" +indent_width = 4 + +quote_style = "AutoPreferDouble" + +call_parentheses = "Always"