From 31ae703ececd69a79a474ea244a75a33900011d0 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Sun, 9 Sep 2018 17:15:21 +0200 Subject: [PATCH] initial refreshed docgen implementation This adds a new template engine as well as removes doctree and a bunch of other changes, but for now does not actually utilize the templates. More work will come in very soon, cleaning up the codebase, templating it and fixing the C# support. --- README.md => README-docgen.md | 0 docgen/eolian_utils.lua | 539 +++++++ docgen/keyref.lua | 41 + docgen/mono.lua | 621 ++++++++ docgen/stats.lua | 278 ++++ docgen/util.lua | 50 + docgen/writer.lua | 403 +++++ gendoc.lua | 2668 +++++++++++++++++---------------- template.lua | 254 ++++ 9 files changed, 3553 insertions(+), 1301 deletions(-) rename README.md => README-docgen.md (100%) create mode 100644 docgen/eolian_utils.lua create mode 100644 docgen/keyref.lua create mode 100644 docgen/mono.lua create mode 100644 docgen/stats.lua create mode 100644 docgen/util.lua create mode 100644 docgen/writer.lua create mode 100644 template.lua diff --git a/README.md b/README-docgen.md similarity index 100% rename from README.md rename to README-docgen.md diff --git a/docgen/eolian_utils.lua b/docgen/eolian_utils.lua new file mode 100644 index 0000000..00f0d72 --- /dev/null +++ b/docgen/eolian_utils.lua @@ -0,0 +1,539 @@ +local ffi = require("ffi") +local eolian = require("eolian") +local keyref = require("docgen.keyref") + +local M = {} + +local writer + +local func_type_str = { + [eolian.function_type.PROPERTY] = "property", + [eolian.function_type.PROP_GET] = "property", + [eolian.function_type.PROP_SET] = "property", + [eolian.function_type.METHOD] = "method" +} + +M.tok_ref_resolve = function(tok, eos, root) + local tp, d1, d2 = tok:ref_resolve(eos) + local ret = {} + for tokp in d1:name_get():gmatch("[^%.]+") do + ret[#ret + 1] = tokp:lower() + end + if tp == eolian.object_type.FUNCTION then + ret[#ret + 1] = func_type_str[d2:type_get()] + ret[#ret + 1] = d2:name_get():lower() + elseif tp == eolian.object_type.EVENT then + ret[#ret + 1] = "event" + ret[#ret + 1] = d2:name_get():lower() + end + if root ~= nil then + ret[#ret + 1] = not not root + end + return ret +end + +M.obj_matches_filter = function(obj) + local ns = M.obj_nspaces_get(obj) + if #ns and (ns[1] == "efl" or ns[1] == "eina") then + return true + end + return false +end + +M.classes_get_filtered = function(eos) + local ret = {} + for cl in eos:classes_get() do + if M.obj_matches_filter(cl) then + ret[#ret + 1] = cl + end + end + return ret +end + +M.obj_id_get = function(obj) + return tonumber(ffi.cast("uintptr_t", obj)) +end + +M.obj_nspaces_get = function(obj, root) + local tbl = obj:namespaces_get():to_array() + for i = 1, #tbl do + tbl[i] = tbl[i]:lower() + end + tbl[#tbl + 1] = obj:short_name_get():lower() + if root ~= nil then + tbl[#tbl + 1] = not not root + end + return tbl +end + +M.event_nspaces_get = function(obj, cl, root) + local tbl = M.obj_nspaces_get(cl) + tbl[#tbl + 1] = "event" + tbl[#tbl + 1] = obj:name_get():lower():gsub(",", "_") + if root ~= nil then + tbl[#tbl + 1] = not not root + end + return tbl +end + +M.func_nspaces_get = function(obj, cl, root) + local tbl = M.obj_nspaces_get(cl) + tbl[#tbl + 1] = func_type_str[obj:type_get()] + tbl[#tbl + 1] = obj:name_get():lower() + if root ~= nil then + tbl[#tbl + 1] = not not root + end + return tbl +end + +local gen_doc_refd = function(str, eos) + if not writer then + writer = require("docgen.writer") + end + if not str then + return nil + end + local pars = eolian.documentation_string_split(str) + for i = 1, #pars do + pars[i] = writer.Buffer():write_par(pars[i], eos):finish() + end + return table.concat(pars, "\n\n") +end + +local add_since = function(str, since) + if not writer then + writer = require("docgen.writer") + end + if not since then + return str + end + local buf = writer.Buffer() + if not str then + buf:write_i("Since " .. since) + return buf:finish() + end + buf:write_raw(str) + buf:write_nl(2) + buf:write_i("Since " .. since) + return buf:finish() +end + +M.doc_summary_get = function(obj) + if not obj then + return nil + end + return obj:summary_get() +end + +M.doc_description_get = function(obj) + if not obj then + return nil + end + return obj:description_get() +end + +M.doc_since_get = function(obj) + if not obj then + return nil + end + return obj:since_get() +end + +M.doc_brief_get = function(eos, obj, obj2) + if not obj and not obj2 then + return "No description supplied." + end + if not obj then + obj = obj2 + end + return gen_doc_refd(obj:summary_get(), eos) +end + +M.doc_full_get = function(eos, obj, obj2, write_since) + if not obj and not obj2 then + return "No description supplied." + end + if not obj then + obj, obj2 = obj2, obj + end + local sum1 = obj:summary_get() + local desc1 = obj:description_get() + local edoc = "" + local since + if obj2 then + local sum2 = obj2:summary_get() + local desc2 = obj2:descirption_get() + if not desc2 then + if sum2 then + edoc = "\n\n" .. sum2 + end + else + edoc = "\n\n" .. sum2 .. "\n\n" .. desc2 + end + if write_since then + since = obj2:since_get() + end + end + if not since and write_since then + since = obj:since_get() + end + if not desc1 then + return add_since(gen_doc_refd(sum1 .. edoc, eos), since) + end + return add_since(gen_doc_refd(sum1 .. "\n\n" .. desc1 .. edoc, eos), since) +end + +local class_type_str = { + [eolian.class_type.REGULAR] = "class", + [eolian.class_type.ABSTRACT] = "class", + [eolian.class_type.MIXIN] = "mixin", + [eolian.class_type.INTERFACE] = "interface" +} + +M.class_type_str_get = function(cl) + return class_type_str[cl:type_get()] +end + +local param_dirs = { + [eolian.parameter_dir.IN] = "in", + [eolian.parameter_dir.OUT] = "out", + [eolian.parameter_dir.INOUT] = "inout" +} + +M.param_get_dir_name = function(param) + return assert(param_dirs[param:direction_get()], "unknown parameter direction") +end + +M.type_cstr_get = function(tp, suffix) + tp = tp or "void" + local ct = (type(tp) == "string") and tp or tp:c_type_get(eolian.c_type_type.DEFAULT) + if not suffix then + return ct + end + if ct:sub(#ct) == "*" then + return ct .. suffix + else + return ct .. " " .. suffix + end +end + +local serialize_type +local serialize_tdecl +local serialize_tdecl_c +local serialize_var +local serialize_var_c + +local wrap_type_attrs = function(tp, str) + if tp:is_const() then + str = "const(" .. str .. ")" + end + -- TODO: implement new ownership system into docs + --if tp:is_own() then + -- str = "own(" .. str .. ")" + --end + local ffunc = tp:free_func_get() + if ffunc then + str = "free(" .. str .. ", " .. ffunc .. ")" + end + if tp:is_ptr() then + str = "ptr(" .. str .. ")" + end + return str +end + +serialize_type = function(tp) + local tpt = tp:type_get() + if tpt == eolian.type_type.UNKNOWN then + error("unknown type: " .. tp:name_get()) + elseif tpt == eolian.type_type.VOID then + return wrap_type_attrs(tp, "void") + elseif tpt == eolian.type_type.UNDEFINED then + return wrap_type_attrs(tp, "__undefined_type") + elseif tpt == eolian.type_type.REGULAR or tpt == eolian.type_type.CLASS then + local stp = tp:base_type_get() + if stp then + local stypes = {} + while stp do + stypes[#stypes + 1] = serialize_type(stp) + stp = stp:next_type_get() + end + return wrap_type_attrs(tp, tp:name_get() .. "<" + .. table.concat(stypes, ", ") .. ">") + end + return wrap_type_attrs(tp, tp:name_get()) + end + error("unhandled type type: " .. tpt) +end + +local add_typedecl_attrs = function(tp, buf) + if tp:is_extern() then + buf[#buf + 1] = "@extern " + end + local ffunc = tp:free_func_get() + if ffunc then + buf[#buf + 1] = "@free(" + buf[#buf + 1] = ffunc + buf[#buf + 1] = ") " + end +end + +serialize_tdecl = function(tp) + local tpt = tp:type_get() + if tpt == eolian.typedecl_type.UNKNOWN then + error("unknown typedecl: " .. tp:name_get()) + elseif tpt == eolian.typedecl_type.STRUCT or + tpt == eolian.typedecl_type.STRUCT_OPAQUE then + local buf = { "struct " } + add_typedecl_attrs(tp, buf) + buf[#buf + 1] = tp:name_get() + if tpt == eolian.typedecl_type.STRUCT_OPAQUE then + buf[#buf + 1] = ";" + return table.concat(buf) + end + local fields = tp:struct_fields_get():to_array() + if #fields == 0 then + buf[#buf + 1] = " {}" + return table.concat(buf) + end + buf[#buf + 1] = " {\n" + for i, fld in ipairs(fields) do + buf[#buf + 1] = " " + buf[#buf + 1] = fld:name_get() + buf[#buf + 1] = ": " + buf[#buf + 1] = serialize_type(fld:type_get()) + buf[#buf + 1] = ";\n" + end + buf[#buf + 1] = "}" + return table.concat(buf) + elseif tpt == eolian.typedecl_type.ENUM then + local buf = { "enum " } + add_typedecl_attrs(tp, buf) + buf[#buf + 1] = tp:name_get() + local fields = tp:enum_fields_get():to_array() + if #fields == 0 then + buf[#buf + 1] = " {}" + return table.concat(buf) + end + buf[#buf + 1] = " {\n" + for i, fld in ipairs(fields) do + buf[#buf + 1] = " " + buf[#buf + 1] = fld:name_get() + local val = fld:value_get() + if val then + buf[#buf + 1] = ": " + buf[#buf + 1] = val:serialize() + end + if i == #fields then + buf[#buf + 1] = "\n" + else + buf[#buf + 1] = ",\n" + end + end + buf[#buf + 1] = "}" + return table.concat(buf) + elseif tpt == eolian.typedecl_type.ALIAS then + local buf = { "type " } + add_typedecl_attrs(tp, buf) + buf[#buf + 1] = tp:name_get() + buf[#buf + 1] = ": " + buf[#buf + 1] = serialize_type(tp:base_type_get()) + buf[#buf + 1] = ";" + return table.concat(buf) + elseif tpt == eolian.typedecl_type.FUNCTION_POINTER then + return "TODO" + end + error("unhandled typedecl type: " .. tpt) +end + +serialize_tdecl_c = function(tp, ns) + local tpt = tp:type_get() + if tpt == eolian.typedecl_type.UNKNOWN then + error("unknown typedecl: " .. tp:name_get()) + elseif tpt == eolian.typedecl_type.STRUCT or + tpt == eolian.typedecl_type.STRUCT_OPAQUE then + local buf = { "typedef struct " } + local fulln = tp:name_get():gsub("%.", "_"); + keyref.add(fulln, ns, "c") + buf[#buf + 1] = "_" .. fulln; + if tpt == eolian.typedecl_type.STRUCT_OPAQUE then + buf[#buf + 1] = " " .. fulln .. ";" + return table.concat(buf) + end + local fields = tp:struct_fields_get():to_array() + if #fields == 0 then + buf[#buf + 1] = " {} " .. fulln .. ";" + return table.concat(buf) + end + buf[#buf + 1] = " {\n" + for i, fld in ipairs(fields) do + buf[#buf + 1] = " " + buf[#buf + 1] = M.type_cstr_get(fld:type_get(), fld:name_get()) + buf[#buf + 1] = ";\n" + end + buf[#buf + 1] = "} " .. fulln .. ";" + return table.concat(buf) + elseif tpt == eolian.typedecl_type.ENUM then + local buf = { "typedef enum" } + local fulln = tp:name_get():gsub("%.", "_"); + keyref.add(fulln, ns, "c") + local fields = tp:enum_fields_get():to_array() + if #fields == 0 then + buf[#buf + 1] = " {} " .. fulln .. ";" + return table.concat(buf) + end + buf[#buf + 1] = " {\n" + for i, fld in ipairs(fields) do + buf[#buf + 1] = " " + local cn = fld:c_name_get() + buf[#buf + 1] = cn + keyref.add(cn, ns, "c") + local val = fld:value_get() + if val then + buf[#buf + 1] = " = " + local ev = val:eval(eolian.expression_mask.INT) + local lit = ev:to_literal() + buf[#buf + 1] = lit + local ser = val:serialize() + if ser and ser ~= lit then + buf[#buf + 1] = " /* " .. ser .. " */" + end + end + if i == #fields then + buf[#buf + 1] = "\n" + else + buf[#buf + 1] = ",\n" + end + end + buf[#buf + 1] = "} " .. fulln .. ";" + return table.concat(buf) + elseif tpt == eolian.typedecl_type.ALIAS then + local fulln = tp:name_get():gsub("%.", "_"); + keyref.add(fulln, ns, "c") + return "typedef " + .. M.type_cstr_get(tp:base_type_get(), fulln) .. ";" + elseif tpt == eolian.typedecl_type.FUNCTION_POINTER then + return "TODO" + end + error("unhandled typedecl type: " .. tpt) +end + +serialize_var = function(var) + local buf = {} + if var:type_get() == eolian.variable_type.GLOBAL then + buf[#buf + 1] = "var " + else + buf[#buf + 1] = "const " + end + if var:is_extern() then + buf[#buf + 1] = "@extern " + end + buf[#buf + 1] = var:name_get() + buf[#buf + 1] = ": " + buf[#buf + 1] = serialize_type(var:base_type_get()) + local val = var:value_get() + if val then + buf[#buf + 1] = " = " + buf[#buf + 1] = val:serialize() + end + buf[#buf + 1] = ";" + return table.concat(buf) +end + +serialize_var_c = function(var, ns) + local buf = {} + local bt = var:base_type_get() + local fulln = var:name_get():gsub("%.", "_"):upper() + keyref.add(fulln, ns, "c") + if var:type_get() == eolian.variable_type.GLOBAL then + local ts = bt:c_type_get(eolian.c_type_type.DEFAULT) + buf[#buf + 1] = ts + if ts:sub(#ts) ~= "*" then + buf[#buf + 1] = " " + end + buf[#buf + 1] = fulln + local val = var:value_get() + if val then + buf[#buf + 1] = " = " + local vt = val:eval_type(bt) + local lv = vt:to_literal() + local sv = val:serialize() + buf[#buf + 1] = lv + if lv ~= sv then + buf[#buf + 1] = "/* " .. sv .. " */" + end + end + buf[#buf + 1] = ";" + else + buf[#buf + 1] = "#define " + buf[#buf + 1] = fulln + buf[#buf + 1] = " " + local val = var:value_get() + local vt = val:eval_type(bt) + local lv = vt:to_literal() + local sv = val:serialize() + buf[#buf + 1] = lv + if lv ~= sv then + buf[#buf + 1] = "/* " .. sv .. " */" + end + end + return table.concat(buf) +end + +local sert = { + [eolian.object_type.TYPE] = serialize_type, + [eolian.object_type.TYPEDECL] = serialize_tdecl, + [eolian.object_type.VARIABLE] = serialize_var +} + +M.obj_serialize = function(obj) + local f = sert[obj:object_get():type_get()] + assert(f, "cannot serialize object") + return f(obj) +end + +local sertc = { + [eolian.object_type.TYPEDECL] = serialize_tdecl_c, + [eolian.object_type.VARIABLE] = serialize_var_c +} + +M.obj_serialize_c = function(obj, ns) + local f = sertc[obj:object_get():type_get()] + assert(f, "cannot serialize object") + return f(obj, ns) +end + +M.impl_is_overridden = function(obj, cl) + return obj:class_get() ~= cl +end + +M.impl_fallback_doc_get = function(obj) + local ig, is = obj:is_prop_get(), obj:is_prop_set() + if ig and not is then + return obj:documentation_get(eolian.function_type.PROP_GET) + elseif is and not ig then + return obj:documentation_get(eolian.function_type.PROP_SET) + end + return nil +end + +local revh = {} + +M.class_children_get = function(cl) + return revh[cl:name_get()] or {} +end + +M.build_class_children = function(eos) + for cl in eos:classes_get() do + for icl in cl:inherits_get() do + local icln = icl:name_get() + local t = revh[icln] + if not t then + t = {} + revh[icln] = t + end + t[#t + 1] = cl + end + end +end + +return M diff --git a/docgen/keyref.lua b/docgen/keyref.lua new file mode 100644 index 0000000..56311d9 --- /dev/null +++ b/docgen/keyref.lua @@ -0,0 +1,41 @@ +local M = {} + +local key_refs = {} + +M.add = function(key, link, lang) + local rfs = key_refs[lang] + if not rfs then + key_refs[lang] = {} + rfs = key_refs[lang] + end + rfs[key] = link +end + +M.build = function() + local writer = require("docgen.writer") + local dutil = require("docgen.util") + for lang, rfs in pairs(key_refs) do + local f = writer.Writer({ "ref", lang, "keyword-list" }) + local arr = {} + for refn, v in pairs(rfs) do + arr[#arr + 1] = refn + local rf = writer.Writer({ "ref", lang, "key", refn }) + v[#v + 1] = true + rf:write_include(rf.INCLUDE_PAGE, v) + rf:finish() + end + table.sort(arr) + f:write_raw(table.concat(arr, "\n")) + f:write_nl() + f:finish() + local lf = writer.Writer({ "ref", lang, "keyword-link" }) + lf:write_raw("/", dutil.path_join( + dutil.nspace_to_path(dutil.get_root_ns()), + "ref", lang, "key", "{FNAME}" + )) + lf:write_nl() + lf:finish() + end +end + +return M diff --git a/docgen/mono.lua b/docgen/mono.lua new file mode 100644 index 0000000..3d764a0 --- /dev/null +++ b/docgen/mono.lua @@ -0,0 +1,621 @@ + +local writer = require("docgen.writer") + +local eolian = require("eolian") +local eoutils = require("docgen.eolian_utils") + +local eos + +local M = {} + +local propt_to_type = { + [eolian.function_type.PROPERTY] = "(get, set)", + [eolian.function_type.PROP_GET] = "(get)", + [eolian.function_type.PROP_SET] = "(set)", +} + +local verbs = { + "add", + "get", + "is", + "del", + "thaw", + "freeze", + "save", + "wait", + "eject", + "raise", + "lower", + "load", + "dup", + "reset", + "unload", + "close", + "set", + "interpolate", + "has", + "grab", + "check", + "find", + "ungrab", + "unset", + "clear", + "pop", + "new", + "peek", + "push", + "update", + "show", + "move", + "hide", + "calculate", + "resize", + "attach", + "pack", + "unpack", + "emit", + "call", + "append" +} + +local not_verbs = { + "below", + "above", + "name", + "unfreezable", + "value", + "r", + "g", + "b", + "a", + "finalize", + "destructor", + "to", + "circle", + "rect", + "path", + "commands", + "type", + "colorspace", + "op", + "type", + "properties", + "status", + "status", + "relative", + "ptr", + "pair", + "pos", + "end" +} + +local get_class_name = function(cls) + local words = {} + local klass = cls:name_get() + for word in string.gmatch(klass, "%a+") do + words[#words+1] = word + end + for i = 1, #words -1 do + words[i] = string.lower(words[i]) + end + return table.concat(words, '.') +end + +local get_mono_type +get_mono_type = function(tp) + if not tp then + return "void " + end + + tpt = tp:type_get() + tpdecl = tp:typedecl_get() + + if tpt == eolian.type_type.REGULAR then + if tp:name_get() == "string" then + return "System.String" + elseif tp:name_get() == "list" then + ntp = tp:base_type_get() + --assert(btp ~= nil) + --ntp = btp:next_type_get() + return "eina.List<" .. get_mono_type(ntp) .. ">" + elseif tpdecl then + --print("typedecl type is ", tp:name_get()) + tpt = tpdecl:type_get() + return get_class_name(tp) --tp:name_get() + else + --print("regular type is ", tp:name_get()) + return tp:name_get() + end + elseif tpt == eolian.type_type.CLASS then + return get_class_name(tp) + else + return "unknown" + end +end + + +local is_verb = function(word) + for i = 1, #verbs do + if verbs[i] == word then + return true + end + end + return false +end + +local mono_method_name_get = function(f, ftype) + local cn = f:name_get(ftype) + + local words = {} + + for word in string.gmatch(cn, "%a+") do + words[#words+1] = word + end + + if #words > 1 and is_verb(words[#words]) then + local tmp = words[#words] + words[#words] = words[1] + words[1] = tmp + end + + for i = 1, #words do + words[i] = words[i]:gsub("^%l", string.upper) + end + + if ftype == eolian.function_type.PROP_GET then + table.insert(words, 1, "Get") + elseif ftype == eolian.function_type.PROP_SET then + table.insert(words, 1, "Set") + end + + return table.concat(words) +end + +local gen_mono_param = function(par, out) + local part = par:type_get() + out = out or (par:direction_get() == eolian.parameter_dir.OUT) + if out then + out = "out " + else + out = "" + end + + return out .. get_mono_type(par:type_get()) .. ' ' .. par:name_get() + --local tstr = part:c_type_get() + --return out .. eoutils.type_cstr_get(tstr, par:name_get()) +end + +local get_func_mono_sig_part = function(cn, tp) + return get_mono_type(tp) .. " " .. cn +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 + --if get_class_name(impl) == 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 eoutils.doc_brief_get(eos) + 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 eoutils.doc_brief_get(eos, pdocf) +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 = eoutils.doc_brief_get(eos, docf) + end + if bdoc ~= "No description supplied." then + f:write_raw(bdoc) + 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_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 gen_func_mono_sig = function(f, ftype) + ftype = ftype or eolian.function_type.METHOD + assert(ftype ~= eolian.function_type.PROPERTY) + + local cn = mono_method_name_get(f, ftype) + local rtype = f:return_type_get(ftype) + local prefix = "" + local suffix = "" + + if f:is_class() then + prefix = "static " + elseif f:is_const() or ftype == eolian.function_type.PROP_GET then + suffix = " const" + end + + if f:type_get() == eolian.function_type.METHOD then + local pars = f:parameters_get():to_array() + local cnrt = get_func_mono_sig_part(cn, rtype) + for i = 1, #pars do + pars[i] = gen_mono_param(pars[i]) + end + return prefix .. cnrt .. "(" .. table.concat(pars, ", ") .. ")" .. suffix .. ";" + 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_mono_sig_part(cn, rtype) + local pars = {} + for i, par in ipairs(keys) do + pars[#pars + 1] = gen_mono_param(par) + end + for i, par in ipairs(vals) do + pars[#pars + 1] = gen_mono_param(par) + end + return cnrt .. "(" .. table.concat(pars, ", ") .. ");" + end + + -- getters + local cnrt + if not rtype then + if #vals == 1 then + cnrt = get_func_mono_sig_part(cn, vals[1]:type_get()) + table.remove(vals, 1) + else + cnrt = get_func_mono_sig_part(cn) + end + else + cnrt = get_func_mono_sig_part(cn, rtype) + end + local pars = {} + for i, par in ipairs(keys) do + pars[#pars + 1] = gen_mono_param(par) + end + for i, par in ipairs(vals) do + --print('parameter is value for get, so out') + pars[#pars + 1] = gen_mono_param(par, true) + end + + return cnrt .. "(" .. table.concat(pars, ", ") .. ");" +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 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 + +M.build_inherits = function(cl, t, lvl) + t = t or {} + lvl = lvl or 0 + local lbuf = writer.Buffer() + if lvl > 0 then + local cln = eoutils.obj_nspaces_get(cl, true) + cln[#cln] = nil + cln[#cln] = cln[#cln] .. "_mono" + cln = ":" .. 'develop:api' .. ":" + .. table.concat(cln, ":") + lbuf:write_raw("[[", cln, "|", get_class_name(cl), "]]") + --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 + M.build_inherits(acl, t, lvl + 1) + end + return t +end + +M.build_inherit_summary = function(cl, buf) + buf = buf or writer.Buffer() + buf:write_raw(" => ") + + local cln = eoutils.obj_nspaces_get(cl, true) + cln[#cln] = nil + cln[#cln] = cln[#cln] .. "_mono" + cln = ":" .. 'develop:api' .. ":" + .. table.concat(cln, ":") + buf:write_raw("[[", cln, "|", get_class_name(cl), "]]") + buf:write_raw(" ") + buf:write_i("(" .. eoutils.class_type_str_get(cl) .. ")") + + local inherits = cl:inherits_get():to_array() + if #inherits ~= 0 then + M.build_inherit_summary(inherits[1], buf) + end + return buf +end + +M.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 + +M.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_mono_sig(func, func:type_get()) + else + codes[#codes + 1] = gen_func_mono_sig(func, eolian.function_type.PROP_GET) + codes[#codes + 1] = gen_func_mono_sig(func, eolian.function_type.PROP_SET) + end + f:write_code(table.concat(codes, "\n"), "c") + f:write_br(true) + end + f:write_nl() +end + +M.build_class = function(cl) + local cln = eoutils.obj_nspaces_get(cl) + local fulln = cl:name_get() + --table.insert(cln, "mono") + cln[#cln] = cln[#cln] .. "_mono" + --printgen("Generating (MONO) class: " .. fulln .. " in ns ", unpack(cln)) + local f = writer.Writer(cln, fulln .. " (mono)") + f:write_h(cl:name_get() .. " (" .. eoutils.class_type_str_get(cl) .. ")", 1) + + f:write_h("Description", 2) + f:write_raw(eoutils.doc_full_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(M.build_inherit_summary(inherits[1]):finish()) + f:write_nl() + + f:write_folded("Full hierarchy", function() + f:write_list(M.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) + M.write_functable(f, cl, meths, true) + if #omeths ~= 0 then + f:write_h("Inherited", 3) + end + M.write_inherit_functable(f, cl, omeths, false) + + f:finish() +end + +M.init = function(eosh) + eos = eosh +end + +return M + + diff --git a/docgen/stats.lua b/docgen/stats.lua new file mode 100644 index 0000000..a7a57af --- /dev/null +++ b/docgen/stats.lua @@ -0,0 +1,278 @@ +local eolian = require("eolian") +local eoutils = require("docgen.eolian_utils") + +local is_verbose = false + +local M = {} + +local stats = {} + +local stats_pd = function(n) + local ret = 0 + if n == 0 then + return 1 + end + while (n ~= 0) do + n = math.floor(n / 10) + ret = ret + 1 + end + return ret +end + +local fcol = 30 +local ncol = 0 + +local get_percent = function(sv, svu) + return (sv == 0) and 100 or math.floor(((sv - svu) / sv) * 100 + 0.5) +end + +local print_stat = function(printname, statname) + local sv = stats[statname] or 0 + local svu = stats[statname .. "_undoc"] or 0 + local percent = get_percent(sv, svu) + local tb = (" "):rep(math.max(0, fcol - #printname - 1) + ncol - stats_pd(sv)) + local dtb = (" "):rep(ncol - stats_pd(sv - svu)) + local ptb = (" "):rep(3 - stats_pd(percent)) + print(("%s:%s%d (documented: %s%d or %s%d%%)") + :format(printname, tb, sv, dtb, sv - svu, ptb, percent)) +end + +local get_secstats = function(...) + local sv, svu = 0, 0 + for i, v in ipairs({ ... }) do + sv = sv + (stats[v] or 0) + svu = svu + (stats[v .. "_undoc"] or 0) + end + return sv - svu, sv, get_percent(sv, svu) +end + +M.print = function() + for k, v in pairs(stats) do + ncol = math.max(ncol, stats_pd(v)) + end + + print(("=== CLASS SECTION: %d out of %d (%d%%) ===\n") + :format(get_secstats("class", "interface", "mixin", "event"))) + print_stat("Classes", "class") + print_stat("Interfaces", "interface") + print_stat("Mixins", "mixin") + print_stat("Events", "event") + + print(("\n=== FUNCTION SECTION: %d out of %d (%d%%) ===\n") + :format(get_secstats("method", "param", "mret", + "getter", "gret", "gkey", "gvalue", + "setter", "sret", "skey", "svalue"))) + print_stat("Methods", "method") + print_stat(" Method params", "param") + print_stat(" Method returns", "mret") + print_stat("Getters", "getter") + print_stat(" Getter returns", "gret") + print_stat(" Getter keys", "gkey") + print_stat(" Getter values", "gvalue") + print_stat("Setters", "setter") + print_stat(" Setter returns", "sret") + print_stat(" Setter keys", "skey") + print_stat(" Setter values", "svalue") + + print(("\n=== TYPE SECTION: %d out of %d (%d%%) ===\n") + :format(get_secstats("alias", "struct", "sfield", "enum", "efield"))) + print_stat("Aliases", "alias") + print_stat("Structs", "struct") + print_stat("Struct fields", "sfield") + print_stat("Enums", "enum") + print_stat("Enum fields", "efield") + + print(("\n=== VARIABLE SECTION: %d out of %d (%d%%) ===\n") + :format(get_secstats("constant", "global"))) + print_stat("Constants", "constant") + print_stat("Globals", "global") + + local sv, svu = 0, 0 + for k, v in pairs(stats) do + if k:match(".*_undoc$") then + svu = svu + v + else + sv = sv + v + end + end + print(("\n=== TOTAL: %d out of %d (%d%%) ===") + :format(sv - svu, sv, get_percent(sv, svu))) +end + +local stat_incr = function(name, missing) + if not stats[name] then + stats[name], stats[name .. "_undoc"] = 0, 0 + end + stats[name] = stats[name] + 1 + if missing then + stats[name .. "_undoc"] = stats[name .. "_undoc"] + 1 + end +end + +local print_missing = function(name, tp) + if not is_verbose then + return + end + print(tp .. " '" .. name .. "'" .. " missing documentation") +end + +M.check_class = function(cl) + local ct = eoutils.class_type_str_get(cl) + if not ct then + return + end + if not cl:documentation_get() then + print_missing(cl:name_get(), ct) + stat_incr(ct, true) + else + stat_incr(ct, false) + end + + for ev in cl:events_get() do + if not ev:documentation_get() then + print_missing(cl:name_get() .. "." .. ev:name_get(), "event") + stat_incr("event", true) + else + stat_incr("event", false) + end + end +end + +M.check_method = function(fn, cl) + local fulln = cl:name_get() .. "." .. fn:name_get() + if fn:return_type_get(eolian.function_type.METHOD) then + if not fn:return_documentation_get(eolian.function_type.METHOD) then + print_missing(fulln, "method return") + stat_incr("mret", true) + else + stat_incr("mret", false) + end + end + if not fn:implement_get():documentation_get(eolian.function_type.METHOD) then + print_missing(fulln, "method") + stat_incr("method", true) + else + stat_incr("method", false) + end + for p in fn:parameters_get() do + if not p:documentation_get() then + print_missing(fulln .. "." .. p:name_get(), "method parameter") + stat_incr("param", true) + else + stat_incr("param", false) + end + end +end + +M.check_property = function(fn, cl, ft) + local pfxs = { + [eolian.function_type.PROP_GET] = "g", + [eolian.function_type.PROP_SET] = "s", + } + local pfx = pfxs[ft] + + local fulln = cl:name_get() .. "." .. fn:name_get() + if fn:return_type_get(ft) then + if not fn:return_documentation_get(ft) then + print_missing(fulln, pfx .. "etter return") + stat_incr(pfx .. "ret", true) + else + stat_incr(pfx .. "ret", false) + end + end + + local pimp = fn:implement_get() + + if not pimp:documentation_get(eolian.function_type.PROPERTY) and + not pimp:documentation_get(ft) then + print_missing(fulln, pfx .. "etter") + stat_incr(pfx .. "etter", true) + else + stat_incr(pfx .. "etter", false) + end + + for p in fn:property_keys_get(ft) do + if not p:documentation_get() then + print_missing(fulln .. "." .. p:name_get(), pfx .. "etter key") + stat_incr(pfx .. "key", true) + else + stat_incr(pfx .. "key", false) + end + end + + for p in fn:property_values_get(ft) do + if not p:documentation_get() then + print_missing(fulln .. "." .. p:name_get(), pfx .. "etter value") + stat_incr(pfx .. "value", true) + else + stat_incr(pfx .. "value", false) + end + end +end + +M.check_alias = function(v) + if not v:documentation_get() then + print_missing(v:name_get(), "alias") + stat_incr("alias", true) + else + stat_incr("alias", false) + end +end + +M.check_struct = function(v) + if not v:documentation_get() then + print_missing(v:name_get(), "struct") + stat_incr("struct", true) + else + stat_incr("struct", false) + end + for fl in v:struct_fields_get() do + if not fl:documentation_get() then + print_missing(v:name_get() .. "." .. fl:name_get(), "struct field") + stat_incr("sfield", true) + else + stat_incr("sfield", false) + end + end +end + +M.check_enum = function(v) + if not v:documentation_get() then + print_missing(v:name_get(), "enum") + stat_incr("enum", true) + else + stat_incr("enum", false) + end + for fl in v:enum_fields_get() do + if not fl:documentation_get() then + print_missing(v:name_get() .. "." .. fl:name_get(), "enum field") + stat_incr("efield", true) + else + stat_incr("efield", false) + end + end +end + +M.check_constant = function(v) + if not v:documentation_get() then + print_missing(v:name_get(), "constant") + stat_incr("constant", true) + else + stat_incr("constant", false) + end +end + +M.check_global = function(v) + if not v:documentation_get() then + print_missing(v:name_get(), "global") + stat_incr("global", true) + else + stat_incr("global", false) + end +end + +M.init = function(verbose) + is_verbose = verbose +end + +return M diff --git a/docgen/util.lua b/docgen/util.lua new file mode 100644 index 0000000..65968ac --- /dev/null +++ b/docgen/util.lua @@ -0,0 +1,50 @@ +local cutil = require("cutil") +local ffi = require("ffi") + +local M = {} + +local doc_root, root_ns + +local path_sep, rep_sep = "/", "\\" +if ffi.os == "Windows" then + path_sep, rep_sep = rep_sep, path_sep +end + +M.path_join = function(...) + return (table.concat({ ... }, path_sep):gsub(rep_sep, path_sep)) +end + +M.path_to_nspace = function(p) + return p:gsub(rep_sep, ":"):gsub(path_sep, ":"):lower() +end + +M.nspace_to_path = function(ns) + return ns:gsub(":", path_sep):gsub(rep_sep, path_sep):lower() +end + +M.make_page = function(path, ext) + return M.path_join(doc_root, path .. "." .. ext) +end + +M.get_root_ns = function() + return root_ns +end + +M.mkdir_r = function(dirn) + assert(cutil.file_mkpath(M.path_join(doc_root, dirn))) +end + +M.mkdir_p = function(path) + M.mkdir_r(path:match("(.+)" .. path_sep .. "([^" .. path_sep .. "]+)")) +end + +M.rm_root = function() + cutil.file_rmrf(doc_root) +end + +M.init = function(root, rns) + doc_root = root:gsub(rep_sep, path_sep) + root_ns = rns +end + +return M diff --git a/docgen/writer.lua b/docgen/writer.lua new file mode 100644 index 0000000..288e21c --- /dev/null +++ b/docgen/writer.lua @@ -0,0 +1,403 @@ +local util = require("util") + +local eolian = require("eolian") +local eoutils = require("docgen.eolian_utils") + +local dutil = require("docgen.util") + +local M = {} + +local root_nspace, features + +M.has_feature = function(fname) + if not features then + return false + end + return not not features[fname] +end + +local allowed_incflags = { + noheader = { "noheader", "showheader" }, + firstseconly = { "firstseconly", "fullpage" }, + readmore = { "readmore", "noreadmore" }, + footer = { "footer", "nofooter" }, + link = { "link", "nolink" }, + permalink = { "permalink", "nopermalink" }, + date = { "date", "nodate" }, + mdate = { "mdate", "nomdate" }, + user = { "user", "nouser" }, + comments = { "comments", "nocomments" }, + linkbacks = { "linkbacks", "nolinkbacks" }, + tags = { "tags", "notags" }, + editbutton = { "editbtn", "noeditbtn" }, + redirect = { "redirect", "noredirect" }, + indent = { "indent", "noindent" }, + linkonly = { "linkonly", "nolinkonly" }, + title = { "title", "notitle" }, + pageexists = { "pageexists", "nopageexists" }, + parlink = { "parlink", "noparlink" }, + order = { { "id", "title", "created", "modified", "indexmenu", "custom" } }, + rsort = { "rsort", "sort" }, + depth = 0, + inline = true, + beforeeach = "", + aftereach = "" +} + +local writers = {} + +local Buffer = { + __ctor = function(self) + self.buf = {} + end, + + write_raw = function(self, ...) + for i, v in ipairs({ ... }) do + self.buf[#self.buf + 1] = v + end + return self + end, + + finish = function(self) + self.result = table.concat(self.buf) + self.buf = {} + return self.result + end +} + +local write_include = function(self, tp, name, flags) + local it_to_tp = { + [self.INCLUDE_PAGE] = "page", + [self.INCLUDE_SECTION] = "section", + [self.INCLUDE_NAMESPACE] = "namespace", + [self.INCLUDE_TAG] = "tagtopic" + } + if type(name) == "table" then + if name[#name] == true then + name[#name] = nil + name = ":" .. root_nspace .. ":" + .. table.concat(name, ":") + elseif name[#name] == false then + name[#name] = nil + name = ":" .. root_nspace .. "-include:" + .. table.concat(name, ":") + else + name = table.concat(name, ":") + end + end + self:write_raw("{{", it_to_tp[tp], ">", name); + if flags then + if tp == self.INCLUDE_SECTION and flags.section then + self:write_raw("#", flags.section) + end + flags.section = nil + local flstr = {} + for k, v in pairs(flags) do + local allow = allowed_incflags[k] + if allow ~= nil then + if type(allow) == "boolean" then + flstr[#flstr + 1] = k + elseif type(allow) == "number" or type(allow) == "string" then + if type(v) ~= type(allow) then + error("invalid value type for flag " .. k) + end + flstr[#flstr + 1] = k .. "=" .. v + elseif type(allow) == "table" then + if type(allow[1]) == "table" then + local valid = false + for i, vv in ipairs(allow[1]) do + if v == vv then + flstr[#flstr + 1] = k .. "=" .. v + valid = true + break + end + end + if not valid then + error("invalid value " .. v .. " for flag " .. k) + end + elseif type(allow[1]) == "string" and + type(allow[2]) == "string" then + if v then + flstr[#flstr + 1] = allow[1] + else + flstr[#flstr + 1] = allow[2] + end + end + end + else + error("invalid include flag: " .. tostring(k)) + end + end + flstr = table.concat(flstr, "&") + if #flstr > 0 then + self:write_raw("&", flstr) + end + end + self:write_raw("}}") + self:write_nl() + return self +end + +M.set_backend = function(bend) + M.Writer = assert(writers[bend], "invalid generation backend") + M.Buffer = M.Writer:clone(Buffer) +end + +writers["dokuwiki"] = util.Object:clone { + INCLUDE_PAGE = 0, + INCLUDE_SECTION = 1, + INCLUDE_NAMESPACE = 2, + INCLUDE_TAG = 3, + + __ctor = function(self, path, title) + local subs + if type(path) == "table" then + subs = dutil.path_join(unpack(path)) + else + subs = dutil.nspace_to_path(path) + end + dutil.mkdir_p(subs) + self.file = assert(io.open(dutil.make_page(subs, "txt"), "w")) + if title then + if M.has_feature("title") then + self:write_raw("~~Title: ", title, "~~") + self:write_nl() + else + self:write_h(title, 1) + end + end + end, + + write_raw = function(self, ...) + self.file:write(...) + return self + end, + + write_nl = function(self, n) + self:write_raw(("\n"):rep(n or 1)) + return self + end, + + write_h = function(self, heading, level, nonl) + local s = ("="):rep(7 - level) + self:write_raw(s, " ", heading, " ", s, "\n") + if not nonl then + self:write_nl() + end + return self + end, + + write_include = function(self, tp, name, flags) + return write_include(self, tp, name, flags) + end, + + write_editable = function(self, ns, name) + ns[#ns + 1] = name + ns[#ns + 1] = false + self:write_include(self.INCLUDE_PAGE, ns, { + date = false, user = false, link = false + }) + -- restore the table for later reuse + -- the false gets deleted by write_include + ns[#ns] = nil + end, + + write_inherited = function(self, ns) + ns[#ns + 1] = true + self:write_include(self.INCLUDE_PAGE, ns, { + editbutton = false, date = false, user = false, link = false + }) + end, + + write_fmt = function(self, fmt1, fmt2, ...) + self:write_raw(fmt1, ...) + self:write_raw(fmt2) + return self + end, + + write_b = function(self, ...) + self:write_fmt("**", "**", ...) + return self + end, + + write_i = function(self, ...) + self:write_fmt("//", "//", ...) + return self + end, + + write_u = function(self, ...) + self:write_fmt("__", "__", ...) + return self + end, + + write_s = function(self, ...) + self:write_fmt("", "", ...) + return self + end, + + write_m = function(self, ...) + self:write_fmt("''", "''", ...) + return self + end, + + write_sub = function(self, ...) + self:write_fmt("", "", ...) + return self + end, + + write_sup = function(self, ...) + self:write_fmt("", "", ...) + return self + end, + + write_br = function(self, nl) + self:write_raw("\\\\", nl and "\n" or " ") + return self + end, + + write_pre_inline = function(self, ...) + self:write_fmt("%%", "%%", ...) + return self + end, + + write_pre = function(self, ...) + self:write_fmt("\n", "\n", ...) + return self + end, + + write_code = function(self, str, lang) + lang = lang and (" " .. lang) or "" + self:write_raw("\n", str, "\n\n") + end, + + write_link = function(self, target, title) + if type(target) == "table" then + if target[#target] == false then + target[#target] = nil + target = ":" .. root_nspace .. "-include:" + .. table.concat(target, ":") + else + target[#target] = nil + target = ":" .. root_nspace .. ":" + .. table.concat(target, ":") + end + end + if not title then + self:write_raw("[[", target:lower(), "|", target, "]]") + return + end + target = target:lower() + if type(title) == "string" then + self:write_raw("[[", target, "|", title, "]]") + return self + end + self:write_raw("[[", target, "|") + title(self) + self:write_raw("]]") + return self + end, + + write_table = function(self, titles, tbl) + if titles then + self:write_raw("^ ", table.concat(titles, " ^ "), " ^\n") + end + for i, v in ipairs(tbl) do + self:write_raw("| ", table.concat(v, " | "), " |\n") + end + return self + end, + + write_list = function(self, tbl, ord) + local prec = ord and "-" or "*" + for i, v in ipairs(tbl) do + local lvl, str = 1, v + if type(v) == "table" then + lvl, str = v[1] + 1, v[2] + end + local pbeg, pend = str:match("([^\n]+)\n(.+)") + if not pbeg then + pbeg = str + end + self:write_raw((" "):rep(lvl), prec, " ", str, "\n") + if pend then + self:write_raw(pend, "\n") + end + end + return self + end, + + write_par = function(self, str, eos) + local tokp = eolian.doc_token_init() + local notetypes = M.has_feature("notes") and { + [eolian.doc_token_type.MARK_NOTE] = "\n", + [eolian.doc_token_type.MARK_WARNING] = "\n", + [eolian.doc_token_type.MARK_REMARK] = "\n", + [eolian.doc_token_type.MARK_TODO] = "\n**TODO:** " + } or {} + local hasraw, hasnote = false, false + local tokf = function() + str = eolian.documentation_tokenize(str, tokp) + return not not str + end + while tokf() do + local tp = tokp:type_get() + local tag = notetypes[tp] + if tag then + self:write_raw(tag) + hasnote = true + else + if not hasraw then + self:write_raw("%%") + hasraw = true + end + if tp == eolian.doc_token_type.REF then + local reft = eoutils.tok_ref_resolve(tokp, eos, true) + local str = tokp:text_get() + if str:sub(1, 1) == "[" then + str = str:sub(2, #str - 1) + end + self:write_raw("%%") + self:write_link(reft, str) + self:write_raw("%%") + else + local str = tokp:text_get() + assert(str, "internal tokenizer error") + -- replace possible %% chars + str = str:gsub("%%%%", "%%%%%%%%%%%%") + if tp == eolian.doc_token_type.MARKUP_MONOSPACE then + self:write_raw("%%''" .. str .. "''%%") + else + self:write_raw(str) + end + end + end + end + self:write_raw("%%") + if hasnote then + self:write_raw("\n") + end + return self + end, + + write_folded = function(self, title, func) + if M.has_feature("folds") then + self:write_raw("++++ ", title, " |\n\n") + end + func(self) + if M.has_feature("folds") then + self:write_raw("\n\n++++") + end + return self + end, + + finish = function(self) + self.file:close() + end +} + +M.init = function(root_ns, ftrs) + root_nspace = root_ns + features = ftrs +end + +return M diff --git a/gendoc.lua b/gendoc.lua index 8b8b710..ebfd808 100644 --- a/gendoc.lua +++ b/gendoc.lua @@ -1,947 +1,19 @@ -local eolian = require("eolian") local getopt = require("getopt") -local cutil = require("cutil") -local util = require("util") -local ffi = require("ffi") -local global_opts = {} +local serializer = require("serializer") --- utils +local eolian = require("eolian") +local eoutils = require("docgen.eolian_utils") -local path_sep, rep_sep = "/", "\\" -if ffi.os == "Windows" then - path_sep, rep_sep = rep_sep, path_sep -end +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 path_join = function(...) - return table.concat({ ... }, path_sep):gsub(rep_sep, path_sep) -end +local eos -local path_to_nspace = function(p) - return p:gsub(rep_sep, ":"):gsub(path_sep, ":"):lower() -end - -local nspace_to_path = function(ns) - return ns:gsub(":", path_sep):gsub(rep_sep, path_sep):lower() -end - -local make_page = function(path) - return path_join(global_opts.doc_root, path .. ".txt") -end - -local mkdir_r = function(dirn) - local dr = global_opts.doc_root - assert(cutil.file_mkpath(dirn and path_join(dr, dirn) or dr)) -end - -local mkdir_p = function(path) - mkdir_r(path:match("(.+)" .. path_sep .. "([^" .. path_sep .. "]+)")) -end - -local str_split = function(str, delim) - if not str then - return nil - end - local s, e = str:find(delim, 1, true) - if not s then - return { str } - end - local t = {} - while s do - t[#t + 1] = str:sub(1, s - 1) - str = str:sub(e + 1) - s, e = str:find(delim, 1, true) - if not s then - t[#t + 1] = str - end - end - return t -end - --- translation tables and funcs - -local classt_to_str = { - [eolian.class_type.REGULAR] = "class", - [eolian.class_type.ABSTRACT] = "class", - [eolian.class_type.MIXIN] = "mixin", - [eolian.class_type.INTERFACE] = "interface" -} - -local funct_to_str = { - [eolian.function_type.PROPERTY] = "property", - [eolian.function_type.PROP_GET] = "property", - [eolian.function_type.PROP_SET] = "property", - [eolian.function_type.METHOD] = "method" -} - -local decl_to_nspace = function(decl) - local dt = eolian.declaration_type - local decltypes = { - [dt.ALIAS] = "alias", - [dt.STRUCT] = "struct", - [dt.ENUM] = "enum", - [dt.VAR] = "var" - } - local ns = decltypes[decl:type_get()] - if ns then - return ns - elseif decl:type_get() == dt.CLASS then - local ret = classt_to_str[decl:class_get():type_get()] - if not ret then - error("unknown class type for class '" .. decl:name_get() .. "'") - end - return ret - else - error("unknown declaration type for declaration '" - .. decl:name_get() .. "'") - end -end - -local gen_nsp_eo = function(eobj, subn, root) - local tbl = eobj:namespaces_get():to_array() - for i = 1, #tbl do - tbl[i] = tbl[i]:lower() - end - table.insert(tbl, 1, subn) - tbl[#tbl + 1] = eobj:name_get():lower() - if root then - tbl[#tbl + 1] = true - end - return tbl -end - -local gen_nsp_class = function(cl, root) - return gen_nsp_eo(cl, classt_to_str[cl:type_get()], root) -end - -local gen_nsp_func = function(fn, cl, root) - local tbl = gen_nsp_class(cl) - tbl[#tbl + 1] = funct_to_str[fn:type_get()] - tbl[#tbl + 1] = fn:name_get():lower() - if root then - tbl[#tbl + 1] = true - end - return tbl -end - -local gen_nsp_ref -gen_nsp_ref = function(str, root) - local decl = eolian.declaration_get_by_name(str) - if decl then - local t = { decl_to_nspace(decl) } - for tok in str:gmatch("[^%.]+") do - t[#t + 1] = tok:lower() - end - if root then t[#t + 1] = true end - return t - end - - -- field or func - local bstr = str:match("(.+)%.[^.]+") - if not bstr then - error("invalid reference '" .. str .. "'") - end - - local sfx = str:sub(#bstr + 1) - - decl = eolian.declaration_get_by_name(bstr) - if decl then - local dt = eolian.declaration_type - local tp = decl:type_get() - if tp == dt.STRUCT or tp == dt.ENUM then - -- TODO: point to the actual item - return gen_nsp_ref(bstr, root) - end - end - - local ftp = eolian.function_type - - local cl = eolian.class_get_by_name(bstr) - local fn - local ftype = ftp.UNRESOLVED - if not cl then - if sfx == ".get" then - ftype = ftp.PROP_GET - elseif sfx == ".set" then - ftype = ftp.PROP_SET - end - local mname - if ftype ~= ftp.UNRESOLVED then - mname = bstr:match(".+%.([^.]+)") - if not mname then - error("invalid reference '" .. str .. "'") - end - bstr = bstr:match("(.+)%.[^.]+") - cl = eolian.class_get_by_name(bstr) - if cl then - fn = cl:function_get_by_name(mname, ftype) - end - end - else - fn = cl:function_get_by_name(sfx:sub(2), ftype) - if fn then ftype = fn:type_get() end - end - - if not fn or not funct_to_str[ftype] then - error("invalid reference '" .. str .. "'") - end - - local ret = gen_nsp_ref(bstr) - ret[#ret + 1] = funct_to_str[ftype] - ret[#ret + 1] = fn:name_get():lower() - if root then ret[#ret + 1] = true end - return ret -end - --- statistics - -local stats = {} - -local stats_pd = function(n) - local ret = 0 - if n == 0 then - return 1 - end - while (n ~= 0) do - n = math.floor(n / 10) - ret = ret + 1 - end - return ret -end - -local fcol = 30 -local ncol = 0 - -local print_stat = function(printname, statname, sub) - local sv = stats[statname] or 0 - local svu = stats[statname .. "_undoc"] or 0 - local percent = (sv == 0) and 100 or math.floor(((sv - svu) / sv) * 100 + 0.5) - local tb = (" "):rep(math.max(0, fcol - #printname - 1) + ncol - stats_pd(sv)) - local dtb = (" "):rep(ncol - stats_pd(sv - svu)) - local ptb = (" "):rep(3 - stats_pd(percent)) - print(("%s:%s%d (documented: %s%d or %s%d%%)") - :format(printname, tb, sv, dtb, sv - svu, ptb, percent)) -end - -local print_stats = function() - for k, v in pairs(stats) do - ncol = math.max(ncol, stats_pd(v)) - end - - print("=== CLASS SECTION ===\n") - print_stat("Classes", "class") - print_stat("Interfaces", "interface") - print_stat("Mixins", "mixin") - print_stat("Events", "event") - - print("\n=== FUNCTION SECTION ===\n") - print_stat("Methods", "method") - print_stat(" Method params", "param") - print_stat(" Method returns", "mret") - print_stat("Getters", "getter") - print_stat(" Getter returns", "gret") - print_stat(" Getter keys", "gkey") - print_stat(" Getter values", "gvalue") - print_stat("Setters", "setter") - print_stat(" Setter returns", "sret") - print_stat(" Setter keys", "skey") - print_stat(" Setter values", "svalue") - - print("\n=== TYPE SECTION ===\n") - print_stat("Aliases", "alias") - print_stat("Structs", "struct") - print_stat("Struct fields", "sfield") - print_stat("Enums", "enum") - print_stat("Enum fields", "efield") - - print("\n=== VARIABLE SECTION ===\n") - print_stat("Constants", "constant") - print_stat("Globals", "global") -end - -local stat_incr = function(name, missing) - if not stats[name] then - stats[name], stats[name .. "_undoc"] = 0, 0 - end - stats[name] = stats[name] + 1 - if missing then - stats[name .. "_undoc"] = stats[name .. "_undoc"] + 1 - end -end - -local print_missing = function(name, tp) - if not global_opts.verbose then - return - end - print(tp .. " '" .. name .. "'" .. " missing documentation") -end - -local check_class = function(cl) - local ct = classt_to_str[cl:type_get()] - if not ct then - return - end - if not cl:documentation_get() then - print_missing(cl:full_name_get(), ct) - stat_incr(ct, true) - else - stat_incr(ct, false) - end - - for ev in cl:events_get() do - if not ev:documentation_get() then - print_missing(cl:full_name_get() .. "." .. ev:name_get(), "event") - stat_incr("event", true) - else - stat_incr("event", false) - end - end -end - -local check_method = function(fn, cl) - local fts = eolian.function_type - local fulln = cl:full_name_get() .. "." .. fn:name_get() - if fn:return_type_get(fts.METHOD) then - if not fn:return_documentation_get(fts.METHOD) then - print_missing(fulln, "method return") - stat_incr("mret", true) - else - stat_incr("mret", false) - end - end - if not fn:documentation_get(fts.METHOD) then - print_missing(fulln, "method") - stat_incr("method", true) - else - stat_incr("method", false) - end - for p in fn:parameters_get() do - if not p:documentation_get() then - print_missing(fulln .. "." .. p:name_get(), "method parameter") - stat_incr("param", true) - else - stat_incr("param", false) - end - end -end - -local check_property = function(fn, cl, ft) - local fts = eolian.function_type - - local pfxs = { - [fts.PROP_GET] = "g", - [fts.PROP_SET] = "s", - } - local pfx = pfxs[ft] - - local fulln = cl:full_name_get() .. "." .. fn:name_get() - if fn:return_type_get(ft) then - if not fn:return_documentation_get(ft) then - print_missing(fulln, pfx .. "etter return") - stat_incr(pfx .. "ret", true) - else - stat_incr(pfx .. "ret", false) - end - end - - if not fn:documentation_get(fts.PROPERTY) and not fn:documentation_get(ft) then - print_missing(fulln, pfx .. "etter") - stat_incr(pfx .. "etter", true) - else - stat_incr(pfx .. "etter", false) - end - - for p in fn:property_keys_get(ft) do - if not p:documentation_get() then - print_missing(fulln .. "." .. p:name_get(), pfx .. "etter key") - stat_incr(pfx .. "key", true) - else - stat_incr(pfx .. "key", false) - end - end - - for p in fn:property_values_get(ft) do - if not p:documentation_get() then - print_missing(fulln .. "." .. p:name_get(), pfx .. "etter value") - stat_incr(pfx .. "value", true) - else - stat_incr(pfx .. "value", false) - end - end -end - -local check_alias = function(v) - if not v:documentation_get() then - print_missing(v:full_name_get(), "alias") - stat_incr("alias", true) - else - stat_incr("alias", false) - end -end - -local check_struct = function(v) - if not v:documentation_get() then - print_missing(v:full_name_get(), "struct") - stat_incr("struct", true) - else - stat_incr("struct", false) - end - for fl in v:struct_fields_get() do - if not fl:documentation_get() then - print_missing(v:full_name_get() .. "." .. fl:name_get(), "struct field") - stat_incr("sfield", true) - else - stat_incr("sfield", false) - end - end -end - -local check_enum = function(v) - if not v:documentation_get() then - print_missing(v:full_name_get(), "enum") - stat_incr("enum", true) - else - stat_incr("enum", false) - end - for fl in v:enum_fields_get() do - if not fl:documentation_get() then - print_missing(v:full_name_get() .. "." .. fl:name_get(), "enum field") - stat_incr("efield", true) - else - stat_incr("efield", false) - end - end -end - -local check_constant = function(v) - if not v:documentation_get() then - print_missing(v:full_name_get(), "constant") - stat_incr("constant", true) - else - stat_incr("constant", false) - end -end - -local check_global = function(v) - if not v:documentation_get() then - print_missing(v:full_name_get(), "global") - stat_incr("global", true) - else - stat_incr("global", false) - end -end - --- generator - -local Writer = util.Object:clone { - __ctor = function(self, path) - local subs - if type(path) == "table" then - subs = path_join(unpack(path)) - else - subs = nspace_to_path(path) - end - mkdir_p(subs) - self.file = assert(io.open(make_page(subs), "w")) - end, - - write_raw = function(self, ...) - self.file:write(...) - return self - end, - - write_nl = function(self, n) - self:write_raw(("\n"):rep(n or 1)) - return self - end, - - write_h = function(self, heading, level, nonl) - local s = ("="):rep(7 - level) - self:write_raw(s, " ", heading, " ", s, "\n") - if not nonl then - self:write_nl() - end - return self - end, - - write_fmt = function(self, fmt1, fmt2, ...) - self:write_raw(fmt1, ...) - self:write_raw(fmt2) - return self - end, - - write_b = function(self, ...) - self:write_fmt("**", "**", ...) - return self - end, - - write_i = function(self, ...) - self:write_fmt("//", "//", ...) - return self - end, - - write_u = function(self, ...) - self:write_fmt("__", "__", ...) - return self - end, - - write_s = function(self, ...) - self:write_fmt("", "", ...) - return self - end, - - write_m = function(self, ...) - self:write_fmt("''", "''", ...) - return self - end, - - write_sub = function(self, ...) - self:write_fmt("", "", ...) - return self - end, - - write_sup = function(self, ...) - self:write_fmt("", "", ...) - return self - end, - - write_br = function(self, nl) - self:write_raw("\\\\", nl and "\n" or " ") - return self - end, - - write_pre_inline = function(self, ...) - self:write_fmt("%%", "%%", ...) - return self - end, - - write_pre = function(self, ...) - self:write_fmt("\n", "\n", ...) - return self - end, - - write_code = function(self, str, lang) - lang = lang and (" " .. lang) or "" - self:write_raw("\n", str, "\n\n") - end, - - write_link = function(self, target, title) - if type(target) == "table" then - if target[#target] == true then - target[#target] = nil - target = ":" .. global_opts.root_nspace .. ":" - .. table.concat(target, ":") - else - target = table.concat(target, ":") - end - end - if not title then - self:write_raw("[[", target:lower(), "|", target, "]]") - return - end - target = target:lower() - if type(title) == "string" then - self:write_raw("[[", target, "|", title, "]]") - return self - end - self:write_raw("[[", target, "|") - title(self) - self:write_raw("]]") - return self - end, - - write_graph = function(self, tbl) - self:write_raw("\n") - self:write_raw("digraph ", tbl.type, " {\n") - - for k, v in pairs(tbl.attrs or {}) do - self:write_raw(" ", k, " = \"", v, "\"\n") - end - - local write_node = function(nname, attrs) - self:write_raw(" ", nname, " [") - local first = true - for k, v in pairs(attrs) do - if not first then - self:write_raw(", ") - end - self:write_raw(k, " = \"", v, "\"") - first = false - end - self:write_raw("]\n") - end - - if tbl.node then - self:write_nl() - write_node("node", tbl.node) - end - if tbl.edge then - if not tbl.node then self:write_nl() end - write_node("edge", tbl.edge) - end - - self:write_nl() - for i, v in ipairs(tbl.nodes) do - local nname = v.name - v.name = nil - write_node(nname, v) - end - - self:write_nl() - for i, v in ipairs(tbl.connections) do - local from, to, sep = v[1], v[2], (v[3] or "->") - if type(from) == "table" then - self:write_raw(" {", table.concat(from, ", "), "}") - else - self:write_raw(" ", from) - end - self:write_raw(" ", sep, " ") - if type(to) == "table" then - self:write_raw("{", table.concat(to, ", "), "}") - else - self:write_raw(to) - end - self:write_nl() - end - - self:write_raw("}\n") - end, - - write_table = function(self, titles, tbl) - self:write_raw("^ ", table.concat(titles, " ^ "), " ^\n") - for i, v in ipairs(tbl) do - self:write_raw("| ", table.concat(v, " | "), " |\n") - end - return self - end, - - write_list = function(self, tbl, ord) - local prec = ord and "-" or "*" - for i, v in ipairs(tbl) do - local lvl, str = 1, v - if type(v) == "table" then - lvl, str = v[1] + 1, v[2] - end - local pbeg, pend = str:match("([^\n]+)\n(.+)") - if not pbeg then - pbeg = str - end - self:write_raw((" "):rep(lvl), prec, " ", str, "\n") - if pend then - self:write_raw(pend, "\n") - end - end - return self - end, - - write_par_markup = function(self, str) - self:write_raw("%%") - local f = str:gmatch(".") - local c = f() - while c do - if c == "\\" then - c = f() - if c ~= "@" and c ~= "$" then - self:write_raw("\\") - end - self:write_raw(c) - c = f() - elseif c == "$" then - c = f() - if c and c:match("[a-zA-Z_]") then - local wbuf = { c } - c = f() - while c and c:match("[a-zA-Z0-9_]") do - wbuf[#wbuf + 1] = c - c = f() - end - self:write_raw("%%''" .. table.concat(wbuf) .. "''%%") - else - self:write_raw("$") - end - elseif c == "@" then - c = f() - if c and c:match("[a-zA-Z_]") then - local rbuf = { c } - c = f() - while c and c:match("[a-zA-Z0-9_.]") do - rbuf[#rbuf + 1] = c - c = f() - end - local ldot = false - if rbuf[#rbuf] == "." then - ldot = true - rbuf[#rbuf] = nil - end - local title = table.concat(rbuf) - self:write_raw("%%") - self:write_link(gen_nsp_ref(title, true), title) - self:write_raw("%%") - if ldot then - self:write_raw(".") - end - else - self:write_raw("@") - end - elseif c == "%" then - c = f() - if c == "%" then - c = f() - self:write_raw("%%%%%%") - else - self:write_raw("%") - end - else - self:write_raw(c) - c = f() - end - end - self:write_raw("%%") - return self - end, - - write_par = function(self, str) - local notetypes = global_opts.use_notes and { - ["Note: "] = "\n", - ["Warning: "] = "\n", - ["Remark: "] = "\n", - ["TODO: "] = "\n**TODO:** " - } or {} - local tag - for k, v in pairs(notetypes) do - if str:match("^" .. k) then - tag = v - str = str:sub(#k + 1) - break - end - end - if tag then - self:write_raw(tag) - self:write_par_markup(str) - self:write_raw("\n") - else - self:write_par_markup(str) - end - return self - end, - - finish = function(self) - self.file:close() - end -} - -local Buffer = Writer:clone { - __ctor = function(self) - self.buf = {} - end, - - write_raw = function(self, ...) - for i, v in ipairs({ ... }) do - self.buf[#self.buf + 1] = v - end - return self - end, - - finish = function(self) - self.result = table.concat(self.buf) - self.buf = {} - return self.result - end -} - --- eolian to various doc elements conversions - -local wrap_type_attrs = function(tp, str) - if tp:is_const() then - str = "const(" .. str .. ")" - end - if tp:is_own() then - str = "own(" .. str .. ")" - end - local ffunc = tp:free_func_get() - if ffunc then - str = "free(" .. str .. ", " .. ffunc .. ")" - end - return str -end - -local get_type_str -get_type_str = function(tp) - local tps = eolian.type_type - local tpt = tp:type_get() - if tpt == tps.UNKNOWN then - error("unknown type: " .. tp:full_name_get()) - elseif tpt == tps.VOID then - return wrap_type_attrs(tp, "void") - elseif tpt == tps.UNDEFINED then - return wrap_type_attrs(tp, "__undefined_type") - elseif tpt == tps.REGULAR or tpt == tps.CLASS then - return wrap_type_attrs(tp, tp:full_name_get()) - elseif tpt == tps.COMPLEX then - local stypes = {} - for stp in tp:subtypes_get() do - stypes[#stypes + 1] = get_type_str(stp) - end - return wrap_type_attrs(tp, tp:full_name_get() .. "<" - .. table.concat(stypes, ", ") .. ">") - elseif tpt == tps.POINTER then - local btp = tp:base_type_get() - local suffix = " *" - if btp:type_get() == tps.POINTER then - suffix = "*" - end - return wrap_type_attrs(tp, get_type_str(btp) .. suffix) - end - error("unhandled type type: " .. tpt) -end - -local add_typedecl_attrs = function(tp, buf) - if tp:is_extern() then - buf[#buf + 1] = "@extern " - end - local ffunc = tp:free_func_get() - if ffunc then - buf[#buf + 1] = "@free(" - buf[#buf + 1] = ffunc - buf[#buf + 1] = ") " - end -end - -local get_typedecl_str = function(tp) - local tps = eolian.typedecl_type - local tpt = tp:type_get() - if tpt == tps.UNKNOWN then - error("unknown typedecl: " .. tp:full_name_get()) - elseif tpt == tps.STRUCT or tpt == tps.STRUCT_OPAQUE then - local buf = { "struct " } - add_typedecl_attrs(tp, buf) - buf[#buf + 1] = tp:full_name_get() - if tpt == tps.STRUCT_OPAQUE then - buf[#buf + 1] = ";" - return table.concat(buf) - end - local fields = tp:struct_fields_get():to_array() - if #fields == 0 then - buf[#buf + 1] = " {}" - return table.concat(buf) - end - buf[#buf + 1] = " {\n" - for i, fld in ipairs(fields) do - buf[#buf + 1] = " " - buf[#buf + 1] = fld:name_get() - buf[#buf + 1] = ": " - buf[#buf + 1] = get_type_str(fld:type_get()) - buf[#buf + 1] = ";\n" - end - buf[#buf + 1] = "}" - return table.concat(buf) - elseif tpt == tps.ENUM then - local buf = { "enum " } - add_typedecl_attrs(tp, buf) - buf[#buf + 1] = tp:full_name_get() - local fields = tp:enum_fields_get():to_array() - if #fields == 0 then - buf[#buf + 1] = " {}" - return table.concat(buf) - end - buf[#buf + 1] = " {\n" - for i, fld in ipairs(fields) do - buf[#buf + 1] = " " - buf[#buf + 1] = fld:name_get() - local val = fld:value_get() - if val then - buf[#buf + 1] = ": " - buf[#buf + 1] = val:serialize() - end - if i == #fields then - buf[#buf + 1] = "\n" - else - buf[#buf + 1] = ",\n" - end - end - buf[#buf + 1] = "}" - return table.concat(buf) - elseif tpt == tps.ALIAS then - local buf = { "type " } - add_typedecl_attrs(tp, buf) - buf[#buf + 1] = tp:full_name_get() - buf[#buf + 1] = ": " - buf[#buf + 1] = get_type_str(tp:base_type_get()) - buf[#buf + 1] = ";" - return table.concat(buf) - end - error("unhandled typedecl type: " .. tpt) -end - -local gen_doc_refd = function(str) - if not str then - return nil - end - local pars = str_split(str, "\n\n") - for i = 1, #pars do - pars[i] = Buffer():write_par(pars[i]):finish() - end - return table.concat(pars, "\n\n") -end - -local get_fallback_fdoc = function(f, ftype) - if not ftype then - local ft = f:type_get() - local ftt = eolian.function_type - if ft == ftt.PROP_GET or ft == ftt.PROP_SET then - ftype = ft - end - end - if ftype then - return f:documentation_get(ftype) - end - return nil -end - -local get_brief_doc = function(doc1, doc2) - if not doc1 and not doc2 then - return "No description supplied." - end - if not doc1 then - doc1, doc2 = doc2, doc1 - end - return gen_doc_refd(doc1:summary_get()) -end - -local get_brief_fdoc = function(f, ftype) - return get_brief_doc(f:documentation_get(eolian.function_type.METHOD), - get_fallback_fdoc(f, ftype)) -end - -local get_full_doc = function(doc1, doc2) - if not doc1 and not doc2 then - return "No description supplied." - end - if not doc1 then - doc1, doc2 = doc2, doc1 - end - local sum1 = doc1:summary_get() - local desc1 = doc1:description_get() - local edoc = "" - if doc2 then - local sum2 = doc2:summary_get() - local desc2 = doc2:description_get() - if not desc2 then - if sum2 then edoc = "\n\n" .. sum2 end - else - edoc = "\n\n" .. sum2 .. "\n\n" .. desc2 - end - end - if not desc1 then - return gen_doc_refd(sum1 .. edoc) - end - return gen_doc_refd(sum1 .. "\n\n" .. desc1 .. edoc) -end - -local get_full_fdoc = function(f, ftype) - return get_full_doc(f:documentation_get(eolian.function_type.METHOD), - get_fallback_fdoc(f, ftype)) -end +local printgen = function() end local propt_to_type = { [eolian.function_type.PROPERTY] = "(get, set)", @@ -949,25 +21,21 @@ local propt_to_type = { [eolian.function_type.PROP_SET] = "(set)", } -local gen_func_sig = function(f, ftype) - ftype = ftype or eolian.function_type.METHOD -end - local gen_cparam = function(par, out) local part = par:type_get() - local tstr out = out or (par:direction_get() == eolian.parameter_dir.OUT) - if part:type_get() == eolian.type_type.POINTER then - tstr = part:c_type_get() - if out then - tstr = tstr .. "*" - end - elseif out then - tstr = part:c_type_get() .. " *" - else - tstr = part:c_type_get() .. " " + local tstr = part:c_type_get(eolian.c_type_type.DEFAULT) + if out then + tstr = eoutils.type_cstr_get(tstr, "*") end - return tstr .. par:name_get() + 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) @@ -978,13 +46,15 @@ local gen_func_csig = function(f, ftype) local rtype = f:return_type_get(ftype) local fparam = "Eo *obj" - if f:is_const() or f:is_class() or ftype == eolian.function_type.PROP_GET then + 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 = rtype and rtype:c_type_named_get(cn) or ("void " .. cn) + local cnrt = get_func_csig_part(cn, rtype) for i = 1, #pars do pars[i] = gen_cparam(pars[i]) end @@ -996,7 +66,7 @@ local gen_func_csig = function(f, ftype) local vals = f:property_values_get(ftype):to_array() if ftype == eolian.function_type.PROP_SET then - local cnrt = rtype and rtype:c_type_named_get(cn) or ("void " .. cn) + local cnrt = get_func_csig_part(cn, rtype) local pars = {} for i, par in ipairs(keys) do pars[#pars + 1] = gen_cparam(par) @@ -1012,13 +82,13 @@ local gen_func_csig = function(f, ftype) local cnrt if not rtype then if #vals == 1 then - cnrt = vals[1]:type_get():c_type_named_get(cn) + cnrt = get_func_csig_part(cn, vals[1]:type_get()) table.remove(vals, 1) else - cnrt = "void " .. cn + cnrt = get_func_csig_part(cn) end else - cnrt = rtype:c_type_named_get(cn) + cnrt = get_func_csig_part(cn, rtype) end local pars = {} for i, par in ipairs(keys) do @@ -1031,16 +101,24 @@ local gen_func_csig = function(f, ftype) return cnrt .. "(" .. table.concat(pars, ", ") .. ");" end -local gen_func_namesig = function(fn, cl, buf) - if fn:type_get() ~= eolian.function_type.METHOD then +local gen_func_namesig = function(fn, cl, buf, isprop, isget, isset) + if isprop then buf[#buf + 1] = "@property " end - buf[#buf + 1] = cl:full_name_get() - buf[#buf + 1] = "." buf[#buf + 1] = fn:name_get() buf[#buf + 1] = " " - if fn:scope_get() == eolian.object_scope.PROTECTED then - buf[#buf + 1] = "@protected " + 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 " @@ -1048,23 +126,17 @@ local gen_func_namesig = function(fn, cl, buf) if fn:is_const() then buf[#buf + 1] = "@const " end - if fn:is_c_only() then - buf[#buf + 1] = "@c_only " - end end local gen_func_param = function(fp, buf, nodir) -- TODO: default value buf[#buf + 1] = " " - local dirs = { - [eolian.parameter_dir.IN] = "@in ", - [eolian.parameter_dir.OUT] = "@out ", - [eolian.parameter_dir.INOUT] = "@inout ", - } - if not nodir then buf[#buf + 1] = dirs[fp:direction_get()] end + 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] = get_type_str(fp:type_get()) + buf[#buf + 1] = eoutils.obj_serialize(fp:type_get()) local dval = fp:default_value_get() if dval then buf[#buf + 1] = " (" @@ -1090,7 +162,7 @@ local gen_func_return = function(fp, ftype, buf, indent) end buf[#buf + 1] = indent and (" "):rep(indent) or " " buf[#buf + 1] = "return: " - buf[#buf + 1] = get_type_str(rett) + buf[#buf + 1] = eoutils.obj_serialize(rett) local dval = fp:return_default_value_get(ftype) if dval then buf[#buf + 1] = " (" @@ -1105,9 +177,12 @@ end local gen_method_sig = function(fn, cl) local buf = {} - gen_func_namesig(fn, cl, buf) - if fn:is_virtual_pure(eolian.function_type.METHOD) then - buf[#buf + 1] = "@virtual_pure " + 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() @@ -1155,25 +230,26 @@ end local gen_prop_sig = function(fn, cl) local buf = {} - gen_func_namesig(fn, cl, buf) local fnt = fn:type_get() - local ftt = eolian.function_type - local isget = (fnt == ftt.PROPERTY or fnt == ftt.PROP_GET) - local isset = (fnt == ftt.PROPERTY or fnt == ftt.PROP_SET) + 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 gvirt = fn:is_virtual_pure(ftt.PROP_GET) - local svirt = fn:is_virtual_pure(ftt.PROP_SET) + 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] = "@virtual_pure " + buf[#buf + 1] = "@pure_virtual " end - local gkeys = isget and fn:property_keys_get(ftt.PROP_GET):to_array() or {} - local skeys = isset and fn:property_keys_get(ftt.PROP_SET):to_array() or {} - local gvals = isget and fn:property_values_get(ftt.PROP_GET):to_array() or {} - local svals = isget and fn:property_values_get(ftt.PROP_SET):to_array() or {} - local grtt = isget and fn:return_type_get(ftt.PROP_GET) or nil - local srtt = isset and fn:return_type_get(ftt.PROP_SET) or nil + 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) @@ -1181,7 +257,12 @@ local gen_prop_sig = function(fn, cl) buf[#buf + 1] = "{\n" if isget then - buf[#buf + 1] = " get {" + 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" @@ -1190,14 +271,19 @@ local gen_prop_sig = function(fn, cl) 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, ftt.PROP_GET, buf, 2) + 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 {" + 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" @@ -1206,7 +292,7 @@ local gen_prop_sig = function(fn, cl) 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, ftt.PROP_SET, buf, 2) + gen_func_return(fn, eolian.function_type.PROP_SET, buf, 2) end buf[#buf + 1] = " }\n" end @@ -1219,359 +305,990 @@ local gen_prop_sig = function(fn, cl) return table.concat(buf) end -local get_property_sig = function(fn, cl) - local buf = {} - gen_func_namesig(fn, cl, buf) - buf[#buf + 1] = "{" - buf[#buf + 1] = "}" - return table.concat(buf) -end - -- builders -local build_method, build_property +local nspaces_group = function(ns) + if #ns <= 2 then + return ns[1] + end -local build_reftable = function(f, title, ctitle, ctype, t) + 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 - f:write_h(title, 2) + local nt = {} for i, v in ipairs(t) do nt[#nt + 1] = { - Buffer():write_link(gen_nsp_eo(v, ctype, true), - v:full_name_get()):finish(), - get_brief_doc(v:documentation_get()) + writer.Buffer():write_link( + eoutils.obj_nspaces_get(v), + v:name_get() + ):finish(), + eoutils.doc_brief_get(eos, v:documentation_get()) } end table.sort(nt, function(v1, v2) return v1[1] < v2[1] end) - f:write_table({ ctitle, "Brief description" }, nt) - f:write_nl() + f:write_table({ title, "Brief description" }, nt) end -local build_functable = function(f, title, ctitle, cl, tp) - local t = cl:functions_get(tp):to_array() - if #t == 0 then - return - end - f:write_h(title, 3) - local nt = {} - for i, v in ipairs(t) do - local lbuf = Buffer() - lbuf:write_link(gen_nsp_func(v, cl, true), v:name_get()) - local pt = propt_to_type[v:type_get()] - if pt then - lbuf:write_raw(" ") - lbuf:write_i(pt) - end - nt[#nt + 1] = { - lbuf:finish(), get_brief_fdoc(v) - } - if funct_to_str[v:type_get()] == "property" then - build_property(v, cl, linkt) - else - build_method(v, cl, linkt) - end - end - table.sort(nt, function(v1, v2) return v1[1] < v2[1] end) - f:write_table({ ctitle, "Brief description" }, nt) - f:write_nl() -end - -local build_ref = function() - local f = Writer("reference") - f:write_h("EFL Reference", 2) - +local build_ref_group = function(f, ns, classlist, aliases, structs, enums, consts, globals) local classes = {} local ifaces = {} local mixins = {} - local clt = eolian.class_type - - for cl in eolian.all_classes_get() do + for i, cl in ipairs(classlist) do local tp = cl:type_get() - if tp == clt.REGULAR or tp == clt.ABSTRACT then + if tp == eolian.class_type.REGULAR or tp == eolian.class_type.ABSTRACT then classes[#classes + 1] = cl - elseif tp == clt.MIXIN then + elseif tp == eolian.class_type.MIXIN then mixins[#mixins + 1] = cl - elseif tp == clt.INTERFACE then + elseif tp == eolian.class_type.INTERFACE then ifaces[#ifaces + 1] = cl - else - error("unknown class: " .. cl:full_name_get()) end end - build_reftable(f, "Classes", "Class name", "class", classes) - build_reftable(f, "Interfaces", "Interface name", "interface", ifaces) - build_reftable(f, "Mixins", "Mixin name", "mixin", mixins) + 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, "Aliases", "Alias name", "alias", - eolian.typedecl_all_aliases_get():to_array()) + build_reftable(f, "Classes", "class", classes, true) + build_reftable(f, "Interfaces", "interface", ifaces, true) + build_reftable(f, "Mixins", "mixin", mixins, true) - build_reftable(f, "Structures", "Struct name", "struct", - eolian.typedecl_all_structs_get():to_array()) + 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) - build_reftable(f, "Enums", "Enum name", "enum", - eolian.typedecl_all_enums_get():to_array()) + f:write_nl() +end - build_reftable(f, "Constants", "Constant name", "constant", - eolian.variable_all_constants_get():to_array()) +local build_ref = function() + local f = writer.Writer("start", "EFL Reference") + printgen("Generating reference...") - build_reftable(f, "Globals", "Global name", "global", - eolian.variable_all_globals_get():to_array()) + 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 write_full_doc = function(f, doc1, doc2) - f:write_raw(get_full_doc(doc1, doc2)) - local since - if doc2 then - since = doc2:since_get() - end - if not since and doc1 then - since = doc1:since_get() - end - if since then - f:write_nl(2) - f:write_i("Since " .. since) - end -end - -local write_full_fdoc = function(f, fn, ftype) - write_full_doc(f, fn:documentation_get(eolian.function_type.METHOD), - get_fallback_fdoc(fn, ftype)) -end - local build_inherits build_inherits = function(cl, t, lvl) t = t or {} lvl = lvl or 0 - local lbuf = Buffer() - lbuf:write_link(gen_nsp_class(cl, true), cl:full_name_get()) - lbuf:write_raw(" ") - lbuf:write_i("(" .. classt_to_str[cl:type_get()] .. ")") - if lvl == 0 then - lbuf:write_b(lbuf:finish()) + 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 - t[#t + 1] = { lvl, lbuf:finish() } - for cln in cl:inherits_get() do - local acl = eolian.class_get_by_name(cln) - if not acl then - error("error retrieving inherited class " .. cln) - 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(" => ") -local class_to_color = function(cl) - local classt_to_color = { - [eolian.class_type.REGULAR] = { "black", "gray" }, - [eolian.class_type.ABSTRACT] = { "black", "gray" }, - [eolian.class_type.MIXIN] = { "blue", "skyblue" }, - [eolian.class_type.INTERFACE] = { "cornflowerblue", "azure" } - } - return classt_to_color[cl:type_get()] + 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 class_to_node = function(cl, main) - local ret = {} +local default_theme_light = { + classes = { + regular = { + style = "filled", + color = "black", + fill_color = "white", + font_color = "black", + primary_color = "black", + primary_fill_color = "gray", + primary_font_color = "black" + }, + abstract = { + style = "filled", + color = "black", + fill_color = "white", + font_color = "black", + primary_color = "black", + primary_fill_color = "gray", + primary_font_color = "black" + }, + mixin = { + style = "filled", + color = "blue", + fill_color = "white", + font_color = "black", + primary_color = "blue", + primary_fill_color = "skyblue", + primary_font_color = "black" + }, + interface = { + style = "filled", + color = "cornflowerblue", + fill_color = "white", + font_color = "black", + primary_color = "cornflowerblue", + primary_fill_color = "azure", + primary_font_color = "black" + } + }, + node = { + shape = "box" + }, + edge = { + color = "black" + }, + bg_color = "transparent", + rank_dir = "TB", + size = "6" +} - ret.label = cl:full_name_get() - ret.name = ret.label:lower():gsub("%.", "_") +local default_theme_dark = { + classes = { + regular = { + style = "filled", + color = "gray15", + fill_color = "gray15", + font_color = "white", + primary_color = "gray15", + primary_fill_color = "black", + primary_font_color = "white" + }, + abstract = { + style = "filled", + color = "gray15", + fill_color = "gray15", + font_color = "white", + primary_color = "gray15", + primary_fill_color = "black", + primary_font_color = "white" + }, + mixin = { + style = "filled", + color = "deepskyblue", + fill_color = "gray15", + font_color = "white", + primary_color = "deepskyblue", + primary_fill_color = "deepskyblue4", + primary_font_color = "white" + }, + interface = { + style = "filled", + color = "cornflowerblue", + fill_color = "gray15", + font_color = "white", + primary_color = "cornflowerblue", + primary_fill_color = "dodgerblue4", + primary_font_color = "white" + } + }, + node = { + shape = "box" + }, + edge = { + color = "gray35" + }, + bg_color = "transparent", + rank_dir = "TB", + size = "6" +} - local clr = class_to_color(cl) - ret.style = "filled" - ret.color = clr[1] - ret.fillcolor = main and clr[2] or "white" +local current_theme = default_theme_dark - -- FIXME: need a dokuwiki graphviz plugin with proper URL support - -- the existing one only supports raw URLs (no dokuwikí namespaces) - --ret.URL = ":" .. global_opts.root_nspace .. ":" - -- .. table.concat(gen_nsp_class(cl), ":") - - return ret +local validate_ctheme = function(tb, name) + if type(tb.classes[name]) ~= "table" then + return false + end + local t = tb.classes[name] + if type(t.style) ~= "string" then + return false + end + if type(t.color) ~= "string" then + return false + end + if type(t.fill_color) ~= "string" then + return false + end + if type(t.font_color) ~= "string" then + return false + end + if not t.primary_color then + t.primary_color = t.color + end + if not t.primary_fill_color then + t.primary_fill_color = t.fill_color + end + if not t.primary_font_color then + t.primary_font_color = t.font_color + end + if type(t.primary_color) ~= "string" then + return false + end + if type(t.primary_fill_color) ~= "string" then + return false + end + if type(t.primary_font_color) ~= "string" then + return false + end + return true end -local build_igraph_r -build_igraph_r = function(cl, nbuf, ibuf) - local sn = cl:full_name_get():lower():gsub("%.", "_") - for cln in cl:inherits_get() do - local acl = eolian.class_get_by_name(cln) - if not acl then - error("error retrieving inherited class " .. cln) +local validate_theme = function(tb) + if type(tb) ~= "table" then + return false + end + if type(tb.classes) ~= "table" then + return false + end + if not tb.node then + tb.node = current_theme.node + end + if not tb.edge then + tb.edge = current_theme.edge + end + if not tb.bg_color then + tb.bg_color = current_theme.bg_color + end + if not tb.rank_dir then + tb.rank_dir = current_theme.rank_dir + end + if not tb.size then + tb.size = current_theme.size + end + if type(tb.node) ~= "table" then + return false + end + if type(tb.edge) ~= "table" then + return false + end + if type(tb.bg_color) ~= "string" then + return false + end + if type(tb.rank_dir) ~= "string" then + return false + end + if type(tb.size) ~= "string" then + return false + end + if not validate_ctheme(tb, "regular") then + return false + end + if not validate_ctheme(tb, "abstract") then + return false + end + if not validate_ctheme(tb, "mixin") then + return false + end + if not validate_ctheme(tb, "interface") then + return false + end + return true +end + +local set_theme = function(tname) + local tf = io.open(tname) + if tf then + local cont = tf:read("*all") + tf:close() + local tb, err = serializer.deserialize(cont) + if not tb then + error("error parsing theme '" .. tname .. "': " .. err) end - nbuf[#nbuf + 1] = class_to_node(acl) - ibuf[#ibuf + 1] = { sn, (cln:lower():gsub("%.", "_")) } - build_igraph_r(acl, nbuf, ibuf) + if not validate_theme(tb) then + error("invalid theme '" .. tname .. "'") + end + current_theme = tb + else + error("theme '" .. tname .. "' does not exist") end end -local build_igraph = function(cl) - local graph = { - type = "hierarchy", - attrs = { - rankdir = "TB", - size = "6", - bgcolor = "transparent" - }, - node = { shape = "box" } +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 eoutils.doc_brief_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 eoutils.doc_brief_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 = eoutils.doc_brief_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 nbuf = {} - local ibuf = {} - nbuf[#nbuf + 1] = class_to_node(cl, true) - build_igraph_r(cl, nbuf, ibuf) +local write_functable = function(f, tcl, tbl) + if #tbl == 0 then + return + end + local nt = build_functable(t, tcl, tbl) - graph.nodes = nbuf - graph.connections = ibuf + local wrote = false + for i, wt in ipairs(nt) do + local cl = wt[0] + local func = wt[1] + local impl = wt[2] - return graph + 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 = eoutils.doc_brief_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 = eoutils.doc_brief_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 f = Writer(gen_nsp_class(cl)) - check_class(cl) + local cln = eoutils.obj_nspaces_get(cl) + local fulln = cl:name_get() + local f = writer.Writer(cln, fulln) + printgen("Generating class: " .. fulln) - f:write_h(cl:full_name_get(), 2) + mono.build_class(cl) - f:write_h("Inheritance hierarchy", 3) - f:write_list(build_inherits(cl)) - f:write_nl() - if global_opts.use_dot then - f:write_graph(build_igraph(cl)) - f:write_nl(2) - end - - f:write_h("Description", 3) - write_full_doc(f, cl:documentation_get()) + f:write_h(cl:name_get() .. " (" .. eoutils.class_type_str_get(cl) .. ")", 1) + + f:write_h("Description", 2) + f:write_raw(eoutils.doc_full_get(eos, cl:documentation_get(), nil, true)) f:write_nl(2) - build_functable(f, "Methods", "Method name", cl, eolian.function_type.METHOD) - build_functable(f, "Properties", "Property name", - cl, eolian.function_type.PROPERTY) + f:write_editable(cln, "description") + f:write_nl() - f:write_h("Events", 3) - local evs = cl:events_get():to_array() - if #evs == 0 then - f:write_raw("This class does not define any events.\n") - else - local first = true - for i, ev in ipairs(evs) do - if not first then - f:write_nl(2) - end - f:write_h(ev:name_get(), 4) - write_full_doc(f, ev:documentation_get()) - first = false - end + 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 eolian.all_classes_get() do - local ct = cl:type_get() - if not classt_to_str[ct] then - error("unknown class: " .. cl:full_name_get()) + for cl in eos:classes_get() do + if eoutils.obj_matches_filter(cl) then + build_class(cl) end - build_class(cl) end end -local write_tsigs = function(f, tp) - f:write_h(tp:full_name_get(), 2) - - f:write_h("Signature", 3) - f:write_code(get_typedecl_str(tp)) +local write_tsigs = function(f, tp, ns) + f:write_h("Signature", 2) + f:write_code(eoutils.obj_serialize(tp)) f:write_nl() - f:write_h("C signature", 3) - f:write_code(tp:c_type_get() .. ";") + f:write_h("C signature", 2) + f:write_code(eoutils.obj_serialize_c(tp, ns), "c") f:write_nl() end local build_alias = function(tp) - local f = Writer(gen_nsp_eo(tp, "alias")) - check_alias(tp) + local ns = eoutils.obj_nspaces_get(tp) + local fulln = tp:name_get() + local f = writer.Writer(ns, fulln) + printgen("Generating alias: " .. fulln) - write_tsigs(f, tp) + f:write_h("Description", 2) + f:write_raw(eoutils.doc_full_get(eos, tp:documentation_get(), nil, true)) + f:write_nl(2) + + f:write_editable(ns, "description") + f:write_nl() + + write_tsigs(f, tp, ns) f:finish() end local build_struct = function(tp) - local f = Writer(gen_nsp_eo(tp, "struct")) - check_struct(tp) + local ns = eoutils.obj_nspaces_get(tp) + local fulln = tp:name_get() + local f = writer.Writer(ns, fulln) + printgen("Generating struct: " .. fulln) - write_tsigs(f, tp) + f:write_h("Description", 2) + f:write_raw(eoutils.doc_full_get(eos, tp:documentation_get(), nil, true)) + f:write_nl(2) + + f:write_editable(ns, "description") + f:write_nl() + + f:write_h("Fields", 2) + + f:write_editable(ns, "fields") + f:write_nl() + + local arr = {} + for fl in tp:struct_fields_get() do + local buf = writer.Buffer() + buf:write_b(fl:name_get()) + buf:write_raw(" - ", eoutils.doc_full_get(eos, fl:documentation_get())) + arr[#arr + 1] = buf:finish() + end + f:write_list(arr) + f:write_nl() + + write_tsigs(f, tp, ns) f:finish() end local build_enum = function(tp) - local f = Writer(gen_nsp_eo(tp, "enum")) - check_enum(tp) + local ns = eoutils.obj_nspaces_get(tp) + local fulln = tp:name_get() + local f = writer.Writer(ns, fulln) + printgen("Generating enum: " .. fulln) - write_tsigs(f, tp) + f:write_h("Description", 2) + f:write_raw(eoutils.doc_full_get(eos, tp:documentation_get(), nil, true)) + f:write_nl(2) + + f:write_editable(ns, "description") + f:write_nl() + + f:write_h("Fields", 2) + + f:write_editable(ns, "fields") + f:write_nl() + + local arr = {} + for fl in tp:enum_fields_get() do + local buf = writer.Buffer() + buf:write_b(fl:name_get()) + buf:write_raw(" - ", eoutils.doc_full_get(eos, fl:documentation_get())) + arr[#arr + 1] = buf:finish() + end + f:write_list(arr) + f:write_nl() + + write_tsigs(f, tp, ns) f:finish() end local build_variable = function(v, constant) - local f = Writer(gen_nsp_eo(v, constant and "constant" or "global")) - if constant then - check_constant(v) - else - check_global(v) - end + local ns = eoutils.obj_nspaces_get(v) + local fulln = v:name_get() + local f = writer.Writer(ns, fulln) + printgen("Generating variable: " .. fulln) + + f:write_h("Description", 2) + f:write_raw(eoutils.doc_full_get(eos, v:documentation_get(), nil, true)) + f:write_nl(2) + + f:write_editable(ns, "description") + f:write_nl() + + write_tsigs(f, v, ns) f:finish() end local build_typedecls = function() - for tp in eolian.typedecl_all_aliases_get() do + for tp in eos:aliases_get() do build_alias(tp) end - for tp in eolian.typedecl_all_structs_get() do + for tp in eos:structs_get() do build_struct(tp) end - for tp in eolian.typedecl_all_enums_get() do + for tp in eos:enums_get() do build_enum(tp) end end local build_variables = function() - for v in eolian.variable_all_constants_get() do + for v in eos:constants_get() do build_variable(v, true) end - for v in eolian.variable_all_globals_get() do + for v in eos:globals_get() do build_variable(v, false) end end -local pdir_to_str = { - [eolian.parameter_dir.IN] = "(in)", - [eolian.parameter_dir.OUT] = "(out)", - [eolian.parameter_dir.INOUT] = "(inout)" -} - local build_parlist = function(f, pl, nodir) local params = {} for i, p in ipairs(pl) do - local buf = Buffer() + local buf = writer.Buffer() buf:write_b(p:name_get()) if not nodir then buf:write_raw(" ") - buf:write_i(pdir_to_str[p:direction_get()]) + buf:write_i("(", eoutils.param_get_dir_name(p), ")") end - buf:write_raw(" - ", get_full_doc(p:documentation_get())) + buf:write_raw(" - ", eoutils.doc_full_get(eos, p:documentation_get())) params[#params + 1] = buf:finish() end f:write_list(params) @@ -1589,125 +1306,419 @@ local build_vallist = function(f, pg, ps, title) if same then ps = {} end end if #pg > 0 or #ps > 0 then - f:write_h(title, 3) + f:write_h(title, 2) if #pg > 0 then if #ps > 0 then - f:write_h("Getter", 4) + f:write_h("Getter", 3) end build_parlist(f, pg, true) end if #ps > 0 then if #pg > 0 then - f:write_h("Setter", 4) + f:write_h("Setter", 3) end build_parlist(f, ps, true) end end end -build_method = function(fn, cl) - local f = Writer(gen_nsp_func(fn, cl)) - check_method(fn, cl) +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 - f:write_h(cl:full_name_get() .. "." .. fn:name_get(), 2) +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 - f:write_h("Signature", 3) +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(eoutils.doc_full_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", 3) - f:write_code(gen_func_csig(fn), "c") + 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", 3) + f:write_h("Parameters", 2) build_parlist(f, pars) f:write_nl() end - f:write_h("Description", 3) - write_full_doc(f, fn:documentation_get(eolian.function_type.METHOD)) + write_ilist(f, impl, cl) f:write_nl() f:finish() end -build_property = function(fn, cl) - local f = Writer(gen_nsp_func(fn, cl)) +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 fts = eolian.function_type - local ft = fn:type_get() - local isget = (ft == fts.PROP_GET or ft == fts.PROPERTY) - local isset = (ft == fts.PROP_SET or ft == fts.PROPERTY) + local pimp = fn:implement_get() - if isget then check_property(fn, cl, fts.PROP_GET) end - if isset then check_property(fn, cl, fts.PROP_SET) end + local isget = pimp:is_prop_get() + local isset = pimp:is_prop_set() - local doc = fn:documentation_get(fts.PROPERTY) - local gdoc = fn:documentation_get(fts.PROP_GET) - local sdoc = fn:documentation_get(fts.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) - f:write_h(cl:full_name_get() .. "." .. fn:name_get(), 2) - - f:write_h("Signature", 3) - f:write_code(gen_prop_sig(fn, cl)) - f:write_nl() - - f:write_h("C signature", 3) - local codes = {} - if isget then - codes[#codes + 1] = gen_func_csig(fn, fts.PROP_GET) + 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 isset then - codes[#codes + 1] = gen_func_csig(fn, fts.PROP_SET) - end - f:write_code(table.concat(codes, "\n"), "c") - f:write_nl() - - local pgkeys = isget and fn:property_keys_get(fts.PROP_GET):to_array() or {} - local pskeys = isset and fn:property_keys_get(fts.PROP_SET):to_array() or {} - build_vallist(f, pgkeys, pskeys, "Keys") - - local pgvals = isget and fn:property_values_get(fts.PROP_GET):to_array() or {} - local psvals = isset and fn:property_values_get(fts.PROP_SET):to_array() or {} - build_vallist(f, pgvals, psvals, "Values") if isget and isset then - f:write_h("Description", 3) + f:write_h("Description", 2) if doc or (not gdoc and not sdoc) then - write_full_doc(f, doc) + f:write_raw(eoutils.doc_full_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", 4) + f:write_h("Getter", 3) else - f:write_h("Description", 3) + f:write_h("Description", 2) end - write_full_doc(f, gdoc) + f:write_raw(eoutils.doc_full_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", 4) + f:write_h("Setter", 3) else - f:write_h("Description", 3) + f:write_h("Description", 2) + end + f:write_raw(eoutils.doc_full_get(eos, sdoc, nil, true)) + if isget then + f:write_nl() + f:write_editable(pns, "getter_description") + f:write_nl() end - write_full_doc(f, sdoc) 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(eoutils.doc_full_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 = { @@ -1716,12 +1727,20 @@ getopt.parse { 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-graphviz", false, help = "Disable graphviz usage." }, - { nil, "disable-notes", false, help = "Disable notes plugin usage." } + { nil, "graph-theme", true, help = "Optional graph theme." }, + { nil, "graph-theme-light", false, help = "Use light builtin graph theme." }, + { 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") @@ -1731,41 +1750,88 @@ getopt.parse { if opts["h"] then return end - global_opts.verbose = not not opts["v"] - global_opts.use_dot = not opts["disable-graphviz"] - global_opts.use_notes = not opts["disable-notes"] - global_opts.root_nspace = (not opts["n"] or opts["n"] == "") - and "efl" or opts["n"] - if not opts["r"] or opts["r"] == "" then - opts["r"] = "dokuwiki/data/pages" + if opts["p"] then + printgen = function(...) print(...) end end - global_opts.doc_root = path_join(opts["r"], - nspace_to_path(global_opts.root_nspace)) + if opts["graph-theme-dark"] then + current_theme = default_theme_light + end + if opts["graph-theme"] then + set_theme(opts["graph-theme"]) + 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 - if not eolian.system_directory_scan() then - error("failed scanning system directory") - end + scan_directory() else for i, p in ipairs(args) do - if not eolian.directory_scan(p) then - error("failed scanning directory: " .. p) - end + scan_directory(p) end end - if not eolian.all_eot_files_parse() then - error("failed parsing eo type files") + + 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 - if not eolian.all_eo_files_parse() then - error("failed parsing eo files") + + local wfeatures = { + notes = not opts["disable-notes"], + folds = not opts["disable-folded"], + title = not opts["disable-title"] + } + writer.init(rootns, wfeatures) + 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 - cutil.file_rmrf(path_join(global_opts.doc_root)) - mkdir_r(nil) - build_ref() - build_classes() - build_typedecls() - build_variables() - print_stats() end } -return true \ No newline at end of file +return true diff --git a/template.lua b/template.lua new file mode 100644 index 0000000..45fb56e --- /dev/null +++ b/template.lua @@ -0,0 +1,254 @@ +local M = {} + +local is_nl = function(c) + return (c == "\n") or (c == "\r") +end + +local wses = { + [" "] = true, ["\t"] = true, ["\v"] = true, ["\f"] = true +} + +local is_ws = function(c) + return not not wses[c] +end + +local next_char = function(ls) + local oc = ls.current + ls.current = ls.stream() + return oc +end + +local next_line = function(ls) + local pc = next_char(ls) + assert(is_nl(pc)) + if is_nl(ls.current) and (ls.current ~= pc) then + local nc = next_char(ls) + return "\n" + end + ls.lnum = ls.lnum + 1 + return "\n" +end + +local lex_new = function(s) + local ret = { + stream = s, + lnum = 1 + } + next_char(ret) + return ret +end + +local lex_error = function(ls, msg) + error(("input:%d: %s near '%s'"):format(ls.lnum, msg, ls.current or "EOF")) +end + +local pair_chars = { + ["*"] = true, ["%"] = true, ["-"] = true +} + +local lex_token + +lex_token = function(ls) + -- keep track of line numbers but consider newlines + -- tokens in order to preserve format exactly as it was + -- other whitespace needs no tracking + if is_nl(ls.current) then + return next_line(ls) + end + if ls.current == "\\" then + next_char(ls) + local c = ls.current + if not c then + lex_error(ls, "unfinished escape") + end + next_char(ls) + return c + end + if ls.current == "{" then + next_char(ls) + -- comment + if ls.current == "#" then + next_char(ls) + while true do + if not ls.current then + lex_error(ls, "unfinished comment") + end + if ls.current == "\\" then + next_char(ls) + next_char(ls) + end + local c = ls.current + next_char(ls) + if c == "#" and ls.current == "}" then + next_char(ls) + -- eliminated tail call + return lex_token(ls) + end + end + end + local c = ls.current + if c == "{" or c == "[" or pair_chars[c] then + next_char(ls) + return "{" .. c + end + return c + end + local c = ls.current + next_char(ls) + if ls.current == "}" then + if c == "}" or c == "]" or pair_chars[c] then + local nc = ls.current + next_char(ls) + return c .. nc + end + end + return c +end + +local lex = function(ls) + local ret = ls.lookahead + if ret then + ls.lookahead = nil + return ret + end + return lex_token(ls) +end + +local lookahead = function(ls) + local ret = lex_token(ls) + ls.lookahead = ret + return ret +end + +local get_until = function(ls, etok) + local tok = lex(ls) + local buf = {} + while tok ~= etok do + buf[#buf + 1] = tok + tok = lex(ls) + if not tok then + lex_error(ls, ("'%s' expected"):format(etok)) + end + end + return table.concat(buf) +end + +local save_exp = function(cbuf, exp) + cbuf[#cbuf + 1] = " write(" .. exp .. ") " +end + +local save_code = function(cbuf, code) + cbuf[#cbuf + 1] = " " .. code .. " " +end + +local save_acc = function(cbuf, acc) + save_exp(cbuf, ("%q"):format(table.concat(acc))) + return {} +end + + +local parse = function(ls) + local acc, cbuf = {}, {"local context, escape = ... "} + local tok = lex(ls) + local was_nl = false + while tok ~= nil do + local cnl, skip_tok = false, false + if tok == "{{" then + acc = save_acc(cbuf, acc) + save_exp(cbuf, "escape(" .. get_until(ls, "}}") .. ")") + elseif tok == "{*" then + acc = save_acc(cbuf, acc) + save_exp(cbuf, get_until(ls, "*}")) + elseif tok == "{%" then + local code = get_until(ls, "%}") + local lah = lookahead(ls) + skip_tok = true + if not was_nl or (lah ~= "\n") then + acc = save_acc(cbuf, acc) + else + acc[#acc] = nil + end + save_code(cbuf, code) + elseif was_nl and is_ws(tok) then + acc[#acc] = acc[#acc] .. tok + cnl = true + else + cnl = (tok == "\n") + if cnl then + acc = save_acc(cbuf, acc) + end + acc[#acc + 1] = tok + end + was_nl = cnl + tok = lex(ls) + end + save_acc(cbuf, acc) + return table.concat(cbuf) +end + +-- turn input into a character stream +local make_stream = function(str) + local f + -- if a string, test if it's a file path, otherwise consider a stream + if type(str) == "string" then + f = io.open(str) + else + f = str + end + -- a string and cannot be opened, treat as it is + if not f then + return str:gmatch(".") + end + -- otherwise turn into a special stream + -- but if we can't read nything, return empty + local chunksize = 64 + local ip = f:read(chunksize) + if not ip or #ip == 0 then + return function() end + end + local ss = ip:gmatch(".") + return function() + local c = ss() + if not c then + ip = f:read(chunksize) + if not ip or #ip == 0 then + return nil + end + c = ss() + end + return c + end +end + +local default_esc = function(str) + return str +end + +local default_mt = { + -- not invoked for actual fields of the environment, but when invoked, + -- look up in context first (that way we can access context variables + -- without prefixing every time) and if that fails, global environment + __index = function(self, n) + local cf = self.context[n] + if cf == nil then + return getfenv(0)[n] + end + return cf + end +} + +M.compile = function(str) + -- environment is nicely encapsulated... + local denv = setmetatable({ template = M }, default_mt) + local f = setfenv( + assert(loadstring(parse(lex_new(make_stream(str))))), + denv + ) + return function(ctx, escape, write) + denv.context = ctx + denv.write = write or io.write + return f(ctx, escape or default_esc) + end +end + +return M