summaryrefslogtreecommitdiffstats
path: root/Server/Plugins/APIDump/main_APIDump.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Server/Plugins/APIDump/main_APIDump.lua')
-rw-r--r--Server/Plugins/APIDump/main_APIDump.lua342
1 files changed, 277 insertions, 65 deletions
diff --git a/Server/Plugins/APIDump/main_APIDump.lua b/Server/Plugins/APIDump/main_APIDump.lua
index 32de2890e..c2d70734d 100644
--- a/Server/Plugins/APIDump/main_APIDump.lua
+++ b/Server/Plugins/APIDump/main_APIDump.lua
@@ -36,6 +36,16 @@ local function LoadAPIFiles(a_Folder, a_DstTable)
break
end
for k, cls in pairs(Tables) do
+ if (a_DstTable[k]) then
+ -- The class is documented in two files, warn and store into a file (so that CIs can mark build as failure):
+ LOGWARNING(string.format(
+ "APIDump warning: class %s is documented at two places, the documentation in file %s will overwrite the previously loaded one!",
+ k, FileName
+ ))
+ local f = io.open("DuplicateDocs.txt", "a")
+ f:write(k, "\t", FileName)
+ f:close()
+ end
a_DstTable[k] = cls;
end
end -- if (TablesFn)
@@ -146,6 +156,12 @@ local function CreateAPITables()
end
end
+ -- Remove the built-in Lua libraries:
+ API.debug = nil
+ API.io = nil
+ API.string = nil
+ API.table = nil
+
return API, Globals;
end
@@ -505,8 +521,8 @@ local function ReadDescriptions(a_API, a_Desc)
local DoxyFunctions = {}; -- This will contain all the API functions together with their documentation
- local function AddFunction(a_Name, a_Params, a_Return, a_IsStatic, a_Notes)
- table.insert(DoxyFunctions, {Name = a_Name, Params = a_Params, Return = a_Return, IsStatic = a_IsStatic, Notes = a_Notes});
+ local function AddFunction(a_Name, a_Params, a_Returns, a_IsStatic, a_Notes)
+ table.insert(DoxyFunctions, {Name = a_Name, Params = a_Params, Returns = a_Returns, IsStatic = a_IsStatic, Notes = a_Notes});
end
if (APIDesc.Functions ~= nil) then
@@ -524,11 +540,11 @@ local function ReadDescriptions(a_API, a_Desc)
-- Description is available
if (FnDesc[1] == nil) then
-- Single function definition
- AddFunction(func.Name, FnDesc.Params, FnDesc.Return, FnDesc.IsStatic, FnDesc.Notes);
+ AddFunction(func.Name, FnDesc.Params, FnDesc.Returns, FnDesc.IsStatic, FnDesc.Notes);
else
-- Multiple function overloads
for _, desc in ipairs(FnDesc) do
- AddFunction(func.Name, desc.Params, desc.Return, desc.IsStatic, desc.Notes);
+ AddFunction(func.Name, desc.Params, desc.Returns, desc.IsStatic, desc.Notes);
end -- for k, desc - FnDesc[]
end
FnDesc.IsExported = true;
@@ -676,13 +692,6 @@ local function ReadDescriptions(a_API, a_Desc)
-- Sort the functions (they may have been renamed):
table.sort(cls.Functions,
function(f1, f2)
- if (f1.Name == f2.Name) then
- -- Same name, either comparing the same function to itself, or two overloads, in which case compare the params
- if ((f1.Params == nil) or (f2.Params == nil)) then
- return 0;
- end
- return (f1.Params < f2.Params);
- end
return (f1.Name < f2.Name);
end
);
@@ -761,7 +770,91 @@ end
-local function WriteHtmlClass(a_ClassAPI, a_ClassMenu)
+--- Returns a HTML string describing the (parameter) type, linking to the type's documentation, if available
+-- a_Type is the string containing the type (such as "cPlugin" or "number"), or nil
+-- a_API is the complete API description (used for searching the classnames)
+local function LinkifyType(a_Type, a_API)
+ -- Check params:
+ assert(type(a_Type) == "string")
+ assert(type(a_API) == "table")
+
+ -- If the type is a known class, return a direct link to it:
+ if (a_API[a_Type]) then
+ return "<a href=\"" .. a_Type .. ".html\">" .. a_Type .. "</a>"
+ end
+
+ -- If the type has a hash sign, it's a child enum of a class:
+ local idxColon = a_Type:find("#")
+ if (idxColon) then
+ local classType = a_Type:sub(1, idxColon - 1)
+ if (a_API[classType]) then
+ local enumType = a_Type:sub(idxColon + 1)
+ return "<a href=\"" .. classType .. ".html#" .. enumType .. "\">" .. enumType .. "</a>"
+ end
+ end
+
+ -- If the type is a ConstantGroup within the Globals, it's a global enum:
+ if ((a_API.Globals.ConstantGroups or {})[a_Type]) then
+ return "<a href=\"Globals.html#" .. a_Type .. "\">" .. a_Type .. "</a>"
+ end
+
+ -- Unknown or built-in type, output just text:
+ return a_Type
+end
+
+
+
+
+
+--- Returns an HTML string describing all function parameters (or return values)
+-- a_FnParams is an array-table or string description of the parameters
+-- a_ClassName is the name of the class for which the function is being documented (for Linkification)
+-- a_API is the complete API description (for cross-type linkification)
+local function CreateFunctionParamsDescription(a_FnParams, a_ClassName, a_API)
+ local pt = type(a_FnParams)
+ assert((pt == "string") or (pt == "table"))
+ assert(type(a_ClassName) == "string")
+ assert(type(a_API) == "table")
+
+ -- If the params description is a string (old format), just linkify it:
+ if (pt == "string") then
+ return LinkifyString(a_FnParams, a_ClassName)
+ end
+
+ -- If the params description is an empty table, give no description at all:
+ if not(a_FnParams[1]) then
+ return ""
+ end
+
+ -- The params description is a table, output the full desc:
+ local res = {"<table border=0 cellspacing=0>"}
+ local idx = 2
+ for _, param in ipairs(a_FnParams) do
+ res[idx] = "<tr><td>"
+ res[idx + 1] = param.Name or ""
+ res[idx + 2] = "</td><td><i>"
+ res[idx + 3] = LinkifyType(param.Type, a_API)
+ res[idx + 4] = "</i></td></tr>"
+ idx = idx + 5
+ end
+ res[idx] = "</tr></table>"
+ return table.concat(res)
+end
+
+
+
+
+
+--- Writes an HTML file containing the class API description for the given class
+-- a_ClassAPI is the API description of the class to output
+-- a_ClassMenu is the HTML string containing the code for the menu sidebar
+-- a_API is the complete API (for cross-type linkification)
+local function WriteHtmlClass(a_ClassAPI, a_ClassMenu, a_API)
+ -- Check params:
+ assert(type(a_ClassAPI) == "table")
+ assert(type(a_ClassMenu) == "string")
+ assert(type(a_API) == "table")
+
local cf, err = io.open("API/" .. a_ClassAPI.Name .. ".html", "w");
if (cf == nil) then
LOGINFO("Cannot write HTML API for class " .. a_ClassAPI.Name .. ": " .. err)
@@ -770,22 +863,28 @@ local function WriteHtmlClass(a_ClassAPI, a_ClassMenu)
-- Writes a table containing all functions in the specified list, with an optional "inherited from" header when a_InheritedName is valid
local function WriteFunctions(a_Functions, a_InheritedName)
- if (#a_Functions == 0) then
+ if not(a_Functions[1]) then
+ -- No functions to write
return;
end
- if (a_InheritedName ~= nil) then
+ if (a_InheritedName) then
cf:write("<h2>Functions inherited from ", a_InheritedName, "</h2>\n");
end
cf:write("<table>\n<tr><th>Name</th><th>Parameters</th><th>Return value</th><th>Notes</th></tr>\n");
+ -- Store all function names, to create unique anchor names for all functions
+ local TableOverloadedFunctions = {}
for _, func in ipairs(a_Functions) do
local StaticClause = ""
if (func.IsStatic) then
StaticClause = "(STATIC) "
end
- cf:write("<tr><td>", func.Name, "</td>\n");
- cf:write("<td>", LinkifyString(func.Params or "", (a_InheritedName or a_ClassAPI.Name)), "</td>\n");
- cf:write("<td>", LinkifyString(func.Return or "", (a_InheritedName or a_ClassAPI.Name)), "</td>\n");
+ -- Increase number by one
+ TableOverloadedFunctions[func.Name] = (TableOverloadedFunctions[func.Name] or 0) + 1
+ -- Add the anchor names as a title
+ cf:write("<tr><td id=\"", func.Name, "_", TableOverloadedFunctions[func.Name], "\" title=\"", func.Name, "_", TableOverloadedFunctions[func.Name], "\">", func.Name, "</td>\n");
+ cf:write("<td>", CreateFunctionParamsDescription(func.Params or {}, a_InheritedName or a_ClassAPI.Name, a_API), "</td>\n");
+ cf:write("<td>", CreateFunctionParamsDescription(func.Returns or {}, a_InheritedName or a_ClassAPI.Name, a_API), "</td>\n");
cf:write("<td>", StaticClause .. LinkifyString(func.Notes or "<i>(undocumented)</i>", (a_InheritedName or a_ClassAPI.Name)), "</td></tr>\n");
end
cf:write("</table>\n");
@@ -794,7 +893,7 @@ local function WriteHtmlClass(a_ClassAPI, a_ClassMenu)
local function WriteConstantTable(a_Constants, a_Source)
cf:write("<table>\n<tr><th>Name</th><th>Value</th><th>Notes</th></tr>\n");
for _, cons in ipairs(a_Constants) do
- cf:write("<tr><td>", cons.Name, "</td>\n");
+ cf:write("<tr><td id=\"", cons.Name, "\">", cons.Name, "</td>\n");
cf:write("<td>", cons.Value, "</td>\n");
cf:write("<td>", LinkifyString(cons.Notes or "", a_Source), "</td></tr>\n");
end
@@ -837,7 +936,7 @@ local function WriteHtmlClass(a_ClassAPI, a_ClassMenu)
cf:write("<table><tr><th>Name</th><th>Type</th><th>Notes</th></tr>\n");
for _, var in ipairs(a_Variables) do
- cf:write("<tr><td>", var.Name, "</td>\n");
+ cf:write("<tr><td id=\"", var.Name, "\">", var.Name, "</td>\n");
cf:write("<td>", LinkifyString(var.Type or "<i>(undocumented)</i>", a_InheritedName or a_ClassAPI.Name), "</td>\n");
cf:write("<td>", LinkifyString(var.Notes or "", a_InheritedName or a_ClassAPI.Name), "</td>\n </tr>\n");
end
@@ -1027,7 +1126,7 @@ local function WriteClasses(f, a_API, a_ClassMenu)
]]);
for _, cls in ipairs(a_API) do
f:write("<li><a href=\"", cls.Name, ".html\">", cls.Name, "</a></li>\n");
- WriteHtmlClass(cls, a_ClassMenu);
+ WriteHtmlClass(cls, a_ClassMenu, a_API);
end
f:write([[
</ul></p>
@@ -1447,6 +1546,7 @@ local function WriteZBSMethods(f, a_Methods)
f:write("\t\t\t[\"", func.Name, "\"] =\n")
f:write("\t\t\t{\n")
f:write("\t\t\t\ttype = \"method\",\n")
+ -- No way to indicate multiple signatures to ZBS, so don't output any params at all
if ((func.Notes ~= nil) and (func.Notes ~= "")) then
f:write("\t\t\t\tdescription = [[", CleanUpDescription(func.Notes or ""), " ]],\n")
end
@@ -1643,14 +1743,10 @@ local function PrepareApi()
-- Load the API descriptions from the Classes and Hooks subfolders:
-- This needs to be done each time the command is invoked because the export modifies the tables' contents
local apiDesc = dofile(g_PluginFolder .. "/APIDesc.lua")
- if (apiDesc.Classes == nil) then
- apiDesc.Classes = {};
- end
- if (apiDesc.Hooks == nil) then
- apiDesc.Hooks = {};
- end
- LoadAPIFiles("/Classes/", apiDesc.Classes);
- LoadAPIFiles("/Hooks/", apiDesc.Hooks);
+ apiDesc.Classes = apiDesc.Classes or {}
+ apiDesc.Hooks = apiDesc.Hooks or {}
+ LoadAPIFiles("/Classes/", apiDesc.Classes)
+ LoadAPIFiles("/Hooks/", apiDesc.Hooks)
-- Reset the stats:
g_TrackedPages = {}; -- List of tracked pages, to be checked later whether they exist. Each item is an array of referring pagenames.
@@ -1684,6 +1780,7 @@ local function PrepareApi()
-- Add Globals into the API:
Globals.Name = "Globals";
table.insert(API, Globals);
+ API.Globals = Globals
-- Read in the descriptions:
LOG("Reading descriptions...");
@@ -1719,43 +1816,12 @@ end
-local function HandleWebAdminDump(a_Request)
- if (a_Request.PostParams["Dump"] ~= nil) then
- DumpApi()
- end
- return
- [[
- <p>Pressing the button will generate the API dump on the server. Note that this can take some time.</p>
- <form method="POST"><input type="submit" name="Dump" value="Dump the API"/></form>
- ]]
-end
-
-
-
-
-
-local function HandleCmdApi(a_Split)
- DumpApi()
- return true
-end
-
-
-
-
-
-local function HandleCmdApiShow(a_Split, a_EntireCmd)
- os.execute("API" .. cFile:GetPathSeparator() .. "index.html")
- return true, "Launching the browser to show the API docs..."
-end
-
-
-
-
-
-local function HandleCmdApiCheck(a_Split, a_EntireCmd)
+--- Checks the currently undocumented symbols against an "official" undocumented symbol list
+-- Returns an array-table of strings representing the newly-undocumented symbol names
+local function CheckNewUndocumentedSymbols()
-- Download the official API stats on undocumented stuff:
-- (We need a blocking downloader, which is impossible with the current cNetwork API)
- assert(os.execute("wget -O official_undocumented.lua http://apidocs.cuberite.org/_undocumented.lua"))
+ assert(os.execute("wget -q -O official_undocumented.lua http://apidocs.cuberite.org/_undocumented.lua"))
local OfficialStats = cFile:ReadWholeFile("official_undocumented.lua")
if (OfficialStats == "") then
return true, "Cannot load official stats"
@@ -1819,7 +1885,7 @@ local function HandleCmdApiCheck(a_Split, a_EntireCmd)
-- Bail out if no items found:
if not(res[1]) then
- return true, "No new undocumented functions"
+ return
end
-- Save any found items to a file:
@@ -1828,7 +1894,153 @@ local function HandleCmdApiCheck(a_Split, a_EntireCmd)
f:write("\n")
f:close()
- return true, "Newly undocumented items: " .. #res .. "\n" .. table.concat(res, "\n")
+ return res
+end
+
+
+
+
+
+--- Checks the API description for unknown types listed in Params or Returns
+-- Returns an array-table of { Location = "cClass:function(), param #1", Type = "UnknownType" }
+-- Returns nil if no unknown types are found
+local function CheckBadTypes()
+ -- Load the API and preprocess known types:
+ local api = PrepareApi()
+ local knownTypes =
+ {
+ string = true,
+ number = true,
+ boolean = true,
+ any = true,
+ self = true,
+ table = true,
+ ["function"] = true,
+ ["..."] = true,
+ ["SQLite DB object"] = true,
+ ["<unknown>"] = true, -- Allow "<unknown>" types, for now, until the API is properly documented
+ }
+ for _, clsDesc in ipairs(api) do
+ knownTypes[clsDesc.Name] = true -- The class is a known type
+ for grpName, _ in pairs(clsDesc.ConstantGroups or {}) do -- All class' enums are known types (with namespacing)
+ knownTypes[clsDesc.Name .. "#" .. grpName] = true
+ end
+ if (clsDesc.Name == "Globals") then
+ for grpName, _ in pairs(clsDesc.ConstantGroups or {}) do -- All Globals' enums are known types without namespacing, too
+ knownTypes[grpName] = true
+ end
+ end
+ end -- for cls - classes
+
+ -- Check types:
+ local res = {}
+ for _, clsDesc in ipairs(api) do
+ for _, fnDesc in ipairs(clsDesc.Functions or {}) do
+ local fnName = fnDesc.Name
+ local fn = fnDesc[1] and fnDesc or { fnDesc } -- Unify the format, fn is an array of function signatures
+ for idxS, signature in ipairs(fn) do
+ for idxP, param in ipairs(signature.Params or {}) do
+ if not(knownTypes[param.Type]) then
+ table.insert(res, {
+ Location = string.format("%s:%s(), signature #%d, param #%d", clsDesc.Name, fnName, idxS, idxP),
+ Type = param.Type,
+ })
+ end
+ end -- for param
+ if (type(signature.Returns) == "table") then
+ for idxR, ret in ipairs(signature.Returns) do
+ if not(knownTypes[ret.Type]) then
+ table.insert(res, {
+ Location = string.format("%s:%s(), signature #%d, return #%d", clsDesc.Name, fnName, idxS, idxR),
+ Type = ret.Type,
+ })
+ end
+ end -- for ret
+ elseif not(signature.Returns) then
+ else
+ table.insert(res, {
+ Location = string.format("%s:%s(), signature #%d, return string", clsDesc.Name, fnName, idxS),
+ Type = tostring(signature.Returns),
+ })
+ end
+ end -- for signature
+ end -- for fn - functions
+ end -- for cls - classes
+
+ -- If no problems found, bail out:
+ if not(res[1]) then
+ return
+ end
+
+ -- Write the problems into a file:
+ local f = io.open("UnknownTypes.lua", "w")
+ f:write("return\n{\n")
+ for _, item in ipairs(res) do
+ f:write("\t{ ", string.format("{ Location = %q, Type = %q", item.Location, item.Type), "},\n")
+ end
+ f:write("}\n")
+ f:close()
+ return res
+end
+
+
+
+
+
+local function HandleWebAdminDump(a_Request)
+ if (a_Request.PostParams["Dump"] ~= nil) then
+ DumpApi()
+ end
+ return
+ [[
+ <p>Pressing the button will generate the API dump on the server. Note that this can take some time.</p>
+ <form method="POST"><input type="submit" name="Dump" value="Dump the API"/></form>
+ ]]
+end
+
+
+
+
+
+local function HandleCmdApi(a_Split)
+ DumpApi()
+ return true
+end
+
+
+
+
+
+local function HandleCmdApiShow(a_Split, a_EntireCmd)
+ os.execute("API" .. cFile:GetPathSeparator() .. "index.html")
+ return true, "Launching the browser to show the API docs..."
+end
+
+
+
+
+
+local function HandleCmdApiCheck(a_Split, a_EntireCmd)
+ -- Check the Params and Returns types:
+ LOG("Checking API for bad types...")
+ local badTypes = CheckBadTypes()
+ if (badTypes) then
+ -- Serialize into descriptions:
+ local descs = {}
+ for idx, t in ipairs(badTypes) do
+ descs[idx] = string.format("Location %q, type %q", t.Location, t.Type)
+ end
+ return true, "Found bad types:\n" .. table.concat(descs, "\n")
+ end
+
+ -- Check for new symbols that are not documented:
+ LOG("Checking API for newly undocumented symbols...")
+ local newUndocumented = CheckNewUndocumentedSymbols()
+ if (newUndocumented) then
+ return true, "Found new undocumented symbols:\n" .. table.concat(newUndocumented, "\n")
+ end
+
+ return true, "API check completed successfully"
end