mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 04:50:36 +00:00
Implement docs file generation & docs in the luau type definitions file
This commit is contained in:
parent
ee5b67bf3a
commit
0657e05cc0
13 changed files with 1175 additions and 105 deletions
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
|
@ -79,6 +79,16 @@ jobs:
|
|||
asset_name: luneTypes.d.luau
|
||||
asset_content_type: application/x-luau
|
||||
|
||||
- name: Upload documentation file 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: luneDocs.json
|
||||
asset_name: luneDocs.json
|
||||
asset_content_type: application/json
|
||||
|
||||
release:
|
||||
needs: ["init", "create-release"]
|
||||
strategy:
|
||||
|
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -3,6 +3,7 @@
|
|||
"luau-lsp.sourcemap.enabled": false,
|
||||
"luau-lsp.types.roblox": false,
|
||||
"luau-lsp.types.definitionFiles": ["luneTypes.d.luau"],
|
||||
"luau-lsp.types.documentationFiles": ["luneDocs.json"],
|
||||
"luau-lsp.require.mode": "relativeToFile",
|
||||
// Rust
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
|
|
181
Cargo.lock
generated
181
Cargo.lock
generated
|
@ -8,6 +8,15 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.68"
|
||||
|
@ -135,6 +144,12 @@ version = "0.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[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"
|
||||
|
@ -170,6 +185,12 @@ version = "3.12.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be0fdd54b507df8f22012890aadd099979befdba27713c767993f8380112ca7c"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
|
@ -228,6 +249,12 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
|
@ -246,6 +273,19 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.24"
|
||||
|
@ -301,6 +341,12 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
|
@ -310,6 +356,34 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "full_moon"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d58cb343df2e63e8a496de3e344e5f2b97f010bc1359e994be38a99586888f5"
|
||||
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",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.25"
|
||||
|
@ -368,6 +442,12 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
|
@ -393,6 +473,16 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
|
@ -460,6 +550,29 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luau0-src"
|
||||
version = "0.5.1+luau558"
|
||||
|
@ -475,8 +588,10 @@ version = "0.1.3"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"full_moon",
|
||||
"mlua",
|
||||
"os_str_bytes",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
|
@ -548,6 +663,25 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
|
||||
dependencies = [
|
||||
"paste-impl",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
|
@ -610,6 +744,12 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.50"
|
||||
|
@ -628,6 +768,23 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
|
@ -649,6 +806,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.7"
|
||||
|
@ -691,6 +857,12 @@ dependencies = [
|
|||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
|
@ -767,6 +939,15 @@ dependencies = [
|
|||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
|
|
|
@ -26,10 +26,12 @@ panic = "abort" # Remove extra panic info
|
|||
|
||||
anyhow = "1.0.68"
|
||||
os_str_bytes = "6.4.1"
|
||||
regex = "1.7.1"
|
||||
serde_json = "1.0.91"
|
||||
smol = "1.3.0"
|
||||
ureq = "2.6.2"
|
||||
|
||||
clap = { version = "4.1.1", features = ["derive"] }
|
||||
full_moon = { version = "0.17.0", features = ["roblox"] }
|
||||
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
|
|
110
README.md
110
README.md
|
@ -33,102 +33,15 @@ Check out the examples on how to write a script in the [.lune](.lune) folder ! <
|
|||
A great starting point and walkthrough of Lune can be found in the [Hello, Lune](.lune/hello_lune.luau) example.
|
||||
|
||||
<details>
|
||||
<summary><b>🔎 Full list of APIs</b></summary>
|
||||
<summary><b>🔎 List of APIs</b></summary>
|
||||
|
||||
<details>
|
||||
<summary><b>console</b> - Logging & formatting</summary>
|
||||
`console` - Logging & formatting <br />
|
||||
`fs` - Filesystem <br />
|
||||
`net` - Networking <br />
|
||||
`process` - Current process & child processes <br />
|
||||
`task` - Task scheduler & thread spawning <br />
|
||||
|
||||
```lua
|
||||
type console = {
|
||||
resetColor: () -> (),
|
||||
setColor: (color: "black" | "red" | "green" | "yellow" | "blue" | "purple" | "cyan" | "white") -> (),
|
||||
resetStyle: () -> (),
|
||||
setStyle: (color: "bold" | "dim") -> (),
|
||||
format: (...any) -> (string),
|
||||
log: (...any) -> (),
|
||||
info: (...any) -> (),
|
||||
warn: (...any) -> (),
|
||||
error: (...any) -> (),
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>fs</b> - Filesystem</summary>
|
||||
|
||||
```lua
|
||||
type 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,
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>net</b> - Networking</summary>
|
||||
|
||||
```lua
|
||||
type net = {
|
||||
request: (config: string | {
|
||||
url: string,
|
||||
method: ("GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH")?,
|
||||
headers: { [string]: string }?,
|
||||
body: string?,
|
||||
}) -> {
|
||||
ok: boolean,
|
||||
statusCode: number,
|
||||
statusMessage: string,
|
||||
headers: { [string]: string },
|
||||
body: string,
|
||||
},
|
||||
jsonEncode: (value: any, pretty: boolean?) -> string,
|
||||
jsonDecode: (encoded: string) -> any,
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>process</b> - Current process & child processes</summary>
|
||||
|
||||
```lua
|
||||
type process = {
|
||||
args: { string },
|
||||
env: { [string]: string? },
|
||||
exit: (code: number?) -> (),
|
||||
spawn: (program: string, params: { string }?) -> {
|
||||
ok: boolean,
|
||||
code: number,
|
||||
stdout: string,
|
||||
stderr: string,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>task</b> - Task scheduler & thread spawning</summary>
|
||||
|
||||
```lua
|
||||
type task = {
|
||||
cancel: (thread: thread) -> (),
|
||||
defer: <T...>(functionOrThread: thread | (T...) -> (...any), T...) -> thread,
|
||||
delay: <T...>(duration: number?, functionOrThread: thread | (T...) -> (...any), T...) -> thread,
|
||||
spawn: <T...>(functionOrThread: thread | (T...) -> (...any), T...) -> thread,
|
||||
wait: (duration: number?) -> (number),
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
Documentation for individual members and types can be found using your editor of choice and [Luau LSP](https://github.com/JohnnyMorganz/luau-lsp).
|
||||
|
||||
</details>
|
||||
|
||||
|
@ -174,9 +87,12 @@ Lune puts developer experience first, and as such provides type definitions and
|
|||
<details>
|
||||
<summary>Luau LSP</summary>
|
||||
|
||||
1. Use `lune --download-luau-types` to download Luau types (`luneTypes.d.luau`) to the current directory
|
||||
2. Set your definition files setting to include `luneTypes.d.luau`
|
||||
3. Set the require mode setting to `relativeToFile`
|
||||
1. Set the require mode setting to `relativeToFile`
|
||||
2. Use `lune --download-luau-types` to download Luau types (`luneTypes.d.luau`) to the current directory
|
||||
3. Set your definition files setting to include `luneTypes.d.luau`
|
||||
4. Generate the documentation file using `lune --generate-docs-file`
|
||||
- NOTE: This is a temporary solution and a docs file separate from type definitions will not be necessary in the future
|
||||
5. Set your documentation files setting to include `luneDocs.json`
|
||||
|
||||
An example of these settings can be found in the [.vscode](.vscode) folder in this repository
|
||||
|
||||
|
|
305
luneDocs.json
Normal file
305
luneDocs.json
Normal file
|
@ -0,0 +1,305 @@
|
|||
{
|
||||
"@roblox/global/console": {
|
||||
"code_sample": "",
|
||||
"documentation": "Logging & formatting",
|
||||
"keys": {
|
||||
"console": "@roblox/global/console.console"
|
||||
},
|
||||
"learn_more_link": ""
|
||||
},
|
||||
"@roblox/global/console.error": {
|
||||
"code_sample": "",
|
||||
"documentation": "Prints arguments as a human-readable string with syntax highlighting for tables to stderr.\n\nThis will also prepend an [ERROR] tag at the beginning of the message.\n\nUsing this function will automatically set the exit code of the process\nto 1, unless it gets manually specified afterwards using `process.exit`.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.format": {
|
||||
"code_sample": "",
|
||||
"documentation": "Formats arguments into a human-readable string with syntax highlighting for tables.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.info": {
|
||||
"code_sample": "",
|
||||
"documentation": "Prints arguments as a human-readable string with syntax highlighting for tables to stdout.\n\nThis will also prepend an [INFO] tag at the beginning of the message.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.log": {
|
||||
"code_sample": "",
|
||||
"documentation": "Prints arguments as a human-readable string with syntax highlighting for tables to stdout.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.resetColor": {
|
||||
"code_sample": "",
|
||||
"documentation": "Resets the current persistent output color.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.resetStyle": {
|
||||
"code_sample": "",
|
||||
"documentation": "Resets the current persistent output style.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.setColor": {
|
||||
"code_sample": "",
|
||||
"documentation": "Sets the current persistent output color.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.setStyle": {
|
||||
"code_sample": "",
|
||||
"documentation": "Sets the current persistent output style.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/console.warn": {
|
||||
"code_sample": "",
|
||||
"documentation": "Prints arguments as a human-readable string with syntax highlighting for tables to stdout.\n\nThis will also prepend an [INFO] tag at the beginning of the message.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs": {
|
||||
"code_sample": "",
|
||||
"documentation": "Filesystem",
|
||||
"keys": {
|
||||
"fs": "@roblox/global/fs.fs"
|
||||
},
|
||||
"learn_more_link": ""
|
||||
},
|
||||
"@roblox/global/fs.isDir": {
|
||||
"code_sample": "",
|
||||
"documentation": "Checks if a given path is a directory.\n\nAn error will be thrown in the following situations:\n\n* The current process lacks permissions to read at `path`.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.isFile": {
|
||||
"code_sample": "",
|
||||
"documentation": "Checks if a given path is a file.\n\nAn error will be thrown in the following situations:\n\n* The current process lacks permissions to read at `path`.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.readDir": {
|
||||
"code_sample": "",
|
||||
"documentation": "Reads entries in a directory at `path`.\n\nAn error will be thrown in the following situations:\n\n* `path` does not point to an existing directory.\n* The current process lacks permissions to read the contents of the directory.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.readFile": {
|
||||
"code_sample": "",
|
||||
"documentation": "Reads a file at `path`.\n\nAn error will be thrown in the following situations:\n\n* `path` does not point to an existing file.\n* The current process lacks permissions to read the file.\n* The contents of the file cannot be read as a UTF-8 string.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.removeDir": {
|
||||
"code_sample": "",
|
||||
"documentation": "Removes a directory and all of its contents.\n\nAn error will be thrown in the following situations:\n\n* `path` is not an existing and empty directory.\n* The current process lacks permissions to remove the directory.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.removeFile": {
|
||||
"code_sample": "",
|
||||
"documentation": "Removes a file.\n\nAn error will be thrown in the following situations:\n\n* `path` does not point to an existing file.\n* The current process lacks permissions to remove the file.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.writeDir": {
|
||||
"code_sample": "",
|
||||
"documentation": "Creates a directory and its parent directories if they are missing.\n\nAn error will be thrown in the following situations:\n\n* `path` already points to an existing file or directory.\n* The current process lacks permissions to create the directory or its missing parents.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/fs.writeFile": {
|
||||
"code_sample": "",
|
||||
"documentation": "Writes to a file at `path`.\n\nAn error will be thrown in the following situations:\n\n* The file's parent directory does not exist.\n* The current process lacks permissions to write to the file.\n* Some other I/O error occurred.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/net": {
|
||||
"code_sample": "",
|
||||
"documentation": "Networking",
|
||||
"keys": {
|
||||
"net": "@roblox/global/net.net"
|
||||
},
|
||||
"learn_more_link": ""
|
||||
},
|
||||
"@roblox/global/net.jsonDecode": {
|
||||
"code_sample": "",
|
||||
"documentation": "Decodes the given JSON string into a lua value.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/net.jsonEncode": {
|
||||
"code_sample": "",
|
||||
"documentation": "Encodes the given value as JSON.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/net.request": {
|
||||
"code_sample": "",
|
||||
"documentation": "Sends an HTTP request using the given url and / or parameters, and returns a dictionary that describes the response received.\n\nOnly throws an error if a miscellaneous network or I/O error occurs, never for unsuccessful status codes.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/process": {
|
||||
"code_sample": "",
|
||||
"documentation": "Current process & child processes",
|
||||
"keys": {
|
||||
"process": "@roblox/global/process.process"
|
||||
},
|
||||
"learn_more_link": ""
|
||||
},
|
||||
"@roblox/global/process.args": {
|
||||
"code_sample": "",
|
||||
"documentation": "The arguments given when running the Lune script.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/process.env": {
|
||||
"code_sample": "",
|
||||
"documentation": "Current environment variables for this process.\n\nSetting a value on this table will set the corresponding environment variable.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/process.exit": {
|
||||
"code_sample": "",
|
||||
"documentation": "Exits the currently running script as soon as possible with the given exit code.\n\nExit code 0 is treated as a successful exit, any other value is treated as an error.\n\nSetting the exit code using this function will override any otherwise automatic exit code.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/process.spawn": {
|
||||
"code_sample": "",
|
||||
"documentation": "Spawns a child process that will run the program `program` with the given `params` as arguments, and returns a dictionary that describes the final status and ouput of the child process.",
|
||||
"learn_more_link": "",
|
||||
"params": [],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/task": {
|
||||
"code_sample": "",
|
||||
"documentation": "Task scheduler & thread spawning",
|
||||
"keys": {
|
||||
"task": "@roblox/global/task.task"
|
||||
},
|
||||
"learn_more_link": ""
|
||||
},
|
||||
"@roblox/global/task.cancel": {
|
||||
"code_sample": "",
|
||||
"documentation": "Stops a currently scheduled thread from resuming.",
|
||||
"learn_more_link": "",
|
||||
"params": [
|
||||
{
|
||||
"documentation": "@roblox/global/task.cancel/param/0",
|
||||
"name": "thread"
|
||||
}
|
||||
],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/task.cancel/param/0": {
|
||||
"documentation": "The thread to cancel"
|
||||
},
|
||||
"@roblox/global/task.defer": {
|
||||
"code_sample": "",
|
||||
"documentation": "Defers a thread or function to run at the end of the current task queue.",
|
||||
"learn_more_link": "",
|
||||
"params": [
|
||||
{
|
||||
"documentation": "@roblox/global/task.defer/param/0",
|
||||
"name": "functionOrThread"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
"@roblox/global/task.defer/return/0"
|
||||
]
|
||||
},
|
||||
"@roblox/global/task.defer/param/0": {
|
||||
"documentation": "The function or thread to defer"
|
||||
},
|
||||
"@roblox/global/task.defer/return/0": {
|
||||
"documentation": "The thread that will be deferred"
|
||||
},
|
||||
"@roblox/global/task.delay": {
|
||||
"code_sample": "",
|
||||
"documentation": "Delays a thread or function to run after `duration` seconds.",
|
||||
"learn_more_link": "",
|
||||
"params": [
|
||||
{
|
||||
"documentation": "@roblox/global/task.delay/param/0",
|
||||
"name": "functionOrThread"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
"@roblox/global/task.delay/return/0"
|
||||
]
|
||||
},
|
||||
"@roblox/global/task.delay/param/0": {
|
||||
"documentation": "The function or thread to delay"
|
||||
},
|
||||
"@roblox/global/task.delay/return/0": {
|
||||
"documentation": "The thread that will be delayed"
|
||||
},
|
||||
"@roblox/global/task.spawn": {
|
||||
"code_sample": "",
|
||||
"documentation": "Instantly runs a thread or function.\n\nIf the spawned task yields, the thread that spawned the task\nwill resume, letting the spawned task run in the background.",
|
||||
"learn_more_link": "",
|
||||
"params": [
|
||||
{
|
||||
"documentation": "@roblox/global/task.spawn/param/0",
|
||||
"name": "functionOrThread"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
"@roblox/global/task.spawn/return/0"
|
||||
]
|
||||
},
|
||||
"@roblox/global/task.spawn/param/0": {
|
||||
"documentation": "The function or thread to spawn"
|
||||
},
|
||||
"@roblox/global/task.spawn/return/0": {
|
||||
"documentation": "The thread that was spawned"
|
||||
},
|
||||
"@roblox/global/task.wait": {
|
||||
"code_sample": "",
|
||||
"documentation": "Waits for the given duration, with a minimum wait time of 10 milliseconds.",
|
||||
"learn_more_link": "",
|
||||
"params": [
|
||||
{
|
||||
"documentation": "@roblox/global/task.wait/param/0",
|
||||
"name": "duration"
|
||||
}
|
||||
],
|
||||
"returns": [
|
||||
"@roblox/global/task.wait/return/0"
|
||||
]
|
||||
},
|
||||
"@roblox/global/task.wait/param/0": {
|
||||
"documentation": "The amount of time to wait"
|
||||
},
|
||||
"@roblox/global/task.wait/return/0": {
|
||||
"documentation": "The exact amount of time waited"
|
||||
}
|
||||
}
|
251
luneTypes.d.luau
251
luneTypes.d.luau
|
@ -1,29 +1,192 @@
|
|||
-- Lune v0.1.3
|
||||
|
||||
--[=[
|
||||
@class console
|
||||
|
||||
Logging & formatting
|
||||
]=]
|
||||
declare console: {
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Resets the current persistent output color.
|
||||
]=]
|
||||
resetColor: () -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Sets the current persistent output color.
|
||||
]=]
|
||||
setColor: (color: "black" | "red" | "green" | "yellow" | "blue" | "purple" | "cyan" | "white") -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Resets the current persistent output style.
|
||||
]=]
|
||||
resetStyle: () -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Sets the current persistent output style.
|
||||
]=]
|
||||
setStyle: (style: "bold" | "dim") -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Formats arguments into a human-readable string with syntax highlighting for tables.
|
||||
]=]
|
||||
format: (...any) -> (string),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Prints arguments as a human-readable string with syntax highlighting for tables to stdout.
|
||||
]=]
|
||||
log: (...any) -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Prints arguments as a human-readable string with syntax highlighting for tables to stdout.
|
||||
|
||||
This will also prepend an [INFO] tag at the beginning of the message.
|
||||
]=]
|
||||
info: (...any) -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Prints arguments as a human-readable string with syntax highlighting for tables to stdout.
|
||||
|
||||
This will also prepend an [INFO] tag at the beginning of the message.
|
||||
]=]
|
||||
warn: (...any) -> (),
|
||||
--[=[
|
||||
@within console
|
||||
|
||||
Prints arguments as a human-readable string with syntax highlighting for tables to stderr.
|
||||
|
||||
This will also prepend an [ERROR] tag at the beginning of the message.
|
||||
|
||||
Using this function will automatically set the exit code of the process
|
||||
to 1, unless it gets manually specified afterwards using `process.exit`.
|
||||
]=]
|
||||
error: (...any) -> (),
|
||||
}
|
||||
|
||||
--[=[
|
||||
@class fs
|
||||
|
||||
Filesystem
|
||||
]=]
|
||||
declare fs: {
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Reads a file at `path`.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* `path` does not point to an existing file.
|
||||
* The current process lacks permissions to read the file.
|
||||
* The contents of the file cannot be read as a UTF-8 string.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
readFile: (path: string) -> string,
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Reads entries in a directory at `path`.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* `path` does not point to an existing directory.
|
||||
* The current process lacks permissions to read the contents of the directory.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
readDir: (path: string) -> { string },
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Writes to a file at `path`.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* The file's parent directory does not exist.
|
||||
* The current process lacks permissions to write to the file.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
writeFile: (path: string, contents: string) -> (),
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Creates a directory and its parent directories if they are missing.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* `path` already points to an existing file or directory.
|
||||
* The current process lacks permissions to create the directory or its missing parents.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
writeDir: (path: string) -> (),
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Removes a file.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* `path` does not point to an existing file.
|
||||
* The current process lacks permissions to remove the file.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
removeFile: (path: string) -> (),
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Removes a directory and all of its contents.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* `path` is not an existing and empty directory.
|
||||
* The current process lacks permissions to remove the directory.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
removeDir: (path: string) -> (),
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Checks if a given path is a file.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* The current process lacks permissions to read at `path`.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
isFile: (path: string) -> boolean,
|
||||
--[=[
|
||||
@within fs
|
||||
|
||||
Checks if a given path is a directory.
|
||||
|
||||
An error will be thrown in the following situations:
|
||||
|
||||
* The current process lacks permissions to read at `path`.
|
||||
* Some other I/O error occurred.
|
||||
]=]
|
||||
isDir: (path: string) -> boolean,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@class net
|
||||
|
||||
Networking
|
||||
]=]
|
||||
declare net: {
|
||||
--[=[
|
||||
@within net
|
||||
|
||||
Sends an HTTP request using the given url and / or parameters, and returns a dictionary that describes the response received.
|
||||
|
||||
Only throws an error if a miscellaneous network or I/O error occurs, never for unsuccessful status codes.
|
||||
]=]
|
||||
request: (config: string | {
|
||||
url: string,
|
||||
method: ("GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH")?,
|
||||
|
@ -36,14 +199,55 @@ declare net: {
|
|||
headers: { [string]: string },
|
||||
body: string,
|
||||
},
|
||||
--[=[
|
||||
@within net
|
||||
|
||||
Encodes the given value as JSON.
|
||||
]=]
|
||||
jsonEncode: (value: any, pretty: boolean?) -> string,
|
||||
--[=[
|
||||
@within net
|
||||
|
||||
Decodes the given JSON string into a lua value.
|
||||
]=]
|
||||
jsonDecode: (encoded: string) -> any,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@class process
|
||||
|
||||
Current process & child processes
|
||||
]=]
|
||||
declare process: {
|
||||
--[=[
|
||||
@within process
|
||||
|
||||
The arguments given when running the Lune script.
|
||||
]=]
|
||||
args: { string },
|
||||
--[=[
|
||||
@within process
|
||||
|
||||
Current environment variables for this process.
|
||||
|
||||
Setting a value on this table will set the corresponding environment variable.
|
||||
]=]
|
||||
env: { [string]: string? },
|
||||
--[=[
|
||||
@within process
|
||||
|
||||
Exits the currently running script as soon as possible with the given exit code.
|
||||
|
||||
Exit code 0 is treated as a successful exit, any other value is treated as an error.
|
||||
|
||||
Setting the exit code using this function will override any otherwise automatic exit code.
|
||||
]=]
|
||||
exit: (code: number?) -> (),
|
||||
--[=[
|
||||
@within process
|
||||
|
||||
Spawns a child process that will run the program `program` with the given `params` as arguments, and returns a dictionary that describes the final status and ouput of the child process.
|
||||
]=]
|
||||
spawn: (program: string, params: { string }?) -> {
|
||||
ok: boolean,
|
||||
code: number,
|
||||
|
@ -52,10 +256,57 @@ declare process: {
|
|||
},
|
||||
}
|
||||
|
||||
--[=[
|
||||
@class task
|
||||
|
||||
Task scheduler & thread spawning
|
||||
]=]
|
||||
declare task: {
|
||||
--[=[
|
||||
@within task
|
||||
|
||||
Stops a currently scheduled thread from resuming.
|
||||
|
||||
@param thread The thread to cancel
|
||||
]=]
|
||||
cancel: (thread: thread) -> (),
|
||||
--[=[
|
||||
@within task
|
||||
|
||||
Defers a thread or function to run at the end of the current task queue.
|
||||
|
||||
@param functionOrThread The function or thread to defer
|
||||
@return The thread that will be deferred
|
||||
]=]
|
||||
defer: <T...>(functionOrThread: thread | (T...) -> (...any), T...) -> thread,
|
||||
--[=[
|
||||
@within task
|
||||
|
||||
Delays a thread or function to run after `duration` seconds.
|
||||
|
||||
@param functionOrThread The function or thread to delay
|
||||
@return The thread that will be delayed
|
||||
]=]
|
||||
delay: <T...>(duration: number?, functionOrThread: thread | (T...) -> (...any), T...) -> thread,
|
||||
--[=[
|
||||
@within task
|
||||
|
||||
Instantly runs a thread or function.
|
||||
|
||||
If the spawned task yields, the thread that spawned the task
|
||||
will resume, letting the spawned task run in the background.
|
||||
|
||||
@param functionOrThread The function or thread to spawn
|
||||
@return The thread that was spawned
|
||||
]=]
|
||||
spawn: <T...>(functionOrThread: thread | (T...) -> (...any), T...) -> thread,
|
||||
--[=[
|
||||
@within task
|
||||
|
||||
Waits for the given duration, with a minimum wait time of 10 milliseconds.
|
||||
|
||||
@param duration The amount of time to wait
|
||||
@return The exact amount of time waited
|
||||
]=]
|
||||
wait: (duration: number?) -> (number),
|
||||
}
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
use std::{fs::read_to_string, process::ExitCode};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
|
||||
use lune::Lune;
|
||||
use smol::fs::{read_to_string, write};
|
||||
|
||||
use crate::utils::{
|
||||
files::find_parse_file_path,
|
||||
github::Client as GithubClient,
|
||||
listing::{find_lune_scripts, print_lune_scripts, sort_lune_scripts},
|
||||
use crate::{
|
||||
gen::generate_docs_json_from_definitions,
|
||||
utils::{
|
||||
files::find_parse_file_path,
|
||||
github::Client as GithubClient,
|
||||
listing::{find_lune_scripts, print_lune_scripts, sort_lune_scripts},
|
||||
},
|
||||
};
|
||||
|
||||
const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
|
||||
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
||||
const LUNE_DOCS_FILE_NAME: &str = "luneDocs.json";
|
||||
|
||||
/// Lune CLI
|
||||
#[derive(Parser, Debug, Default)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Cli {
|
||||
/// Path to the file to run, or the name
|
||||
/// of a luau file in a lune directory
|
||||
|
@ -37,6 +43,10 @@ pub struct Cli {
|
|||
/// definitions file to the current directory
|
||||
#[clap(long)]
|
||||
download_luau_types: bool,
|
||||
/// Pass this flag to generate the Lune documentation file
|
||||
/// from a luau type definitions file in the current directory
|
||||
#[clap(long)]
|
||||
generate_docs_file: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -123,10 +133,18 @@ impl Cli {
|
|||
.await?;
|
||||
}
|
||||
}
|
||||
// Generate docs file, if wanted
|
||||
if self.generate_docs_file {
|
||||
let defs_contents = read_to_string(LUNE_LUAU_FILE_NAME).await?;
|
||||
let docs_root = generate_docs_json_from_definitions(&defs_contents, "roblox/global")?;
|
||||
let docs_contents = serde_json::to_string_pretty(&docs_root)?;
|
||||
write(LUNE_DOCS_FILE_NAME, &docs_contents).await?;
|
||||
}
|
||||
if self.script_path.is_none() {
|
||||
// Only downloading types without running a script is completely
|
||||
// fine, and we should just exit the program normally afterwards
|
||||
if download_types_requested {
|
||||
// Same thing goes for generating the docs file
|
||||
if download_types_requested || self.generate_docs_file {
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
// HACK: We know that we didn't get any arguments here but since
|
||||
|
@ -138,7 +156,7 @@ impl Cli {
|
|||
}
|
||||
// Parse and read the wanted file
|
||||
let file_path = find_parse_file_path(&self.script_path.unwrap())?;
|
||||
let file_contents = read_to_string(&file_path)?;
|
||||
let file_contents = read_to_string(&file_path).await?;
|
||||
// Display the file path relative to cwd with no extensions in stack traces
|
||||
let file_display_name = file_path.with_extension("").display().to_string();
|
||||
// Create a new lune object with all globals & run the script
|
||||
|
|
46
src/cli/gen/doc.rs
Normal file
46
src/cli/gen/doc.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct DocsGlobal {
|
||||
pub documentation: String,
|
||||
pub keys: HashMap<String, String>,
|
||||
pub learn_more_link: String,
|
||||
pub code_sample: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct DocsFunctionParamLink {
|
||||
pub name: String,
|
||||
pub documentation: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct DocsFunction {
|
||||
#[serde(skip)]
|
||||
pub global_name: String,
|
||||
pub documentation: String,
|
||||
pub params: Vec<DocsFunctionParamLink>,
|
||||
pub returns: Vec<String>,
|
||||
pub learn_more_link: String,
|
||||
pub code_sample: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct DocsParam {
|
||||
#[serde(skip)]
|
||||
pub global_name: String,
|
||||
#[serde(skip)]
|
||||
pub function_name: String,
|
||||
pub documentation: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct DocsReturn {
|
||||
#[serde(skip)]
|
||||
pub global_name: String,
|
||||
#[serde(skip)]
|
||||
pub function_name: String,
|
||||
pub documentation: String,
|
||||
}
|
83
src/cli/gen/mod.rs
Normal file
83
src/cli/gen/mod.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use full_moon::{parse as parse_luau_ast, visitors::Visitor};
|
||||
|
||||
mod doc;
|
||||
mod tag;
|
||||
mod visitor;
|
||||
|
||||
use self::{doc::DocsFunctionParamLink, visitor::DocumentationVisitor};
|
||||
|
||||
fn parse_definitions(contents: &str) -> Result<DocumentationVisitor> {
|
||||
let (regex, replacement) = (
|
||||
Regex::new(r#"declare (?P<n>\w+): \{"#).unwrap(),
|
||||
r#"export type $n = {"#,
|
||||
);
|
||||
let defs_ast = parse_luau_ast(®ex.replace_all(contents, replacement))?;
|
||||
let mut visitor = DocumentationVisitor::new();
|
||||
visitor.visit_ast(&defs_ast);
|
||||
Ok(visitor)
|
||||
}
|
||||
|
||||
pub fn generate_docs_json_from_definitions(contents: &str, namespace: &str) -> Result<Value> {
|
||||
let visitor = parse_definitions(contents)?;
|
||||
/*
|
||||
Extract globals, functions, params, returns from the visitor
|
||||
Here we will also convert the plain names into proper namespaced names according to the spec at
|
||||
https://raw.githubusercontent.com/MaximumADHD/Roblox-Client-Tracker/roblox/api-docs/en-us.json
|
||||
*/
|
||||
let mut map = Map::new();
|
||||
for (name, mut doc) in visitor.globals {
|
||||
doc.keys = doc
|
||||
.keys
|
||||
.iter()
|
||||
.map(|(key, value)| (key.clone(), format!("@{namespace}/{name}.{value}")))
|
||||
.collect::<HashMap<String, String>>();
|
||||
map.insert(format!("@{namespace}/{name}"), serde_json::to_value(doc)?);
|
||||
}
|
||||
for (name, mut doc) in visitor.functions {
|
||||
doc.params = doc
|
||||
.params
|
||||
.iter()
|
||||
.map(|param| DocsFunctionParamLink {
|
||||
name: param.name.clone(),
|
||||
documentation: format!(
|
||||
"@{namespace}/{}.{name}/param/{}",
|
||||
doc.global_name, param.documentation
|
||||
),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
doc.returns = doc
|
||||
.returns
|
||||
.iter()
|
||||
.map(|ret| format!("@{namespace}/{}.{name}/return/{ret}", doc.global_name))
|
||||
.collect::<Vec<_>>();
|
||||
map.insert(
|
||||
format!("@{namespace}/{}.{name}", doc.global_name),
|
||||
serde_json::to_value(doc)?,
|
||||
);
|
||||
}
|
||||
for (name, doc) in visitor.params {
|
||||
map.insert(
|
||||
format!(
|
||||
"@{namespace}/{}.{}/param/{name}",
|
||||
doc.global_name, doc.function_name
|
||||
),
|
||||
serde_json::to_value(doc)?,
|
||||
);
|
||||
}
|
||||
for (name, doc) in visitor.returns {
|
||||
map.insert(
|
||||
format!(
|
||||
"@{namespace}/{}.{}/return/{name}",
|
||||
doc.global_name, doc.function_name
|
||||
),
|
||||
serde_json::to_value(doc)?,
|
||||
);
|
||||
}
|
||||
Ok(Value::Object(map))
|
||||
}
|
64
src/cli/gen/tag.rs
Normal file
64
src/cli/gen/tag.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use anyhow::{bail, Result};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DocsTagKind {
|
||||
Class,
|
||||
Within,
|
||||
Param,
|
||||
Return,
|
||||
}
|
||||
|
||||
impl DocsTagKind {
|
||||
pub fn parse(s: &str) -> Result<Self> {
|
||||
match s.trim().to_ascii_lowercase().as_ref() {
|
||||
"class" => Ok(Self::Class),
|
||||
"within" => Ok(Self::Within),
|
||||
"param" => Ok(Self::Param),
|
||||
"return" => Ok(Self::Return),
|
||||
s => bail!("Unknown docs tag: '{}'", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocsTag {
|
||||
pub kind: DocsTagKind,
|
||||
pub name: String,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocsTagList {
|
||||
tags: Vec<DocsTag>,
|
||||
}
|
||||
|
||||
impl DocsTagList {
|
||||
pub fn new() -> Self {
|
||||
Self { tags: vec![] }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, tag: DocsTag) {
|
||||
self.tags.push(tag);
|
||||
}
|
||||
|
||||
pub fn contains(&mut self, kind: DocsTagKind) -> bool {
|
||||
self.tags.iter().any(|tag| tag.kind == kind)
|
||||
}
|
||||
|
||||
pub fn find(&mut self, kind: DocsTagKind) -> Option<&DocsTag> {
|
||||
self.tags.iter().find(|tag| tag.kind == kind)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tags.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DocsTagList {
|
||||
type Item = DocsTag;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.tags.into_iter()
|
||||
}
|
||||
}
|
188
src/cli/gen/visitor.rs
Normal file
188
src/cli/gen/visitor.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
use full_moon::{
|
||||
ast::types::{ExportedTypeDeclaration, TypeField, TypeFieldKey},
|
||||
tokenizer::{Token, TokenType},
|
||||
visitors::Visitor,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
use super::{
|
||||
doc::{DocsFunction, DocsFunctionParamLink, DocsGlobal, DocsParam, DocsReturn},
|
||||
tag::{DocsTag, DocsTagKind, DocsTagList},
|
||||
};
|
||||
|
||||
pub struct DocumentationVisitor {
|
||||
pub globals: Vec<(String, DocsGlobal)>,
|
||||
pub functions: Vec<(String, DocsFunction)>,
|
||||
pub params: Vec<(String, DocsParam)>,
|
||||
pub returns: Vec<(String, DocsReturn)>,
|
||||
tag_regex: Regex,
|
||||
}
|
||||
|
||||
impl DocumentationVisitor {
|
||||
pub fn new() -> Self {
|
||||
let tag_regex = Regex::new(r#"^@(\w+)\s+(\w+)(.*)$"#).unwrap();
|
||||
Self {
|
||||
globals: vec![],
|
||||
functions: vec![],
|
||||
params: vec![],
|
||||
returns: vec![],
|
||||
tag_regex,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_moonwave_style_tag(&self, line: &str) -> Option<DocsTag> {
|
||||
if self.tag_regex.is_match(line) {
|
||||
let captures = self.tag_regex.captures(line).unwrap();
|
||||
let tag_kind = captures.get(1).unwrap().as_str();
|
||||
let tag_name = captures.get(2).unwrap().as_str();
|
||||
let tag_contents = captures.get(3).unwrap().as_str();
|
||||
Some(DocsTag {
|
||||
kind: DocsTagKind::parse(tag_kind).unwrap(),
|
||||
name: tag_name.to_string(),
|
||||
contents: tag_contents.to_string(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_moonwave_style_comment(&self, comment: &str) -> (String, DocsTagList) {
|
||||
let lines = comment.lines().map(str::trim).collect::<Vec<_>>();
|
||||
let indent_len = lines.iter().fold(usize::MAX, |acc, line| {
|
||||
let first = line.chars().enumerate().find_map(|(idx, ch)| {
|
||||
if ch.is_alphanumeric() {
|
||||
Some(idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(first_alphanumeric) = first {
|
||||
if first_alphanumeric > 0 {
|
||||
acc.min(first_alphanumeric - 1)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
let unindented_lines = lines.iter().map(|line| &line[indent_len..]);
|
||||
let mut doc_lines = Vec::new();
|
||||
let mut doc_tags = DocsTagList::new();
|
||||
for line in unindented_lines {
|
||||
if let Some(tag) = self.parse_moonwave_style_tag(line) {
|
||||
doc_tags.push(tag);
|
||||
} else {
|
||||
doc_lines.push(line);
|
||||
}
|
||||
}
|
||||
(doc_lines.join("\n").trim().to_owned(), doc_tags)
|
||||
}
|
||||
|
||||
fn extract_moonwave_comment(&mut self, token: &Token) -> Option<(String, DocsTagList)> {
|
||||
if let TokenType::MultiLineComment { comment, .. } = token.token_type() {
|
||||
let (doc, tags) = self.parse_moonwave_style_comment(comment);
|
||||
if doc.is_empty() && tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((doc, tags))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor for DocumentationVisitor {
|
||||
fn visit_exported_type_declaration(&mut self, node: &ExportedTypeDeclaration) {
|
||||
for token in node.export_token().leading_trivia() {
|
||||
if let Some((doc, mut tags)) = self.extract_moonwave_comment(token) {
|
||||
if tags.contains(DocsTagKind::Class) {
|
||||
self.globals.push((
|
||||
node.type_declaration().type_name().token().to_string(),
|
||||
DocsGlobal {
|
||||
documentation: doc,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_type_field(&mut self, node: &TypeField) {
|
||||
// Parse out names, moonwave comments from the ast
|
||||
let mut parsed_data = Vec::new();
|
||||
if let TypeFieldKey::Name(name) = node.key() {
|
||||
for token in name.leading_trivia() {
|
||||
if let Some((doc, mut tags)) = self.extract_moonwave_comment(token) {
|
||||
if let Some(within) = tags.find(DocsTagKind::Within).map(ToOwned::to_owned) {
|
||||
parsed_data.push((within.name, name, doc, tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (global_name, name, doc, tags) in parsed_data {
|
||||
// Find the global definition, which is guaranteed to
|
||||
// be visited and parsed before its inner members, and
|
||||
// add a ref to the found function / member to it
|
||||
let name = name.token().to_string();
|
||||
for (name, global) in &mut self.globals {
|
||||
if name == &global_name {
|
||||
global.keys.insert(name.clone(), name.clone());
|
||||
}
|
||||
}
|
||||
// Look through tags to find and create doc params and returns
|
||||
let mut param_links = Vec::new();
|
||||
let mut return_links = Vec::new();
|
||||
for tag in tags {
|
||||
match tag.kind {
|
||||
DocsTagKind::Param => {
|
||||
let idx_string = param_links.len().to_string();
|
||||
self.params.push((
|
||||
idx_string.clone(),
|
||||
DocsParam {
|
||||
global_name: global_name.clone(),
|
||||
function_name: name.clone(),
|
||||
documentation: tag.contents.trim().to_owned(),
|
||||
},
|
||||
));
|
||||
param_links.push(DocsFunctionParamLink {
|
||||
name: tag.name.clone(),
|
||||
documentation: idx_string.clone(),
|
||||
});
|
||||
}
|
||||
DocsTagKind::Return => {
|
||||
// NOTE: Returns don't have names but we still parse
|
||||
// them as such, so we should concat name & contents
|
||||
let doc = format!("{} {}", tag.name.trim(), tag.contents.trim());
|
||||
let idx_string = return_links.len().to_string();
|
||||
self.returns.push((
|
||||
idx_string.clone(),
|
||||
DocsReturn {
|
||||
global_name: global_name.clone(),
|
||||
function_name: name.clone(),
|
||||
documentation: doc,
|
||||
},
|
||||
));
|
||||
return_links.push(idx_string.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Finally, add our complete doc
|
||||
// function with links into the list
|
||||
self.functions.push((
|
||||
name,
|
||||
DocsFunction {
|
||||
global_name,
|
||||
documentation: doc,
|
||||
params: param_links,
|
||||
returns: return_links,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
#![deny(clippy::all)]
|
||||
#![warn(clippy::cargo, clippy::pedantic)]
|
||||
#![allow(clippy::needless_pass_by_value, clippy::match_bool)]
|
||||
#![allow(
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::match_bool,
|
||||
clippy::module_name_repetitions
|
||||
)]
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
|
@ -8,6 +12,7 @@ use anyhow::Result;
|
|||
use clap::Parser;
|
||||
|
||||
mod cli;
|
||||
mod gen;
|
||||
mod utils;
|
||||
|
||||
use cli::Cli;
|
||||
|
|
Loading…
Reference in a new issue