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 (referred 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 -- explicit 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
|