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 = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }
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
|