3 -- Reads comments from lua files and uses them to generate documentation. Why? Because having
4 -- the documentation in the same location as stuff that's being documented makes life easier.
6 -- You can probably read this file for examples on how to document stuff.
9 loadfile("fileutil.lua")()
13 "../Generic/table.lua",
14 "../Generic/cron.lua",
15 "../Generic/sortedlist.lua",
16 "../Generic/graph.lua"
19 local empty_table = {}
23 -- object = getObject(name, list)
24 -- .name (string) The name of the documented item.
25 -- .list (table) The table to search for the item in.
26 -- .object (docobj) The object pointed to by name.
27 local function getObject(name, list, parent, rel)
38 local _, _, real_name, new_rel, remainder = string.find(name, "(.-)%s*([%.:])%s*(.+)")
44 local item = list[name]
51 item.fname = (parent and parent.fname and parent.fname .. "." .. name) or name
52 item.type = "<unknown>"
53 item.file = "<unknown>"
55 item.anchor = "anchor_"..next_anchor
56 next_anchor = next_anchor + 1
65 print("Remainder: "..remainder)
66 print("Misplaced ':' character?")
69 return getObject(remainder, item.children, item, new_rel)
75 -- valid = isVariableString(var)
76 -- .var (string) The name of a variable.
77 -- .valid (boolean) True if var is a valid variable name.
78 -- Checks if a string would make a valid variable name.
79 local function isVariableString(var)
80 return string.len(var) > 0 and string.find(var, "^[%a_][%a%d_]*$") or var == "..."
83 -- array = readList(list)
84 -- .list (string) A comma seperated list of items.
85 -- .array (array) The list, broken up into tokens.
86 -- Breaks string containing a comma seperated list of items into tokens.
87 -- Returns nil if the list couldn't be parsed.
88 local function readVariableList(list)
91 for arg in string.gmatch(list, "%s*([^,%s]+)%s*,?") do
92 if isVariableString(arg) then
93 table.insert(result, arg == "..." and "!" or arg)
102 -- name, arguments, returns = readFunctionLine(line)
103 -- .line (string) The line to read.
104 -- .name(string) The name of the function.
105 -- .arguments (array) An array of arguments to the function.
106 -- .returns (array) An array of return values from the function.
107 -- Returns nothing if it couldn't parse the line.
108 local function readFunctionLine(line)
109 local function_chunk, argument_chunk = select(3, string.find(line, "^(.-)%((.*)%)%s*$"))
110 if not function_chunk then return end
112 local return_chunk, function_name_chunk = select(3, string.find(function_chunk, "^(.*)=%s*([^%s]+)%s*$"))
114 if not function_name_chunk then
115 return_chunk, function_name_chunk = "", select(3, string.find(function_chunk, "^%s*([^%s]+)%s*$"))
118 if function_name_chunk then
119 local arguments, returns = readVariableList(argument_chunk), readVariableList(return_chunk)
121 if arguments and returns then
122 return function_name_chunk, arguments, returns
127 -- processComment(file, line, comment)
128 -- .file (string) The name of the file the comment came from.
129 -- .line (number) The line number of the file the comment came from.
130 -- .comment (table) An array of strings, the lines making up the comment.
131 -- Parses comments to extract information from them.
132 local function processComment(file, line, comment)
133 local obj, rel = nil, nil
136 local func, arg, ret = readFunctionLine(comment[1])
139 obj, rel = getObject(func)
140 obj.type = "function"
144 local var, typename, desc = select(3, string.find(line, "^%s*%.(.-)%s*%((%a+)%)%s*(.-)%s*$"))
146 obj, rel = getObject(func)
155 if obj.arg and rel == ":" then
156 table.insert(obj.arg, 1, "self")
159 print((next(ret) and table.concat(ret, ", ") or "<nil>").." = "..func.."("..table.concat(arg, ", ")..")")
161 for i = 2,#comment do
162 local line = comment[i]
163 local item, typename, desc = select(3, string.find(line, "^%s*%.(.-)%s*%((%a+)%)%s*(.-)%s*$"))
166 local cobj = getObject(obj.fname.."."..(item == "..." and "!" or item))
170 table.insert(cobj.notes, desc)
172 table.insert(obj.notes, line)
177 -- TODO: Parse comments.
179 --for i, line in ipairs(comment) do
185 -- .tbl (table) The table to clear.
186 -- Goes through a table and deletes all its keys.
187 local function clearTable(tbl)
188 for key in pairs(tbl) do
194 -- .file (string) The name of the file to read.
195 -- Reads the comments from a file and passes the comments to [processComment]
196 local function readLuaFile(file)
197 local stream = io.open(file, "r")
200 print("Unable to open file: "..file)
204 local comment = {}, comment_line, comment_type
206 local line_number = 0
211 if line_remainder and line_remainder ~= "" then
212 line = line_remainder
215 line_number = line_number + 1
217 if not line then break end
220 if next(comment) and comment_type == 2 then
222 comment_text, line_remainder = select(3, string.find(line, "^%s*(.-)%s*%]%](.*)"))
226 table.insert(comment, comment_text)
227 processComment(file, comment_line, comment)
230 table.insert(comment, line)
233 local comment_text = select(3, string.find(line, "^%s*%-%-%[%[%s*(.-)%s*$"))
236 if next(comment) then
237 processComment(file, comment_line, comment)
242 short_text, line_remainder = select(3, string.find(comment_text, "^(.-)%s*%]%](.*)"))
245 table.insert(comment, short_text)
246 processComment(file, line, comment)
249 table.insert(comment, comment_text)
251 comment_line = line_number
254 comment_text = select(3, string.find(line, "^%s*%-%-%s*(.-)%s*$"))
256 if next(comment) then
257 table.insert(comment, comment_text)
259 table.insert(comment, comment_text)
261 comment_line = line_number
263 elseif next(comment) then
264 processComment(file, comment_line, comment)
271 if next(comment) then
272 processComment(file, comment_line, comment)
278 for i, file in ipairs(#arg > 0 and arg or default_files) do
282 local function HTMLText(text)
283 return string.gsub(text, ".", function (c)
284 if c == "<" then return "<" end
285 if c == ">" then return ">" end
286 if c == " " then return " " end
287 if c == "&" then return "&" end
288 if c == "\"" then return """ end
289 if c == "\n" then return "<br/>" end
294 local function ParseParagraph(text)
295 -- TODO: Do this right.
296 return "<p>"..HTMLText(text).."</p>"
299 local function DescriptionText(lines)
303 for i, line in ipairs(lines) do
304 -- TODO: Actually parse the lines.
305 line = select(3, string.find(line, "^%s*(.-)%s*$"))
308 result = result .. ParseParagraph(paragraph)
311 paragraph = paragraph .. " " .. line
315 if paragraph ~= "" then
316 result = result .. ParseParagraph(paragraph)
324 local function WriteDocObjectList(list, prefix, buffer)
326 for key in pairs(list) do
327 table.insert(array, key)
332 for i, key in ipairs(array) do
333 buffer:add("<div class=\"item\">")
334 WriteDocObject(list[key], prefix, buffer)
339 local function WriteDocObjectDescription(obj, prefix, buffer)
341 buffer:add("<p>No information available.</p>")
342 elseif obj.type == "function" then
344 for i, name in ipairs(obj.ret) do
345 buffer:add("<span class=\"argument\">")
346 buffer:add(name == "!" and "..." or name)
347 buffer:add("<div class=\"description\">")
348 WriteDocObjectDescription(obj.children[name], nil, buffer)
350 buffer:add("</span>")
351 if i ~= #obj.ret then buffer:add(", ") end
357 if obj.arg[1] == "self" then
359 buffer:add((prefix or "???")..":")
361 buffer:add((prefix and (prefix .. ".")) or "")
364 buffer:add("<span class=\"argument\">")
365 buffer:add(obj.name == "!" and "..." or obj.name)
366 buffer:add("</span>(")
368 for arg = first_arg, #obj.arg do
369 local name = obj.arg[arg]
370 buffer:add("<span class=\"argument\">")
371 buffer:add(name == "!" and "..." or name)
372 buffer:add("<div class=\"description\">")
373 WriteDocObjectDescription(obj.children[name], nil, buffer)
375 buffer:add("</span>")
376 if arg ~= #obj.arg then buffer:add(", ") end
379 buffer:add(") <span class=\"typename\">function</span>")
380 buffer:add(DescriptionText(obj.notes))
382 buffer:add("<span class=\"argument\">"..HTMLText(obj.name or "unknown").."</span> <span class=\"typename\">"..HTMLText(obj.type or "unknown").."</span>")
383 WriteDocObjectList(obj.children, prefix and (prefix.."."..obj.name) or obj.name, buffer)
384 buffer:add(DescriptionText(obj.notes))
388 WriteDocObject = function(obj, prefix, buffer)
390 buffer:add("No information available.")
392 buffer:add("<a name=\""..obj.anchor.."\">")
393 WriteDocObjectDescription(obj, prefix, buffer)
398 local function WritePage(filename, title, prefix, list)
399 local buffer = CreateBuffer()
400 buffer:add(string.format(
402 <?xml version="1.0" encoding="UTF-8"?>
403 <?xml-stylesheet href="style.css" type="text/css"?>
404 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
405 <html xmlns="http://www.w3.org/1999/xhtml"><head>
407 </head><body><h1>%s</h1>
410 WriteDocObjectList(list, prefix, buffer)
411 buffer:add("</body></html>")
413 local stream = io.open(filename, "w")
415 stream:write(buffer:dump())
418 print("Unable to write file: "..filename)
422 FileUtil.createDirectory("API")
423 FileUtil.copyFile("Data/style.css", "API/style.css")
424 WritePage("API/api.xhtml", "QuestHelper API Documentation", nil, items)