packages feed

hslua-annotations (empty) → 0.1.0

raw patch · 10 files changed

+901/−0 lines, 10 filesdep +basedep +hslua-annotationsdep +hslua-core

Dependencies added: base, hslua-annotations, hslua-core, hslua-marshalling, hslua-module-path, hslua-module-version, hslua-packaging, tasty, tasty-golden, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,64 @@+## 0.1.0+> 2026-04-05++## Summary++Initial release of `hslua-annotations`, a utility library to generate Lua type+annotations and Markdown documentation for [hslua](https://github.com/hslua/hslua)+modules and functions.++### Commits+- [[`1aa9062`](https://github.com/sondr3/hslua-annotations/commit/1aa9062)] Add Hackage version badge+- [[`2cff0c6`](https://github.com/sondr3/hslua-annotations/commit/2cff0c6)] Add publishing step to CI+- [[`5c2a75a`](https://github.com/sondr3/hslua-annotations/commit/5c2a75a)] Add category+- [[`fe00c06`](https://github.com/sondr3/hslua-annotations/commit/fe00c06)] Update docs+- [[`6b045ad`](https://github.com/sondr3/hslua-annotations/commit/6b045ad)] Remove redundant function calls+- [[`a6ede1e`](https://github.com/sondr3/hslua-annotations/commit/a6ede1e)] Change return type to table, move description and version down+- [[`ec3becb`](https://github.com/sondr3/hslua-annotations/commit/ec3becb)] Handle [] as having no return type+- [[`331f5f0`](https://github.com/sondr3/hslua-annotations/commit/331f5f0)] Remove untyped return types+- [[`6913e1d`](https://github.com/sondr3/hslua-annotations/commit/6913e1d)] Add ? for optional params in lua block+- [[`7e2f9db`](https://github.com/sondr3/hslua-annotations/commit/7e2f9db)] Extract return type and create typed lua block for functions+- [[`3e3215c`](https://github.com/sondr3/hslua-annotations/commit/3e3215c)] Indent params properly+- [[`2b21218`](https://github.com/sondr3/hslua-annotations/commit/2b21218)] Take inspiration from rustdoc+- [[`fb38417`](https://github.com/sondr3/hslua-annotations/commit/fb38417)] Render parameter docs as a table+- [[`0ab42c9`](https://github.com/sondr3/hslua-annotations/commit/0ab42c9)] Change function headings to match fields+- [[`f398aee`](https://github.com/sondr3/hslua-annotations/commit/f398aee)] Refactor to not use LambdaCase+- [[`7e495ca`](https://github.com/sondr3/hslua-annotations/commit/7e495ca)] Fix multiline function descriptions+- [[`e085ef9`](https://github.com/sondr3/hslua-annotations/commit/e085ef9)] Add type annotations to fields+- [[`f8f5819`](https://github.com/sondr3/hslua-annotations/commit/f8f5819)] Refactor field rendering some more+- [[`d9826e4`](https://github.com/sondr3/hslua-annotations/commit/d9826e4)] Create utility function for filtering unlines+- [[`5bdf26c`](https://github.com/sondr3/hslua-annotations/commit/5bdf26c)] Wrap module headings with backticks+- [[`b937e35`](https://github.com/sondr3/hslua-annotations/commit/b937e35)] Remove comments, refactor things slightly+- [[`2a8b825`](https://github.com/sondr3/hslua-annotations/commit/2a8b825)] Render class fields as a table+- [[`2a0aaac`](https://github.com/sondr3/hslua-annotations/commit/2a0aaac)] Extract 'typeToText' to shared module+- [[`47c463b`](https://github.com/sondr3/hslua-annotations/commit/47c463b)] Import old, deprecated render functionality, update to latest HsLua+- [[`ed7d2f8`](https://github.com/sondr3/hslua-annotations/commit/ed7d2f8)] Extract shared functions to shared module+- [[`004b1ed`](https://github.com/sondr3/hslua-annotations/commit/004b1ed)] Rejigger tests, add annotation and markdown test trees+- [[`9d4ed09`](https://github.com/sondr3/hslua-annotations/commit/9d4ed09)] Start sketching out markdown rendering+- [[`a8b0827`](https://github.com/sondr3/hslua-annotations/commit/a8b0827)] Split out the annotation into an internal file+- [[`a071088`](https://github.com/sondr3/hslua-annotations/commit/a071088)] Implement 'typeToText' to better align with annotations+- [[`4aa5f81`](https://github.com/sondr3/hslua-annotations/commit/4aa5f81)] Slacken text package requirements+- [[`34b76cb`](https://github.com/sondr3/hslua-annotations/commit/34b76cb)] Minor README adjustments+- [[`e7b2247`](https://github.com/sondr3/hslua-annotations/commit/e7b2247)] Rename annotateMethod... not really a thing in Lua+- [[`37901ee`](https://github.com/sondr3/hslua-annotations/commit/37901ee)] Filter out empty function descriptions+- [[`285b8ed`](https://github.com/sondr3/hslua-annotations/commit/285b8ed)] Add dependabot+- [[`d86ec4c`](https://github.com/sondr3/hslua-annotations/commit/d86ec4c)] Remove unused dependencies, move test only to test package+- [[`cb45364`](https://github.com/sondr3/hslua-annotations/commit/cb45364)] Add README and document things+- [[`605f044`](https://github.com/sondr3/hslua-annotations/commit/605f044)] Remove duplicate function annotation function+- [[`6099d8e`](https://github.com/sondr3/hslua-annotations/commit/6099d8e)] Filter out empty lines+- [[`dff4da6`](https://github.com/sondr3/hslua-annotations/commit/dff4da6)] And annotate operators+- [[`fbf3e27`](https://github.com/sondr3/hslua-annotations/commit/fbf3e27)] Use T.unlines to make multilines more readable+- [[`2a5523b`](https://github.com/sondr3/hslua-annotations/commit/2a5523b)] Annotate module functions+- [[`54843d0`](https://github.com/sondr3/hslua-annotations/commit/54843d0)] Stick with GHC 9.12 for now+- [[`5700b5e`](https://github.com/sondr3/hslua-annotations/commit/5700b5e)] Fix hlint suggestions+- [[`3c9fe23`](https://github.com/sondr3/hslua-annotations/commit/3c9fe23)] Start sketching out module annotation, test on hslua-module-path+- [[`bb48b5d`](https://github.com/sondr3/hslua-annotations/commit/bb48b5d)] Move factorial to test file+- [[`99e1e70`](https://github.com/sondr3/hslua-annotations/commit/99e1e70)] Add a Justfile for running things+- [[`84a1e1b`](https://github.com/sondr3/hslua-annotations/commit/84a1e1b)] Add parameter annotations to functions+- [[`d81deb6`](https://github.com/sondr3/hslua-annotations/commit/d81deb6)] Get the function description and output it too+- [[`f9f1de5`](https://github.com/sondr3/hslua-annotations/commit/f9f1de5)] Correct pipeline name+- [[`969bf50`](https://github.com/sondr3/hslua-annotations/commit/969bf50)] Add CI pipeline+- [[`34d0bce`](https://github.com/sondr3/hslua-annotations/commit/34d0bce)] Fix unused imports+- [[`1c1c666`](https://github.com/sondr3/hslua-annotations/commit/1c1c666)] Initial pass at getting types from HsLua+- [[`1dfaa20`](https://github.com/sondr3/hslua-annotations/commit/1dfaa20)] In the beginning there was darkness...+
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2026 Sondre Aasemoen++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ LICENSE_APACHE view
@@ -0,0 +1,176 @@+                              Apache License+                        Version 2.0, January 2004+                     http://www.apache.org/licenses/++TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++1. Definitions.++   "License" shall mean the terms and conditions for use, reproduction,+   and distribution as defined by Sections 1 through 9 of this document.++   "Licensor" shall mean the copyright owner or entity authorized by+   the copyright owner that is granting the License.++   "Legal Entity" shall mean the union of the acting entity and all+   other entities that control, are controlled by, or are under common+   control with that entity. For the purposes of this definition,+   "control" means (i) the power, direct or indirect, to cause the+   direction or management of such entity, whether by contract or+   otherwise, or (ii) ownership of fifty percent (50%) or more of the+   outstanding shares, or (iii) beneficial ownership of such entity.++   "You" (or "Your") shall mean an individual or Legal Entity+   exercising permissions granted by this License.++   "Source" form shall mean the preferred form for making modifications,+   including but not limited to software source code, documentation+   source, and configuration files.++   "Object" form shall mean any form resulting from mechanical+   transformation or translation of a Source form, including but+   not limited to compiled object code, generated documentation,+   and conversions to other media types.++   "Work" shall mean the work of authorship, whether in Source or+   Object form, made available under the License, as indicated by a+   copyright notice that is included in or attached to the work+   (an example is provided in the Appendix below).++   "Derivative Works" shall mean any work, whether in Source or Object+   form, that is based on (or derived from) the Work and for which the+   editorial revisions, annotations, elaborations, or other modifications+   represent, as a whole, an original work of authorship. For the purposes+   of this License, Derivative Works shall not include works that remain+   separable from, or merely link (or bind by name) to the interfaces of,+   the Work and Derivative Works thereof.++   "Contribution" shall mean any work of authorship, including+   the original version of the Work and any modifications or additions+   to that Work or Derivative Works thereof, that is intentionally+   submitted to Licensor for inclusion in the Work by the copyright owner+   or by an individual or Legal Entity authorized to submit on behalf of+   the copyright owner. For the purposes of this definition, "submitted"+   means any form of electronic, verbal, or written communication sent+   to the Licensor or its representatives, including but not limited to+   communication on electronic mailing lists, source code control systems,+   and issue tracking systems that are managed by, or on behalf of, the+   Licensor for the purpose of discussing and improving the Work, but+   excluding communication that is conspicuously marked or otherwise+   designated in writing by the copyright owner as "Not a Contribution."++   "Contributor" shall mean Licensor and any individual or Legal Entity+   on behalf of whom a Contribution has been received by Licensor and+   subsequently incorporated within the Work.++2. Grant of Copyright License. Subject to the terms and conditions of+   this License, each Contributor hereby grants to You a perpetual,+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable+   copyright license to reproduce, prepare Derivative Works of,+   publicly display, publicly perform, sublicense, and distribute the+   Work and such Derivative Works in Source or Object form.++3. Grant of Patent License. Subject to the terms and conditions of+   this License, each Contributor hereby grants to You a perpetual,+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable+   (except as stated in this section) patent license to make, have made,+   use, offer to sell, sell, import, and otherwise transfer the Work,+   where such license applies only to those patent claims licensable+   by such Contributor that are necessarily infringed by their+   Contribution(s) alone or by combination of their Contribution(s)+   with the Work to which such Contribution(s) was submitted. If You+   institute patent litigation against any entity (including a+   cross-claim or counterclaim in a lawsuit) alleging that the Work+   or a Contribution incorporated within the Work constitutes direct+   or contributory patent infringement, then any patent licenses+   granted to You under this License for that Work shall terminate+   as of the date such litigation is filed.++4. Redistribution. You may reproduce and distribute copies of the+   Work or Derivative Works thereof in any medium, with or without+   modifications, and in Source or Object form, provided that You+   meet the following conditions:++   (a) You must give any other recipients of the Work or+       Derivative Works a copy of this License; and++   (b) You must cause any modified files to carry prominent notices+       stating that You changed the files; and++   (c) You must retain, in the Source form of any Derivative Works+       that You distribute, all copyright, patent, trademark, and+       attribution notices from the Source form of the Work,+       excluding those notices that do not pertain to any part of+       the Derivative Works; and++   (d) If the Work includes a "NOTICE" text file as part of its+       distribution, then any Derivative Works that You distribute must+       include a readable copy of the attribution notices contained+       within such NOTICE file, excluding those notices that do not+       pertain to any part of the Derivative Works, in at least one+       of the following places: within a NOTICE text file distributed+       as part of the Derivative Works; within the Source form or+       documentation, if provided along with the Derivative Works; or,+       within a display generated by the Derivative Works, if and+       wherever such third-party notices normally appear. The contents+       of the NOTICE file are for informational purposes only and+       do not modify the License. You may add Your own attribution+       notices within Derivative Works that You distribute, alongside+       or as an addendum to the NOTICE text from the Work, provided+       that such additional attribution notices cannot be construed+       as modifying the License.++   You may add Your own copyright statement to Your modifications and+   may provide additional or different license terms and conditions+   for use, reproduction, or distribution of Your modifications, or+   for any such Derivative Works as a whole, provided Your use,+   reproduction, and distribution of the Work otherwise complies with+   the conditions stated in this License.++5. Submission of Contributions. Unless You explicitly state otherwise,+   any Contribution intentionally submitted for inclusion in the Work+   by You to the Licensor shall be under the terms and conditions of+   this License, without any additional terms or conditions.+   Notwithstanding the above, nothing herein shall supersede or modify+   the terms of any separate license agreement you may have executed+   with Licensor regarding such Contributions.++6. Trademarks. This License does not grant permission to use the trade+   names, trademarks, service marks, or product names of the Licensor,+   except as required for reasonable and customary use in describing the+   origin of the Work and reproducing the content of the NOTICE file.++7. Disclaimer of Warranty. Unless required by applicable law or+   agreed to in writing, Licensor provides the Work (and each+   Contributor provides its Contributions) on an "AS IS" BASIS,+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+   implied, including, without limitation, any warranties or conditions+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+   PARTICULAR PURPOSE. You are solely responsible for determining the+   appropriateness of using or redistributing the Work and assume any+   risks associated with Your exercise of permissions under this License.++8. Limitation of Liability. In no event and under no legal theory,+   whether in tort (including negligence), contract, or otherwise,+   unless required by applicable law (such as deliberate and grossly+   negligent acts) or agreed to in writing, shall any Contributor be+   liable to You for damages, including any direct, indirect, special,+   incidental, or consequential damages of any character arising as a+   result of this License or out of the use or inability to use the+   Work (including but not limited to damages for loss of goodwill,+   work stoppage, computer failure or malfunction, or any and all+   other commercial damages or losses), even if such Contributor+   has been advised of the possibility of such damages.++9. Accepting Warranty or Additional Liability. While redistributing+   the Work or Derivative Works thereof, You may choose to offer,+   and charge a fee for, acceptance of support, warranty, indemnity,+   or other liability obligations and/or rights consistent with this+   License. However, in accepting such obligations, You may act only+   on Your own behalf and on Your sole responsibility, not on behalf+   of any other Contributor, and only if You agree to indemnify,+   defend, and hold each Contributor harmless for any liability+   incurred by, or claims asserted against, such Contributor by reason+   of your accepting any such warranty or additional liability.++END OF TERMS AND CONDITIONS
+ README.md view
@@ -0,0 +1,69 @@+<h1 align="center">hslua-annotations</h1>++<p align="center">+   <a href="https://github.com/sondr3/hslua-annotations/actions"><img alt="GitHub Actions Status" src="https://github.com/sondr3/hslua-annotations/workflows/pipeline/badge.svg" /></a>+   <a href="https://hackage.haskell.org/package/hslua-annotations"><img alt="Hackage version" src="https://img.shields.io/hackage/v/hslua-annotations.svg?logo=haskell" /></a>+   <br />+</p>++<p align="center">+   <strong>A type annotation generator for HSLua targetting LuaLS and EmmyLua</strong>+</p>++<details>+<summary>Table of Contents</summary>+<br />++**Table of Contents**++- [Installation](#installation)+- [Usage](#usage)+- [License](#license)++</details>++This is a small library to generate [LuaLS](https://luals.github.io/wiki/annotations) and [EmmyLua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust/blob/main/docs/emmylua_doc/annotations_EN/README.md) +annotations from HsLua.++## Installation++Add `hslua-annotations` to your Cabal file:++```cabal+build-depends:+  hslua-annotations ^>= 0.1+```++## Usage++```haskell+import HsLua.Annotations (annotateModule, documentModule)+import HsLua.Module.Path qualified as Path+import HsLua.Packaging+import HsLua.Core qualified as Lua+import Data.Text (Text)+import Data.Text.IO qualified as TIO++-- utility function might be needed to narrow the `Module e` type+renderDocs :: Module Lua.Exception -> Text+renderDocs mod = documentModule mod++-- utility function might be needed to narrow the `Module e` type+renderAnnotations :: Module Lua.Exception -> Text+renderAnnotations mod = annotateModule mod++main :: IO ()+main = do+  TIO.writeFile "path.md" $ renderDocs Path.documentedModule+  TIO.writeFile "path.lua" $ renderAnnotations Path.documentedModule+```++## License++This project is licensed under either of++- Apache License, Version 2.0, ([LICENSE_APACHE](LICENSE_APACHE) or+  http://www.apache.org/licenses/LICENSE-2.0)+- MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT)++at your option.
+ hslua-annotations.cabal view
@@ -0,0 +1,84 @@+cabal-version: 3.0+name: hslua-annotations+version: 0.1.0+synopsis: A type annotation generator for HSLua+description: A type annotation generator for HSLua targetting LuaLS and EmmyLua+homepage: https://github.com/sondr3/hslua-annotations+bug-reports: https://github.com/sondr3/hslua-annotations/issues+license: (MIT OR Apache-2.0)+license-file: LICENSE LICENSE_APACHE+author: Sondre Aasemoen+maintainer: sondre@eons.io+category: Foreign+build-type: Simple+extra-doc-files:+  CHANGELOG.md+  README.md++tested-with:+  ghc ==9.10+  ghc ==9.12+  ghc ==9.14++source-repository head+  type: git+  location: https://github.com/sondr3/hslua-annotations.git++common config+  default-language: GHC2021+  default-extensions:+    DerivingStrategies+    DuplicateRecordFields+    LambdaCase+    MultiWayIf+    NamedFieldPuns+    OverloadedStrings+    TypeFamilies++  ghc-options:+    -Weverything+    -Wno-unsafe+    -Wno-implicit-prelude+    -Wno-missing-safe-haskell-mode+    -Wno-missing-kind-signatures+    -Wno-missing-import-lists+    -Wno-missed-specialisations+    -Wno-all-missed-specialisations+    -Werror=missing-deriving-strategies+    -fhide-source-paths+    -fwrite-ide-info++  build-depends:+    base >=4.12 && <5,+    hslua-core ^>=2.3,+    hslua-packaging ^>=2.4,+    text >=2 && <3,++library+  import: config+  hs-source-dirs: lib+  exposed-modules:+    HsLua.Annotations++  other-modules:+    HsLua.Annotations.Internal+    HsLua.Annotations.Markdown+    HsLua.Annotations.Shared++test-suite hslua-annotations-golden+  import: config+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  main-is: Main.hs+  build-depends:+    hslua-annotations,+    hslua-marshalling ^>=2.3,+    hslua-module-path ^>=1.2,+    hslua-module-version ^>=1.2,+    tasty ^>=1.5,+    tasty-golden ^>=2.3,++  ghc-options:+    -threaded+    -rtsopts+    -with-rtsopts=-N
+ lib/HsLua/Annotations.hs view
@@ -0,0 +1,44 @@+{-# OPTIONS_HADDOCK show-extensions #-}++-- |+-- Module: HsLua.Annotations+-- Description: A type annotation generator for HSLua+-- Copyright: (c) Sondre Aasemoen+-- SPDX-License-Identifier: (MIT OR Apache-2.0)+--+-- Maintainer: Sondre Aasemoen <sondre@eons.io>+-- Stability: experimental+-- Portability: portable+-- Safe Haskell: Safe+--+-- A type annotation generator for HSLua targetting LuaLS and EmmyLua+--+-- == Usage+--+-- To use this library you need to add it as a dependency in your project, e.g.+-- by adding it to @build-depends@ in your @.cabal@ file.+--+-- @+-- build-depends: hslua-annotations ^>= 0.1.0+-- @+--+-- This module does not use common names and can be directly imported:+--+-- @+-- __import__ HsLua.Annotations+-- @+module HsLua.Annotations+  ( -- * Type annotation+    annotateFunction,+    annotateModule,++    -- * Markdown rendering+    documentFunction,+    documentModule,+  )+where++--++import HsLua.Annotations.Internal (annotateFunction, annotateModule)+import HsLua.Annotations.Markdown (documentFunction, documentModule)
+ lib/HsLua/Annotations/Internal.hs view
@@ -0,0 +1,157 @@+module HsLua.Annotations.Internal+  ( annotateFunction,+    annotateModule,+  )+where++import Data.Text (Text)+import Data.Text qualified as T+import HsLua.Annotations.Shared (hasReturnType, nameToText, returnType, typeToText, unlinesNonEmpty)+import HsLua.Core (Name (..))+import HsLua.Packaging++-- | Create type annotations for a documented function.+--+-- See the below example or the generated test annotations [here](https://github.com/sondr3/hslua-annotations/blob/main/test/golden/factorial.lua).+--+--+-- === __Example__+--+-- Given the example `factorial` function from HsLua:+--+-- @+-- factorial :: DocumentedFunction Lua.Exception+-- factorial =+--   defun "factorial" (liftPure $ \n -> product [1 .. n] :: Integer)+--     <#> parameter peekIntegral "integer" "n" ""+--     =#> functionResult pushIntegral "integer" "factorial"+--     #? "Calculates the factorial of a positive integer."+-- @+--+-- The output of running @annotateFunction factorial@ will be the following string:+--+-- @+-- ---Calculates the factorial of a positive integer.+-- ---\@param n integer+-- ---\@return integer+-- function factorial(n)+-- @+--+-- @since 0.1.0+annotateFunction :: DocumentedFunction a -> Text+annotateFunction = annotateFunction' Nothing++-- | Create type annotations for a documented module.+--+-- See the below example or the generated test annotations [here](https://github.com/sondr3/hslua-annotations/blob/main/test/golden/path.lua).+--+-- === __Example__+--+-- Building annotations for @HsLua.Module.Path@ using the exported @documentedModule@+-- you can generate annotations for the whole module. Note that you probably will+-- have to narrow the type of @Module a@ to for example @Module Lua.Exception@.+--+-- The output of running @annotateModule documentedModule@ will be the following string:+--+-- @+-- ---\@meta path+-- ---Module for file path manipulations.+--+-- ---\@class (exact) path+-- ---\@field separator string The character that separates directories.+-- ---\@field search_path_separator string The character that is used to separate the entries in the `PATH` environment variable.+-- local path = {}+--+-- ---Gets the directory name, i.e., removes the last directory separator and everything after from the given path.+-- ---\@param filepath string path+-- ---\@return string+-- function path.directory(filepath)+--+-- ---Get the file name.+-- ---\@param filepath string path+-- ---\@return string+-- function path.filename(filepath)+--+-- ...snip+-- @+--+-- @since 0.1.0+annotateModule :: Module a -> Text+annotateModule (Module {moduleName, moduleDescription, moduleFields, moduleFunctions, moduleOperations}) =+  unlinesNonEmpty+    [ "---@meta " <> nameToText moduleName,+      "---" <> moduleDescription <> "\n",+      "---@class (exact) " <> nameToText moduleName,+      T.intercalate "\n" (map fieldDesc moduleFields),+      T.intercalate "\n" (map opDesc moduleOperations),+      "local " <> nameToText moduleName <> " = {}\n",+      T.intercalate "\n" (map (annotateFunction' $ Just moduleName) moduleFunctions)+    ]++annotateFunction' :: Maybe Name -> DocumentedFunction a -> Text+annotateFunction' parent (DocumentedFunction {functionName, functionDoc}) = do+  let name = nameToText functionName+      desc = functionDesc functionDoc+      typ = returnType functionDoc+      paramAnn = paramsDesc functionDoc+      params = T.intercalate ", " $ paramNames functionDoc+      paren = maybe "" (\n -> nameToText n <> ".") parent+   in unlinesNonEmpty+        [ if T.null desc then mempty else desc,+          paramAnn,+          if hasReturnType (funDocResults functionDoc) then "---@return " <> typ else "",+          "function " <> paren <> name <> "(" <> params <> ")"+        ]++opDesc :: (Operation, DocumentedFunction a) -> Text+opDesc (op, DocumentedFunction {functionDoc}) = "---@operator " <> opName op <> ":" <> returnType functionDoc++fieldDesc :: Field a -> Text+fieldDesc (Field {fieldName, fieldDoc}) = "---@field " <> nameToText fieldName <> " " <> docs fieldDoc+  where+    docs (FieldDoc {fieldDocDescription, fieldDocType}) = typeToText fieldDocType <> " " <> fieldDocDescription++functionDesc :: FunctionDoc -> Text+functionDesc (FunDoc {funDocDescription}) = T.strip . unlinesNonEmpty $ map ("---" <>) $ T.lines funDocDescription++paramNames :: FunctionDoc -> [Text]+paramNames (FunDoc {funDocParameters}) = map go funDocParameters+  where+    go (ParameterDoc {parameterName}) = parameterName++paramsDesc :: FunctionDoc -> Text+paramsDesc (FunDoc {funDocParameters}) = T.intercalate "\n" $ map paramDesc funDocParameters++paramDesc :: ParameterDoc -> Text+paramDesc (ParameterDoc {parameterName, parameterType, parameterDescription, parameterIsOptional}) = do+  let isOpt = if parameterIsOptional then "?" else ""+      typ = typeToText parameterType+   in "---@param " <> parameterName <> isOpt <> " " <> typ <> " " <> parameterDescription++opName :: Operation -> Text+opName = \case+  Add -> "add"+  Sub -> "sub"+  Mul -> "mul"+  Div -> "div"+  Mod -> "mod"+  Pow -> "pow"+  Unm -> "unm"+  Idiv -> "idiv"+  Band -> "band"+  Bor -> "bor"+  Bxor -> "bxor"+  Bnot -> "bnot"+  Shl -> "shl"+  Shr -> "shr"+  Concat -> "concat"+  Len -> "len"+  Eq -> "eq"+  Lt -> "lt"+  Le -> "le"+  Index -> "index"+  Newindex -> "newindex"+  Call -> "call"+  Tostring -> "tostring"+  Pairs -> "pairs"+  CustomOperation x -> nameToText x
+ lib/HsLua/Annotations/Markdown.hs view
@@ -0,0 +1,184 @@+module HsLua.Annotations.Markdown+  ( documentFunction,+    documentModule,+  )+where++import Data.Bool (bool)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Version (showVersion)+import HsLua.Annotations.Shared (hasReturnType, nameToText, returnType, typeToText, unlinesNonEmpty)+import HsLua.Core+import HsLua.Core.Utf8 qualified as Utf8+import HsLua.Packaging++-- | Create markdown documentation for a documented function.+--+-- See the below example or the generated test annotations [here](https://github.com/sondr3/hslua-annotations/blob/main/test/golden/factorial.md).+--+-- === __Example__+--+-- Given the example `factorial` function from HsLua:+--+-- @+-- factorial :: DocumentedFunction Lua.Exception+-- factorial =+--   defun "factorial" (liftPure $ \n -> product [1 .. n] :: Integer)+--     <#> parameter peekIntegral "integer" "n" ""+--     =#> functionResult pushIntegral "integer" "factorial"+--     #? "Calculates the factorial of a positive integer."+-- @+--+-- The output of running @documentFunction factorial@ will be the following string:+--+-- @+-- `function factorial(n)`+--+-- #### Parameters+--+-- | Name | Type | Description |+-- | ---- | ---- | ----------- |+-- | `n` | `integer` |  |+--+-- #### Returns+--+-- | Type | Description |+-- | ---- | ----------- |+-- | `integer` | factorial |+--+-- Calculates the factorial of a positive integer.+--+-- ```lua+-- function factorial(n: integer): integer+-- ```+--+-- *Since: 1.0.0*+-- @+--+-- @since 0.1.0+documentFunction :: DocumentedFunction e -> Text+documentFunction fn =+  let fnDoc = functionDoc fn+      fnName = Utf8.toText $ fromName (functionName fn)+      name =+        if T.null fnName+          then "<anonymous function>"+          else fnName+   in unlinesNonEmpty+        [ "`function " <> name <> "(" <> renderFunctionParams fnDoc <> ")" <> "`\n",+          renderFunctionDoc name fnDoc+        ]++-- | Create markdown documentation for a documented module.+--+-- See the below example or the generated test annotations [here](https://github.com/sondr3/hslua-annotations/blob/main/test/golden/path.md).+--+-- === __Example__+--+-- Building documentation for @HsLua.Module.Path@ using the exported @documentedModule@+-- you can generate Markdown documentation for the whole module. Note that you probably will+-- have to narrow the type of @Module a@ to for example @Module Lua.Exception@.+--+-- The output of running @documentModule documentedModule@ on it will be the+-- following string:+--+-- @+-- # `path`+-- Module for file path manipulations.+--+-- ## Fields+--+-- ### `separator`+-- The character that separates directories.+--+-- ```lua+-- path.separator: string+-- ```+--+-- ...snip+-- @+--+-- @since 0.1.0+documentModule :: Module e -> Text+documentModule (Module {moduleFields, moduleName, moduleDescription, moduleFunctions}) =+  unlinesNonEmpty+    [ "# `" <> nameToText moduleName <> "`",+      moduleDescription,+      renderFields moduleName moduleFields,+      renderFunctions moduleFunctions+    ]++renderFunctions :: [DocumentedFunction e] -> Text+renderFunctions [] = mempty+renderFunctions fs = "## Functions\n\n" <> T.intercalate "\n" (map (("### " <>) . documentFunction) fs)++renderFunctionParams :: FunctionDoc -> Text+renderFunctionParams (FunDoc {funDocParameters}) = T.intercalate ", " $ map parameterName funDocParameters++renderFields :: Name -> [Field e] -> Text+renderFields _ [] = mempty+renderFields name fs =+  unlinesNonEmpty+    [ "\n## Fields",+      T.intercalate "\n" (map (renderField name) fs)+    ]++renderField :: Name -> Field e -> Text+renderField modName (Field {fieldName, fieldDoc}) = do+  let name = nameToText fieldName+      parent = nameToText modName+      typ = typeToText $ fieldDocType fieldDoc+  unlinesNonEmpty+    [ "\n### `" <> name <> "`",+      fieldDocDescription fieldDoc,+      "\n```lua\n" <> parent <> "." <> name <> ": " <> typ <> "\n```"+    ]++renderFunctionDoc :: Text -> FunctionDoc -> Text+renderFunctionDoc name fd@(FunDoc {funDocDescription, funDocSince, funDocParameters, funDocResults}) =+  let sinceTag = case funDocSince of+        Nothing -> mempty+        Just version -> T.pack $ "*Since: " <> showVersion version <> "*"+   in unlinesNonEmpty+        [ renderParamTable funDocParameters,+          renderResultsDoc funDocResults,+          funDocDescription,+          "\n```lua\n" <> "function " <> name <> renderParamType funDocParameters <> (if hasReturnType funDocResults then ": " <> returnType fd else "") <> "\n```\n",+          sinceTag+        ]++renderParamTable :: [ParameterDoc] -> Text+renderParamTable [] = mempty+renderParamTable ps =+  T.unlines+    [ "#### Parameters\n",+      "| Name | Type | Description |",+      "| ---- | ---- | ----------- |",+      T.intercalate "\n" (map renderParamField ps)+    ]++renderParamType :: [ParameterDoc] -> Text+renderParamType [] = "()"+renderParamType ps = "(" <> T.intercalate ", " (map (\p -> parameterName p <> bool "" "?" (parameterIsOptional p) <> ": " <> typeToText (parameterType p)) ps) <> ")"++renderParamField :: ParameterDoc -> Text+renderParamField (ParameterDoc {parameterName, parameterDescription, parameterType, parameterIsOptional}) =+  "| `" <> parameterName <> "` | `" <> typeToText parameterType <> bool "" "?" parameterIsOptional <> "` | " <> parameterDescription <> " |"++renderResultsDoc :: ResultsDoc -> Text+renderResultsDoc (ResultsDocList []) = mempty+renderResultsDoc (ResultsDocList rds) =+  unlinesNonEmpty+    [ "#### Returns\n",+      "| Type | Description |",+      "| ---- | ----------- |",+      T.intercalate "\n" (map renderResultValueDoc rds)+    ]+renderResultsDoc (ResultsDocMult txt) = " -  " <> indent 4 txt++renderResultValueDoc :: ResultValueDoc -> Text+renderResultValueDoc (ResultValueDoc {resultValueType, resultValueDescription}) = "| `" <> typeToText resultValueType <> "` | " <> resultValueDescription <> " |"++indent :: Int -> Text -> Text+indent n = T.replace "\n" (T.replicate n " ")
+ lib/HsLua/Annotations/Shared.hs view
@@ -0,0 +1,51 @@+module HsLua.Annotations.Shared+  ( nameToText,+    typeToText,+    unlinesNonEmpty,+    hasReturnType,+    returnType,+  )+where++import Data.Char (toLower)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding (decodeUtf8)+import HsLua.Core (Name (..), Type (..))+import HsLua.Packaging++unlinesNonEmpty :: [Text] -> Text+unlinesNonEmpty xs = T.unlines $ filter (not . T.null) xs++nameToText :: Name -> Text+nameToText = decodeUtf8 . fromName++typeToText :: TypeSpec -> Text+typeToText = \case+  BasicType t -> basicTypeName t+  NamedType nt -> decodeUtf8 $ fromName nt+  AnyType -> "any"+  FunType {} -> "function"+  RecType {} -> "table"+  SeqType t -> typeToText t <> "[]"+  SumType specs -> T.intercalate "|" (map typeToText specs)++basicTypeName :: Type -> Text+basicTypeName = \case+  TypeLightUserdata -> "light userdata"+  t -> T.pack $ map toLower . drop 4 $ show t++hasReturnType :: ResultsDoc -> Bool+hasReturnType (ResultsDocList []) = False+hasReturnType (ResultsDocList _) = True+hasReturnType _ = False++returnType :: FunctionDoc -> Text+returnType (FunDoc {funDocResults}) = go funDocResults+  where+    go (ResultsDocList [rs]) = resultValueDesc rs+    go (ResultsDocList xs) = T.intercalate "|" $ map resultValueDesc xs+    go (ResultsDocMult _) = ""++resultValueDesc :: ResultValueDoc -> Text+resultValueDesc (ResultValueDoc {resultValueType}) = typeToText resultValueType
+ test/Main.hs view
@@ -0,0 +1,52 @@+module Main (main) where++import Data.Text (Text)+import Data.Text.Lazy (fromStrict)+import Data.Text.Lazy.Encoding (encodeUtf8)+import Data.Version (makeVersion)+import HsLua.Annotations (annotateFunction, annotateModule, documentFunction, documentModule)+import HsLua.Core qualified as Lua+import HsLua.Marshalling (peekIntegral, pushIntegral)+import HsLua.Module.Path qualified as Path+import HsLua.Module.Version qualified as Version+import HsLua.Packaging+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.Golden++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests = testGroup "Tests" [annotations, markdown]++annotations :: TestTree+annotations =+  testGroup+    "Annotations"+    [ goldenVsString "factorial" "test/golden/factorial.lua" (pure $ encodeUtf8 (fromStrict $ annotateFunction factorial)),+      goldenVsString "path" "test/golden/path.lua" (pure $ encodeUtf8 $ fromStrict (annModule Path.documentedModule)),+      goldenVsString "version" "test/golden/version.lua" (pure $ encodeUtf8 $ fromStrict (annModule Version.documentedModule))+    ]+  where+    annModule :: Module Lua.Exception -> Text+    annModule = annotateModule++markdown :: TestTree+markdown =+  testGroup+    "Markdown"+    [ goldenVsString "factorial" "test/golden/factorial.md" (pure $ encodeUtf8 (fromStrict $ documentFunction factorial)),+      goldenVsString "path" "test/golden/path.md" (pure $ encodeUtf8 $ fromStrict (docModule Path.documentedModule)),+      goldenVsString "version" "test/golden/version.md" (pure $ encodeUtf8 $ fromStrict (docModule Version.documentedModule))+    ]+  where+    docModule :: Module Lua.Exception -> Text+    docModule = documentModule++factorial :: DocumentedFunction Lua.Exception+factorial =+  defun "factorial" (liftPure $ \n -> product [1 .. n] :: Integer)+    <#> parameter peekIntegral "integer" "n" ""+    =#> functionResult pushIntegral "integer" "factorial"+    #? "Calculates the factorial of a positive integer."+    `since` makeVersion [1, 0, 0]