From 57f730e78861fcf837b360a2bea7922ea688f2fc Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 22 Jul 2023 14:44:28 +0200 Subject: [PATCH] Remove old docs, move typedefs dir --- .gitbook.yaml | 5 - .gitignore | 1 - .justfile | 19 -- .vscode/settings.json | 2 +- CHANGELOG.md | 4 +- Cargo.lock | 121 +------- Cargo.toml | 2 - docs/SUMMARY.md | 24 -- docs/modules/remodel.luau | 283 ------------------- docs/pages/home/Editor-Setup.md | 22 -- docs/pages/home/Installation.md | 36 --- docs/pages/home/Running-Scripts.md | 51 ---- docs/pages/home/Writing-Scripts.md | 326 ---------------------- docs/pages/roblox/Api-Status.md | 76 ------ docs/pages/roblox/Examples.md | 78 ------ docs/pages/roblox/Introduction.md | 10 - src/cli/{gen/typedef_files.rs => gen.rs} | 28 +- src/cli/gen/definitions/builder.rs | 129 --------- src/cli/gen/definitions/item.rs | 164 ----------- src/cli/gen/definitions/kind.rs | 64 ----- src/cli/gen/definitions/mod.rs | 14 - src/cli/gen/definitions/moonwave.rs | 85 ------ src/cli/gen/definitions/parser.rs | 159 ----------- src/cli/gen/definitions/tag.rs | 102 ------- src/cli/gen/definitions/tree.rs | 53 ---- src/cli/gen/definitions/type_info_ext.rs | 333 ----------------------- src/cli/gen/gitbook_dir.rs | 240 ---------------- src/cli/gen/mod.rs | 54 ---- src/cli/{setup/mod.rs => setup.rs} | 2 +- {docs/typedefs => types}/FS.luau | 0 {docs/typedefs => types}/Net.luau | 0 {docs/typedefs => types}/Process.luau | 0 {docs/typedefs => types}/Roblox.luau | 0 {docs/typedefs => types}/Serde.luau | 0 {docs/typedefs => types}/Stdio.luau | 0 {docs/typedefs => types}/Task.luau | 0 36 files changed, 31 insertions(+), 2456 deletions(-) delete mode 100644 .gitbook.yaml delete mode 100644 docs/SUMMARY.md delete mode 100644 docs/modules/remodel.luau delete mode 100644 docs/pages/home/Editor-Setup.md delete mode 100644 docs/pages/home/Installation.md delete mode 100644 docs/pages/home/Running-Scripts.md delete mode 100644 docs/pages/home/Writing-Scripts.md delete mode 100644 docs/pages/roblox/Api-Status.md delete mode 100644 docs/pages/roblox/Examples.md delete mode 100644 docs/pages/roblox/Introduction.md rename src/cli/{gen/typedef_files.rs => gen.rs} (61%) delete mode 100644 src/cli/gen/definitions/builder.rs delete mode 100644 src/cli/gen/definitions/item.rs delete mode 100644 src/cli/gen/definitions/kind.rs delete mode 100644 src/cli/gen/definitions/mod.rs delete mode 100644 src/cli/gen/definitions/moonwave.rs delete mode 100644 src/cli/gen/definitions/parser.rs delete mode 100644 src/cli/gen/definitions/tag.rs delete mode 100644 src/cli/gen/definitions/tree.rs delete mode 100644 src/cli/gen/definitions/type_info_ext.rs delete mode 100644 src/cli/gen/gitbook_dir.rs delete mode 100644 src/cli/gen/mod.rs rename src/cli/{setup/mod.rs => setup.rs} (98%) rename {docs/typedefs => types}/FS.luau (100%) rename {docs/typedefs => types}/Net.luau (100%) rename {docs/typedefs => types}/Process.luau (100%) rename {docs/typedefs => types}/Roblox.luau (100%) rename {docs/typedefs => types}/Serde.luau (100%) rename {docs/typedefs => types}/Stdio.luau (100%) rename {docs/typedefs => types}/Task.luau (100%) diff --git a/.gitbook.yaml b/.gitbook.yaml deleted file mode 100644 index 3ff5e87..0000000 --- a/.gitbook.yaml +++ /dev/null @@ -1,5 +0,0 @@ -root: ./docs - -structure: - readme: ./README.md - summary: ./SUMMARY.md diff --git a/.gitignore b/.gitignore index 89c4bf0..a05cc5f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ /bin /target -/gitbook # Autogenerated files diff --git a/.justfile b/.justfile index a5a924a..04e1760 100644 --- a/.justfile +++ b/.justfile @@ -9,22 +9,3 @@ run-file FILE_NAME: # Run tests for the Lune library test: cargo test --lib -- --test-threads 1 - -# Generate gitbook directory -generate-gitbook: - rm -rf ./gitbook - - mkdir gitbook - mkdir gitbook/docs - - cp -R docs gitbook - cp README.md gitbook/docs/README.md - cp .gitbook.yaml gitbook/.gitbook.yaml - - rm -rf gitbook/docs/typedefs - - cargo run -- --generate-gitbook-dir - -# Publish gitbook directory to gitbook branch -publish-gitbook: generate-gitbook - npx push-dir --dir=gitbook --branch=gitbook diff --git a/.vscode/settings.json b/.vscode/settings.json index a6cfda4..e19a802 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ "luau-lsp.types.roblox": false, "luau-lsp.require.mode": "relativeToFile", "luau-lsp.require.directoryAliases": { - "@lune/": "./docs/typedefs/" + "@lune/": "~/.lune/.typedefs/0.7.4/" }, // Luau - ignore type defs file in docs dir and dev scripts we use "luau-lsp.ignoreGlobs": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e2fdda..85f2304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -267,7 +267,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for instance tags & `CollectionService` in the `roblox` built-in.
- Currently implemented methods are listed on the [docs site](https://lune.gitbook.io/lune/roblox/api-status). + Currently implemented methods are listed on the [docs site](https://lune-org.github.io/docs/roblox/4-api-status). ### Fixed @@ -305,7 +305,7 @@ This release adds some new features and fixes for the `roblox` built-in. - Added a `roblox` built-in 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.
- There are just too many new APIs to list in this changelog, so head over to the [docs sit](https://lune.gitbook.io/lune/roblox/intro) to learn more! + 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! - Added a `serde` built-in diff --git a/Cargo.lock b/Cargo.lock index e96c2d4..aa7a236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,12 +198,6 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bitflags" version = "1.3.2" @@ -287,12 +281,6 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" -[[package]] -name = "bytecount" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" - [[package]] name = "byteorder" version = "0.5.3" @@ -416,12 +404,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cookie" version = "0.15.2" @@ -475,19 +457,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] - [[package]] name = "dialoguer" version = "0.10.4" @@ -672,34 +641,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "full_moon" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9a9bf5e42aec08f4b59be1438d66b01ab0a0f51dca309626e219697b60871c" -dependencies = [ - "bytecount", - "cfg-if", - "derive_more", - "full_moon_derive", - "logos", - "paste", - "serde", - "smol_str", -] - -[[package]] -name = "full_moon_derive" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b4bd12ce56927d1dc5478d21528ea8c4b93ca85ff8f8043b6a5351a2a3c6f7" -dependencies = [ - "indexmap 1.9.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "futures-channel" version = "0.3.28" @@ -1106,29 +1047,6 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -[[package]] -name = "logos" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "syn 1.0.109", -] - [[package]] name = "luau0-src" version = "0.5.11+luau583" @@ -1151,7 +1069,6 @@ dependencies = [ "directories", "dunce", "env_logger 0.10.0", - "full_moon", "futures-util", "glam", "hyper", @@ -1647,7 +1564,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.4", + "regex-syntax", ] [[package]] @@ -1658,15 +1575,9 @@ checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.7.4" @@ -1779,16 +1690,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.18", + "semver", ] [[package]] @@ -1896,12 +1798,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - [[package]] name = "semver-parser" version = "0.7.0" @@ -2040,15 +1936,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" -[[package]] -name = "smol_str" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" -dependencies = [ - "serde", -] - [[package]] name = "socket2" version = "0.4.9" @@ -2087,7 +1974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", - "rustc_version 0.2.3", + "rustc_version", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", diff --git a/Cargo.toml b/Cargo.toml index 6017323..b6c5885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ cli = [ "dep:env_logger", "dep:itertools", "dep:clap", - "dep:full_moon", "dep:include_dir", "dep:regex", ] @@ -111,7 +110,6 @@ env_logger = { optional = true, version = "0.10" } itertools = { optional = true, version = "0.10" } clap = { optional = true, version = "4.1", features = ["derive"] } -full_moon = { optional = true, version = "0.18", features = ["roblox"] } include_dir = { optional = true, version = "0.7.3", features = ["glob"] } regex = { optional = true, version = "1.7", default-features = false, features = [ "std", diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md deleted file mode 100644 index 30680d0..0000000 --- a/docs/SUMMARY.md +++ /dev/null @@ -1,24 +0,0 @@ -# Summary - -## Home - -- [Installation](pages/home/Installation.md) -- [Writing Scripts](pages/home/Writing-Scripts.md) -- [Running Scripts](pages/home/Running-Scripts.md) -- [Editor Setup](pages/home/Editor-Setup.md) - -## Roblox - -- [Introduction](pages/roblox/Introduction.md) -- [Examples](pages/roblox/Examples.md) -- [API Status](pages/roblox/Api-Status.md) - -## API Reference - -- [fs](pages/api/fs.md) -- [net](pages/api/net.md) -- [process](pages/api/process.md) -- [roblox](pages/api/roblox.md) -- [serde](pages/api/serde.md) -- [stdio](pages/api/stdio.md) -- [task](pages/api/task.md) diff --git a/docs/modules/remodel.luau b/docs/modules/remodel.luau deleted file mode 100644 index 81830d9..0000000 --- a/docs/modules/remodel.luau +++ /dev/null @@ -1,283 +0,0 @@ ---!strict - -local fs = require("@lune/fs") -local net = require("@lune/net") -local serde = require("@lune/serde") -local process = require("@lune/process") -local roblox = require("@lune/roblox") - -export type LuneDataModel = roblox.DataModel -export type LuneInstance = roblox.Instance - -local function getAuthCookieWithFallbacks() - local cookie = roblox.getAuthCookie() - if cookie then - return cookie - end - - local cookieFromEnv = process.env.REMODEL_AUTH - if cookieFromEnv and #cookieFromEnv > 0 then - return `.ROBLOSECURITY={cookieFromEnv}` - end - - for index, arg in process.args do - if arg == "--auth" then - local cookieFromArgs = process.args[index + 1] - if cookieFromArgs and #cookieFromArgs > 0 then - return `.ROBLOSECURITY={cookieFromArgs}` - end - break - end - end - - error([[ - Failed to find ROBLOSECURITY cookie for authentication! - Make sure you have logged into studio, or set the ROBLOSECURITY environment variable. - ]]) -end - -local function downloadAssetId(assetId: number) - -- 1. Try to find the auth cookie for the current user - local cookie = getAuthCookieWithFallbacks() - - -- 2. Send a request to the asset delivery API, - -- which will respond with cdn download link(s) - local assetApiResponse = net.request({ - url = `https://assetdelivery.roblox.com/v2/assetId/{assetId}`, - headers = { - Accept = "application/json", - Cookie = cookie, - }, - }) - if not assetApiResponse.ok then - error( - string.format( - "Failed to fetch asset download link for asset id %s!\n%s (%s)\n%s", - tostring(assetId), - tostring(assetApiResponse.statusCode), - tostring(assetApiResponse.statusMessage), - tostring(assetApiResponse.body) - ) - ) - end - - -- 3. Make sure we got a valid response body - local assetApiBody = serde.decode("json", assetApiResponse.body) - if type(assetApiBody) ~= "table" then - error( - string.format( - "Asset delivery API returned an invalid response body!\n%s", - assetApiResponse.body - ) - ) - elseif type(assetApiBody.locations) ~= "table" then - error( - string.format( - "Asset delivery API returned an invalid response body!\n%s", - assetApiResponse.body - ) - ) - end - - -- 4. Grab the first asset download location - we only - -- requested one in our query, so this will be correct - local firstLocation = assetApiBody.locations[1] - if type(firstLocation) ~= "table" then - error( - string.format( - "Asset delivery API returned no download locations!\n%s", - assetApiResponse.body - ) - ) - elseif type(firstLocation.location) ~= "string" then - error( - string.format( - "Asset delivery API returned no valid download locations!\n%s", - assetApiResponse.body - ) - ) - end - - -- 5. Fetch the place contents from the cdn - local cdnResponse = net.request({ - url = firstLocation.location, - headers = { - Cookie = cookie, - }, - }) - if not cdnResponse.ok then - error( - string.format( - "Failed to download asset with id %s from the Roblox cdn!\n%s (%s)\n%s", - tostring(assetId), - tostring(cdnResponse.statusCode), - tostring(cdnResponse.statusMessage), - tostring(cdnResponse.body) - ) - ) - end - - -- 6. The response body should now be the contents of the asset file - return cdnResponse.body -end - -local function uploadAssetId(assetId: number, contents: string) - -- 1. Try to find the auth cookie for the current user - local cookie = getAuthCookieWithFallbacks() - - -- 2. Create request headers in advance, we might re-use them for CSRF challenges - local headers = { - ["User-Agent"] = "Roblox/WinInet", - ["Content-Type"] = "application/octet-stream", - Accept = "application/json", - Cookie = cookie, - } - - -- 3. Create and send a request to the upload url - local uploadResponse = net.request({ - url = `https://data.roblox.com/Data/Upload.ashx?assetid={assetId}`, - body = contents, - method = "POST", - headers = headers, - }) - - -- 4. Check if we got a valid response, we might have gotten a CSRF - -- challenge and need to send the request with a token included - if - not uploadResponse.ok - and uploadResponse.statusCode == 403 - and uploadResponse.headers["x-csrf-token"] ~= nil - then - headers["X-CSRF-Token"] = uploadResponse.headers["x-csrf-token"] - uploadResponse = net.request({ - url = `https://data.roblox.com/Data/Upload.ashx?assetid={assetId}`, - body = contents, - method = "POST", - headers = headers, - }) - end - - if not uploadResponse.ok then - error( - string.format( - "Failed to upload asset with id %s to Roblox!\n%s (%s)\n%s", - tostring(assetId), - tostring(uploadResponse.statusCode), - tostring(uploadResponse.statusMessage), - tostring(uploadResponse.body) - ) - ) - end -end - -local remodel = {} - ---[=[ - Load an `rbxl` or `rbxlx` file from the filesystem. - - Returns a `DataModel` instance, equivalent to `game` from within Roblox. -]=] -function remodel.readPlaceFile(filePath: string) - local placeFile = fs.readFile(filePath) - local place = roblox.deserializePlace(placeFile) - return place -end - ---[=[ - Load an `rbxm` or `rbxmx` file from the filesystem. - - Note that this function returns a **list of instances** instead of a single instance! - This is because models can contain mutliple top-level instances. -]=] -function remodel.readModelFile(filePath: string) - local modelFile = fs.readFile(filePath) - local model = roblox.deserializeModel(modelFile) - return model -end - ---[=[ - Reads a place asset from Roblox, equivalent to `remodel.readPlaceFile`. - - ***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!* -]=] -function remodel.readPlaceAsset(assetId: number) - local contents = downloadAssetId(assetId) - local place = roblox.deserializePlace(contents) - return place -end - ---[=[ - Reads a model asset from Roblox, equivalent to `remodel.readModelFile`. - - ***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!* -]=] -function remodel.readModelAsset(assetId: number) - local contents = downloadAssetId(assetId) - local place = roblox.deserializeModel(contents) - return place -end - ---[=[ - Saves an `rbxl` or `rbxlx` file out of the given `DataModel` instance. - - If the instance is not a `DataModel`, this function will throw. - Models should be saved with `writeModelFile` instead. -]=] -function remodel.writePlaceFile(filePath: string, dataModel: LuneDataModel) - local asBinary = string.sub(filePath, -5) == ".rbxl" - local asXml = string.sub(filePath, -6) == ".rbxlx" - assert(asBinary or asXml, "File path must have .rbxl or .rbxlx extension") - local placeFile = roblox.serializePlace(dataModel, asXml) - fs.writeFile(filePath, placeFile) -end - ---[=[ - Saves an `rbxm` or `rbxmx` file out of the given `Instance`. - - If the instance is a `DataModel`, this function will throw. - Places should be saved with `writePlaceFile` instead. -]=] -function remodel.writeModelFile(filePath: string, instance: LuneInstance) - local asBinary = string.sub(filePath, -5) == ".rbxm" - local asXml = string.sub(filePath, -6) == ".rbxmx" - assert(asBinary or asXml, "File path must have .rbxm or .rbxmx extension") - local placeFile = roblox.serializeModel({ instance }, asXml) - fs.writeFile(filePath, placeFile) -end - ---[=[ - Uploads the given `DataModel` instance to Roblox, overwriting an existing place. - - If the instance is not a `DataModel`, this function will throw. - Models should be uploaded with `writeExistingModelAsset` instead. - - ***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!* -]=] -function remodel.writeExistingPlaceAsset(dataModel: LuneDataModel, assetId: number) - local placeFile = roblox.serializePlace(dataModel) - uploadAssetId(assetId, placeFile) -end - ---[=[ - Uploads the given instance to Roblox, overwriting an existing model. - - If the instance is a `DataModel`, this function will throw. - Places should be uploaded with `writeExistingPlaceAsset` instead. - - ***NOTE:** This function requires authentication using a ROBLOSECURITY cookie!* -]=] -function remodel.writeExistingModelAsset(instance: LuneInstance, assetId: number) - local modelFile = roblox.serializeModel({ instance }) - uploadAssetId(assetId, modelFile) -end - -remodel.readFile = fs.readFile -remodel.readDir = fs.readDir -remodel.writeFile = fs.writeFile -remodel.createDirAll = fs.writeDir -remodel.removeFile = fs.removeFile -remodel.removeDir = fs.removeDir -remodel.isFile = fs.isFile -remodel.isDir = fs.isDir - -return remodel diff --git a/docs/pages/home/Editor-Setup.md b/docs/pages/home/Editor-Setup.md deleted file mode 100644 index 33d75f3..0000000 --- a/docs/pages/home/Editor-Setup.md +++ /dev/null @@ -1,22 +0,0 @@ -# 🧑‍đŸ’ģ Configuring VSCode and tooling for Lune - -Lune puts developer experience first, and as such provides type definitions and configurations for several tools out of the box. - -These steps assume you have already installed Lune and that it is available to run in the current directory. - -## Luau LSP - -1. Run `lune --setup` to generate Luau type definitions for your installed version of Lune -2. Verify that type definition files have been generated -3. Modify your VSCode settings, either by using the settings menu or in `settings.json`: - - ```json - "luau-lsp.require.mode": "relativeToFile", // Set the require mode to work with Lune - "luau-lsp.require.fileAliases": { // Add type definitions for Lune builtins - "@lune/fs": ".../.lune/.typedefs/x.y.z/fs.luau", - "@lune/net": ".../.lune/.typedefs/x.y.z/net.luau", - "@lune/...": "..." - } - ``` - - _**NOTE:** If you already had a `.vscode/settings.json` file in your current directory the type definition files may have been added automatically!_ diff --git a/docs/pages/home/Installation.md b/docs/pages/home/Installation.md deleted file mode 100644 index 71b1f8b..0000000 --- a/docs/pages/home/Installation.md +++ /dev/null @@ -1,36 +0,0 @@ - - -# ⚙ī¸ Installation - -The preferred way of installing Lune is using [Aftman](https://github.com/lpghatguy/aftman). - -Running this command in your terminal will add `lune` to an `aftman.toml` file in the current directory, or create one if it does not exist: - -```sh -aftman add filiptibell/lune -``` - -## Other options - -### Building from source - -Building and installing from source requires the latest version of [Rust & Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) to be installed on your system.
-Once installed, run the following command in your terminal: - -```sh -cargo install lune --locked -``` - -Note that Lune does not make any minimum supported rust version (MSRV) guarantees and you may need to upgrade your version of Rust to update Lune in the future. - -### Using GitHub Releases - -You can download pre-built binaries for most systems directly from the [GitHub Releases](https://github.com/filiptibell/lune/releases) page.
-There are many tools that can install binaries directly from releases, and it is up to you to choose what tool to use when installing here. - -## Next steps - -Congratulations! You've installed Lune and are now ready to write your first script. - -- If you want to write standalone scripts, head over to the [Writing Scripts](https://lune.gitbook.io/lune/home/writing-scripts) page. -- If you want to write Lune scripts specifically for Roblox, check out the [Roblox](https://lune.gitbook.io/lune/roblox/intro) section. diff --git a/docs/pages/home/Running-Scripts.md b/docs/pages/home/Running-Scripts.md deleted file mode 100644 index 65142e9..0000000 --- a/docs/pages/home/Running-Scripts.md +++ /dev/null @@ -1,51 +0,0 @@ - - -# 🏃 Running Lune Scripts - -After you've written a script file, for example `script-name.luau`, you can run it: - -```sh -lune script-name -``` - -This will look for the file `script-name.luau`**_[1]_** in a few locations: - -- The current directory -- The folder `lune` in the current directory, if it exists -- The folder `.lune` in the current directory, if it exists -- The folder `lune` in the _home_ directory, if it exists -- The folder `.lune` in the _home_ directory, if it exists - -## 🎛ī¸ Passing Command-Line Arguments - -Arguments can be passed to a Lune script directory from the command line when running it: - -```sh -lune script-name arg1 arg2 "argument three" -``` - -These arguments will then be available in your script using `process.args`: - -```lua -print(process.args) ---> { "arg1", "arg2", "argument three" } -``` - -## 💭 Additional Commands - -```sh -lune --list -``` - -Lists all scripts found in `lune` or `.lune` directories, including any top-level description comments.
-Lune description comments are always written at the top of a file and start with a lua-style comment arrow (`-->`). - -```sh -lune - -``` - -Runs a script passed to Lune using stdin. Occasionally useful for running scripts piped to Lune from external sources. - ---- - -**_[1]_** _Lune also supports files with the `.lua` extension but using the `.luau` extension is highly recommended. Additionally, if you don't want Lune to look in sub-directories or try to find files with `.lua` / `.luau` extensions at all, you can provide an absolute file path. This will disable all file path parsing and checks, and just run the file directly._ diff --git a/docs/pages/home/Writing-Scripts.md b/docs/pages/home/Writing-Scripts.md deleted file mode 100644 index 2367abc..0000000 --- a/docs/pages/home/Writing-Scripts.md +++ /dev/null @@ -1,326 +0,0 @@ - - - -# ✏ī¸ Writing Lune Scripts - -If you've already written some version of Lua (or Luau) scripts before, this walkthrough will make you feel right at home. - -Once you have a script you want to run, head over to the [Running Scripts](https://lune.gitbook.io/lune/home/running-scripts) page. - -## Hello, Lune! - -```lua ---[[ - EXAMPLE #1 - - Using arguments given to the program -]] - -if #process.args > 0 then - print("Got arguments:") - print(process.args) - if #process.args > 3 then - error("Too many arguments!") - end -else - print("Got no arguments ☚ī¸") -end - - - ---[[ - EXAMPLE #2 - - Using the stdio library to prompt for terminal input -]] - -local text = stdio.prompt("text", "Please write some text") - -print("You wrote '" .. text .. "'!") - -local confirmed = stdio.prompt("confirm", "Please confirm that you wrote some text") -if confirmed == false then - error("You didn't confirm!") -else - print("Confirmed!") -end - - - ---[[ - EXAMPLE #3 - - Get & set environment variables - - Checks if environment variables are empty or not, - prints out ❌ if empty and ✅ if they have a value -]] - -print("Reading current environment 🔎") - --- Environment variables can be read directly -assert(process.env.PATH ~= nil, "Missing PATH") -assert(process.env.PWD ~= nil, "Missing PWD") - --- And they can also be accessed using Luau's generalized iteration (but not pairs()) -for key, value in process.env do - local box = if value and value ~= "" then "✅" else "❌" - print(string.format("[%s] %s", box, key)) -end - - - ---[[ - EXAMPLE #4 - - Writing a module - - Modularizing and splitting up your code is Lune is very straight-forward, - in contrast to other scripting languages and shells such as bash -]] - -local module = {} - -function module.sayHello() - print("Hello, Lune! 🌙") -end - -return module - - - ---[[ - EXAMPLE #5 - - Using a function from another module / script - - Lune has path-relative imports, similar to other popular languages such as JavaScript -]] - -local module = require("../modules/module") -module.sayHello() - - - ---[[ - EXAMPLE #6 - - Spawning concurrent tasks - - These tasks will run at the same time as other Lua code which lets you do primitive multitasking -]] - -task.spawn(function() - print("Spawned a task that will run instantly but not block") - task.wait(5) -end) - -print("Spawning a delayed task that will run in 5 seconds") -task.delay(5, function() - print("...") - task.wait(1) - print("Hello again!") - task.wait(1) - print("Goodbye again! 🌙") -end) - - - ---[[ - EXAMPLE #7 - - Read files in the current directory - - This prints out directory & file names with some fancy icons -]] - -print("Reading current dir 🗂ī¸") -local entries = fs.readDir(".") - --- NOTE: We have to do this outside of the sort function --- to avoid yielding across the metamethod boundary, all --- of the filesystem APIs are asynchronous and yielding -local entryIsDir = {} -for _, entry in entries do - entryIsDir[entry] = fs.isDir(entry) -end - --- Sort prioritizing directories first, then alphabetically -table.sort(entries, function(entry0, entry1) - if entryIsDir[entry0] ~= entryIsDir[entry1] then - return entryIsDir[entry0] - end - return entry0 < entry1 -end) - --- Make sure we got some known files that should always exist -assert(table.find(entries, "Cargo.toml") ~= nil, "Missing Cargo.toml") -assert(table.find(entries, "Cargo.lock") ~= nil, "Missing Cargo.lock") - --- Print the pretty stuff -for _, entry in entries do - if fs.isDir(entry) then - print("📁 " .. entry) - else - print("📄 " .. entry) - end -end - - - ---[[ - EXAMPLE #8 - - Call out to another program / executable - - You can also get creative and combine this with example #6 to spawn several programs at the same time! -]] - -print("Sending 4 pings to google 🌏") -local result = process.spawn("ping", { - "google.com", - "-c 4", -}) - - - ---[[ - EXAMPLE #9 - - Using the result of a spawned process, exiting the process - - This looks scary with lots of weird symbols, but, it's just some Lua-style pattern matching - to parse the lines of "min/avg/max/stddev = W/X/Y/Z ms" that the ping program outputs to us -]] - -if result.ok then - assert(#result.stdout > 0, "Result output was empty") - local min, avg, max, stddev = string.match( - result.stdout, - "min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms" - ) - print(string.format("Minimum ping time: %.3fms", assert(tonumber(min)))) - print(string.format("Maximum ping time: %.3fms", assert(tonumber(max)))) - print(string.format("Average ping time: %.3fms", assert(tonumber(avg)))) - print(string.format("Standard deviation: %.3fms", assert(tonumber(stddev)))) -else - print("Failed to send ping to google!") - print(result.stderr) - process.exit(result.code) -end - - - ---[[ - EXAMPLE #10 - - Using the built-in networking library, encoding & decoding json -]] - -print("Sending PATCH request to web API 📤") -local apiResult = net.request({ - url = "https://jsonplaceholder.typicode.com/posts/1", - method = "PATCH", - headers = { - ["Content-Type"] = "application/json", - }, - body = net.jsonEncode({ - title = "foo", - body = "bar", - }), -}) - -if not apiResult.ok then - print("Failed to send network request!") - print(string.format("%d (%s)", apiResult.statusCode, apiResult.statusMessage)) - print(apiResult.body) - process.exit(1) -end - -type ApiResponse = { - id: number, - title: string, - body: string, - userId: number, -} - -local apiResponse: ApiResponse = net.jsonDecode(apiResult.body) -assert(apiResponse.title == "foo", "Invalid json response") -assert(apiResponse.body == "bar", "Invalid json response") -print("Got valid JSON response with changes applied") - - - ---[[ - EXAMPLE #11 - - Using the stdio library to print pretty -]] - -print("Printing with pretty colors and auto-formatting 🎨") - -print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset")) - -print("API response:", apiResponse) -warn({ - Oh = { - No = { - TooMuch = { - Nesting = { - "Will not print", - }, - }, - }, - }, -}) - -print(stdio.color("blue") .. string.rep("—", 22) .. stdio.color("reset")) - - - ---[[ - EXAMPLE #12 - - Saying goodbye 😔 -]] - -print("Goodbye, lune! 🌙") - -``` - -More real-world examples of how to write Lune scripts can be found in the [examples](https://github.com/filiptibell/lune/blob/main/.lune/examples/) folder. - -Documentation for individual APIs and types can be found in "API Reference" in the sidebar of this wiki. - -## Extras - -### 🔀 Example translation from Bash - -```bash -#!/bin/bash -VALID=true -COUNT=1 -while [ $VALID ] -do - echo $COUNT - if [ $COUNT -eq 5 ]; - then - break - fi - ((COUNT++)) -done -``` - -**_With Lune & Luau:_** - -```lua -local valid = true -local count = 1 -while valid do - print(count) - if count == 5 then - break - end - count += 1 -end -``` diff --git a/docs/pages/roblox/Api-Status.md b/docs/pages/roblox/Api-Status.md deleted file mode 100644 index ec8ef87..0000000 --- a/docs/pages/roblox/Api-Status.md +++ /dev/null @@ -1,76 +0,0 @@ - - - -# API Status - -This is a page indicating the current implementation status for instance methods and datatypes in the `roblox` library. - -If an API on a class is not listed here it may not be within the scope for Lune and may not be implemented in the future.
-However, if a recently added datatype is missing, and it can be used as an instance property, it is likely that it will be implemented. - -## Classes - -### `Instance` - -Currently implemented APIs: - -- [`new`](https://create.roblox.com/docs/reference/engine/datatypes/Instance#new) - note that this does not include the second `parent` argument -- [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag) -- [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone) -- [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy) -- [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren) -- [`FindFirstAncestor`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestor) -- [`FindFirstAncestorOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorOfClass) -- [`FindFirstAncestorWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorWhichIsA) -- [`FindFirstChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChild) -- [`FindFirstChildOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildOfClass) -- [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA) -- [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute) -- [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes) -- [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren) -- [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants) -- [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName) -- [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags) -- [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag) -- [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA) -- [`IsAncestorOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsAncestorOf) -- [`IsDescendantOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsDescendantOf) -- [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag) -- [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute) - -### `DataModel` - -Currently implemented APIs: - -- [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService) -- [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService) - -## Datatypes - -Currently implemented datatypes: - -- [`Axes`](https://create.roblox.com/docs/reference/engine/datatypes/Axes) -- [`BrickColor`](https://create.roblox.com/docs/reference/engine/datatypes/BrickColor) -- [`CFrame`](https://create.roblox.com/docs/reference/engine/datatypes/CFrame) -- [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) -- [`ColorSequence`](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence) -- [`ColorSequenceKeypoint`](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint) -- [`Enum`](https://create.roblox.com/docs/reference/engine/datatypes/Enum) -- [`Faces`](https://create.roblox.com/docs/reference/engine/datatypes/Faces) -- [`Font`](https://create.roblox.com/docs/reference/engine/datatypes/Font) -- [`NumberRange`](https://create.roblox.com/docs/reference/engine/datatypes/NumberRange) -- [`NumberSequence`](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequence) -- [`NumberSequenceKeypoint`](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequenceKeypoint) -- [`PhysicalProperties`](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties) -- [`Ray`](https://create.roblox.com/docs/reference/engine/datatypes/Ray) -- [`Rect`](https://create.roblox.com/docs/reference/engine/datatypes/Rect) -- [`Region3`](https://create.roblox.com/docs/reference/engine/datatypes/Region3) -- [`Region3int16`](https://create.roblox.com/docs/reference/engine/datatypes/Region3int16) -- [`UDim`](https://create.roblox.com/docs/reference/engine/datatypes/UDim) -- [`UDim2`](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) -- [`Vector2`](https://create.roblox.com/docs/reference/engine/datatypes/Vector2) -- [`Vector2int16`](https://create.roblox.com/docs/reference/engine/datatypes/Vector2int16) -- [`Vector3`](https://create.roblox.com/docs/reference/engine/datatypes/Vector3) -- [`Vector3int16`](https://create.roblox.com/docs/reference/engine/datatypes/Vector3int16) - -Note that these datatypes are kept as up-to-date as possible, but recently added members & methods may be missing. diff --git a/docs/pages/roblox/Examples.md b/docs/pages/roblox/Examples.md deleted file mode 100644 index 822f4be..0000000 --- a/docs/pages/roblox/Examples.md +++ /dev/null @@ -1,78 +0,0 @@ - - - -# Example Lune Scripts for Roblox - -These are a few examples of things you can do using the built-in `roblox` library. - -## `1` - Make all parts anchored in a place file - -```lua -local roblox = require("@lune/roblox") - --- Read the place file called myPlaceFile.rbxl into a DataModel called "game" --- This works exactly the same as in Roblox, except "game" does not exist by default - you have to load it from a file! -local game = roblox.readPlaceFile("myPlaceFile.rbxl") -local workspace = game:GetService("Workspace") - --- Make all of the parts in the workspace anchored -for _, descendant in workspace:GetDescendants() do - if descendant:IsA("BasePart") then - descendant.Anchored = true - end -end - --- Save the DataModel (game) back to the file that we read it from -roblox.writePlaceFile("myPlaceFile.rbxl") -``` - ---- - -## `2` - Save instances in a place as individual model files - -```lua -local roblox = require("@lune/roblox") -local fs = require("@lune/fs") - --- Here we load a file just like in the first example -local game = roblox.readPlaceFile("myPlaceFile.rbxl") -local workspace = game:GetService("Workspace") - --- We use a normal Lune API to make sure a directory exists to save our models in -fs.writeDir("models") - --- Then we save all of our instances in Workspace as model files, in our new directory --- Note that a model file can actually contain several instances at once, so we pass a table here -for _, child in workspace:GetChildren() do - roblox.writeModelFile("models/" .. child.Name, { child }) -end -``` - ---- - -## `3` - Make a new place from scratch - -```lua -local roblox = require("@lune/roblox") -local Instance = roblox.Instance - --- You can even create a new DataModel using Instance.new, which is not normally possible in Roblox --- This is normal - most instances that are not normally accessible in Roblox can be manipulated using Lune! -local game = Instance.new("DataModel") -local workspace = game:GetService("Workspace") - --- Here we just make a bunch of models with parts in them for demonstration purposes -for i = 1, 50 do - local model = Instance.new("Model") - model.Name = "Model #" .. tostring(i) - model.Parent = workspace - for j = 1, 4 do - local part = Instance.new("Part") - part.Name = "Part #" .. tostring(j) - part.Parent = model - end -end - --- As always, we have to save the DataModel (game) to a file when we're done -roblox.writePlaceFile("myPlaceWithLotsOfModels.rbxl") -``` diff --git a/docs/pages/roblox/Introduction.md b/docs/pages/roblox/Introduction.md deleted file mode 100644 index 5b657ca..0000000 --- a/docs/pages/roblox/Introduction.md +++ /dev/null @@ -1,10 +0,0 @@ - - - -# ✏ī¸ Writing Lune Scripts for Roblox - -Lune has a powerful built-in library and set of APIs for manipulating Roblox place files and model files. It contains APIs for reading & writing files, and gives you instances to use, just as if you were scripting inside of the Roblox engine, albeit with a more limited API. - -For examples on how to write Roblox-specific Lune scripts, check out the [Examples](https://lune.gitbook.io/lune/roblox/examples) page. - -For a full list of the currently implemented Roblox APIs, check out the [API Status](https://lune.gitbook.io/lune/roblox/api-status) page. diff --git a/src/cli/gen/typedef_files.rs b/src/cli/gen.rs similarity index 61% rename from src/cli/gen/typedef_files.rs rename to src/cli/gen.rs index b08b651..eb6268b 100644 --- a/src/cli/gen/typedef_files.rs +++ b/src/cli/gen.rs @@ -2,14 +2,32 @@ use std::collections::HashMap; use anyhow::{Context, Result}; use directories::UserDirs; - use futures_util::future::try_join_all; +use include_dir::Dir; use tokio::fs::{create_dir_all, write}; -#[allow(clippy::too_many_lines)] -pub async fn generate_from_type_definitions( - typedef_files: HashMap>, -) -> Result { +pub async fn generate_typedef_files_from_definitions(dir: &Dir<'_>) -> Result { + let contents = read_typedefs_dir_contents(dir); + write_typedef_files(contents).await +} + +fn read_typedefs_dir_contents(dir: &Dir<'_>) -> HashMap> { + let mut definitions = HashMap::new(); + + for entry in dir.find("*.luau").unwrap() { + let entry_file = entry.as_file().unwrap(); + let entry_name = entry_file.path().file_name().unwrap().to_string_lossy(); + + let typedef_name = entry_name.trim_end_matches(".luau"); + let typedef_contents = entry_file.contents().to_vec(); + + definitions.insert(typedef_name.to_string(), typedef_contents); + } + + definitions +} + +async fn write_typedef_files(typedef_files: HashMap>) -> Result { let version_string = env!("CARGO_PKG_VERSION"); let mut dirs_to_write = Vec::new(); let mut files_to_write = Vec::new(); diff --git a/src/cli/gen/definitions/builder.rs b/src/cli/gen/definitions/builder.rs deleted file mode 100644 index 960a47b..0000000 --- a/src/cli/gen/definitions/builder.rs +++ /dev/null @@ -1,129 +0,0 @@ -use anyhow::{bail, Result}; - -use super::{ - item::{DefinitionsItem, DefinitionsItemFunctionArg, DefinitionsItemFunctionRet}, - kind::DefinitionsItemKind, -}; - -#[derive(Debug, Default, Clone)] -pub struct DefinitionsItemBuilder { - exported: bool, - kind: Option, - typ: Option, - name: Option, - meta: Option, - value: Option, - children: Vec, - args: Vec, - rets: Vec, -} - -#[allow(dead_code)] -impl DefinitionsItemBuilder { - pub fn new() -> Self { - Self { - ..Default::default() - } - } - - #[allow(clippy::wrong_self_convention)] - pub fn as_exported(mut self) -> Self { - self.exported = true; - self - } - - pub fn with_kind(mut self, kind: DefinitionsItemKind) -> Self { - self.kind = Some(kind); - self - } - - pub fn with_name>(mut self, name: S) -> Self { - self.name = Some(name.as_ref().to_string()); - self - } - - pub fn with_type(mut self, typ: String) -> Self { - self.typ = Some(typ); - self - } - - pub fn with_meta>(mut self, meta: S) -> Self { - self.meta = Some(meta.as_ref().to_string()); - self - } - - pub fn with_value>(mut self, value: S) -> Self { - self.value = Some(value.as_ref().to_string()); - self - } - - pub fn with_child(mut self, child: DefinitionsItem) -> Self { - self.children.push(child); - self - } - - pub fn with_children(mut self, children: &[DefinitionsItem]) -> Self { - self.children.extend_from_slice(children); - self - } - - pub fn with_arg(mut self, arg: DefinitionsItemFunctionArg) -> Self { - self.args.push(arg); - self - } - - pub fn with_args(mut self, args: &[DefinitionsItemFunctionArg]) -> Self { - for arg in args { - self.args.push(arg.clone()); - } - self - } - - pub fn with_ret(mut self, ret: DefinitionsItemFunctionRet) -> Self { - self.rets.push(ret); - self - } - - pub fn with_rets(mut self, rets: &[DefinitionsItemFunctionRet]) -> Self { - for ret in rets { - self.rets.push(ret.clone()); - } - self - } - - pub fn build(self) -> Result { - if let Some(kind) = self.kind { - let mut children = self.children; - children.sort_by(|left, right| left.name.cmp(&right.name)); - Ok(DefinitionsItem { - exported: self.exported, - kind, - typ: self.typ, - name: self.name, - meta: self.meta, - value: self.value, - children, - args: self.args, - rets: self.rets, - }) - } else { - bail!("Missing doc item kind") - } - } -} - -impl From<&DefinitionsItem> for DefinitionsItemBuilder { - fn from(value: &DefinitionsItem) -> Self { - Self { - exported: value.exported, - kind: Some(value.kind), - typ: value.typ.clone(), - name: value.name.clone(), - meta: value.meta.clone(), - value: value.value.clone(), - children: value.children.clone(), - args: value.args.clone(), - rets: value.rets.clone(), - } - } -} diff --git a/src/cli/gen/definitions/item.rs b/src/cli/gen/definitions/item.rs deleted file mode 100644 index b8f819b..0000000 --- a/src/cli/gen/definitions/item.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::cmp::Ordering; - -use serde::{Deserialize, Serialize}; - -use super::kind::DefinitionsItemKind; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DefinitionsItemFunctionArg { - pub name: String, - pub typedef: String, - pub typedef_simple: String, -} - -impl DefinitionsItemFunctionArg { - pub fn new(name: N, typedef: T, typedef_simple: TS) -> Self - where - N: Into, - T: Into, - TS: Into, - { - Self { - name: name.into(), - typedef: typedef.into(), - typedef_simple: typedef_simple.into(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DefinitionsItemFunctionRet { - pub typedef: String, - pub typedef_simple: String, -} - -impl DefinitionsItemFunctionRet { - pub fn new(typedef: T, typedef_simple: TS) -> Self - where - T: Into, - TS: Into, - { - Self { - typedef: typedef.into(), - typedef_simple: typedef_simple.into(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DefinitionsItem { - #[serde(skip_serializing_if = "skip_serialize_is_false")] - pub(super) exported: bool, - pub(super) kind: DefinitionsItemKind, - pub(super) typ: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(super) name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(super) meta: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(super) value: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub(super) children: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub(super) args: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub(super) rets: Vec, -} - -#[allow(clippy::trivially_copy_pass_by_ref)] -fn skip_serialize_is_false(b: &bool) -> bool { - !b -} - -impl PartialOrd for DefinitionsItem { - fn partial_cmp(&self, other: &Self) -> Option { - match self.kind.partial_cmp(&other.kind).unwrap() { - Ordering::Equal => {} - ord => return Some(ord), - } - match self.name.partial_cmp(&other.name).unwrap() { - Ordering::Equal => {} - ord => return Some(ord), - } - match (&self.value, &other.value) { - (Some(value_self), Some(value_other)) => { - match value_self.partial_cmp(value_other).unwrap() { - Ordering::Equal => {} - ord => return Some(ord), - } - } - (Some(_), None) => return Some(Ordering::Less), - (None, Some(_)) => return Some(Ordering::Greater), - (None, None) => {} - } - Some(Ordering::Equal) - } -} - -#[allow(dead_code)] -impl DefinitionsItem { - pub fn is_exported(&self) -> bool { - self.exported - } - - pub fn is_root(&self) -> bool { - self.kind.is_root() - } - - pub fn is_type(&self) -> bool { - self.kind.is_type() - } - - pub fn is_table(&self) -> bool { - self.kind.is_table() - } - - pub fn is_property(&self) -> bool { - self.kind.is_property() - } - - pub fn is_function(&self) -> bool { - self.kind.is_function() - } - - pub fn is_description(&self) -> bool { - self.kind.is_description() - } - - pub fn is_tag(&self) -> bool { - self.kind.is_tag() - } - - pub fn kind(&self) -> DefinitionsItemKind { - self.kind - } - - pub fn get_name(&self) -> Option<&str> { - self.name.as_deref() - } - - pub fn get_type(&self) -> Option { - self.typ.clone() - } - - pub fn get_meta(&self) -> Option<&str> { - self.meta.as_deref() - } - - pub fn get_value(&self) -> Option<&str> { - self.value.as_deref() - } - - pub fn children(&self) -> &[DefinitionsItem] { - &self.children - } - - pub fn args(&self) -> Vec<&DefinitionsItemFunctionArg> { - self.args.iter().collect() - } - - pub fn rets(&self) -> Vec<&DefinitionsItemFunctionRet> { - self.rets.iter().collect() - } -} diff --git a/src/cli/gen/definitions/kind.rs b/src/cli/gen/definitions/kind.rs deleted file mode 100644 index 08e4457..0000000 --- a/src/cli/gen/definitions/kind.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::fmt; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub enum DefinitionsItemKind { - Root, - Type, - Table, - Property, - Function, - Description, - Tag, -} - -#[allow(dead_code)] -impl DefinitionsItemKind { - pub fn is_root(self) -> bool { - self == DefinitionsItemKind::Root - } - - pub fn is_type(self) -> bool { - self == DefinitionsItemKind::Type - } - - pub fn is_table(self) -> bool { - self == DefinitionsItemKind::Table - } - - pub fn is_property(self) -> bool { - self == DefinitionsItemKind::Property - } - - pub fn is_function(self) -> bool { - self == DefinitionsItemKind::Function - } - - pub fn is_description(self) -> bool { - self == DefinitionsItemKind::Description - } - - pub fn is_tag(self) -> bool { - self == DefinitionsItemKind::Tag - } -} - -impl fmt::Display for DefinitionsItemKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::Root => "Root", - Self::Type => "Type", - Self::Table => "Table", - Self::Property => "Property", - Self::Function => "Function", - Self::Description => "Description", - Self::Tag => "Tag", - } - ) - } -} diff --git a/src/cli/gen/definitions/mod.rs b/src/cli/gen/definitions/mod.rs deleted file mode 100644 index fe80f7c..0000000 --- a/src/cli/gen/definitions/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod builder; -mod item; -mod kind; -mod moonwave; -mod parser; -mod tag; -mod tree; -mod type_info_ext; - -pub use builder::DefinitionsItemBuilder; -pub use item::DefinitionsItem; -pub use kind::DefinitionsItemKind; -pub use tag::DefinitionsItemTag; -pub use tree::DefinitionsTree; diff --git a/src/cli/gen/definitions/moonwave.rs b/src/cli/gen/definitions/moonwave.rs deleted file mode 100644 index d81800c..0000000 --- a/src/cli/gen/definitions/moonwave.rs +++ /dev/null @@ -1,85 +0,0 @@ -use regex::Regex; - -use super::{builder::DefinitionsItemBuilder, item::DefinitionsItem, kind::DefinitionsItemKind}; - -fn should_separate_tag_meta(tag_kind: &str) -> bool { - matches!(tag_kind.trim().to_ascii_lowercase().as_ref(), "param") -} - -fn parse_moonwave_style_tag(line: &str) -> Option { - let tag_regex = Regex::new(r#"^@(\S+)\s*(.*)$"#).unwrap(); - if tag_regex.is_match(line) { - let captures = tag_regex.captures(line).unwrap(); - let tag_kind = captures.get(1).unwrap().as_str(); - let tag_rest = captures.get(2).unwrap().as_str(); - let mut tag_words = tag_rest.split_whitespace().collect::>(); - let tag_name = if !tag_words.is_empty() && should_separate_tag_meta(tag_kind) { - tag_words.remove(0).to_string() - } else { - String::new() - }; - let tag_contents = tag_words.join(" "); - if tag_kind.is_empty() { - None - } else { - let mut builder = DefinitionsItemBuilder::new() - .with_kind(DefinitionsItemKind::Tag) - .with_name(tag_kind); - if !tag_name.is_empty() { - builder = builder.with_meta(tag_name); - } - if !tag_contents.is_empty() { - builder = builder.with_value(tag_contents); - } - Some(builder.build().unwrap()) - } - } else { - None - } -} - -pub(super) fn parse_moonwave_style_comment(comment: &str) -> Vec { - let no_tabs = comment.replace('\t', " "); - let lines = no_tabs.split('\n').collect::>(); - let indent_len = - lines.iter().fold(usize::MAX, |acc, line| { - let first = line.chars().enumerate().find_map(|(idx, ch)| { - if ch.is_whitespace() { - None - } else { - Some(idx) - } - }); - if let Some(first_non_whitespace) = first { - acc.min(first_non_whitespace) - } else { - acc - } - }); - let unindented_lines = lines.iter().map(|line| { - if line.chars().any(|c| !c.is_whitespace()) { - &line[indent_len..] - } else { - line - } - }); - let mut doc_items = Vec::new(); - let mut doc_lines = Vec::new(); - for line in unindented_lines { - if let Some(tag) = parse_moonwave_style_tag(line) { - doc_items.push(tag); - } else { - doc_lines.push(line); - } - } - if !doc_lines.is_empty() { - doc_items.push( - DefinitionsItemBuilder::new() - .with_kind(DefinitionsItemKind::Description) - .with_value(doc_lines.join("\n")) - .build() - .unwrap(), - ); - } - doc_items -} diff --git a/src/cli/gen/definitions/parser.rs b/src/cli/gen/definitions/parser.rs deleted file mode 100644 index dff51e2..0000000 --- a/src/cli/gen/definitions/parser.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; - -use anyhow::{Context, Result}; -use full_moon::{ - ast::{ - types::{TypeFieldKey, TypeInfo}, - Stmt, - }, - tokenizer::{TokenReference, TokenType}, -}; - -use super::{ - builder::DefinitionsItemBuilder, item::DefinitionsItem, moonwave::parse_moonwave_style_comment, - type_info_ext::TypeInfoExt, DefinitionsItemKind, -}; - -#[derive(Debug, Clone)] -struct DefinitionsParserItem { - name: String, - comment: Option, - type_info: TypeInfo, -} - -#[derive(Debug, Clone)] -pub struct DefinitionsParser { - found_top_level_items: BTreeMap, - found_top_level_types: HashMap, - found_top_level_comments: HashMap>, - found_top_level_exports: Vec, -} - -impl DefinitionsParser { - pub fn new() -> Self { - Self { - found_top_level_items: BTreeMap::new(), - found_top_level_types: HashMap::new(), - found_top_level_comments: HashMap::new(), - found_top_level_exports: Vec::new(), - } - } - - /** - Parses the given Luau type definitions into parser items. - - The parser items will be stored internally and can be converted - into usable definition items using [`DefinitionsParser::drain`]. - */ - pub fn parse(&mut self, contents: S) -> Result<()> - where - S: AsRef, - { - // Parse contents into top-level parser items for later use - let mut found_top_level_items = BTreeMap::new(); - let mut found_top_level_types = HashMap::new(); - let mut found_top_level_comments = HashMap::new(); - let mut found_top_level_exports = HashSet::new(); - let ast = - full_moon::parse(contents.as_ref()).context("Failed to parse type definitions")?; - for stmt in ast.nodes().stmts() { - if let Some((exported, declaration, token_reference)) = match stmt { - Stmt::ExportedTypeDeclaration(exp) => { - Some((true, exp.type_declaration(), exp.export_token())) - } - Stmt::TypeDeclaration(typ) => Some((false, typ, typ.type_token())), - _ => None, - } { - let name = declaration.type_name().token().to_string(); - let comment = find_token_moonwave_comment(token_reference); - found_top_level_items.insert( - name.clone(), - DefinitionsParserItem { - name: name.clone(), - comment: comment.clone(), - type_info: declaration.type_definition().clone(), - }, - ); - found_top_level_types.insert(name.clone(), declaration.type_definition().clone()); - found_top_level_comments.insert(name.clone(), comment); - if exported { - found_top_level_exports.insert(name); - } - } - } - // Store results - self.found_top_level_items = found_top_level_items; - self.found_top_level_types = found_top_level_types; - self.found_top_level_comments = found_top_level_comments; - self.found_top_level_exports = found_top_level_exports.into_iter().collect(); - Ok(()) - } - - fn convert_parser_item_into_doc_item( - &self, - item: DefinitionsParserItem, - kind: Option, - ) -> DefinitionsItem { - let mut builder = DefinitionsItemBuilder::new() - .with_kind(kind.unwrap_or_else(|| item.type_info.parse_definitions_kind())) - .with_name(&item.name) - .with_type(item.type_info.to_string()); - if self.found_top_level_exports.contains(&item.name) { - builder = builder.as_exported(); - } - if let Some(comment) = item.comment { - builder = builder.with_children(&parse_moonwave_style_comment(&comment)); - } - if let Some(args) = item - .type_info - .extract_args_normalized(&self.found_top_level_types) - { - builder = builder.with_args(&args); - } - if let TypeInfo::Table { fields, .. } = item.type_info { - for field in fields.iter() { - if let TypeFieldKey::Name(name) = field.key() { - builder = builder.with_child(self.convert_parser_item_into_doc_item( - DefinitionsParserItem { - name: name.token().to_string(), - comment: find_token_moonwave_comment(name), - type_info: field.value().clone(), - }, - None, - )); - } - } - } - builder.build().unwrap() - } - - /** - Converts currently stored parser items into definition items. - - This will consume (drain) all stored parser items, leaving the parser empty. - */ - #[allow(clippy::unnecessary_wraps)] - pub fn drain(&mut self) -> Result> { - let mut resulting_items = Vec::new(); - for top_level_item in self.found_top_level_items.values() { - resulting_items - .push(self.convert_parser_item_into_doc_item(top_level_item.clone(), None)); - } - self.found_top_level_items = BTreeMap::new(); - self.found_top_level_types = HashMap::new(); - self.found_top_level_comments = HashMap::new(); - self.found_top_level_exports = Vec::new(); - Ok(resulting_items) - } -} - -fn find_token_moonwave_comment(token: &TokenReference) -> Option { - token - .leading_trivia() - .filter_map(|trivia| match trivia.token_type() { - TokenType::MultiLineComment { blocks, comment } if blocks == &1 => Some(comment), - _ => None, - }) - .last() - .map(ToString::to_string) -} diff --git a/src/cli/gen/definitions/tag.rs b/src/cli/gen/definitions/tag.rs deleted file mode 100644 index 3ceb88c..0000000 --- a/src/cli/gen/definitions/tag.rs +++ /dev/null @@ -1,102 +0,0 @@ -use anyhow::{bail, Context, Result}; -use serde::{Deserialize, Serialize}; - -use super::item::DefinitionsItem; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub enum DefinitionsItemTag { - Class(String), - Type(String), - Within(String), - Param((String, String)), - Return(String), - MustUse, - ReadOnly, - ReadWrite, -} - -#[allow(dead_code)] -impl DefinitionsItemTag { - pub fn is_class(&self) -> bool { - matches!(self, Self::Class(_)) - } - - pub fn is_type(&self) -> bool { - matches!(self, Self::Class(_)) - } - - pub fn is_within(&self) -> bool { - matches!(self, Self::Within(_)) - } - - pub fn is_param(&self) -> bool { - matches!(self, Self::Param(_)) - } - - pub fn is_return(&self) -> bool { - matches!(self, Self::Return(_)) - } - - pub fn is_must_use(&self) -> bool { - self == &Self::MustUse - } - - pub fn is_read_only(&self) -> bool { - self == &Self::ReadOnly - } - - pub fn is_read_write(&self) -> bool { - self == &Self::ReadWrite - } -} - -impl TryFrom<&DefinitionsItem> for DefinitionsItemTag { - type Error = anyhow::Error; - fn try_from(value: &DefinitionsItem) -> Result { - if let Some(name) = value.get_name() { - Ok(match name.trim().to_ascii_lowercase().as_ref() { - "class" => Self::Class( - value - .get_value() - .context("Missing class name for class tag")? - .to_string(), - ), - "type" => Self::Class( - value - .get_value() - .context("Missing type name for type tag")? - .to_string(), - ), - "within" => Self::Within( - value - .get_value() - .context("Missing class name for within tag")? - .to_string(), - ), - "param" => Self::Param(( - value - .get_meta() - .context("Missing param name for param tag")? - .to_string(), - value - .get_value() - .context("Missing param value for param tag")? - .to_string(), - )), - "return" => Self::Return( - value - .get_value() - .context("Missing description for return tag")? - .to_string(), - ), - "must_use" => Self::MustUse, - "read_only" => Self::ReadOnly, - "read_write" => Self::ReadWrite, - s => bail!("Unknown docs tag: '{}'", s), - }) - } else { - bail!("Doc item has no name") - } - } -} diff --git a/src/cli/gen/definitions/tree.rs b/src/cli/gen/definitions/tree.rs deleted file mode 100644 index 6df44f5..0000000 --- a/src/cli/gen/definitions/tree.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; - -use super::{ - builder::DefinitionsItemBuilder, item::DefinitionsItem, kind::DefinitionsItemKind, - parser::DefinitionsParser, -}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct DefinitionsTree(DefinitionsItem); - -#[allow(dead_code)] -impl DefinitionsTree { - pub fn from_type_definitions>(type_definitions_contents: S) -> Result { - let mut parser = DefinitionsParser::new(); - parser - .parse(type_definitions_contents) - .context("Failed to parse type definitions AST")?; - let top_level_definition_items = parser - .drain() - .context("Failed to convert parser items into definition items")?; - let root = DefinitionsItemBuilder::new() - .with_kind(DefinitionsItemKind::Root) - .with_name("<<>>") - .with_children(&top_level_definition_items) - .build()?; - Ok(Self(root)) - } - - #[allow(clippy::unused_self)] - pub fn is_root(&self) -> bool { - true - } - - pub fn into_inner(self) -> DefinitionsItem { - self.0 - } -} - -impl Deref for DefinitionsTree { - type Target = DefinitionsItem; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for DefinitionsTree { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/src/cli/gen/definitions/type_info_ext.rs b/src/cli/gen/definitions/type_info_ext.rs deleted file mode 100644 index 666cae0..0000000 --- a/src/cli/gen/definitions/type_info_ext.rs +++ /dev/null @@ -1,333 +0,0 @@ -use std::collections::HashMap; - -use full_moon::{ - ast::types::{TypeArgument, TypeInfo}, - tokenizer::{Symbol, Token, TokenReference, TokenType}, - ShortString, -}; - -use super::{ - item::{DefinitionsItemFunctionArg, DefinitionsItemFunctionRet}, - kind::DefinitionsItemKind, -}; - -pub(crate) trait TypeInfoExt { - fn is_fn(&self) -> bool; - fn parse_definitions_kind(&self) -> DefinitionsItemKind; - fn stringify_simple( - &self, - parent_typ: Option<&TypeInfo>, - type_lookup_table: &HashMap, - ) -> String; - fn extract_args(&self) -> Vec; - fn extract_args_normalized( - &self, - type_lookup_table: &HashMap, - ) -> Option>; - // fn extract_rets(&self) -> Vec; - // fn extract_rets_normalized( - // &self, - // type_lookup_table: &HashMap, - // ) -> Option>; -} - -impl TypeInfoExt for TypeInfo { - /** - Checks if this type represents a function or not. - - If the type is a tuple, union, or intersection, it will be checked recursively. - */ - fn is_fn(&self) -> bool { - match self { - TypeInfo::Callback { .. } => true, - TypeInfo::Tuple { types, .. } => types.iter().all(Self::is_fn), - TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => { - left.is_fn() || right.is_fn() - } - _ => false, - } - } - - /** - Parses the definitions item kind from the type. - - If the type is a tupe, union, or intersection, all the inner types - are required to be equivalent in terms of definitions item kinds. - */ - fn parse_definitions_kind(&self) -> DefinitionsItemKind { - match self { - TypeInfo::Array { .. } | TypeInfo::Table { .. } => DefinitionsItemKind::Table, - TypeInfo::Basic(_) | TypeInfo::String(_) => DefinitionsItemKind::Property, - TypeInfo::Optional { base, .. } => Self::parse_definitions_kind(base.as_ref()), - TypeInfo::Tuple { types, .. } => { - let mut kinds = types - .iter() - .map(Self::parse_definitions_kind) - .collect::>(); - let kinds_all_the_same = kinds.windows(2).all(|w| w[0] == w[1]); - if kinds_all_the_same && !kinds.is_empty() { - kinds.pop().unwrap() - } else { - unimplemented!( - "Missing support for tuple with differing types in type definitions parser", - ) - } - } - TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => { - let kind_left = Self::parse_definitions_kind(left.as_ref()); - let kind_right = Self::parse_definitions_kind(right.as_ref()); - if kind_left == kind_right { - kind_left - } else { - unimplemented!( - "Missing support for union/intersection with differing types in type definitions parser", - ) - } - } - typ if typ.is_fn() => DefinitionsItemKind::Function, - typ => unimplemented!( - "Missing support for TypeInfo in type definitions parser:\n{}", - typ.to_string() - ), - } - } - - /** - Stringifies the type into a simplified type string. - - The simplified type string match one of the following formats: - - * `any` - * `boolean` - * `string` - * `function` - * `table` - * `CustomTypeName` - * `TypeName?` - * `TypeName | OtherTypeName` - * `{ TypeName }` - * `"string-literal"` - */ - fn stringify_simple( - &self, - parent_typ: Option<&TypeInfo>, - type_lookup_table: &HashMap, - ) -> String { - match self { - TypeInfo::Array { type_info, .. } => { - format!( - "{{ {} }}", - type_info - .as_ref() - .stringify_simple(Some(self), type_lookup_table) - ) - } - TypeInfo::Basic(tok) => { - let tok_str = tok.token().to_string().trim().to_string(); - let mut any_str = None; - // If the function that contains this arg has generic and a - // generic is the same as this token, we stringify it as any - if let Some(parent) = parent_typ { - if let Some(TypeInfo::Callback { - generics: Some(callback_generics), - .. - }) = try_extract_callback_type_info(parent) - { - if callback_generics - .generics() - .iter() - .any(|g| g.to_string() == tok_str) - { - any_str = Some("any".to_string()); - } - } - } - // Also check if we got a referenced type, meaning that it - // exists in the lookup table of global types passed to us - if let Some(any_str) = any_str { - any_str - } else if let Some(referenced_typ) = type_lookup_table.get(&tok_str) { - referenced_typ.stringify_simple(None, type_lookup_table) - } else { - tok_str - } - } - TypeInfo::String(str) => str.token().to_string(), - TypeInfo::Boolean(_) => "boolean".to_string(), - TypeInfo::Callback { .. } => "function".to_string(), - TypeInfo::Optional { base, .. } => { - format!( - "{}?", - base.as_ref() - .stringify_simple(Some(self), type_lookup_table) - ) - } - TypeInfo::Table { .. } => "table".to_string(), - TypeInfo::Union { left, right, .. } => { - format!( - "{} {} {}", - left.as_ref() - .stringify_simple(Some(self), type_lookup_table), - Symbol::Pipe, - right - .as_ref() - .stringify_simple(Some(self), type_lookup_table) - ) - } - // FUTURE: Is there any other type that we can - // stringify to a primitive in an obvious way? - _ => "...".to_string(), - } - } - - fn extract_args(&self) -> Vec { - if self.is_fn() { - match self { - TypeInfo::Callback { arguments, .. } => { - arguments.iter().cloned().collect::>() - } - TypeInfo::Tuple { types, .. } => types - .iter() - .next() - .expect("Function tuple type was empty") - .extract_args(), - TypeInfo::Union { left, right, .. } - | TypeInfo::Intersection { left, right, .. } => { - let mut result = Vec::new(); - result = merge_type_argument_vecs(result, left.extract_args()); - result = merge_type_argument_vecs(result, right.extract_args()); - result - } - _ => vec![], - } - } else { - vec![] - } - } - - fn extract_args_normalized( - &self, - type_lookup_table: &HashMap, - ) -> Option> { - if self.is_fn() { - let args_stringified_not_normalized = self - .extract_args() - .iter() - .map(|type_arg| { - ( - type_arg - .name() - .map_or_else(|| "_".to_string(), |n| n.0.to_string()), - type_arg.type_info().to_string(), - type_arg - .type_info() - .stringify_simple(Some(self), type_lookup_table), - ) - }) - .collect::>(); - let mut args = Vec::new(); - for (arg_name, arg_typedef, arg_typedef_simplified) in args_stringified_not_normalized { - args.push(DefinitionsItemFunctionArg::new( - arg_name, - arg_typedef, - normalize_type(&arg_typedef_simplified), - )); - } - Some(args) - } else { - None - } - } -} - -fn try_extract_callback_type_info(type_info: &TypeInfo) -> Option<&TypeInfo> { - match type_info { - TypeInfo::Callback { .. } => Some(type_info), - TypeInfo::Tuple { types, .. } => types.iter().find_map(try_extract_callback_type_info), - TypeInfo::Union { left, right, .. } | TypeInfo::Intersection { left, right, .. } => { - try_extract_callback_type_info(left).or_else(|| try_extract_callback_type_info(right)) - } - _ => None, - } -} - -fn make_empty_type_argument() -> TypeArgument { - TypeArgument::new(TypeInfo::Basic(TokenReference::new( - vec![], - Token::new(TokenType::Symbol { - symbol: Symbol::Nil, - }), - vec![], - ))) -} - -fn merge_type_arguments(left: TypeArgument, right: TypeArgument) -> TypeArgument { - TypeArgument::new(TypeInfo::Union { - left: Box::new(left.type_info().clone()), - pipe: TokenReference::new( - vec![Token::new(TokenType::Whitespace { - characters: ShortString::new(" "), - })], - Token::new(TokenType::Symbol { - symbol: Symbol::Pipe, - }), - vec![Token::new(TokenType::Whitespace { - characters: ShortString::new(" "), - })], - ), - right: Box::new(right.type_info().clone()), - }) -} - -fn merge_type_argument_vecs( - existing: Vec, - new: Vec, -) -> Vec { - let mut result = Vec::new(); - for (index, argument) in new.iter().enumerate() { - if let Some(existing) = existing.get(index) { - result.push(merge_type_arguments(existing.clone(), argument.clone())); - } else { - result.push(merge_type_arguments( - make_empty_type_argument(), - argument.clone(), - )); - } - } - result -} - -fn normalize_type(simplified: &str) -> String { - let separator = format!(" {} ", Symbol::Pipe); - let arg_parts = simplified.split(&separator).collect::>(); - // Check if we got any optional arg, if so then the entire possible - // union of args will be optional when merged together / normalized - let is_optional = arg_parts - .iter() - .any(|part| part == &"nil" || part.ends_with('?')); - // Get rid of any nils or optional markers since we keep track of it above - let mut arg_parts_no_nils = arg_parts - .iter() - .filter_map(|arg_part| { - if arg_part == &"nil" { - None - } else { - Some(arg_part.trim_end_matches('?')) - } - }) - .collect::>(); - arg_parts_no_nils.sort_unstable(); // Sort the args to be able to dedup - arg_parts_no_nils.dedup(); // Deduplicate types that are the exact same shape - if is_optional { - if arg_parts_no_nils.len() > 1 { - // A union of args that is nillable should be enclosed in parens to make - // it more clear that the entire arg is nillable and not just the last type - format!("({})?", arg_parts_no_nils.join(&separator)) - } else { - // Just one nillable arg, does not need any parens - format!("{}?", arg_parts_no_nils.first().unwrap()) - } - } else { - arg_parts_no_nils.join(&separator) - } -} diff --git a/src/cli/gen/gitbook_dir.rs b/src/cli/gen/gitbook_dir.rs deleted file mode 100644 index b5cc9e7..0000000 --- a/src/cli/gen/gitbook_dir.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::{collections::HashMap, fmt::Write, path::PathBuf}; - -use anyhow::{Context, Result}; - -use futures_util::future::try_join_all; -use tokio::fs::{create_dir_all, write}; - -use super::definitions::{ - DefinitionsItem, DefinitionsItemBuilder, DefinitionsItemKind, DefinitionsItemTag, - DefinitionsTree, -}; - -const GENERATED_COMMENT_TAG: &str = ""; - -#[allow(clippy::too_many_lines)] -pub async fn generate_from_type_definitions( - definitions: HashMap, -) -> Result<()> { - let mut dirs_to_write = Vec::new(); - let mut files_to_write = Vec::new(); - // Create the gitbook dir at the repo root - let path_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../") - .canonicalize() - .unwrap(); - let path_gitbook_dir = path_root.join("gitbook"); - let path_gitbook_docs_dir = path_gitbook_dir.join("docs"); - let path_gitbook_pages_dir = path_gitbook_docs_dir.join("pages"); - let path_gitbook_api_dir = path_gitbook_pages_dir.join("api"); - dirs_to_write.push(path_gitbook_dir.clone()); - dirs_to_write.push(path_gitbook_docs_dir.clone()); - dirs_to_write.push(path_gitbook_pages_dir.clone()); - dirs_to_write.push(path_gitbook_api_dir.clone()); - - // Convert definition trees into single root items so that we can parse and write markdown recursively - let mut typedef_items = HashMap::new(); - for (typedef_name, typedef_contents) in definitions { - let main = typedef_contents - .children() - .iter() - .find( - |c| matches!(c.get_name(), Some(s) if s.to_lowercase() == typedef_name.to_lowercase()), - ) - .expect("Failed to find main export for generating typedef file"); - - let children = typedef_contents - .children() - .iter() - .filter_map(|child| { - if child == main { - None - } else { - Some( - DefinitionsItemBuilder::from(child) - .with_kind(DefinitionsItemKind::Type) - .build() - .unwrap(), - ) - } - }) - .collect::>(); - - let root = DefinitionsItemBuilder::new() - .with_kind(main.kind()) - .with_name(main.get_name().unwrap()) - .with_children(main.children()) - .with_children(&children); - let root_item = root.build().expect("Failed to build root definitions item"); - - typedef_items.insert(typedef_name.to_string(), root_item); - } - - // Generate files for all subcategories - for (category_name, category_item) in typedef_items { - let path = path_gitbook_api_dir - .join(category_name.to_ascii_lowercase()) - .with_extension("md"); - let mut contents = String::new(); - write!(contents, "{GENERATED_COMMENT_TAG}\n\n")?; - generate_markdown_documentation(&mut contents, &category_item, None, 0)?; - files_to_write.push((path, post_process_docs(contents))); - } - // Write all dirs and files only when we know generation was successful - let futs_dirs = dirs_to_write - .drain(..) - .map(create_dir_all) - .collect::>(); - let futs_files = files_to_write - .drain(..) - .map(|(path, contents)| write(path, contents)) - .collect::>(); - try_join_all(futs_dirs).await?; - try_join_all(futs_files).await?; - Ok(()) -} - -fn get_name(item: &DefinitionsItem) -> Result { - item.children() - .iter() - .find_map(|child| { - if child.is_tag() { - if let Ok(DefinitionsItemTag::Class(c)) = DefinitionsItemTag::try_from(child) { - Some(c) - } else { - None - } - } else { - None - } - }) - .or_else(|| item.get_name().map(ToString::to_string)) - .context("Definitions item is missing a name") -} - -#[allow(clippy::too_many_lines)] -fn generate_markdown_documentation( - contents: &mut String, - item: &DefinitionsItem, - parent: Option<&DefinitionsItem>, - depth: usize, -) -> Result<()> { - match item.kind() { - DefinitionsItemKind::Type - | DefinitionsItemKind::Table - | DefinitionsItemKind::Property - | DefinitionsItemKind::Function => { - write!( - contents, - "\n{} {}\n", - if item.is_table() { "#" } else { "###" }, - get_name(item)? - )?; - } - DefinitionsItemKind::Description => { - let desc = item.get_value().context("Description is missing a value")?; - write!( - contents, - "\n{}\n", - if depth >= 2 { - // HACK: We know our typedefs are formatted like this and - // it looks nicer to have this bolding instead of two - // headers using "###" in the function definition - desc.replace("### Example usage", "**Example usage:**") - } else { - desc.to_string() - } - )?; - } - _ => {} - } - if item.is_function() && !item.args().is_empty() { - let args = item - .args() - .iter() - .map(|arg| format!("{}: {}", arg.name.trim(), arg.typedef.trim())) - .collect::>() - .join(", ") - .replace("_: T...", "T..."); - let func_name = item.get_name().unwrap_or("_"); - let parent_name = parent.unwrap().get_name().unwrap_or("_"); - let parent_pre = if parent_name.to_lowercase() == "uncategorized" { - String::new() - } else { - format!("{parent_name}.") - }; - write!( - contents, - "\n```lua\nfunction {parent_pre}{func_name}({args})\n```\n", - )?; - } else if item.is_type() { - write!( - contents, - "\n```lua\ntype {} = {}\n```\n", - item.get_name().unwrap_or("_"), - item.get_type().unwrap_or_else(|| "{}".to_string()).trim() - )?; - } - let descriptions = item - .children() - .iter() - .filter(|child| child.is_description()) - .collect::>(); - let properties = item - .children() - .iter() - .filter(|child| child.is_property()) - .collect::>(); - let functions = item - .children() - .iter() - .filter(|child| child.is_function()) - .collect::>(); - let types = item - .children() - .iter() - .filter(|child| child.is_type()) - .collect::>(); - for description in descriptions { - generate_markdown_documentation(contents, description, Some(item), depth + 1)?; - } - if !item.is_type() { - if !properties.is_empty() { - write!(contents, "\n\n---\n\n## Properties\n\n")?; - } - for property in properties { - generate_markdown_documentation(contents, property, Some(item), depth + 1)?; - } - if !functions.is_empty() { - write!(contents, "\n\n---\n\n## Functions\n\n")?; - } - for function in functions { - generate_markdown_documentation(contents, function, Some(item), depth + 1)?; - } - if !types.is_empty() { - write!(contents, "\n\n---\n\n## Types\n\n")?; - } - for typ in types { - generate_markdown_documentation(contents, typ, Some(item), depth + 1)?; - } - } - Ok(()) -} - -fn post_process_docs(contents: String) -> String { - let no_empty_lines = contents - .lines() - .map(|line| { - if line.chars().all(char::is_whitespace) { - "" - } else { - line - } - }) - .collect::>() - .join("\n"); - no_empty_lines - .replace("\n\n---", "\n---") - .replace("\n\n\n", "\n\n") - .replace("\n\n\n", "\n\n") -} diff --git a/src/cli/gen/mod.rs b/src/cli/gen/mod.rs deleted file mode 100644 index 1238fd1..0000000 --- a/src/cli/gen/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::collections::HashMap; - -use anyhow::Result; -use include_dir::Dir; - -use self::definitions::DefinitionsTree; - -mod gitbook_dir; -mod typedef_files; - -pub mod definitions; - -pub async fn generate_gitbook_dir_from_definitions(dir: &Dir<'_>) -> Result<()> { - let definitions = read_typedefs_dir(dir)?; - gitbook_dir::generate_from_type_definitions(definitions).await -} - -pub async fn generate_typedef_files_from_definitions(dir: &Dir<'_>) -> Result { - let contents = read_typedefs_dir_contents(dir); - typedef_files::generate_from_type_definitions(contents).await -} - -fn read_typedefs_dir_contents(dir: &Dir<'_>) -> HashMap> { - let mut definitions = HashMap::new(); - - for entry in dir.find("*.luau").unwrap() { - let entry_file = entry.as_file().unwrap(); - let entry_name = entry_file.path().file_name().unwrap().to_string_lossy(); - - let typedef_name = entry_name.trim_end_matches(".luau"); - let typedef_contents = entry_file.contents().to_vec(); - - definitions.insert(typedef_name.to_string(), typedef_contents); - } - - definitions -} - -fn read_typedefs_dir(dir: &Dir<'_>) -> Result> { - let mut definitions = HashMap::new(); - - for entry in dir.find("*.luau").unwrap() { - let entry_file = entry.as_file().unwrap(); - let entry_name = entry_file.path().file_name().unwrap().to_string_lossy(); - - let typedef_name = entry_name.trim_end_matches(".luau"); - let typedef_contents = entry_file.contents_utf8().unwrap().to_string(); - - let typedef_tree = DefinitionsTree::from_type_definitions(&typedef_contents)?; - definitions.insert(typedef_name.to_string(), typedef_tree); - } - - Ok(definitions) -} diff --git a/src/cli/setup/mod.rs b/src/cli/setup.rs similarity index 98% rename from src/cli/setup/mod.rs rename to src/cli/setup.rs index 42b0c67..4312c74 100644 --- a/src/cli/setup/mod.rs +++ b/src/cli/setup.rs @@ -10,7 +10,7 @@ use serde_json::Value as JsonValue; use super::gen::generate_typedef_files_from_definitions; -pub(crate) static TYPEDEFS_DIR: Dir<'_> = include_dir!("docs/typedefs"); +pub(crate) static TYPEDEFS_DIR: Dir<'_> = include_dir!("types"); pub(crate) static SETTING_NAME_MODE: &str = "luau-lsp.require.mode"; pub(crate) static SETTING_NAME_ALIASES: &str = "luau-lsp.require.directoryAliases"; diff --git a/docs/typedefs/FS.luau b/types/FS.luau similarity index 100% rename from docs/typedefs/FS.luau rename to types/FS.luau diff --git a/docs/typedefs/Net.luau b/types/Net.luau similarity index 100% rename from docs/typedefs/Net.luau rename to types/Net.luau diff --git a/docs/typedefs/Process.luau b/types/Process.luau similarity index 100% rename from docs/typedefs/Process.luau rename to types/Process.luau diff --git a/docs/typedefs/Roblox.luau b/types/Roblox.luau similarity index 100% rename from docs/typedefs/Roblox.luau rename to types/Roblox.luau diff --git a/docs/typedefs/Serde.luau b/types/Serde.luau similarity index 100% rename from docs/typedefs/Serde.luau rename to types/Serde.luau diff --git a/docs/typedefs/Stdio.luau b/types/Stdio.luau similarity index 100% rename from docs/typedefs/Stdio.luau rename to types/Stdio.luau diff --git a/docs/typedefs/Task.luau b/types/Task.luau similarity index 100% rename from docs/typedefs/Task.luau rename to types/Task.luau