mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
docs/API.md is 18.7k lines and its anchors are signature slugs, so editing a signature breaks the 281 internal and 88 README links into it. Blizzard solved the same problem by shipping the API as data plus a browser addon, and warcraft.wiki.gg renders those same tables. One set of descriptors can feed all three. Each registration now carries one next to it: typed arguments and returns, nilability, defaults, and a sentence. Events, enumerations and returned table shapes are described the same way. No Script_* body changed. Game.h gains namespace Game::Doc (Field with Req/Opt/Vararg, FieldList, Function, Method, Structure, Event) and a trailing descriptor argument on all six Game::Lua::Register* plus Event::Custom::AutoReserve. api/Documentation.cpp records every registration on the first pass of each Lua state and seals it, so /reload re-registers but records nothing and the environment comes from which pass saw the name. Export is lazy: nothing is materialized until C_APIDocumentation.GetSystem asks for one system, because eager export would build thousands of tables per reload for a slash command. _classicapi_UndocumentedAPI lists what still has no descriptor. AddOns/!!!ClassicAPI/APIDocumentation/ ports Blizzard_APIDocumentation's eight files. Named /classicapi, not /api: the browser holds only what ClassicAPI adds, so /api would have implied the whole 1.12 surface. Two deliberate differences from the retail source, both noted in the file headers: a system search also matches its namespace, since that is the name people type; and an undocumented function says so, because an empty signature otherwise reads as a real one. LinkUtil gains the SetItemRef to LinkUtil.ProcessLink dispatch. That handler registry was dead code; a new link type now registers a handler instead of stacking another SetItemRef override. Also defines assertsafe there, which was undefined and only mattered once the function went live. Documented: the C_Spell and C_SpellBook surface, the spell globals, both spellbook enumerations, the 13 UNIT_SPELLCAST_* events, GameTooltip as the widget-method example and C_Glue as the glue and mixed-environment example. 66 of 692 function registrations. tools/New-ApiDocSkeletons.ps1 emits paste-ready skeletons to make the rest of the sweep tractable. Verified in game: the browser lists, searches, and prints detail; links render and click through; right-click copies and shift-click dumps.
422 lines
12 KiB
Lua
422 lines
12 KiB
Lua
-- Backport of Blizzard_APIDocumentation/Blizzard_APIDocumentation.lua to
|
|
-- vanilla 1.12 / Lua 5.0.
|
|
--
|
|
-- Implementation differences from the retail source:
|
|
-- - Lua 5.0 has no length operator (#); sequence length comes from
|
|
-- table.getn(t), matching the rest of this addon.
|
|
-- - Lua 5.0 has no `...` inside a function body; WriteLineF takes its
|
|
-- extra values from the implicit `arg` table.
|
|
-- - ChatFrameUtil does not exist. HandleOpenDump uses this client's
|
|
-- ChatFrame_OpenChat, which applies the text from ChatEdit_OnUpdate
|
|
-- rather than immediately -- so the cursor is placed one frame later,
|
|
-- after that text has landed.
|
|
|
|
local tGetN = table.getn;
|
|
|
|
APIDocumentationMixin = {};
|
|
|
|
-- "public"
|
|
function APIDocumentationMixin:OnLoad()
|
|
self.tables = {};
|
|
self.functions = {};
|
|
self.systems = {};
|
|
self.fields = {};
|
|
self.events = {};
|
|
self.callbacks = {};
|
|
|
|
self.Commands = {
|
|
Default = 1,
|
|
CopyAPI = 2,
|
|
OpenDump = 3,
|
|
};
|
|
|
|
if DEFAULT_CHAT_FRAME then
|
|
DEFAULT_CHAT_FRAME:SetMaxLines(2000);
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:HandleSlashCommand(command)
|
|
local commands = { (" "):split(command) };
|
|
|
|
if commands[1] == "?" or commands[1] == "help" or commands[1] == "" then
|
|
self:OutputUsage();
|
|
elseif commands[1] == "stats" then
|
|
self:OutputStats();
|
|
elseif commands[1] == "system" then
|
|
if commands[2] == "list" then
|
|
self:OutputAllSystems();
|
|
else
|
|
self:OutputUsage();
|
|
end
|
|
elseif commands[1] == "s" or commands[1] == "search" then
|
|
self:OutputAllAPIMatches(unpack(commands, 2));
|
|
elseif commands[1] then
|
|
self:TryHandlingSystemSearchCommand(unpack(commands));
|
|
else
|
|
self:OutputUsage();
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:HandleAPILink(link, command)
|
|
local _, type, name, parentName = (":"):split(link);
|
|
local apiInfo = self:FindAPIByName(type, name, parentName);
|
|
if apiInfo then
|
|
if command == self.Commands.CopyAPI then
|
|
self:HandleCopyAPI(apiInfo);
|
|
elseif command == self.Commands.OpenDump then
|
|
self:HandleOpenDump(apiInfo);
|
|
else
|
|
self:HandleDefaultCommand(apiInfo);
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:HandleDefaultCommand(apiInfo)
|
|
self:WriteLine(" ");
|
|
self:WriteAllLines(apiInfo:GetDetailedOutputLines());
|
|
end
|
|
|
|
function APIDocumentationMixin:HandleCopyAPI(apiInfo)
|
|
local clipboardString = apiInfo:GetClipboardString();
|
|
CopyToClipboard(clipboardString);
|
|
self:WriteLineF("Copied to clipboard: %s", clipboardString);
|
|
end
|
|
|
|
function APIDocumentationMixin:HandleOpenDump(apiInfo)
|
|
if apiInfo.Type == "Function" then
|
|
local dumpString;
|
|
local systemNamespace = apiInfo.System and apiInfo.System:GetNamespaceName() or nil;
|
|
if systemNamespace and systemNamespace ~= "" then
|
|
dumpString = ("/dump %s.%s()"):format(systemNamespace, apiInfo.Name);
|
|
else
|
|
dumpString = ("/dump %s()"):format(apiInfo.Name);
|
|
end
|
|
|
|
local desiredCursorPosition = string.len(dumpString) - 1;
|
|
ChatFrame_OpenChat(dumpString);
|
|
RunNextFrame(function()
|
|
if ChatFrameEditBox and ChatFrameEditBox:IsVisible() then
|
|
ChatFrameEditBox:SetCursorPosition(desiredCursorPosition);
|
|
end
|
|
end);
|
|
else
|
|
self:WriteLine("Can only /dump functions");
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:FindAPIByName(apiType, name, parentName)
|
|
local apiTable = self:GetAPITableByTypeName(apiType);
|
|
if apiTable then
|
|
for i, apiInfo in ipairs(apiTable) do
|
|
if apiInfo:MatchesName(name, parentName) then
|
|
return apiInfo;
|
|
end
|
|
end
|
|
end
|
|
return nil;
|
|
end
|
|
|
|
function APIDocumentationMixin:GetAPITableByTypeName(apiType)
|
|
if apiType == "function" then
|
|
return self.functions;
|
|
elseif apiType == "table" then
|
|
return self.tables;
|
|
elseif apiType == "system" then
|
|
return self.systems;
|
|
elseif apiType == "field" then
|
|
return self.fields;
|
|
elseif apiType == "event" then
|
|
return self.events;
|
|
elseif apiType == "callback" then
|
|
return self.callbacks;
|
|
end
|
|
return nil;
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputUsage()
|
|
self:WriteLine("Usage:");
|
|
|
|
self:WriteLine("Search for API");
|
|
self:WriteLine(self:GetIndentString() .. "/classicapi search <api name>");
|
|
self:WriteLine(self:GetIndentString() .. "or");
|
|
self:WriteLine(self:GetIndentString() .. "/classicapi s <api name>");
|
|
self:WriteLine(self:GetIndentString() .. "Example: /classicapi search spell");
|
|
self:WriteLine(" ");
|
|
|
|
self:WriteLine("List all systems");
|
|
self:WriteLine(self:GetIndentString() .. "/classicapi system list");
|
|
self:WriteLine(" ");
|
|
|
|
self:WriteLine("Search system for API");
|
|
self:WriteLine(self:GetIndentString() .. "/classicapi <system name> search <api name>");
|
|
self:WriteLine(self:GetIndentString() .. "or");
|
|
self:WriteLine(self:GetIndentString() .. "/classicapi <system name> s <api name>");
|
|
self:WriteLine(self:GetIndentString() .. "Example: /classicapi spell search cooldown");
|
|
self:WriteLine(" ");
|
|
|
|
self:WriteLine("List all API in a system");
|
|
self:WriteLine(self:GetIndentString() .. "/classicapi <system name> list");
|
|
self:WriteLine(self:GetIndentString() .. "Example: /classicapi spell list");
|
|
self:WriteLine(" ");
|
|
self:WriteLine("All searches support Lua patterns.");
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputStats()
|
|
self:WriteLine("Stats:");
|
|
self:WriteLineF("Total systems: %d", tGetN(self.systems));
|
|
local totalFunctions = 0;
|
|
local totalEvents = 0;
|
|
local totalTables = 0;
|
|
|
|
for i, systemInfo in ipairs(self.systems) do
|
|
totalFunctions = totalFunctions + systemInfo:GetNumFunctions();
|
|
totalEvents = totalEvents + systemInfo:GetNumEvents();
|
|
totalTables = totalTables + systemInfo:GetNumTables();
|
|
end
|
|
|
|
self:WriteLineF("Total functions: %d", totalFunctions);
|
|
self:WriteLineF("Total events: %d", totalEvents);
|
|
self:WriteLineF("Total tables: %d", totalTables);
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputAllSystems()
|
|
self:WriteLineF("All systems (%d):", tGetN(self.systems));
|
|
for i, systemInfo in ipairs(self.systems) do
|
|
self:WriteLine(systemInfo:GetSingleOutputLine());
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:TryHandlingSystemSearchCommand(systemName, subCommand, apiToSearchFor)
|
|
local system = self:FindSystemByName(systemName);
|
|
if system then
|
|
if subCommand == nil then
|
|
self:WriteLine(system:GetSingleOutputLine());
|
|
elseif subCommand == "s" or subCommand == "search" and apiToSearchFor then
|
|
self:OutputAllSystemAPIMatches(system, apiToSearchFor);
|
|
elseif subCommand == "list" then
|
|
self:OutputAllSystemAPI(system);
|
|
else
|
|
self:OutputUsage();
|
|
end
|
|
else
|
|
self:WriteLineF("No system found (%s)", tostring(systemName));
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputAPIMatches(apiMatches, headerName)
|
|
if apiMatches and tGetN(apiMatches) > 0 then
|
|
self:WriteLineF("Found %d %s", tGetN(apiMatches), headerName);
|
|
for i, api in ipairs(apiMatches) do
|
|
self:WriteLine(self:GetIndentString() .. api:GetSingleOutputLine());
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputAllAPIMatches(apiToSearchFor)
|
|
if not apiToSearchFor or apiToSearchFor == "" then
|
|
self:OutputUsage();
|
|
return;
|
|
end
|
|
self:WriteLine(" ");
|
|
|
|
local apiMatches = self:FindAllAPIMatches(apiToSearchFor);
|
|
if apiMatches then
|
|
local total = tGetN(apiMatches.tables) + tGetN(apiMatches.functions) + tGetN(apiMatches.events) + tGetN(apiMatches.systems) + tGetN(apiMatches.callbacks);
|
|
assert(total > 0);
|
|
self:WriteLineF("Found %d API that matches %q", total, apiToSearchFor);
|
|
|
|
self:OutputAPIMatches(apiMatches.systems, "system(s)");
|
|
self:OutputAPIMatches(apiMatches.functions, "function(s)");
|
|
self:OutputAPIMatches(apiMatches.events, "events(s)");
|
|
self:OutputAPIMatches(apiMatches.tables, "table(s)");
|
|
self:OutputAPIMatches(apiMatches.callbacks, "callback(s)");
|
|
else
|
|
self:WriteLineF("No API found that matches %q", apiToSearchFor);
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputAllSystemAPIMatches(system, apiToSearchFor)
|
|
local apiMatches = system:FindAllAPIMatches(apiToSearchFor);
|
|
if apiMatches then
|
|
local total = tGetN(apiMatches.tables) + tGetN(apiMatches.functions) + tGetN(apiMatches.events);
|
|
assert(total > 0);
|
|
self:WriteLineF("Found %d API that matches %q", total, apiToSearchFor);
|
|
|
|
self:OutputAPIMatches(apiMatches.functions, "function(s)");
|
|
self:OutputAPIMatches(apiMatches.events, "events(s)");
|
|
self:OutputAPIMatches(apiMatches.tables, "table(s)");
|
|
else
|
|
self:WriteLineF("No API found that matches %q in %s", apiToSearchFor, system:GenerateAPILink());
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:OutputAllSystemAPI(system)
|
|
local apiMatches = system:ListAllAPI();
|
|
if apiMatches then
|
|
self:WriteLineF("All API in %s", system:GenerateAPILink());
|
|
|
|
self:OutputAPIMatches(apiMatches.functions, "function(s)");
|
|
self:OutputAPIMatches(apiMatches.events, "events(s)");
|
|
self:OutputAPIMatches(apiMatches.tables, "table(s)");
|
|
else
|
|
self:WriteLineF("No API found in %s", system:GenerateAPILink());
|
|
end
|
|
end
|
|
|
|
--[[static]] function APIDocumentationMixin:AddAllMatches(apiContainer, matchesContainer, apiToSearchFor)
|
|
for i, apiInfo in ipairs(apiContainer) do
|
|
if apiInfo:MatchesSearchString(apiToSearchFor) then
|
|
table.insert(matchesContainer, apiInfo);
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:FindAllAPIMatches(apiToSearchFor)
|
|
apiToSearchFor = apiToSearchFor:lower();
|
|
|
|
local matches = {
|
|
tables = {},
|
|
functions = {},
|
|
events = {},
|
|
systems = {},
|
|
callbacks = {},
|
|
};
|
|
|
|
self:AddAllMatches(self.tables, matches.tables, apiToSearchFor);
|
|
self:AddAllMatches(self.functions, matches.functions, apiToSearchFor);
|
|
self:AddAllMatches(self.systems, matches.systems, apiToSearchFor);
|
|
self:AddAllMatches(self.events, matches.events, apiToSearchFor);
|
|
self:AddAllMatches(self.callbacks, matches.callbacks, apiToSearchFor);
|
|
|
|
-- Only return something if we matched anything
|
|
for name, subTable in pairs(matches) do
|
|
if tGetN(subTable) > 0 then
|
|
return matches;
|
|
end
|
|
end
|
|
|
|
return nil;
|
|
end
|
|
|
|
function APIDocumentationMixin:FindSystemByName(systemName)
|
|
systemName = systemName:lower();
|
|
for i, systemInfo in ipairs(self.systems) do
|
|
if systemInfo:MatchesNameCaseInsenstive(systemName) then
|
|
return systemInfo;
|
|
end
|
|
end
|
|
return nil;
|
|
end
|
|
|
|
function APIDocumentationMixin:AddDocumentationTable(documentationInfo)
|
|
if documentationInfo.Name then
|
|
self:AddSystem(documentationInfo);
|
|
else
|
|
for i, tableInfo in ipairs(documentationInfo.Tables) do
|
|
self:AddTable(tableInfo);
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:WriteLine(message)
|
|
if DEFAULT_CHAT_FRAME then
|
|
local info = ChatTypeInfo["SYSTEM"];
|
|
DEFAULT_CHAT_FRAME:AddMessage(message, info.r, info.g, info.b, info.id);
|
|
else
|
|
print(message);
|
|
end
|
|
end
|
|
|
|
-- The `...` in the parameter list is what creates `arg`; a Lua 5.0 function
|
|
-- declared without it has no `arg` at all.
|
|
function APIDocumentationMixin:WriteLineF(format, ...)
|
|
self:WriteLine(string.format(format, unpack(arg)));
|
|
end
|
|
|
|
function APIDocumentationMixin:WriteAllLines(lines)
|
|
for i, line in ipairs(lines) do
|
|
self:WriteLine(line);
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:GetIndentString(numIndent)
|
|
return (" "):rep(numIndent or 1);
|
|
end
|
|
|
|
-- "private"
|
|
function APIDocumentationMixin:AddTable(documentationInfo)
|
|
Mixin(documentationInfo, TablesAPIMixin);
|
|
|
|
table.insert(self.tables, documentationInfo);
|
|
|
|
if documentationInfo.Fields then
|
|
for i, field in ipairs(documentationInfo.Fields) do
|
|
field.Table = documentationInfo;
|
|
self:AddField(field);
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:AddFunction(documentationInfo)
|
|
Mixin(documentationInfo, FunctionsAPIMixin);
|
|
|
|
table.insert(self.functions, documentationInfo);
|
|
|
|
if documentationInfo.Arguments then
|
|
for i, field in ipairs(documentationInfo.Arguments) do
|
|
field.Function = documentationInfo;
|
|
self:AddField(field);
|
|
end
|
|
end
|
|
|
|
if documentationInfo.Returns then
|
|
for i, field in ipairs(documentationInfo.Returns) do
|
|
field.Function = documentationInfo;
|
|
self:AddField(field);
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:AddEvent(documentationInfo)
|
|
Mixin(documentationInfo, EventsAPIMixin);
|
|
|
|
table.insert(self.events, documentationInfo);
|
|
|
|
if documentationInfo.Payload then
|
|
for i, field in ipairs(documentationInfo.Payload) do
|
|
field.Event = documentationInfo;
|
|
self:AddField(field);
|
|
end
|
|
end
|
|
end
|
|
|
|
function APIDocumentationMixin:AddField(documentationInfo)
|
|
Mixin(documentationInfo, FieldsAPIMixin);
|
|
|
|
table.insert(self.fields, documentationInfo);
|
|
end
|
|
|
|
function APIDocumentationMixin:AddSystem(documentationInfo)
|
|
Mixin(documentationInfo, SystemsAPIMixin);
|
|
|
|
table.insert(self.systems, documentationInfo);
|
|
|
|
for i, functionInfo in ipairs(documentationInfo.Functions) do
|
|
functionInfo.System = documentationInfo;
|
|
self:AddFunction(functionInfo);
|
|
end
|
|
|
|
for i, eventInfo in ipairs(documentationInfo.Events) do
|
|
eventInfo.System = documentationInfo;
|
|
self:AddEvent(eventInfo);
|
|
end
|
|
|
|
for i, tableInfo in ipairs(documentationInfo.Tables) do
|
|
tableInfo.System = documentationInfo;
|
|
self:AddTable(tableInfo);
|
|
end
|
|
end
|
|
|
|
APIDocumentation = CreateFromMixins(APIDocumentationMixin);
|
|
APIDocumentation:OnLoad();
|