summaryrefslogtreecommitdiff
path: root/Data/Libraries/LDoc/ldoc/html.lua
blob: 394180229984027350080e68cc0a2e33b663f3e8 (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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
------ generating HTML output ---------
-- Although this can be generalized for outputting any format, since the template
-- is language-agnostic, this implementation concentrates on HTML.
-- This does the actual generation of HTML, and provides support functions in the ldoc
-- table for the template
--
-- A fair amount of the complexity comes from operating in two basic modes; first, where
-- there is a number of modules (classic LuaDoc) or otherwise, where there is only one
-- module and the index contains the documentation for that module.
--
-- Like LuaDoc, LDoc puts similar kinds of documentation files in their own directories.
-- So module docs go into 'modules/', scripts go into 'scripts/', and so forth. LDoc
-- generalizes the idea of these project-level categories and in fact custom categories
-- can be created (refered to as 'kinds' in the code)

local List = require 'pl.List'
local utils = require 'pl.utils'
local path = require 'pl.path'
local stringx = require 'pl.stringx'
local template = require 'pl.template'
local tablex = require 'pl.tablex'
local OrderedMap = require 'pl.OrderedMap'
local tools = require 'ldoc.tools'
local markup = require 'ldoc.markup'
local prettify = require 'ldoc.prettify'
local doc = require 'ldoc.doc'
local unpack = utils.unpack
local html = {}


local quit = utils.quit

local function cleanup_whitespaces(text)
   local lines = stringx.splitlines(text)
   for i = 1, #lines do
      lines[i] = stringx.rstrip(lines[i])
   end
   lines[#lines + 1] = "" -- Little trick: file should end with newline
   return table.concat(lines, "\n")
end

local function get_module_info(m)
   local info = OrderedMap()
   for tag in doc.module_info_tags() do
      local val = m.tags[tag]
      if type(val)=='table' then
         val = table.concat(val,',')
      end
      tag = stringx.title(tag)
      info:set(tag,val)
   end
   if #info:keys() > 0 then
      return info
   end
end

local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" }

function html.generate_output(ldoc, args, project)
   local check_directory, check_file, writefile = tools.check_directory, tools.check_file, tools.writefile
   local original_ldoc

   local function save_and_set_ldoc (set)
      if not set then return end
      if not original_ldoc then
         original_ldoc = tablex.copy(ldoc)
      end
      for s in set:iter() do
         local var,val = s:match('([^=]+)=(.+)')
         local num = tonumber(val)
         if num then val = num
         elseif val == 'true' then val = true
         elseif val == 'false' then val = false
         end
         print('setting',var,val)
         ldoc[var] = val
      end
   end

   local function restore_ldoc ()
      if original_ldoc then
         ldoc = original_ldoc
      end
   end

   function ldoc.escape(str)
      return (str:gsub("['&<>\"]", escape_table))
   end

   function ldoc.prettify(str)
      return prettify.code('lua','usage',str,0,false)
   end

   -- Item descriptions come from combining the summary and description fields
   function ldoc.descript(item)
      return tools.join(' ', item.summary, item.description)
   end

   function ldoc.module_name (mod)
      local name = mod.name
      if args.unqualified and (mod.type == 'module' or mod.type == 'classmod') then -- leave out package
         name = name:gsub('^.-%.','')
      elseif mod.type == 'topic' then
         if mod.display_name then
            name = mod.display_name
         else -- leave out md extension
            name = name:gsub('%..*$','')
         end
      end
      return name
   end

   -- this generates the internal module/function references
   function ldoc.href(see)
      if see.href then -- explict reference, e.g. to Lua manual
         return see.href
      elseif doc.Module:class_of(see) then
         return ldoc.ref_to_module(see)
      else
         return ldoc.ref_to_module(see.mod)..'#'..see.name
      end
   end

   -- this is either called from the 'root' (index or single module) or
   -- from the 'modules' etc directories. If we are in one of those directories,
   -- then linking to another kind is `../kind/name`; to the same kind is just `name`.
   -- If we are in the root, then it is `kind/name`.
   function ldoc.ref_to_module (mod)
      local base = "" -- default: same directory
      mod = mod or ldoc.module
      local kind, module = mod.kind, ldoc.module
      local name = mod.name -- default: name of module
      if not ldoc.single then
         if module then -- we are in kind/
            if module.type ~= type then -- cross ref to ../kind/
               base = "../"..kind.."/"
            end
         else -- we are in root: index
            base = kind..'/'
         end
      else -- single module
         if mod == ldoc.single then
            name = ldoc.output
            if not ldoc.root then base = '../' end
         elseif ldoc.root then -- ref to other kinds (like examples)
            base = kind..'/'
         else
            if module.type ~= type then -- cross ref to ../kind/
               base = "../"..kind.."/"
            end
         end
      end
      return base..name..'.html'
   end

   function ldoc.include_file (file)
      local text,_ = utils.readfile(file)
      if not text then quit("unable to include "..file)
      else
         return text
      end
   end

-- these references are never from the index...?
function ldoc.source_ref (fun)
      local modname = fun.module.name
      local pack,name = tools.split_dotted_name(modname)
      if not pack then
         name = modname
      end
      return (ldoc.single and "" or "../").."source/"..name..'.lua.html#'..fun.lineno
   end

   function ldoc.use_li(ls)
      if #ls > 1 then return '<li>','</li>' else return '','' end
   end

   function ldoc.default_display_name(item)
      -- Project-level items:
      if doc.project_level(item.type) then
        return ldoc.module_name(item)
      end
      -- Module-level items:
      local name = item.display_name or item.name
      if item.type == 'function' or item.type == 'lfunction' then
         if not ldoc.no_space_before_args then
            name = name..' '
         end
         return name..item.args
      else
         return name
      end
   end

   function ldoc.display_name(item)
      if ldoc.custom_display_name_handler then
        return ldoc.custom_display_name_handler(item, ldoc.default_display_name)
      else
        return ldoc.default_display_name(item)
      end
   end

   function ldoc.no_spaces(s)
      s = s:gsub('%s*$','')
      return (s:gsub('%W','_'))
   end

   function ldoc.module_typename(m)
      return doc.presentation_name(m.type)
   end

   function ldoc.is_list (t)
      return type(t) == 'table' and t.append
   end

   function ldoc.strip_header (s)
      if not s then return s end
      return s:gsub('^%s*#+%s+','')
   end

   function ldoc.typename (tp)
      if not tp or tp == '' or tp:match '^@' then return '' end
      local optional
      -- ?<type> is short for ?nil|<type>
      if tp:match("^%?") and not tp:match '|' then
         tp = '?|'..tp:sub(2)
      end
      local tp2 = tp:match("%?|?(.*)")
      if tp2 then
         optional = true
         tp = tp2
      end

      local types = {}
      for name in tp:gmatch("[^|]+") do
         local sym = name:match '([%w%.%:]+)'
         local ref,_ = markup.process_reference(sym,true)
         if ref then
            if ref.label and sym == name then
               name = ref.label
            end
            types[#types+1] = ('<a class="type" href="%s">%s</a>'):format(ldoc.href(ref),name)
         else
            types[#types+1] = '<span class="type">'..name..'</span>'
         end
      end
      local names = table.concat(types, ", ", 1, math.max(#types-1, 1))
      if #types > 1 then names = names.." or "..types[#types] end
      if optional then
         if names ~= '' then
            if #types == 1 then names = "optional "..names end
         else
            names = "optional"
        end
      end
      return names
   end

   -- the somewhat tangled logic that controls whether a type appears in the
   -- navigation sidebar. (At least it's no longer in the template ;))
   function ldoc.allowed_in_contents(type,module)
      local allowed = true
      if ldoc.kinds_allowed then
         allowed = ldoc.kinds_allowed[type]
      elseif ldoc.prettify_files and type == 'file' then
         allowed = ldoc.prettify_files == 'show' or (module and module.type == 'file')
      end
      return allowed
   end

   local function set_charset (ldoc,m)
      m = m or ldoc.module
      ldoc.doc_charset = (m and m.tags.charset) or ldoc.charset
   end

   local module_template,_ = utils.readfile (path.join(args.template,ldoc.templ))
   if not module_template then
      quit("template not found at '"..args.template.."' Use -l to specify directory containing ldoc.ltp")
   end

   -- Runs a template on a module to generate HTML page.
   local function templatize(template_str, ldoc, module)
      local out, err = template.substitute(template_str, {
         ldoc = ldoc,
         module = module,
         _escape = ldoc.template_escape
      })
      if not out then
         quit(("template failed for %s: %s"):format(
               module and module.name or ldoc.output or "index",
               err))
      end
      if ldoc.postprocess_html then
         out = ldoc.postprocess_html(out, module)
      end
      return cleanup_whitespaces(out)
   end

   local css, custom_css = ldoc.css, ldoc.custom_css
   ldoc.output = args.output
   ldoc.ipairs = ipairs
   ldoc.pairs = pairs
   ldoc.print = print

   -- Bang out the index.
   -- in single mode there is one module and the 'index' is the
   -- documentation for that module.
   ldoc.module = ldoc.single
   if ldoc.single and args.one then
      ldoc.kinds_allowed = {module = true, topic = true}
      ldoc.one = true
   end
   ldoc.root = true
   if ldoc.module then
      ldoc.module.info = get_module_info(ldoc.module)
      ldoc.module.ldoc = ldoc
      save_and_set_ldoc(ldoc.module.tags.set)
   end
   set_charset(ldoc)
   local out = templatize(module_template, ldoc, ldoc.module)
   ldoc.root = false
   restore_ldoc()

   check_directory(args.dir) -- make sure output directory is ok

   -- project icon
   if ldoc.icon then
      local dir_data = args.dir .. '/data'
      if not path.isdir(dir_data) then
          -- luacheck: push ignore lfs
          lfs.mkdir(dir_data)
          -- luacheck: pop
      end
      local file = require 'pl.file'
      file.copy(ldoc.icon, dir_data)
   end

   args.dir = args.dir .. path.sep

   if css then -- has CSS been copied?
      check_file(args.dir..css, path.join(args.style,css))
   end

   if custom_css then -- has custom CSS been copied?
      check_file(args.dir..custom_css, custom_css)
   end

   -- write out the module index
   out = cleanup_whitespaces(out)
   writefile(args.dir..args.output..args.ext,out)

   -- in single mode, we exclude any modules since the module has been done;
   -- ext step is then only for putting out any examples or topics
   local mods = List()
   for kind, modules in project() do
      local lkind = kind:lower()
      if not ldoc.single or ldoc.single and lkind ~= 'modules' then
         mods:append {kind, lkind, modules}
      end
   end

   -- write out the per-module documentation
   -- note that we reset the internal ordering of the 'kinds' so that
   -- e.g. when reading a topic the other Topics will be listed first.
   if css then
      ldoc.css = '../'..css
   end
   if custom_css then
      ldoc.custom_css = '../'..custom_css
   end
   for m in mods:iter() do
      local kind, lkind, modules = unpack(m)
      check_directory(args.dir..lkind)
      project:put_kind_first(kind)
      for m in modules() do
         ldoc.module = m
         ldoc.body = m.body
         m.ldoc = ldoc
         if m.tags.set then
            save_and_set_ldoc(m.tags.set)
         end
         set_charset(ldoc)
         m.info = get_module_info(m)
         if ldoc.body and m.postprocess then
            ldoc.body = m.postprocess(ldoc.body)
         end
         local out = templatize(module_template, ldoc, m)
         writefile(args.dir..lkind..'/'..m.name..args.ext,out)
         restore_ldoc()
      end
   end
   if not args.quiet then print('output written to '..tools.abspath(args.dir)) end
end

return html