* luci/contrib: added support for multiple modules per file in luadoc

This commit is contained in:
Jo-Philipp Wich 2008-08-09 16:01:31 +00:00
parent bdb4bbde13
commit e4f0c97551

View file

@ -73,12 +73,12 @@ end
-- Checks if the line contains a module definition. -- Checks if the line contains a module definition.
-- @param line string with line text -- @param line string with line text
-- @param currentmodule module already found, if any -- @param currentmodule module already found, if any
-- @return the name of the defined module, or nil if there is no module -- @return the name of the defined module, or nil if there is no module
-- definition -- definition
local function check_module (line, currentmodule) local function check_module (line, currentmodule)
line = util.trim(line) line = util.trim(line)
-- module"x.y" -- module"x.y"
-- module'x.y' -- module'x.y'
-- module[[x.y]] -- module[[x.y]]
@ -97,9 +97,9 @@ local function check_module (line, currentmodule)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Extracts summary information from a description. The first sentence of each -- Extracts summary information from a description. The first sentence of each
-- doc comment should be a summary sentence, containing a concise but complete -- doc comment should be a summary sentence, containing a concise but complete
-- description of the item. It is important to write crisp and informative -- description of the item. It is important to write crisp and informative
-- initial sentences that can stand on their own -- initial sentences that can stand on their own
-- @param description text with item description -- @param description text with item description
-- @return summary string or nil if description is nil -- @return summary string or nil if description is nil
@ -107,16 +107,16 @@ end
local function parse_summary (description) local function parse_summary (description)
-- summary is never nil... -- summary is never nil...
description = description or "" description = description or ""
-- append an " " at the end to make the pattern work in all cases -- append an " " at the end to make the pattern work in all cases
description = description.." " description = description.." "
-- read until the first period followed by a space or tab -- read until the first period followed by a space or tab
local summary = string.match(description, "(.-%.)[%s\t]") local summary = string.match(description, "(.-%.)[%s\t]")
-- if pattern did not find the first sentence, summary is the whole description -- if pattern did not find the first sentence, summary is the whole description
summary = summary or description summary = summary or description
return summary return summary
end end
@ -151,7 +151,7 @@ end
-- @param block block with comment field -- @param block block with comment field
-- @return block parameter -- @return block parameter
local function parse_comment (block, first_line) local function parse_comment (block, first_line, modulename)
-- get the first non-empty line of code -- get the first non-empty line of code
local code = table.foreachi(block.code, function(_, line) local code = table.foreachi(block.code, function(_, line)
@ -167,7 +167,7 @@ local function parse_comment (block, first_line)
return line return line
end end
end) end)
-- parse first line of code -- parse first line of code
if code ~= nil then if code ~= nil then
local func_info = check_function(code) local func_info = check_function(code)
@ -192,16 +192,16 @@ local function parse_comment (block, first_line)
-- parse @ tags -- parse @ tags
local currenttag = "description" local currenttag = "description"
local currenttext local currenttext
table.foreachi(block.comment, function (_, line) table.foreachi(block.comment, function (_, line)
line = util.trim_comment(line) line = util.trim_comment(line)
local r, _, tag, text = string.find(line, "@([_%w%.]+)%s+(.*)") local r, _, tag, text = string.find(line, "@([_%w%.]+)%s+(.*)")
if r ~= nil then if r ~= nil then
-- found new tag, add previous one, and start a new one -- found new tag, add previous one, and start a new one
-- TODO: what to do with invalid tags? issue an error? or log a warning? -- TODO: what to do with invalid tags? issue an error? or log a warning?
tags.handle(currenttag, block, currenttext) tags.handle(currenttag, block, currenttext)
currenttag = tag currenttag = tag
currenttext = text currenttext = text
else else
@ -214,8 +214,12 @@ local function parse_comment (block, first_line)
-- extracts summary information from the description -- extracts summary information from the description
block.summary = parse_summary(block.description) block.summary = parse_summary(block.description)
assert(string.sub(block.description, 1, 1) ~= " ", string.format("`%s'", block.description)) assert(string.sub(block.description, 1, 1) ~= " ", string.format("`%s'", block.description))
return block if block.name and block.class == "module" then
modulename = block.name
end
return block, modulename
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -239,9 +243,9 @@ local function parse_block (f, line, modulename, first)
-- reached end of comment, read the code below it -- reached end of comment, read the code below it
-- TODO: allow empty lines -- TODO: allow empty lines
line, block.code, modulename = parse_code(f, line, modulename) line, block.code, modulename = parse_code(f, line, modulename)
-- parse information in block comment -- parse information in block comment
block = parse_comment(block, first) block, modulename = parse_comment(block, first, modulename)
return line, block, modulename return line, block, modulename
else else
@ -250,10 +254,10 @@ local function parse_block (f, line, modulename, first)
end end
end end
-- reached end of file -- reached end of file
-- parse information in block comment -- parse information in block comment
block = parse_comment(block, first) block, modulename = parse_comment(block, first, modulename)
return line, block, modulename return line, block, modulename
end end
@ -263,58 +267,78 @@ end
-- @param doc table with documentation -- @param doc table with documentation
-- @return table with documentation -- @return table with documentation
function parse_file (filepath, doc) function parse_file (filepath, doc, handle, prev_line, prev_block, prev_modname)
local blocks = {} local blocks = { prev_block }
local modulename = nil local modulename = prev_modname
-- read each line -- read each line
local f = io.open(filepath, "r") local f = handle or io.open(filepath, "r")
local i = 1 local i = 1
local line = f:read() local line = prev_line or f:read()
local first = true local first = true
while line ~= nil do while line ~= nil do
if string.find(line, "^[\t ]*%-%-%-") then if string.find(line, "^[\t ]*%-%-%-") then
-- reached a luadoc block -- reached a luadoc block
local block local block, newmodname
line, block, modulename = parse_block(f, line, modulename, first) line, block, newmodname = parse_block(f, line, modulename, first)
table.insert(blocks, block)
if modulename and newmodname and newmodname ~= modulename then
doc = parse_file( nil, doc, f, line, block, newmodname )
else
table.insert(blocks, block)
modulename = newmodname
end
else else
-- look for a module definition -- look for a module definition
modulename = check_module(line, modulename) local newmodname = check_module(line, modulename)
if modulename and newmodname and newmodname ~= modulename then
parse_file( nil, doc, f )
else
modulename = newmodname
end
-- TODO: keep beginning of file somewhere -- TODO: keep beginning of file somewhere
line = f:read() line = f:read()
end end
first = false first = false
i = i + 1 i = i + 1
end end
f:close()
-- store blocks in file hierarchy if not handle then
assert(doc.files[filepath] == nil, string.format("doc for file `%s' already defined", filepath)) f:close()
table.insert(doc.files, filepath) end
doc.files[filepath] = {
type = "file", if filepath then
name = filepath, -- store blocks in file hierarchy
doc = blocks, assert(doc.files[filepath] == nil, string.format("doc for file `%s' already defined", filepath))
-- functions = class_iterator(blocks, "function"), table.insert(doc.files, filepath)
-- tables = class_iterator(blocks, "table"), doc.files[filepath] = {
} type = "file",
-- name = filepath,
local first = doc.files[filepath].doc[1] doc = blocks,
if first and modulename then -- functions = class_iterator(blocks, "function"),
doc.files[filepath].author = first.author -- tables = class_iterator(blocks, "table"),
doc.files[filepath].copyright = first.copyright }
doc.files[filepath].description = first.description --
doc.files[filepath].release = first.release local first = doc.files[filepath].doc[1]
doc.files[filepath].summary = first.summary if first and modulename then
doc.files[filepath].author = first.author
doc.files[filepath].copyright = first.copyright
doc.files[filepath].description = first.description
doc.files[filepath].release = first.release
doc.files[filepath].summary = first.summary
end
end end
-- if module definition is found, store in module hierarchy -- if module definition is found, store in module hierarchy
if modulename ~= nil then if modulename ~= nil then
if modulename == "..." then if modulename == "..." then
modulename = string.gsub (filepath, "%.lua$", "") assert( filepath, "Can't determine name for virtual module from filepatch" )
modulename = string.gsub (modulename, "/", ".") modulename = string.gsub (filepath, "%.lua$", "")
modulename = string.gsub (modulename, "/", ".")
end end
if doc.modules[modulename] ~= nil then if doc.modules[modulename] ~= nil then
-- module is already defined, just add the blocks -- module is already defined, just add the blocks
@ -336,14 +360,14 @@ function parse_file (filepath, doc)
release = first and first.release, release = first and first.release,
summary = "", summary = "",
} }
-- find module description -- find module description
for m in class_iterator(blocks, "module")() do for m in class_iterator(blocks, "module")() do
doc.modules[modulename].description = util.concat( doc.modules[modulename].description = util.concat(
doc.modules[modulename].description, doc.modules[modulename].description,
m.description) m.description)
doc.modules[modulename].summary = util.concat( doc.modules[modulename].summary = util.concat(
doc.modules[modulename].summary, doc.modules[modulename].summary,
m.summary) m.summary)
if m.author then if m.author then
doc.modules[modulename].author = m.author doc.modules[modulename].author = m.author
@ -361,7 +385,7 @@ function parse_file (filepath, doc)
doc.modules[modulename].description = doc.modules[modulename].description or (first and first.description) or "" doc.modules[modulename].description = doc.modules[modulename].description or (first and first.description) or ""
doc.modules[modulename].summary = doc.modules[modulename].summary or (first and first.summary) or "" doc.modules[modulename].summary = doc.modules[modulename].summary or (first and first.summary) or ""
end end
-- make functions table -- make functions table
doc.modules[modulename].functions = {} doc.modules[modulename].functions = {}
for f in class_iterator(blocks, "function")() do for f in class_iterator(blocks, "function")() do
@ -370,7 +394,7 @@ function parse_file (filepath, doc)
doc.modules[modulename].functions[f.name] = f doc.modules[modulename].functions[f.name] = f
end end
end end
-- make tables table -- make tables table
doc.modules[modulename].tables = {} doc.modules[modulename].tables = {}
for t in class_iterator(blocks, "table")() do for t in class_iterator(blocks, "table")() do
@ -380,30 +404,32 @@ function parse_file (filepath, doc)
end end
end end
end end
-- make functions table if filepath then
doc.files[filepath].functions = {} -- make functions table
for f in class_iterator(blocks, "function")() do doc.files[filepath].functions = {}
if f and f.name then for f in class_iterator(blocks, "function")() do
table.insert(doc.files[filepath].functions, f.name) if f and f.name then
doc.files[filepath].functions[f.name] = f table.insert(doc.files[filepath].functions, f.name)
doc.files[filepath].functions[f.name] = f
end
end
-- make tables table
doc.files[filepath].tables = {}
for t in class_iterator(blocks, "table")() do
if t and t.name then
table.insert(doc.files[filepath].tables, t.name)
doc.files[filepath].tables[t.name] = t
end
end end
end end
-- make tables table
doc.files[filepath].tables = {}
for t in class_iterator(blocks, "table")() do
if t and t.name then
table.insert(doc.files[filepath].tables, t.name)
doc.files[filepath].tables[t.name] = t
end
end
return doc return doc
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Checks if the file is terminated by ".lua" or ".luadoc" and calls the -- Checks if the file is terminated by ".lua" or ".luadoc" and calls the
-- function that does the actual parsing -- function that does the actual parsing
-- @param filepath full path of the file to parse -- @param filepath full path of the file to parse
-- @param doc table with documentation -- @param doc table with documentation
@ -417,12 +443,12 @@ function file (filepath, doc)
return true return true
end end
end) end)
if valid then if valid then
logger:info(string.format("processing file `%s'", filepath)) logger:info(string.format("processing file `%s'", filepath))
doc = parse_file(filepath, doc) doc = parse_file(filepath, doc)
end end
return doc return doc
end end
@ -437,7 +463,7 @@ function directory (path, doc)
local fullpath = path .. "/" .. f local fullpath = path .. "/" .. f
local attr = posix.stat(fullpath) local attr = posix.stat(fullpath)
assert(attr, string.format("error stating file `%s'", fullpath)) assert(attr, string.format("error stating file `%s'", fullpath))
if attr.type == "regular" then if attr.type == "regular" then
doc = file(fullpath, doc) doc = file(fullpath, doc)
elseif attr.type == "directory" and f ~= "." and f ~= ".." then elseif attr.type == "directory" and f ~= "." and f ~= ".." then
@ -465,7 +491,7 @@ end
function start (files, doc) function start (files, doc)
assert(files, "file list not specified") assert(files, "file list not specified")
-- Create an empty document, or use the given one -- Create an empty document, or use the given one
doc = doc or { doc = doc or {
files = {}, files = {},
@ -473,18 +499,18 @@ function start (files, doc)
} }
assert(doc.files, "undefined `files' field") assert(doc.files, "undefined `files' field")
assert(doc.modules, "undefined `modules' field") assert(doc.modules, "undefined `modules' field")
table.foreachi(files, function (_, path) table.foreachi(files, function (_, path)
local attr = posix.stat(path) local attr = posix.stat(path)
assert(attr, string.format("error stating path `%s'", path)) assert(attr, string.format("error stating path `%s'", path))
if attr.type == "regular" then if attr.type == "regular" then
doc = file(path, doc) doc = file(path, doc)
elseif attr.type == "directory" then elseif attr.type == "directory" then
doc = directory(path, doc) doc = directory(path, doc)
end end
end) end)
-- order arrays alphabetically -- order arrays alphabetically
recsort(doc.files) recsort(doc.files)
recsort(doc.modules) recsort(doc.modules)