488 lines
14 KiB
Lua
488 lines
14 KiB
Lua
#!/usr/bin/lua
|
|
|
|
-- InfoDump.lua
|
|
|
|
--[[
|
|
Loads plugins' Info.lua and dumps its g_PluginInfo into various text formats
|
|
This is used for generating plugin documentation for the forum and for GitHub's INFO.md files
|
|
|
|
This script can be used in two ways:
|
|
Executing "lua InfoDump.lua" will go through all subfolders and dump each Info.lua file it can find
|
|
Note that this mode of operation requires LuaRocks with LFS installed; instructions are printed
|
|
when the prerequisites are not met.
|
|
Executing "lua InfoDump.lua PluginName" will load the Info.lua file from PluginName's folder and dump
|
|
only that one plugin's documentation. This mode of operation doesn't require LuaRocks
|
|
--]]
|
|
|
|
|
|
|
|
|
|
|
|
-- Check Lua version. We use 5.1-specific construct when loading the plugin info, 5.2 is not compatible!
|
|
if (_VERSION ~= "Lua 5.1") then
|
|
print("Unsupported Lua version. This script requires Lua version 5.1, this Lua is version " .. (_VERSION or "<nil>"));
|
|
return;
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Replaces generic formatting with forum-specific formatting
|
|
-- Also removes the single line-ends
|
|
local function ForumizeString(a_Str)
|
|
assert(type(a_Str) == "string");
|
|
|
|
-- Remove the indentation, unless in the code tag:
|
|
-- Only one code or /code tag per line is supported!
|
|
local IsInCode = false;
|
|
local function RemoveIndentIfNotInCode(s)
|
|
if (IsInCode) then
|
|
-- we're in code section, check if this line terminates it
|
|
IsInCode = (s:find("{%%/code}") ~= nil);
|
|
return s .. "\n";
|
|
else
|
|
-- we're not in code section, check if this line starts it
|
|
IsInCode = (s:find("{%%code}") ~= nil);
|
|
return s:gsub("^%s*", "") .. "\n";
|
|
end
|
|
end
|
|
a_Str = a_Str:gsub("(.-)\n", RemoveIndentIfNotInCode);
|
|
|
|
-- Replace multiple line ends with {%p} and single line ends with a space,
|
|
-- so that manual word-wrap in the Info.lua file doesn't wrap in the forum.
|
|
a_Str = a_Str:gsub("\n\n", "{%%p}");
|
|
a_Str = a_Str:gsub("\n", " ");
|
|
|
|
-- Replace the generic formatting:
|
|
a_Str = a_Str:gsub("{%%p}", "\n\n");
|
|
a_Str = a_Str:gsub("{%%b}", "[b]"):gsub("{%%/b}", "[/b]");
|
|
a_Str = a_Str:gsub("{%%i}", "[i]"):gsub("{%%/i}", "[/i]");
|
|
a_Str = a_Str:gsub("{%%list}", "[list]"):gsub("{%%/list}", "[/list]");
|
|
a_Str = a_Str:gsub("{%%li}", "[*]"):gsub("{%%/li}", "");
|
|
-- TODO: Other formatting
|
|
|
|
return a_Str;
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Builds an array of categories, each containing all the commands belonging to the category,
|
|
-- and the category description, if available.
|
|
-- Returns the array table, each item has the following format:
|
|
-- { Name = "CategoryName", Description = "CategoryDescription", Commands = {{CommandString = "/cmd verb", Info = {...}}, ...}}
|
|
local function BuildCategories(a_PluginInfo)
|
|
-- The returned result
|
|
-- This will contain both an array and a dict of the categories, to allow fast search
|
|
local res = {};
|
|
|
|
-- For each command add a reference to it into all of its categories:
|
|
local function AddCommands(a_CmdPrefix, a_Commands)
|
|
for cmd, info in pairs(a_Commands) do
|
|
local NewCmd =
|
|
{
|
|
CommandString = a_CmdPrefix .. cmd,
|
|
Info = info,
|
|
}
|
|
|
|
if ((info.HelpString ~= nil) and (info.HelpString ~= "")) then
|
|
-- Add to each specified category:
|
|
local Category = info.Category;
|
|
if (type(Category) == "string") then
|
|
Category = {Category};
|
|
end
|
|
for idx, cat in ipairs(Category or {""}) do
|
|
local CatEntry = res[cat];
|
|
if (CatEntry == nil) then
|
|
-- First time we came across this category, create it:
|
|
local NewCat = {Name = cat, Description = "", Commands = {NewCmd}};
|
|
table.insert(res, NewCat);
|
|
res[cat] = NewCat;
|
|
else
|
|
-- We already have this category, just add the command to its list of commands:
|
|
table.insert(CatEntry.Commands, NewCmd);
|
|
end
|
|
end -- for idx, cat - Category[]
|
|
end -- if (HelpString valid)
|
|
|
|
-- Recurse all subcommands:
|
|
if (info.Subcommands ~= nil) then
|
|
AddCommands(a_CmdPrefix .. cmd .. " ", info.Subcommands);
|
|
end
|
|
end -- for cmd, info - a_Commands[]
|
|
end -- AddCommands()
|
|
|
|
AddCommands("", a_PluginInfo.Commands);
|
|
|
|
-- Assign descriptions to categories:
|
|
for name, desc in pairs(a_PluginInfo.Categories or {}) do
|
|
local CatEntry = res[name];
|
|
if (CatEntry ~= nil) then
|
|
-- The result has this category, add the description:
|
|
CatEntry.Description = desc.Description;
|
|
end
|
|
end
|
|
|
|
-- Alpha-sort each category's command list:
|
|
for idx, cat in ipairs(res) do
|
|
table.sort(cat.Commands,
|
|
function (cmd1, cmd2)
|
|
return (string.lower(cmd1.CommandString) < string.lower(cmd2.CommandString));
|
|
end
|
|
);
|
|
end
|
|
|
|
return res;
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Returns a string specifying the command.
|
|
-- If a_Command is a simple string, returns a_Command colorized to blue
|
|
-- If a_Command is a table, expects members Name (full command name) and Params (command parameters),
|
|
-- colorizes command name blue and params green
|
|
local function GetCommandRefForum(a_Command)
|
|
if (type(a_Command) == "string") then
|
|
return "[color=blue]" .. a_Command .. "[/color]";
|
|
end
|
|
return "[color=blue]" .. a_Command.Name .. "[/color] [color=green]" .. a_Command.Params .. "[/color]";
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Writes the specified command detailed help array to the output file, in the forum dump format
|
|
local function WriteCommandParameterCombinationsForum(a_CmdString, a_ParameterCombinations, f)
|
|
assert(type(a_CmdString) == "string");
|
|
assert(type(a_ParameterCombinations) == "table");
|
|
assert(f ~= nil);
|
|
|
|
if (#a_ParameterCombinations == 0) then
|
|
-- No explicit parameter combinations to write
|
|
return;
|
|
end
|
|
|
|
f:write("The following parameter combinations are recognized:\n");
|
|
for idx, combination in ipairs(a_ParameterCombinations) do
|
|
f:write("[color=blue]", a_CmdString, "[/color] [color=green]", combination.Params, "[/color]");
|
|
if (combination.Help ~= nil) then
|
|
f:write(" - ", ForumizeString(combination.Help));
|
|
end
|
|
if (combination.Permission ~= nil) then
|
|
f:write(" (Requires permission '[color=red]", combination.Permission, "[/color]')");
|
|
end
|
|
f:write("\n");
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Writes all commands in the specified category to the output file, in the forum dump format
|
|
local function WriteCommandsCategoryForum(a_Category, f)
|
|
-- Write category name:
|
|
local CategoryName = a_Category.Name;
|
|
if (CategoryName == "") then
|
|
CategoryName = "General";
|
|
end
|
|
f:write("\n[size=Large]", ForumizeString(a_Category.DisplayName or CategoryName), "[/size]\n");
|
|
|
|
-- Write description:
|
|
if (a_Category.Description ~= "") then
|
|
f:write(ForumizeString(a_Category.Description), "\n");
|
|
end
|
|
|
|
-- Write commands:
|
|
f:write("\n[list]");
|
|
for idx2, cmd in ipairs(a_Category.Commands) do
|
|
f:write("\n[b]", cmd.CommandString, "[/b] - ", ForumizeString(cmd.Info.HelpString or "UNDOCUMENTED"), "\n");
|
|
if (cmd.Info.Permission ~= nil) then
|
|
f:write("Permission required: [color=red]", cmd.Info.Permission, "[/color]\n");
|
|
end
|
|
if (cmd.Info.DetailedDescription ~= nil) then
|
|
f:write(cmd.Info.DetailedDescription);
|
|
end
|
|
if (cmd.Info.ParameterCombinations ~= nil) then
|
|
WriteCommandParameterCombinationsForum(cmd.CommandString, cmd.Info.ParameterCombinations, f);
|
|
end
|
|
end
|
|
f:write("[/list]\n\n")
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function DumpCommandsForum(a_PluginInfo, f)
|
|
-- Copy all Categories from a dictionary into an array:
|
|
local Categories = BuildCategories(a_PluginInfo);
|
|
|
|
-- Sort the categories by name:
|
|
table.sort(Categories,
|
|
function(cat1, cat2)
|
|
return (string.lower(cat1.Name) < string.lower(cat2.Name));
|
|
end
|
|
);
|
|
|
|
if (#Categories == 0) then
|
|
return;
|
|
end
|
|
|
|
f:write("\n[size=X-Large]Commands[/size]\n");
|
|
|
|
-- Dump per-category commands:
|
|
for idx, cat in ipairs(Categories) do
|
|
WriteCommandsCategoryForum(cat, f);
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function DumpAdditionalInfoForum(a_PluginInfo, f)
|
|
local AInfo = a_PluginInfo.AdditionalInfo;
|
|
if ((AInfo == nil) or (type(AInfo) ~= "table")) then
|
|
-- There is no AdditionalInfo in a_PluginInfo
|
|
return;
|
|
end
|
|
|
|
for idx, info in ipairs(a_PluginInfo.AdditionalInfo) do
|
|
if ((info.Title ~= nil) and (info.Contents ~= nil)) then
|
|
f:write("\n[size=X-Large]", ForumizeString(info.Title), "[/size]\n");
|
|
f:write(ForumizeString(info.Contents), "\n");
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Collects all permissions mentioned in the info, returns them as a sorted array
|
|
-- Each array item is {Name = "PermissionName", Info = { PermissionInfo }}
|
|
local function BuildPermissions(a_PluginInfo)
|
|
-- Collect all used permissions from Commands, reference the commands that use the permission:
|
|
local Permissions = a_PluginInfo.Permissions or {};
|
|
local function CollectPermissions(a_CmdPrefix, a_Commands)
|
|
for cmd, info in pairs(a_Commands) do
|
|
CommandString = a_CmdPrefix .. cmd;
|
|
if ((info.Permission ~= nil) and (info.Permission ~= "")) then
|
|
-- Add the permission to the list of permissions:
|
|
local Permission = Permissions[info.Permission] or {};
|
|
Permissions[info.Permission] = Permission;
|
|
-- Add the command to the list of commands using this permission:
|
|
Permission.CommandsAffected = Permission.CommandsAffected or {};
|
|
table.insert(Permission.CommandsAffected, CommandString);
|
|
end
|
|
|
|
-- Process the command param combinations for permissions:
|
|
local ParamCombinations = info.ParameterCombinations or {};
|
|
for idx, comb in ipairs(ParamCombinations) do
|
|
if ((comb.Permission ~= nil) and (comb.Permission ~= "")) then
|
|
-- Add the permission to the list of permissions:
|
|
local Permission = Permissions[comb.Permission] or {};
|
|
Permissions[info.Permission] = Permission;
|
|
-- Add the command to the list of commands using this permission:
|
|
Permission.CommandsAffected = Permission.CommandsAffected or {};
|
|
table.insert(Permission.CommandsAffected, {Name = CommandString, Params = comb.Params});
|
|
end
|
|
end
|
|
|
|
-- Process subcommands:
|
|
if (info.Subcommands ~= nil) then
|
|
CollectPermissions(CommandString .. " ", info.Subcommands);
|
|
end
|
|
end
|
|
end
|
|
CollectPermissions("", a_PluginInfo.Commands);
|
|
|
|
-- Copy the list of permissions to an array:
|
|
local PermArray = {};
|
|
for name, perm in pairs(Permissions) do
|
|
table.insert(PermArray, {Name = name, Info = perm});
|
|
end
|
|
|
|
-- Sort the permissions array:
|
|
table.sort(PermArray,
|
|
function(p1, p2)
|
|
return (p1.Name < p2.Name);
|
|
end
|
|
);
|
|
return PermArray;
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function DumpPermissionsForum(a_PluginInfo, f)
|
|
-- Get the processed sorted array of permissions:
|
|
local Permissions = BuildPermissions(a_PluginInfo);
|
|
if ((Permissions == nil) or (#Permissions <= 0)) then
|
|
return;
|
|
end
|
|
|
|
-- Dump the permissions:
|
|
f:write("\n[size=X-Large]Permissions[/size]\n[list]\n");
|
|
for idx, perm in ipairs(Permissions) do
|
|
f:write(" - [color=red]", perm.Name, "[/color] - ");
|
|
f:write(perm.Info.Description or "");
|
|
local CommandsAffected = perm.Info.CommandsAffected or {};
|
|
if (#CommandsAffected > 0) then
|
|
f:write("\n[list] Commands affected:\n- ");
|
|
local Affects = {};
|
|
for idx2, cmd in ipairs(CommandsAffected) do
|
|
table.insert(Affects, GetCommandRefForum(cmd));
|
|
end
|
|
f:write(table.concat(Affects, "\n - "));
|
|
f:write("\n[/list]");
|
|
end
|
|
if (perm.Info.RecommendedGroups ~= nil) then
|
|
f:write("\n[list] Recommended groups: ", perm.Info.RecommendedGroups, "[/list]");
|
|
end
|
|
f:write("\n");
|
|
end
|
|
f:write("[/list]");
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function DumpPluginInfoForum(a_PluginFolder, a_PluginInfo)
|
|
-- Open the output file:
|
|
local f, msg = io.open(a_PluginInfo.Name .. "_forum.txt", "w");
|
|
if (f == nil) then
|
|
print("\tCannot dump forum info for plugin " .. a_PluginFolder .. ": " .. msg);
|
|
return;
|
|
end
|
|
|
|
-- Write the description:
|
|
f:write(ForumizeString(a_PluginInfo.Description), "\n");
|
|
DumpAdditionalInfoForum(a_PluginInfo, f);
|
|
DumpCommandsForum(a_PluginInfo, f);
|
|
DumpPermissionsForum(a_PluginInfo, f);
|
|
|
|
f:close();
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function DumpPluginInfoGitHub()
|
|
-- TODO
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Tries to load the g_PluginInfo from the plugin's Info.lua file
|
|
-- Returns the g_PluginInfo table on success, or nil and error message on failure
|
|
local function LoadPluginInfo(a_FolderName)
|
|
-- Load and compile the Info file:
|
|
local cfg, err = loadfile(a_FolderName .. "/Info.lua");
|
|
if (cfg == nil) then
|
|
return nil, "Cannot open 'Info.lua': " .. err;
|
|
end
|
|
|
|
-- Execute the loaded file in a sandbox:
|
|
-- This is Lua-5.1-specific and won't work in Lua 5.2!
|
|
local Sandbox = {};
|
|
setfenv(cfg, Sandbox);
|
|
cfg();
|
|
if (Sandbox.g_PluginInfo == nil) then
|
|
return nil, "Info.lua doesn't contain the g_PluginInfo declaration";
|
|
end
|
|
return Sandbox.g_PluginInfo;
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function ProcessPluginFolder(a_FolderName)
|
|
local PluginInfo, Msg = LoadPluginInfo(a_FolderName);
|
|
if (PluginInfo == nil) then
|
|
if (Msg ~= nil) then
|
|
print("\t" .. Msg);
|
|
end
|
|
return;
|
|
end
|
|
DumpPluginInfoForum(a_FolderName, PluginInfo);
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--- Tries to load LFS through LuaRocks, returns the LFS instance, or nil on error
|
|
local function LoadLFS()
|
|
-- Try to load lfs, do not abort if not found ...
|
|
local lfs, err = pcall(
|
|
function()
|
|
return require("lfs")
|
|
end
|
|
);
|
|
|
|
-- ... rather, print a nice message with instructions:
|
|
if not(lfs) then
|
|
print([[
|
|
Cannot load LuaFileSystem
|
|
Install it through luarocks by executing the following command:
|
|
luarocks install luafilesystem (Windows)
|
|
sudo luarocks install luafilesystem (*nix)
|
|
|
|
If you don't have luarocks installed, you need to install them using your OS's package manager, usually:
|
|
sudo apt-get install luarocks (Ubuntu / Debian)
|
|
On windows, a binary distribution can be downloaded from the LuaRocks homepage, http://luarocks.org/en/Download
|
|
]]);
|
|
|
|
print("Original error text: ", err);
|
|
return nil;
|
|
end
|
|
|
|
-- We now know that LFS is present, get it normally:
|
|
return require("lfs");
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local Arg1 = ...;
|
|
if ((Arg1 ~= nil) and (Arg1 ~= "")) then
|
|
-- Called with a plugin folder name, export only that one
|
|
ProcessPluginFolder(Arg1)
|
|
else
|
|
-- Called without any arguments, process all subfolders:
|
|
local lfs = LoadLFS();
|
|
if (lfs == nil) then
|
|
-- LFS not loaded, error has already been printed, just bail out
|
|
return;
|
|
end
|
|
print("Processing plugin subfolders:");
|
|
for fnam in lfs.dir(".") do
|
|
if ((fnam ~= ".") and (fnam ~= "..")) then
|
|
local Attributes = lfs.attributes(fnam);
|
|
if (Attributes ~= nil) then
|
|
if (Attributes.mode == "directory") then
|
|
print(fnam);
|
|
ProcessPluginFolder(fnam);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
print("Done.");
|
|
|
|
|