diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 143e7c6..7eee2bf 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -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:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 04aaefb..4b0eb13 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -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",
diff --git a/Cargo.lock b/Cargo.lock
index 564eaea..c9fc97d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 46a6f5a..bc97447 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
diff --git a/README.md b/README.md
index 9c8d2b2..02ecd2f 100644
--- a/README.md
+++ b/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.
-🔎 Full list of APIs
+🔎 List of APIs
-
-console - Logging & formatting
+`console` - Logging & formatting
+`fs` - Filesystem
+`net` - Networking
+`process` - Current process & child processes
+`task` - Task scheduler & thread spawning
-```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) -> (),
-}
-```
-
-
-
-
-fs - Filesystem
-
-```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,
-}
-```
-
-
-
-
-net - Networking
-
-```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,
-}
-```
-
-
-
-
-process - Current process & child processes
-
-```lua
-type process = {
- args: { string },
- env: { [string]: string? },
- exit: (code: number?) -> (),
- spawn: (program: string, params: { string }?) -> {
- ok: boolean,
- code: number,
- stdout: string,
- stderr: string,
- },
-}
-```
-
-
-
-
-task - Task scheduler & thread spawning
-
-```lua
-type task = {
- cancel: (thread: thread) -> (),
- defer: (functionOrThread: thread | (T...) -> (...any), T...) -> thread,
- delay: (duration: number?, functionOrThread: thread | (T...) -> (...any), T...) -> thread,
- spawn: (functionOrThread: thread | (T...) -> (...any), T...) -> thread,
- wait: (duration: number?) -> (number),
-}
-```
-
-
+Documentation for individual members and types can be found using your editor of choice and [Luau LSP](https://github.com/JohnnyMorganz/luau-lsp).
@@ -174,9 +87,12 @@ Lune puts developer experience first, and as such provides type definitions and
Luau LSP
-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
diff --git a/luneDocs.json b/luneDocs.json
new file mode 100644
index 0000000..0d731fd
--- /dev/null
+++ b/luneDocs.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/luneTypes.d.luau b/luneTypes.d.luau
index 4bd9542..a5b3ad3 100644
--- a/luneTypes.d.luau
+++ b/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: (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: (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: (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),
}
diff --git a/src/cli/cli.rs b/src/cli/cli.rs
index a1359f9..643ce73 100644
--- a/src/cli/cli.rs
+++ b/src/cli/cli.rs
@@ -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
diff --git a/src/cli/gen/doc.rs b/src/cli/gen/doc.rs
new file mode 100644
index 0000000..af928fa
--- /dev/null
+++ b/src/cli/gen/doc.rs
@@ -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,
+ 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,
+ pub returns: Vec,
+ 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,
+}
diff --git a/src/cli/gen/mod.rs b/src/cli/gen/mod.rs
new file mode 100644
index 0000000..5daa0b6
--- /dev/null
+++ b/src/cli/gen/mod.rs
@@ -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 {
+ let (regex, replacement) = (
+ Regex::new(r#"declare (?P\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 {
+ 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::>();
+ 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::>();
+ doc.returns = doc
+ .returns
+ .iter()
+ .map(|ret| format!("@{namespace}/{}.{name}/return/{ret}", doc.global_name))
+ .collect::>();
+ 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))
+}
diff --git a/src/cli/gen/tag.rs b/src/cli/gen/tag.rs
new file mode 100644
index 0000000..a117604
--- /dev/null
+++ b/src/cli/gen/tag.rs
@@ -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 {
+ 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,
+}
+
+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;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.tags.into_iter()
+ }
+}
diff --git a/src/cli/gen/visitor.rs b/src/cli/gen/visitor.rs
new file mode 100644
index 0000000..44d4389
--- /dev/null
+++ b/src/cli/gen/visitor.rs
@@ -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 {
+ 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::>();
+ 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()
+ },
+ ));
+ }
+ }
+}
diff --git a/src/cli/main.rs b/src/cli/main.rs
index 3416f98..98e121d 100644
--- a/src/cli/main.rs
+++ b/src/cli/main.rs
@@ -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;