aboutsummaryrefslogtreecommitdiff
path: root/pandoc-lua-engine/src/Text/Pandoc/Lua/Documentation.hs
blob: 833d576031758c5adbfba1795c009dbf27922ce7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes        #-}
{- |
   Module      : Text.Pandoc.Lua.Documentation
   Copyright   : Copyright © 2026 Albert Krewinkel
   License     : GPL-2.0-or-later
   Maintainer  : Albert Krewinkel <[email protected]>

Render Lua documentation
-}
module Text.Pandoc.Lua.Documentation
  ( renderDocumentation
  ) where

import Data.Default (def)
import Data.List (intersperse)
import Data.Sequence (Seq ((:|>)))
import Data.Version (showVersion)
import HsLua as Lua
import Text.Pandoc.Class (runPure)
import Text.Pandoc.Definition (Pandoc (Pandoc))
import Text.Pandoc.Extensions (extensionsFromList, Extension (..))
import Text.Pandoc.Options (ReaderOptions (readerExtensions))
import Text.Pandoc.Readers (readCommonMark)
import Text.Pandoc.Shared (compactify)
import Text.Pandoc.Walk (walk)

import qualified Data.Text as T
import qualified Text.Pandoc.Builder as B
import qualified Text.Pandoc.UTF8 as UTF8

-- | Render the documentation object as pandoc Blocks
renderDocumentation :: DocumentationObject -> B.Blocks
renderDocumentation = \case
  DocObjectFunction fn -> renderFunctionDoc Nothing fn
  DocObjectModule mdl  -> renderModuleDoc mdl
  DocObjectType tp     -> renderTypeDoc Nothing tp

renderTypeDoc :: Maybe T.Text -> TypeDoc -> B.Blocks
renderTypeDoc mbmodname td = mconcat
  [ B.headerWith (ident, [], []) 1 (B.str $ typeDocName td)
  , parseCommonMark $ typeDocDescription td
  , if null $ typeDocMethods td
    then mempty
    else
      B.header 2 "Methods" <>
      (shiftHeadings 2 . mconcat . map (renderFunctionDoc Nothing) $
       typeDocMethods td)
  ]
 where
  ident = case mbmodname of
    Just modname  -> mconcat [ "type-", modname, ".", typeDocName td ]
    Nothing       -> mconcat [ "type-", typeDocName td ]

-- Shift headings
shiftHeadings :: Int -> B.Blocks -> B.Blocks
shiftHeadings incr blks = flip walk blks $ \case
  B.Header level attr inner -> B.Header (level + incr) attr inner
  x -> x

renderModuleDoc :: ModuleDoc -> B.Blocks
renderModuleDoc moddoc =
  let modname = moduleDocName moddoc
  in mconcat
  [ B.headerWith ("module-" <> modname, [], []) 1
      (B.str $ "Module " <> modname)
  , parseCommonMark (moduleDocDescription moddoc)
  , if null (moduleDocFields moddoc)
    then mempty
    else
      let ident = modname <> "-fields"
      in B.headerWith (ident, [], []) 2 (B.str "Fields") <>
         shiftHeadings 0 (mconcat (map (renderFieldDoc modname)
                                   (moduleDocFields moddoc)))
  , if null (moduleDocFunctions moddoc)
    then mempty
    else
      let ident = modname <> "-functions"
      in B.headerWith (ident, [], []) 2 (B.str "Functions") <>
         (shiftHeadings 2 . mconcat . map (renderFunctionDoc $ Just modname) $
          moduleDocFunctions moddoc)
  , if null (moduleDocTypes moddoc)
    then mempty
    else
      let ident = modname <> "-types"
      in B.headerWith (ident, [], []) 2 (B.str "Types") <>
         (shiftHeadings 2 . mconcat . map (renderTypeDoc $ Just modname) .
          reverse $ moduleDocTypes moddoc)
  ]

parseCommonMark :: T.Text -> B.Blocks
parseCommonMark txt =
  let exts = extensionsFromList
        [ Ext_wikilinks_title_after_pipe
        , Ext_smart
        ]
      result = runPure $ do
        Pandoc _ blks <- readCommonMark (def {readerExtensions = exts}) txt
        return $ B.fromList blks
  in either mempty id result

appendInlines :: B.Blocks -> B.Inlines -> B.Blocks
appendInlines blks inlns = case B.unMany blks of
  front :|> (B.Para xs) -> B.Many front <> B.para (addTo xs)
  front :|> (B.Plain xs) -> B.Many front <> B.plain (addTo xs)
  _ -> blks <> B.para inlns
 where addTo xs = B.fromList xs <> B.space <> inlns

appendType :: B.Blocks -> TypeSpec -> B.Blocks
appendType blks typespec =
  appendInlines blks (B.str "(" <> typeToInlines typespec <> B.str ")")

typeToInlines :: TypeSpec -> B.Inlines
typeToInlines = \case
  bt@BasicType{}   -> builtin $ tystr bt
  NamedType "integer" -> builtin "integer"
  NamedType name   -> B.linkWith ("", ["documented-type"], [])
                        ("#" <> n2t name) mempty $ B.str (n2t name)
  SeqType itemtype -> "{" <> typeToInlines itemtype <> ",...}"
  SumType summands -> mconcat . intersperse (B.str "|") $ map typeToInlines summands
  AnyType          -> "any"
  x                -> tystr x
 where
  tystr = B.str . T.pack . typeSpecToString
  n2t = UTF8.toText . fromName
  builtin = B.spanWith ("", ["builtin-lua-type"], [])

renderFunctionDoc :: Maybe T.Text -> FunctionDoc -> B.Blocks
renderFunctionDoc mbmodule fndoc =
  let name = case mbmodule of
        Just _   ->  T.takeWhileEnd (/= '.') $ funDocName fndoc
        Nothing  -> funDocName fndoc
      ident = funDocName fndoc
      level = 1
      argsString = argslist (funDocParameters fndoc)
      paramToDefItem p = ( B.code $ parameterName p
                         , compactify
                           [ appendType
                               (parseCommonMark $ parameterDescription p)
                               (parameterType p)
                           ]
                         )
      paramlist = B.definitionList . map paramToDefItem $
                  funDocParameters fndoc
  in mconcat
     [ B.headerWith (ident, [], []) level (B.str name)
     , B.plain (B.code $ name <> " (" <> argsString <> ")")
     , parseCommonMark (funDocDescription fndoc)
     , if null (funDocParameters fndoc)
       then mempty
       else B.para "Parameters:" <> paramlist
     , if funDocResults fndoc == ResultsDocList []
       then mempty
       else B.para "Returns:" <> renderResults (funDocResults fndoc)
     , case funDocSince fndoc of
         Nothing -> mempty
         Just version ->
           B.para $ B.emph $ "Since: " <> (B.str . T.pack $ showVersion version)
     ]

renderResults :: ResultsDoc -> B.Blocks
renderResults (ResultsDocMult descr) = parseCommonMark descr
renderResults (ResultsDocList rvd) = B.bulletList $ map renderResultVal rvd
 where
   renderResultVal (ResultValueDoc typespec descr) =
     parseCommonMark descr `appendType` typespec

argslist :: [ParameterDoc] -> T.Text
argslist params =
  -- Expect optional values to come after required values.
  let (required, optional') = break parameterIsOptional params
      reqs = map parameterName required
      opts = map parameterName optional'
  in if null opts
     then T.intercalate ", " reqs
     else T.intercalate ", " reqs <>
          (if null required then "[" else "[, ") <>
          T.intercalate "[, " opts <> T.replicate (length opts) "]"

renderFieldDoc :: T.Text -> FieldDoc -> B.Blocks
renderFieldDoc _modname fd =
  B.headerWith (ident, [], []) 3 (B.str name) <>
  appendType (parseCommonMark $ fieldDocDescription fd) (fieldDocType fd)
 where
  ident = fieldDocName fd
  name = T.takeWhileEnd (/= '.') $ fieldDocName fd