edocgen/gendoc.lua

1545 lines
44 KiB
Lua

local getopt = require("getopt")
local serializer = require("serializer")
local template = require("template")
local eolian = require("eolian")
local eoutils = require("docgen.eolian_utils")
local docm = require("docgen.doc")
local stats = require("docgen.stats")
local dutil = require("docgen.util")
local writer = require("docgen.writer")
local keyref = require("docgen.keyref")
local mono = require("docgen.mono")
local eos
local printgen = function() end
local render_template = function(tpath, ns, title, ctx)
local nctx = {
doc = docm, eos = eos, eolian = eolian,
eoutils = eoutils, title = docm.title_str_get(title), page_ns = ns
}
if ctx then
nctx = setmetatable(nctx, { __index = ctx })
end
local f = writer.Writer(ns)
tpath = "templates/" .. tpath .. ".txt"
print("rendering template: " .. tpath .. " (" .. title .. ")")
f:write_raw(template.compile(tpath)(nctx, true))
f:finish()
end
local propt_to_type = {
[eolian.function_type.PROPERTY] = "(get, set)",
[eolian.function_type.PROP_GET] = "(get)",
[eolian.function_type.PROP_SET] = "(set)",
}
local gen_cparam = function(par, out)
local part = par:type_get()
out = out or (par:direction_get() == eolian.parameter_dir.OUT)
local tstr = part:c_type_get(eolian.c_type_type.DEFAULT)
if out then
tstr = eoutils.type_cstr_get(tstr, "*")
end
return eoutils.type_cstr_get(tstr, par:name_get())
end
local get_func_csig_part = function(cn, tp)
if not tp then
return "void " .. cn
end
return eoutils.type_cstr_get(tp, cn)
end
local gen_func_csig = function(f, ftype)
ftype = ftype or eolian.function_type.METHOD
assert(ftype ~= eolian.function_type.PROPERTY)
local cn = f:full_c_name_get(ftype)
local rtype = f:return_type_get(ftype)
local fparam = "Eo *obj"
if f:is_class() then
fparam = "Efl_Class *klass"
elseif f:is_const() or ftype == eolian.function_type.PROP_GET then
fparam = "const Eo *obj"
end
if f:type_get() == eolian.function_type.METHOD then
local pars = f:parameters_get():to_array()
local cnrt = get_func_csig_part(cn, rtype)
for i = 1, #pars do
pars[i] = gen_cparam(pars[i])
end
table.insert(pars, 1, fparam);
return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
end
local keys = f:property_keys_get(ftype):to_array()
local vals = f:property_values_get(ftype):to_array()
if ftype == eolian.function_type.PROP_SET then
local cnrt = get_func_csig_part(cn, rtype)
local pars = {}
for i, par in ipairs(keys) do
pars[#pars + 1] = gen_cparam(par)
end
for i, par in ipairs(vals) do
pars[#pars + 1] = gen_cparam(par)
end
table.insert(pars, 1, fparam);
return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
end
-- getters
local cnrt
if not rtype then
if #vals == 1 then
cnrt = get_func_csig_part(cn, vals[1]:type_get())
table.remove(vals, 1)
else
cnrt = get_func_csig_part(cn)
end
else
cnrt = get_func_csig_part(cn, rtype)
end
local pars = {}
for i, par in ipairs(keys) do
pars[#pars + 1] = gen_cparam(par)
end
for i, par in ipairs(vals) do
pars[#pars + 1] = gen_cparam(par, true)
end
table.insert(pars, 1, fparam);
return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
end
local gen_func_namesig = function(fn, cl, buf, isprop, isget, isset)
if isprop then
buf[#buf + 1] = "@property "
end
buf[#buf + 1] = fn:name_get()
buf[#buf + 1] = " "
if fn:is_beta() then
buf[#buf + 1] = "@beta "
end
if not isprop then
if fn:scope_get(eolian.function_type.METHOD) == eolian.object_scope.PROTECTED then
buf[#buf + 1] = "@protected "
end
elseif isget and isset then
if fn:scope_get(eolian.function_type.PROP_GET) == eolian.object_scope.PROTECTED and
fn:scope_get(eolian.function_type.PROP_SET) == eolian.object_scope.PROTECTED then
buf[#buf + 1] = "@protected "
end
end
if fn:is_class() then
buf[#buf + 1] = "@class "
end
if fn:is_const() then
buf[#buf + 1] = "@const "
end
end
local gen_func_param = function(fp, buf, nodir)
-- TODO: default value
buf[#buf + 1] = " "
if not nodir then
buf[#buf + 1] = "@" .. eoutils.param_get_dir_name(fp) .. " "
end
buf[#buf + 1] = fp:name_get()
buf[#buf + 1] = ": "
buf[#buf + 1] = eoutils.obj_serialize(fp:type_get())
local dval = fp:default_value_get()
if dval then
buf[#buf + 1] = " ("
buf[#buf + 1] = dval:serialize()
buf[#buf + 1] = ")"
end
if fp:is_nonull() then
buf[#buf + 1] = " @nonull"
end
if fp:is_nullable() then
buf[#buf + 1] = " @nullable"
end
if fp:is_optional() then
buf[#buf + 1] = " @optional"
end
buf[#buf + 1] = ";\n"
end
local gen_func_return = function(fp, ftype, buf, indent)
local rett = fp:return_type_get(ftype)
if not rett then
return
end
buf[#buf + 1] = indent and (" "):rep(indent) or " "
buf[#buf + 1] = "return: "
buf[#buf + 1] = eoutils.obj_serialize(rett)
local dval = fp:return_default_value_get(ftype)
if dval then
buf[#buf + 1] = " ("
buf[#buf + 1] = dval:serialize()
buf[#buf + 1] = ")"
end
if fp:return_is_warn_unused(ftype) then
buf[#buf + 1] = " @warn_unused"
end
buf[#buf + 1] = ";\n"
end
local gen_method_sig = function(fn, cl)
local buf = {}
gen_func_namesig(fn, cl, buf, false, false, false)
local fimp = fn:implement_get()
if fimp:is_pure_virtual(eolian.function_type.METHOD) then
buf[#buf + 1] = "@pure_virtual "
end
buf[#buf + 1] = "{"
local params = fn:parameters_get():to_array()
local rtp = fn:return_type_get(eolian.function_type.METHOD)
if #params == 0 and not rtp then
buf[#buf + 1] = "}"
return table.concat(buf)
end
buf[#buf + 1] = "\n"
if #params > 0 then
buf[#buf + 1] = " params {\n"
for i, fp in ipairs(params) do
gen_func_param(fp, buf)
end
buf[#buf + 1] = " }\n"
end
gen_func_return(fn, eolian.function_type.METHOD, buf)
buf[#buf + 1] = "}"
return table.concat(buf)
end
local eovals_check_same = function(a1, a2)
if #a1 ~= #a2 then return false end
for i, v in ipairs(a1) do
if v ~= a2[i] then return false end
end
return true
end
local gen_prop_keyvals = function(tbl, kword, buf, indent)
local ind = indent and (" "):rep(indent) or " "
if #tbl == 0 then return end
buf[#buf + 1] = " "
buf[#buf + 1] = ind
buf[#buf + 1] = kword
buf[#buf + 1] = " {\n"
for i, v in ipairs(tbl) do
buf[#buf + 1] = ind
gen_func_param(v, buf, true)
end
buf[#buf + 1] = " "
buf[#buf + 1] = ind
buf[#buf + 1] = "}\n"
end
local gen_prop_sig = function(fn, cl)
local buf = {}
local fnt = fn:type_get()
local isget = (fnt == eolian.function_type.PROPERTY or fnt == eolian.function_type.PROP_GET)
local isset = (fnt == eolian.function_type.PROPERTY or fnt == eolian.function_type.PROP_SET)
gen_func_namesig(fn, cl, buf, true, isget, isset)
local pimp = fn:implement_get()
local gvirt = pimp:is_pure_virtual(eolian.function_type.PROP_GET)
local svirt = pimp:is_pure_virtual(eolian.function_type.PROP_SET)
if (not isget or gvirt) and (not isset or svirt) then
buf[#buf + 1] = "@pure_virtual "
end
local gkeys = isget and fn:property_keys_get(eolian.function_type.PROP_GET):to_array() or {}
local skeys = isset and fn:property_keys_get(eolian.function_type.PROP_SET):to_array() or {}
local gvals = isget and fn:property_values_get(eolian.function_type.PROP_GET):to_array() or {}
local svals = isget and fn:property_values_get(eolian.function_type.PROP_SET):to_array() or {}
local grtt = isget and fn:return_type_get(eolian.function_type.PROP_GET) or nil
local srtt = isset and fn:return_type_get(eolian.function_type.PROP_SET) or nil
local keys_same = eovals_check_same(gkeys, skeys)
local vals_same = eovals_check_same(gvals, svals)
buf[#buf + 1] = "{\n"
if isget then
buf[#buf + 1] = " get "
if fn:scope_get(eolian.function_type.PROP_GET) == eolian.object_scope.PROTECTED and
fn:scope_get(eolian.function_type.PROP_SET) ~= eolian.object_scope.PROTECTED then
buf[#buf + 1] = "@protected "
end
buf[#buf + 1] = "{"
if (#gkeys == 0 or keys_same) and (#gvals == 0 or vals_same) and
(not grtt or grtt == srtt) then
buf[#buf + 1] = "}\n"
else
buf[#buf + 1] = "\n"
if not keys_same then gen_prop_keyvals(gkeys, "keys", buf) end
if not vals_same then gen_prop_keyvals(gvals, "values", buf) end
if grtt ~= srtt then
gen_func_return(fn, eolian.function_type.PROP_GET, buf, 2)
end
buf[#buf + 1] = " }\n"
end
end
if isset then
buf[#buf + 1] = " set "
if fn:scope_get(eolian.function_type.PROP_SET) == eolian.object_scope.PROTECTED and
fn:scope_get(eolian.function_type.PROP_GET) ~= eolian.object_scope.PROTECTED then
buf[#buf + 1] = "@protected "
end
buf[#buf + 1] = "{"
if (#skeys == 0 or keys_same) and (#svals == 0 or vals_same) and
(not srtt or grtt == srtt) then
buf[#buf + 1] = "}\n"
else
buf[#buf + 1] = "\n"
if not keys_same then gen_prop_keyvals(skeys, "keys", buf) end
if not vals_same then gen_prop_keyvals(svals, "values", buf) end
if grtt ~= srtt then
gen_func_return(fn, eolian.function_type.PROP_SET, buf, 2)
end
buf[#buf + 1] = " }\n"
end
end
if keys_same then gen_prop_keyvals(gkeys, "keys", buf, 0) end
if vals_same then gen_prop_keyvals(gvals, "values", buf, 0) end
buf[#buf + 1] = "}"
return table.concat(buf)
end
-- builders
local nspaces_group = function(ns)
if #ns <= 2 then
return ns[1]
end
if ns[1] == "efl" and (ns[2] == "class" or ns[2] == "interface" or
ns[2] == "object" or ns[2] == "promise") then
return ns[1]
end
return ns[1] .. "." .. ns[2]
end
local nspaces_filter = function(items, ns)
local out = {}
for _, item in ipairs(items) do
local group = nspaces_group(eoutils.obj_nspaces_get(item))
if group == ns then out[#out + 1] = item end
end
return out
end
local build_method, build_property, build_event
local build_reftable = function(f, title, ctype, t, iscl)
if not t or #t == 0 then
return
end
local nt = {}
for i, v in ipairs(t) do
nt[#nt + 1] = {
writer.Buffer():write_link(
eoutils.obj_nspaces_get(v),
v:name_get()
):finish(),
docm.brief_str_get(eos, v:documentation_get())
}
end
table.sort(nt, function(v1, v2) return v1[1] < v2[1] end)
f:write_table({ title, "Brief description" }, nt)
end
local build_ref_group = function(f, ns, classlist, aliases, structs, enums, consts, globals)
local classes = {}
local ifaces = {}
local mixins = {}
for i, cl in ipairs(classlist) do
local tp = cl:type_get()
if tp == eolian.class_type.REGULAR or tp == eolian.class_type.ABSTRACT then
classes[#classes + 1] = cl
elseif tp == eolian.class_type.MIXIN then
mixins[#mixins + 1] = cl
elseif tp == eolian.class_type.INTERFACE then
ifaces[#ifaces + 1] = cl
end
end
local title = ns:gsub("(%l)(%w*)", function(a,b) return a:upper()..b end) --string.sub(ns, 1, 1):upper() .. string.sub(ns, 2):lower()
f:write_h(title, 2)
build_reftable(f, "Classes", "class", classes, true)
build_reftable(f, "Interfaces", "interface", ifaces, true)
build_reftable(f, "Mixins", "mixin", mixins, true)
build_reftable(f, "Aliases", "alias", aliases)
build_reftable(f, "Structures", "struct", structs)
build_reftable(f, "Enums", "enum", enums)
build_reftable(f, "Constants", "constant", consts)
build_reftable(f, "Globals", "global", globals)
f:write_nl()
end
local build_ref = function()
local f = writer.Writer("start", "EFL Reference")
printgen("Generating reference...")
f:write_editable({ "reference" }, "general")
f:write_nl()
local classlist = eoutils.classes_get_filtered(eos)
local aliases = eos:aliases_get():to_array()
local structs = eos:structs_get():to_array()
local enums = eos:enums_get():to_array()
local consts = eos:constants_get():to_array()
local globals = eos:globals_get():to_array()
grouped = {}
groups = {}
for i, cl in ipairs(classlist) do
local ns = eoutils.obj_nspaces_get(cl)
local name = nspaces_group(eoutils.obj_nspaces_get(cl))
local group = grouped[name]
if not group then
group = {}
grouped[name] = group
groups[#groups + 1] = name
end
group[#group + 1] = cl
end
table.sort(groups)
for _, ns in ipairs(groups) do
build_ref_group(f, ns, grouped[ns], nspaces_filter(aliases, ns), nspaces_filter(structs, ns),
nspaces_filter(enums, ns), nspaces_filter(consts, ns), nspaces_filter(globals, ns))
end
f:finish()
end
local build_inherits
build_inherits = function(cl, t, lvl)
t = t or {}
lvl = lvl or 0
local lbuf = writer.Buffer()
if lvl > 0 then
lbuf:write_link(eoutils.obj_nspaces_get(cl, true), cl:name_get())
lbuf:write_raw(" ")
lbuf:write_i("(" .. eoutils.class_type_str_get(cl) .. ")")
t[#t + 1] = { lvl - 1, lbuf:finish() }
end
for acl in cl:inherits_get() do
build_inherits(acl, t, lvl + 1)
end
return t
end
local build_inherit_summary
build_inherit_summary = function(cl, buf)
buf = buf or writer.Buffer()
buf:write_raw(" => ")
buf:write_link(eoutils.obj_nspaces_get(cl, true), cl:name_get())
buf:write_raw(" ")
buf:write_i("(" .. eoutils.class_type_str_get(cl) .. ")")
local inherits = cl:inherits_get():to_array()
if #inherits ~= 0 then
build_inherit_summary(inherits[1], buf)
end
return buf
end
local find_parent_impl
find_parent_impl = function(fulln, cl)
for pcl in cl:inherits_get() do
for impl in pcl:implements_get() do
if impl:name_get() == fulln then
return impl, pcl
end
end
local pimpl, pcl = find_parent_impl(fulln, pcl)
if pimpl then
return pimpl, pcl
end
end
return nil, cl
end
local find_parent_briefdoc
find_parent_briefdoc = function(fulln, cl)
local pimpl, pcl = find_parent_impl(fulln, cl)
if not pimpl then
return docm.brief_str_get(eos, nil)
end
local pdoc = pimpl:documentation_get(eolian.function_type.METHOD)
local pdocf = eoutils.impl_fallback_doc_get(pimpl)
if not pdoc and not pdocf then
return find_parent_briefdoc(fulln, pcl)
end
return docm.brief_str_get(eos, pdocf)
end
local build_functable = function(f, tcl, tbl)
if #tbl == 0 then
return
end
local nt = {}
for i, implt in ipairs(tbl) do
local lbuf = writer.Buffer()
local cl, impl = unpack(implt)
local func = impl:function_get()
local wt = {}
wt[0] = cl
wt[1] = func
wt[2] = impl
nt[#nt + 1] = wt
end
local get_best_scope = function(f)
local ft = f:type_get()
if ft == eolian.function_type.PROPERTY then
local fs1, fs2 = f:scope_get(eolian.function_type.PROP_GET), f:scope_get(eolian.function_type.PROP_SET)
if fs1 == eolian.object_scope.PUBLIC or fs2 == eolian.object_scope.PUBLIC then
return eolian.object_scope.PUBLIC
elseif fs1 == eolian.object_scope.PROTECTED or fs2 == eolian.object_scope.PROTECTED then
return eolian.object_scope.PROTECTED
else
return eolian.object_scope.PRIVATE
end
else
return f:scope_get(ft)
end
end
table.sort(nt, function(v1, v2)
local cl1, cl2 = v1[0], v2[0]
if cl1 ~= cl2 then
return cl1:name_get() < cl2:name_get()
end
local f1, f2 = v1[1], v2[1]
local f1s, f2s = get_best_scope(f1), get_best_scope(f2)
if f1s ~= f2s then
if f1s ~= eolian.object_scope.PROTECED then
-- public funcs go first, private funcs go last
return f1s == eolian.object_scope.PUBLIC
else
-- protected funcs go second
return f2s == eolian.object_scope.PRIVATE
end
end
return f1:name_get() < f2:name_get()
end)
return nt
end
local write_description = function(f, impl, func, cl)
local over = eoutils.impl_is_overridden(impl, cl)
local bdoc
local doc = impl:documentation_get(eolian.function_type.METHOD)
local docf = eoutils.impl_fallback_doc_get(impl)
if over and not doc and not docf then
bdoc = find_parent_briefdoc(impl:name_get(), cl)
else
bdoc = docm.brief_str_get(eos, docf)
end
if bdoc ~= "No description supplied." then
f:write_raw(bdoc)
end
end
local write_function = function(f, func, cl)
local llbuf = writer.Buffer()
llbuf:write_link(eoutils.func_nspaces_get(func, cl, true), func:name_get())
f:write_b(llbuf:finish())
local pt = propt_to_type[func:type_get()]
if pt then
f:write_raw(" ")
local llbuf = writer.Buffer()
llbuf:write_b(pt)
f:write_i(llbuf:finish())
end
end
local write_scope = function(f, func)
local ftt = {
[eolian.object_scope.PROTECTED] = "protected",
[eolian.object_scope.PRIVATE] = "private"
}
if func:is_class() then
f:write_raw(" ")
f:write_m("class")
end
if func:type_get() == eolian.function_type.PROPERTY then
local ft1, ft2 = ftt[func:scope_get(eolian.function_type.PROP_GET)],
ftt[func:scope_get(eolian.function_type.PROP_SET)]
if ft1 and ft1 == ft2 then
f:write_raw(" ")
f:write_m(ft1)
elseif ft1 or ft2 then
local s = ""
if ft1 then
s = s .. ft1 .. " get" .. (ft2 and ", " or "")
end
if ft2 then
s = s .. ft2 .. " set"
end
f:write_raw(" ")
f:write_m(s)
end
else
local ft = ftt[func:scope_get(func:type_get())]
if ft then
f:write_raw(" ")
f:write_m(ft)
end
end
end
local write_functable = function(f, tcl, tbl)
if #tbl == 0 then
return
end
local nt = build_functable(t, tcl, tbl)
local wrote = false
for i, wt in ipairs(nt) do
local cl = wt[0]
local func = wt[1]
local impl = wt[2]
local ocl = impl:class_get()
local func = impl:function_get()
local over = eoutils.impl_is_overridden(impl, cl)
-- function
write_function(f, func, cl)
-- scope
write_scope(f, func)
-- overrides
if over then
-- TODO: possibly also mention which part of a property was
-- overridden and where, get/set override point might differ!
-- but we get latest doc every time so it's ok for now
local llbuf = writer.Buffer()
llbuf:write_raw(" [Overridden from ")
llbuf:write_link(eoutils.obj_nspaces_get(ocl, true), ocl:name_get())
llbuf:write_raw("]")
f:write_i(llbuf:finish())
end
-- description
f:write_br(true)
f:write_raw("> ")
write_description(f, impl, func, cl)
-- code snippets
f:write_nl()
local codes = {}
if func:type_get() ~= eolian.function_type.PROPERTY then
codes[#codes + 1] = gen_func_csig(func, func:type_get())
else
codes[#codes + 1] = gen_func_csig(func, eolian.function_type.PROP_GET)
codes[#codes + 1] = gen_func_csig(func, eolian.function_type.PROP_SET)
end
f:write_code(table.concat(codes, "\n"), "c")
f:write_br(true)
if cl == tcl then
if impl:is_prop_get() or impl:is_prop_set() then
build_property(impl, cl)
else
build_method(impl, cl)
end
end
end
f:write_nl()
end
local write_inherit_functable = function(f, tcl, tbl)
if #tbl == 0 then
return
end
local nt = build_functable(t, tcl, tbl)
local prevcl = tcl
for i, wt in ipairs(nt) do
local cl = wt[0]
local func = wt[1]
local impl = wt[2]
local func = impl:function_get()
-- class grouping for inheritance
if cl ~= prevcl then
prevcl = cl
f:write_raw("^ ")
f:write_link(eoutils.obj_nspaces_get(cl, true), cl:name_get())
f:write_raw(" ^^^")
f:write_nl()
end
-- scope
f:write_raw("| ")
write_scope(f, func)
f:write_raw(" | ")
-- function
write_function(f, func, cl)
f:write_raw(" | ")
-- description
write_description(f, impl, func, cl)
f:write_raw(" |")
f:write_nl()
end
f:write_nl()
end
-- finds all stuff that is callable on a class, respecting
-- overrides and not duplicating, does a depth-first search
local find_callables
find_callables = function(cl, omeths, events, written)
for pcl in cl:inherits_get() do
for impl in pcl:implements_get() do
local func = impl:function_get()
local fid = eoutils.obj_id_get(func)
if not written[fid] then
omeths[#omeths + 1] = { pcl, impl }
written[fid] = true
end
end
for ev in pcl:events_get() do
local evid = ev:name_get()
if not written[evid] then
events[#events + 1] = { pcl, ev }
written[evid] = true
end
end
find_callables(pcl, omeths, events, written)
end
end
local build_evcsig = function(ev)
local csbuf = { ev:c_name_get(), "(" }
csbuf[#csbuf + 1] = eoutils.type_cstr_get(ev:type_get())
if ev:is_beta() then
csbuf[#csbuf + 1] = ", @beta"
end
if ev:is_hot() then
csbuf[#csbuf + 1] = ", @hot"
end
if ev:is_restart() then
csbuf[#csbuf + 1] = ", @restart"
end
csbuf[#csbuf + 1] = ")";
return table.concat(csbuf)
end
local build_evtable = function(f, tcl, tbl, newm)
if #tbl == 0 then
return
end
local nt = {}
for i, evt in ipairs(tbl) do
local lbuf = writer.Buffer()
local evn
local cl, ev
if not newm then
cl, ev = evt[1], evt[2]
else
cl, ev = tcl, evt
end
local wt = {}
wt[0] = cl
wt[1] = ev
wt[2] = ev:name_get()
nt[#nt + 1] = wt
end
table.sort(nt, function(v1, v2)
if v1[0] ~= v2[0] then
return v1[0]:name_get() < v2[0]:name_get()
end
return v1[2] < v2[2]
end)
return nt
end
local write_event_scope = function(f, ev)
local ett = {
[eolian.object_scope.PROTECTED] = "protected",
[eolian.object_scope.PRIVATE] = "private"
}
local ets = ett[ev:scope_get()]
if ets then
f:write_raw(" ")
f:write_m(ets)
end
end
local write_evtable = function(f, tcl, tbl)
if #tbl == 0 then
return
end
local nt = build_evtable(f, tcl, tbl, true)
for i, wt in ipairs(nt) do
local evn
local cl, ev = wt[0], wt[1]
local llbuf = writer.Buffer()
llbuf:write_link(eoutils.event_nspaces_get(ev, cl, true), wt[2])
f:write_b(llbuf:finish())
-- scope
write_event_scope(f, ev)
-- description
local bdoc = docm.brief_str_get(eos, ev:documentation_get())
if bdoc ~= "No description supplied." then
f:write_br(true)
f:write_raw("> ")
f:write_raw(bdoc)
end
f:write_nl()
f:write_code(build_evcsig(ev), "c");
f:write_br()
if cl == tcl then
build_event(ev, cl)
end
end
end
local write_inherit_evtable = function(f, tcl, tbl)
if #tbl == 0 then
return
end
local nt = build_evtable(f, tcl, tbl, false)
local prevcl
for i, wt in ipairs(nt) do
local evn
local cl, ev = wt[0], wt[1]
if cl ~= prevcl then
prevcl = cl
f:write_raw("^ ")
f:write_link(eoutils.obj_nspaces_get(cl, true), cl:name_get())
f:write_raw(" ^^^")
f:write_nl()
end
f:write_raw("| ")
-- scope
write_event_scope(f, ev)
f:write_raw(" | ")
local llbuf = writer.Buffer()
llbuf:write_link(eoutils.event_nspaces_get(ev, cl, true), wt[2])
f:write_b(llbuf:finish())
f:write_raw(" | ")
local bdoc = docm.brief_str_get(eos, ev:documentation_get())
if bdoc ~= "No description supplied." then
f:write_raw(bdoc)
end
f:write_raw(" |")
f:write_nl()
end
end
local build_class = function(cl)
local cln = eoutils.obj_nspaces_get(cl)
local fulln = cl:name_get()
local f = writer.Writer(cln, fulln)
printgen("Generating class: " .. fulln)
mono.build_class(cl)
f:write_h(cl:name_get() .. " (" .. eoutils.class_type_str_get(cl) .. ")", 1)
f:write_h("Description", 2)
f:write_raw(docm.full_str_get(eos, cl:documentation_get(), nil, true))
f:write_nl(2)
f:write_editable(cln, "description")
f:write_nl()
local inherits = cl:inherits_get():to_array()
if #inherits ~= 0 then
f:write_h("Inheritance", 2)
f:write_raw(build_inherit_summary(inherits[1]):finish())
f:write_nl()
f:write_folded("Full hierarchy", function()
f:write_list(build_inherits(cl))
end)
f:write_nl()
end
local written = {}
local ievs = {}
local meths, omeths = {}, {}
for impl in cl:implements_get() do
local func = impl:function_get()
written[eoutils.obj_id_get(func)] = true
meths[#meths + 1] = { cl, impl }
end
find_callables(cl, omeths, ievs, written)
f:write_h("Members", 2)
write_functable(f, cl, meths, true)
if #omeths ~= 0 then
f:write_h("Inherited", 3)
end
write_inherit_functable(f, cl, omeths, false)
f:write_h("Events", 2)
write_evtable(f, cl, cl:events_get():to_array(), true)
if #ievs ~= 0 then
f:write_h("Inherited", 3)
end
write_inherit_evtable(f, cl, ievs, false)
f:finish()
end
local build_classes = function()
for cl in eos:classes_get() do
if eoutils.obj_matches_filter(cl) then
build_class(cl)
end
end
end
local build_typedecls = function()
for tp in eos:aliases_get() do
render_template("alias", eoutils.obj_nspaces_get(tp), tp:name_get(), {
type_obj = tp
})
end
for tp in eos:structs_get() do
render_template(
"struct_enum", eoutils.obj_nspaces_get(tp), tp:name_get(), {
type_obj = tp, obj_fields = tp:struct_fields_get()
}
)
end
for tp in eos:enums_get() do
render_template(
"struct_enum", eoutils.obj_nspaces_get(tp), tp:name_get(), {
type_obj = tp, obj_fields = tp:enum_fields_get()
}
)
end
end
local build_variables = function()
for v in eos:constants_get() do
render_template(
"variable", eoutils.obj_nspaces_get(v), v:name_get(), {
var_obj = v, var_is_constant = true
}
)
end
for v in eos:globals_get() do
render_template(
"variable", eoutils.obj_nspaces_get(v), v:name_get(), {
var_obj = v, var_is_constant = false
}
)
end
end
local build_parlist = function(f, pl, nodir)
local params = {}
for i, p in ipairs(pl) do
local buf = writer.Buffer()
buf:write_b(p:name_get())
if not nodir then
buf:write_raw(" ")
buf:write_i("(", eoutils.param_get_dir_name(p), ")")
end
buf:write_raw(" - ", docm.full_str_get(eos, p:documentation_get()))
params[#params + 1] = buf:finish()
end
f:write_list(params)
end
local build_vallist = function(f, pg, ps, title)
if #pg == #ps then
local same = true
for i = 1, #pg do
if pg[i] ~= ps[i] then
same = false
break
end
end
if same then ps = {} end
end
if #pg > 0 or #ps > 0 then
f:write_h(title, 2)
if #pg > 0 then
if #ps > 0 then
f:write_h("Getter", 3)
end
build_parlist(f, pg, true)
end
if #ps > 0 then
if #pg > 0 then
f:write_h("Setter", 3)
end
build_parlist(f, ps, true)
end
end
end
local find_parent_doc
find_parent_doc = function(fulln, cl, ftype)
local pimpl, pcl = find_parent_impl(fulln, cl)
if not pimpl then
return nil
end
local pdoc = pimpl:documentation_get(ftype)
if not pdoc then
return find_parent_doc(fulln, pcl, ftype)
end
return pdoc
end
local write_inherited_from = function(f, impl, cl, over, prop)
if not over then
return
end
local buf = writer.Buffer()
buf:write_raw("Overridden from ")
local pimpl, pcl = find_parent_impl(impl:name_get(), cl)
buf:write_link(
eoutils.func_nspaces_get(impl:function_get(), pcl, true), impl:name_get()
)
if prop then
buf:write_raw(" ")
local lbuf = writer.Buffer()
lbuf:write_raw("(")
if impl:is_prop_get() then
lbuf:write_raw("get")
if impl:is_prop_set() then
lbuf:write_raw(", ")
end
end
if impl:is_prop_set() then
lbuf:write_raw("set")
end
lbuf:write_raw(")")
buf:write_b(lbuf:finish())
end
buf:write_raw(".")
f:write_i(buf:finish())
end
local impls_of = {}
local get_all_impls_of
get_all_impls_of = function(tbl, cl, fn, got)
local cfn = cl:name_get()
if got[cfn] then
return
end
got[cfn] = true
for imp in cl:implements_get() do
local ofn = imp:function_get()
if ofn == fn then
tbl[#tbl + 1] = cl
break
end
end
for i, icl in ipairs(eoutils.class_children_get(cl)) do
get_all_impls_of(tbl, icl, fn, got)
end
end
local write_ilist = function(f, impl, cl)
local fn = impl:function_get()
local fnn = fn:name_get()
local ocl = fn:implement_get():class_get()
local onm = ocl:name_get() .. "." .. fnn
local imps = impls_of[onm]
if not imps then
imps = {}
impls_of[onm] = imps
get_all_impls_of(imps, ocl, fn, {})
end
f:write_h("Implemented by", 2)
local t = {}
for i, icl in ipairs(imps) do
local buf = writer.Buffer()
local cfn = icl:name_get() .. "." .. fnn
if icl == cl then
buf:write_b(cfn)
else
buf:write_link(eoutils.func_nspaces_get(fn, icl, true), cfn)
end
t[#t + 1] = buf:finish()
end
f:write_list(t)
end
build_method = function(impl, cl)
local over = eoutils.impl_is_overridden(impl, cl)
local fn = impl:function_get()
local mns = eoutils.func_nspaces_get(fn, cl)
local methn = cl:name_get() .. "." .. fn:name_get()
local f = writer.Writer(mns, methn)
printgen("Generating method: " .. methn)
local doc = impl:documentation_get(eolian.function_type.METHOD)
if over and not doc then
doc = find_parent_doc(impl:name_get(), cl, eolian.function_type.METHOD)
end
f:write_h("Description", 2)
f:write_raw(docm.full_str_get(eos, doc, nil, true))
f:write_nl()
f:write_editable(mns, "description")
f:write_nl()
write_inherited_from(f, impl, cl, over, false)
f:write_h("Signature", 2)
f:write_code(gen_method_sig(fn, cl))
f:write_nl()
f:write_h("C signature", 2)
f:write_code(gen_func_csig(fn, nil), "c")
f:write_nl()
local pars = fn:parameters_get():to_array()
if #pars > 0 then
f:write_h("Parameters", 2)
build_parlist(f, pars)
f:write_nl()
end
write_ilist(f, impl, cl)
f:write_nl()
f:finish()
end
build_property = function(impl, cl)
local over = eoutils.impl_is_overridden(impl, cl)
local fn = impl:function_get()
local pns = eoutils.func_nspaces_get(fn, cl)
local propn = cl:name_get() .. "." .. fn:name_get()
local f = writer.Writer(pns, propn)
printgen("Generating property: " .. propn)
local pimp = fn:implement_get()
local isget = pimp:is_prop_get()
local isset = pimp:is_prop_set()
local doc = impl:documentation_get(eolian.function_type.PROPERTY)
local gdoc = impl:documentation_get(eolian.function_type.PROP_GET)
local sdoc = impl:documentation_get(eolian.function_type.PROP_SET)
if over then
if not doc then
doc = find_parent_doc(impl:name_get(), cl, eolian.function_type.PROPERTY)
end
if isget and not gdoc then
gdoc = find_parent_doc(impl:name_get(), cl, eolian.function_type.PROP_GET)
end
if isset and not sdoc then
sdoc = find_parent_doc(impl:name_get(), cl, eolian.function_type.PROP_SET)
end
end
if isget and isset then
f:write_h("Description", 2)
if doc or (not gdoc and not sdoc) then
f:write_raw(docm.full_str_get(eos, doc, nil, true))
end
if (isget and gdoc) or (isset and sdoc) then
f:write_nl(2)
end
f:write_nl()
f:write_editable(pns, "description")
f:write_nl()
end
local pgkeys = isget and fn:property_keys_get(eolian.function_type.PROP_GET):to_array() or {}
local pskeys = isset and fn:property_keys_get(eolian.function_type.PROP_SET):to_array() or {}
build_vallist(f, pgkeys, pskeys, "Keys")
local pgvals = isget and fn:property_values_get(eolian.function_type.PROP_GET):to_array() or {}
local psvals = isset and fn:property_values_get(eolian.function_type.PROP_SET):to_array() or {}
build_vallist(f, pgvals, psvals, "Values")
if isget and gdoc then
if isset then
f:write_h("Getter", 3)
else
f:write_h("Description", 2)
end
f:write_raw(docm.full_str_get(eos, gdoc, nil, true))
if isset and sdoc then
f:write_nl(2)
end
if isset then
f:write_nl()
f:write_editable(pns, "getter_description")
f:write_nl()
end
end
if isset and sdoc then
if isget then
f:write_h("Setter", 3)
else
f:write_h("Description", 2)
end
f:write_raw(docm.full_str_get(eos, sdoc, nil, true))
if isget then
f:write_nl()
f:write_editable(pns, "getter_description")
f:write_nl()
end
end
f:write_nl()
if not isget or not isset then
f:write_nl()
f:write_br()
f:write_editable(pns, "description")
f:write_nl()
end
write_inherited_from(f, impl, cl, over, true)
f:write_h("Signature", 2)
f:write_code(gen_prop_sig(fn, cl))
f:write_nl()
f:write_h("C signature", 2)
local codes = {}
if isget then
codes[#codes + 1] = gen_func_csig(fn, eolian.function_type.PROP_GET)
end
if isset then
codes[#codes + 1] = gen_func_csig(fn, eolian.function_type.PROP_SET)
end
f:write_code(table.concat(codes, "\n"), "c")
f:write_nl()
write_ilist(f, impl, cl)
f:write_nl()
f:finish()
end
local build_event_example = function(ev)
local evcn = ev:c_name_get()
local evcnl = evcn:lower()
local dtype = "Data *"
local tbl = { "static void\n" }
tbl[#tbl + 1] = "on_"
tbl[#tbl + 1] = evcnl
tbl[#tbl + 1] = "(void *data, const Efl_Event *event)\n{\n "
tbl[#tbl + 1] = eoutils.type_cstr_get(ev:type_get(), "info = event->info;\n")
tbl[#tbl + 1] = " Eo *obj = event->object;\n "
tbl[#tbl + 1] = eoutils.type_cstr_get(dtype, "d = data;\n\n")
tbl[#tbl + 1] = " /* event hander code */\n}\n\n"
tbl[#tbl + 1] = "static void\nsetup_event_handler(Eo *obj, "
tbl[#tbl + 1] = eoutils.type_cstr_get(dtype, "d")
tbl[#tbl + 1] = ")\n{\n"
tbl[#tbl + 1] = " efl_event_callback_add(obj, "
tbl[#tbl + 1] = evcn
tbl[#tbl + 1] = ", on_"
tbl[#tbl + 1] = evcnl
tbl[#tbl + 1] = ", d);\n}\n"
return table.concat(tbl)
end
build_event = function(ev, cl)
local evn = eoutils.event_nspaces_get(ev, cl)
local evnm = cl:name_get() .. ": " .. ev:name_get()
local f = writer.Writer(evn, evnm)
printgen("Generating event: " .. evnm)
f:write_h("Description", 2)
f:write_raw(docm.full_str_get(eos, ev:documentation_get(), nil, true))
f:write_nl()
f:write_editable(evn, "description")
f:write_nl()
f:write_h("Signature", 2)
local buf = { ev:name_get() }
if ev:scope_get() == eolian.object_scope.PRIVATE then
buf[#buf + 1] = " @private"
elseif ev:scope_get() == eolian.object_scope.PROTECTED then
buf[#buf + 1] = " @protected"
end
if ev:is_beta() then
buf[#buf + 1] = " @beta"
end
if ev:is_hot() then
buf[#buf + 1] = " @hot"
end
if ev:is_restart() then
buf[#buf + 1] = " @restart"
end
local etp = ev:type_get()
if etp then
buf[#buf + 1] = ": "
buf[#buf + 1] = eoutils.obj_serialize(etp)
end
buf[#buf + 1] = ";"
f:write_code(table.concat(buf))
f:write_nl()
f:write_h("C information", 2)
f:write_code(build_evcsig(ev), "c")
f:write_nl()
f:write_h("C usage", 2)
f:write_code(build_event_example(ev), "c")
f:write_nl()
f:finish()
end
local build_stats_keyref = function()
for i, cl in ipairs(eoutils.classes_get_filtered(eos)) do
stats.check_class(cl)
keyref.add(cl:name_get():gsub("%.", "_"), eoutils.obj_nspaces_get(cl), "c")
for imp in cl:implements_get() do
-- TODO: handle doc overrides in stats system
if not eoutils.impl_is_overridden(imp, cl) then
local func = imp:function_get()
local fns = eoutils.func_nspaces_get(func, cl)
if imp:is_prop_get() or imp:is_prop_set() then
if imp:is_prop_get() then
stats.check_property(func, cl, eolian.function_type.PROP_GET)
keyref.add(func:full_c_name_get(eolian.function_type.PROP_GET), fns, "c")
end
if imp:is_prop_set() then
stats.check_property(func, cl, eolian.function_type.PROP_SET)
keyref.add(func:full_c_name_get(eolian.function_type.PROP_SET), fns, "c")
end
else
stats.check_method(func, cl)
keyref.add(func:full_c_name_get(eolian.function_type.METHOD), fns, "c")
end
end
end
for ev in cl:events_get() do
keyref.add(ev:c_name_get(), eoutils.event_nspaces_get(ev, cl), "c")
end
end
for tp in eos:aliases_get() do
stats.check_alias(tp)
end
for tp in eos:structs_get() do
stats.check_struct(tp)
end
for tp in eos:enums_get() do
stats.check_enum(tp)
end
for v in eos:constants_get() do
stats.check_constant(v)
end
for v in eos:globals_get() do
stats.check_global(v)
end
end
local scan_directory = function(dir)
if not dir then
if not eos:system_directory_add() then
error("failed scanning system directory")
end
return
end
if not eos:directory_add(dir) then
error("failed scanning directory: " .. dir)
end
end
local parse = function(st)
if not eos:all_eot_files_parse() then
error("failed parsing eo type files")
end
if st and st:match("%.") then
if not eos:file_parse(st:gsub("%.", "_"):lower() .. ".eo") then
error("failed parsing eo file")
end
else
if not eos:all_eo_files_parse() then
error("failed parsing eo files")
end
end
end
getopt.parse {
args = arg,
descs = {
{ category = "General" },
{ "h", "help", nil, help = "Show this message.", metavar = "CATEGORY",
callback = getopt.help_cb(io.stdout)
},
{ "v", "verbose", false, help = "Be verbose." },
{ "p", "print-gen", false, help = "Print what is being generated." },
{ category = "Generator" },
{ "r", "root", true, help = "Root path of the docs." },
{ "n", "namespace", true, help = "Root namespace of the docs." },
{ nil, "disable-notes", false, help = "Disable notes plugin usage." },
{ nil, "disable-folded", false, help = "Disable folded plugin usage." },
{ nil, "disable-title", false, help = "Disable title plugin usage." },
{ "m", "use-markdown", false,
help = "Generate Markdown instead of DokuWiki syntax." },
{ nil, "pass", true, help = "The pass to run (optional) "
.. "(rm, ref, clist, classes, types, vars, stats or class name)." }
},
error_cb = function(parser, msg)
io.stderr:write(msg, "\n")
getopt.help(parser, io.stderr)
end,
done_cb = function(parser, opts, args)
if opts["h"] then
return
end
if opts["p"] then
printgen = function(...) print(...) end
end
local rootns = (not opts["n"] or opts["n"] == "")
and "develop:api" or opts["n"]
local dr
if not opts["r"] or opts["r"] == "" then
dr = "dokuwiki/data/pages"
else
dr = opts["r"]
end
dr = dutil.path_join(dr, dutil.nspace_to_path(rootns))
dutil.init(dr, rootns)
writer.set_backend("dokuwiki")
eos = eolian:new()
if #args == 0 then
scan_directory()
else
for i, p in ipairs(args) do
scan_directory(p)
end
end
local st = opts["pass"]
parse(st)
eoutils.build_class_children(eos)
mono.init(eos)
if st == "clist" then
for cl in eos:classes_get() do
if eoutils.obj_matches_filter(cl) then
print(cl:name_get())
end
end
return
end
local wfeatures = {
notes = not opts["disable-notes"],
folds = not opts["disable-folded"],
title = not opts["disable-title"]
}
writer.init(rootns, wfeatures)
docm.init(rootns, wfeatures.notes, wfeatures.title)
if not st or st == "rm" then
dutil.rm_root()
dutil.mkdir_r(nil)
end
if not st or st == "ref" then
build_ref()
end
if not st or st == "classes" then
build_classes()
end
if st and st:match("%.") then
local cl = eos:class_by_name_get(st)
if cl then
build_class(cl)
end
end
if not st or st == "types" then
build_typedecls()
end
if not st or st == "vars" then
build_variables()
end
if not st or st == "stats" then
stats.init(not not opts["v"])
build_stats_keyref()
keyref.build()
-- newline if printing what's being generated
printgen()
stats.print()
end
end
}
return true