summaryrefslogtreecommitdiffstats
path: root/MCServer/Plugins/InfoDump.lua
blob: 1b27abef4d7a58e8ca13d02d72b5998d3a52bdd4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
#!/usr/bin/lua

-- InfoDump.lua

-- Goes through all subfolders, loads 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 requires LuaRocks with LFS installed, instructions are printed when this is not present.





-- 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

-- 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:
  sudo luarocks install luafilesystem

If you don't have luarocks installed, you need to install them using your OS's package manager, usually:
  sudo apt-get install luarocks
On windows, a binary distribution can be downloaded from the LuaRocks homepage, http://luarocks.org/en/Download
]]);
  
	print("Original error text: ", err);
	return;
end

-- We now know that LFS is present, get it normally:
lfs = require("lfs");






--- Replaces generic formatting with forum-specific formatting
-- Also removes the single line-ends
local function ForumizeString(a_Str)
	assert(type(a_Str) == "string");
	
	-- 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





--- Returns an array-table of all commands that are in the specified category
-- Each item is a table {Command = "/command string", Info = {<command info in PluginInfo>}}
local function GetCategoryCommands(a_PluginInfo, a_CategoryName)
	local res = {};
	local function AppendCategoryCommand(a_Prefix, a_Commands)
		for cmd, info in pairs(a_Commands) do
			info.Category = info.Category or {};
			if (type(info.Category) == "string") then
				info.Category = {info.Category};
			end
			for idx, cat in ipairs(info.Category) do
				if (cat == a_CategoryName) then
					table.insert(res, {Command = a_Prefix .. cmd, Info = info});
				end
			end
			if (info.Subcommands ~= nil) then
				AppendCategoryCommand(a_Prefix .. cmd .. " ", info.Subcommands);
			end
		end
	end
	AppendCategoryCommand("", a_PluginInfo.Commands);
	return res;
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





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]", a_Category.DisplayName or CategoryName, "[/size]\n");
	
	-- Write description:
	if (a_Category.Description ~= "") then
		f:write(a_Category.Description, "\n");
	end
	
	-- Write commands:
	f:write("\n[list]");
	for idx2, cmd in ipairs(a_Category.Commands) do
		f:write("\nCommand: [b]", cmd.CommandString, "[/b] - ", (cmd.Info.HelpString or "UNDOCUMENTED"), "\n");
		if (cmd.Info.Permission ~= nil) then
			f:write("Permission required: ", cmd.Info.Permission, "\n");
		end
		if (cmd.Info.DetailedDescription ~= nil) then
			f:write(cmd.Info.DetailedDescription);
		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





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(a_PluginInfo.Description);
	DumpAdditionalInfoForum(a_PluginInfo, f);
	DumpCommandsForum(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)
	-- Check if the Info file is present at all:
	local Attribs = lfs.attributes(a_FolderName .. "/Info.lua");
	if ((Attribs == nil) or (Attribs.mode ~= "file")) then
		return nil;
	end
	
	-- 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("\tCannot load Info.lua: " .. Msg);
		end
		return;
	end
	DumpPluginInfoForum(a_FolderName, PluginInfo);
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