From 4bc48dafc3cd46af22de7a354fd979e5bfdf2408 Mon Sep 17 00:00:00 2001 From: steve donovan Date: Mon, 18 Mar 2013 15:51:28 +0200 Subject: [PATCH] modules returning a single function are supported, see tests/styles/func.lua. Parameters may have subfields, see tests/styles/subparams.lua --- ldoc/doc.lua | 204 +++++++++++++++++++++++-------------- ldoc/html.lua | 5 + ldoc/html/ldoc_ltp.lua | 22 ++-- ldoc/parse.lua | 16 +++ tests/styles/four.lua | 8 +- tests/styles/func.lua | 15 +++ tests/styles/subparams.lua | 17 ++++ tests/styles/two.lua | 2 +- 8 files changed, 204 insertions(+), 85 deletions(-) create mode 100644 tests/styles/func.lua create mode 100644 tests/styles/subparams.lua diff --git a/ldoc/doc.lua b/ldoc/doc.lua index eba70b9..9e4f430 100644 --- a/ldoc/doc.lua +++ b/ldoc/doc.lua @@ -493,6 +493,8 @@ local function read_del (tags,name) return ret end +local build_arg_list, split_iden -- forward declaration + function Item:finish() local tags = self.tags @@ -538,74 +540,58 @@ function Item:finish() self.modifiers.field = self.modifiers.param end end - local names, comments = List(), List() + local param_names, comments = List(), List() if params then for line in params:iter() do - local name, comment = line :match('%s*([%w_%.:]+)(.*)') + local name, comment = line:match('%s*([%w_%.:]+)(.*)') if not name then self:error("bad param name format '"..line.."'. Are you missing a parameter name?") end - names:append(name) + param_names:append(name) comments:append(comment) end end self.modifiers['return'] = self.modifiers['return'] or List() self.modifiers[field] = self.modifiers[field] or List() - -- not all arguments may be commented: we use the formal arguments - -- if available as the authoritative list, and warn if there's an inconsistency. - if self.formal_args then - local fargs = self.formal_args - if not self.ret and fargs.return_comment then - local retc = fargs.return_comment - local type,rest = retc:match '([^:]+):(.*)' - if type then - self.modifiers['return']:append{type=type} - retc = rest + -- we use the formal arguments (if available) as the authoritative list. + -- If there are both params and formal args, then they must match; + -- (A formal argument of ... may match any number of params at the end, however.) + -- If there are formal args and no params, we see if the args have any suitable comments. + -- Params may have subfields. + local fargs, formal = self.formal_args + if fargs then + if #param_names == 0 then + --docs may be embedded in argument comments; in either case, use formal arg names + formal = List() + if fargs.return_comment then + local retc = self:parse_argument_comment(fargs.return_comment,'return') + self.ret = List{retc} end - self.ret = List{retc} - end - if #fargs ~= 0 then - local pnames, pcomments = names, comments - names, comments = List(),List() + for i, name in ipairs(fargs) do + formal:append(name) + comments:append(self:parse_argument_comment(fargs.comments[name],self.parameter)) + end + elseif #fargs > 0 then local varargs = fargs[#fargs] == '...' - for i,name in ipairs(fargs) do - if params then -- explicit set of param tags - if pnames[i] ~= name and not varargs then - if pnames[i] then - self:warning("param and formal argument name mismatch: "..quote(name).." "..quote(pnames[i])) - else - self:warning("undocumented formal argument: "..quote(name)) + if varargs then table.remove(fargs) end + local k = 0 + for _,pname in ipairs(param_names) do + local _,field = split_iden(pname) + if not field then + k = k + 1 + if k > #fargs then + if not varargs then + self:warning("extra param with no formal argument: "..quote(pname)) end - elseif varargs then - name = pnames[i] + elseif pname ~= fargs[k] then + self:warning("param and formal argument name mismatch: "..quote(pname).." "..quote(fargs[k])) end end - names:append(name) - local comment = pcomments[i] - if not comment then - -- ldoc allows comments in the formal arg list to be used, if they aren't specified with @param - -- Further, these comments may start with a type followed by a colon, and are then equivalent - -- to a @tparam - comment = fargs.comments[name] - if comment then - comment = comment:gsub('^%-+%s*','') - local type,rest = comment:match '([^:]+):(.*)' - if type then - self.modifiers[field]:append {type = type} - comment = rest - end - end - end - comments:append (comment or '') end - -- A formal argument of ... may match any number of params, however. - if #pnames > #fargs then - for i = #fargs+1,#pnames do - if not varargs then - self:warning("extra param with no formal argument: "..quote(pnames[i])) - else - names:append(pnames[i]) - comments:append(pcomments[i] or '') + if k < #fargs then + for i = k+1,#fargs do + if fargs[i] ~= '...' then + self:warning("undocumented formal argument: "..quote(fargs[i])) end end end @@ -615,38 +601,84 @@ function Item:finish() -- the comments are associated with each parameter by -- adding name-value pairs to the params list (this is -- also done for any associated modifiers) - self.params = names + -- (At this point we patch up any subparameter references) local pmods = self.modifiers[field] - for i,name in ipairs(self.params) do - self.params[name] = comments[i] + local params, fields = List() + local original_names = formal and formal or param_names + local names = List() + self.subparams = {} + for i,name in ipairs(original_names) do + local pname,field = split_iden(name) + if field then + if not fields then + fields = List() + self.subparams[pname] = fields + end + fields:append(name) + else + names:append(name) + params:append(name) + fields = nil + end + + params[name] = comments[i] if pmods then pmods[name] = pmods[i] end end - - -- build up the string representation of the argument list, - -- using any opt and optchain modifiers if present. - -- For instance, '(a [, b])' if b is marked as optional - -- with @param[opt] b - local buffer, npending = { }, 0 - local function acc(x) table.insert(buffer, x) end - for i = 1, #names do - local m = pmods and pmods[i] - if m then - if not m.optchain then - acc ((']'):rep(npending)) - npending=0 - end - if m.opt or m.optchain then acc(' ['); npending=npending+1 end - end - if i>1 then acc (', ') end - acc(names[i]) - end - acc ((']'):rep(npending)) - self.args = '('..table.concat(buffer)..')' + self.params = params + self.args = build_arg_list (names,pmods) end end +-- ldoc allows comments in the formal arg list to be used, if they aren't specified with @param +-- Further, these comments may start with a type followed by a colon, and are then equivalent +-- to a @tparam +function Item:parse_argument_comment (comment,field) + if comment then + comment = comment:gsub('^%-+%s*','') + local type,rest = comment:match '([^:]+):(.*)' + if type then + self.modifiers[field]:append {type = type} + comment = rest + end + end + return comment or '' +end + +function split_iden (name) + if name == '...' then return name end + local pname,field = name:match('(.-)%.(.+)') + if not pname then + return name + else + return pname,field + end +end + +function build_arg_list (names,pmods) + -- build up the string representation of the argument list, + -- using any opt and optchain modifiers if present. + -- For instance, '(a [, b])' if b is marked as optional + -- with @param[opt] b + local buffer, npending = { }, 0 + local function acc(x) table.insert(buffer, x) end + for i = 1, #names do + local m = pmods and pmods[i] + if m then + if not m.optchain then + acc ((']'):rep(npending)) + npending=0 + end + if m.opt or m.optchain then acc(' ['); npending=npending+1 end + end + if i>1 then acc (', ') end + acc(names[i]) + end + acc ((']'):rep(npending)) + return '('..table.concat(buffer)..')' +end + function Item:type_of_param(p) local mods = self.modifiers[self.parameter] if not mods then return '' end @@ -659,6 +691,23 @@ function Item:type_of_ret(idx) return rparam and rparam.type or '' end +function Item:subparam(p) + if self.subparams[p] then + return self.subparams[p],p + else + return {p},nil + end +end + +function Item:display_name_of(p) + local pname,field = split_iden(p) + if field then + return field + else + return pname + end +end + function Item:warning(msg) local file = self.file and self.file.filename @@ -675,6 +724,9 @@ end Module.warning, Module.error = Item.warning, Item.error + +-------- Resolving References ----------------- + function Module:hunt_for_reference (packmod, modules) local mod_ref local package = self.package or '' diff --git a/ldoc/html.lua b/ldoc/html.lua index 3558aba..1cd3ddd 100644 --- a/ldoc/html.lua +++ b/ldoc/html.lua @@ -128,6 +128,10 @@ function html.generate_output(ldoc, args, project) end)) end + function ldoc.is_list (t) + return type(t) == 'table' and t.append + end + function ldoc.typename (tp) if not tp or tp == '' then return '' end local optional @@ -170,6 +174,7 @@ function html.generate_output(ldoc, args, project) ldoc.output = args.output ldoc.ipairs = ipairs ldoc.pairs = pairs + ldoc.print = print -- in single mode there is one module and the 'index' is the -- documentation for that module. diff --git a/ldoc/html/ldoc_ltp.lua b/ldoc/html/ldoc_ltp.lua index 0fa82e1..60f2d72 100644 --- a/ldoc/html/ldoc_ltp.lua +++ b/ldoc/html/ldoc_ltp.lua @@ -157,13 +157,23 @@ return [==[ # if show_parms and item.params and #item.params > 0 then

$(module.kinds:type_of(item).subnames):

# end -- if params diff --git a/ldoc/parse.lua b/ldoc/parse.lua index c232ee9..176dc41 100644 --- a/ldoc/parse.lua +++ b/ldoc/parse.lua @@ -253,8 +253,24 @@ local function parse_file(fname, lang, package, args) end if item_follows or comment:find '@' or comment:find ': ' then tags = extract_tags(comment,args) + -- explicitly named @module (which is recommended) if doc.project_level(tags.class) then module_found = tags.name + -- might be a module returning a single function! + if tags.param or tags['return'] then + local parms, ret, summ = tags.param, tags['return'],tags.summary + tags.param = nil + tags['return'] = nil + tags.summary = nil + add_module(tags,tags.name,false) + tags = { + summary = summ, + name = 'returns...', + class = 'function', + ['return'] = ret, + param = parms + } + end end doc.expand_annotation_item(tags,current_item) -- if the item has an explicit name or defined meaning diff --git a/tests/styles/four.lua b/tests/styles/four.lua index 123007d..f2bcc31 100644 --- a/tests/styles/four.lua +++ b/tests/styles/four.lua @@ -8,11 +8,15 @@ -- @copyright InfoReich 2013 --- a function with typed args. --- Note the the standard tparam aliases +-- Note the the standard tparam aliases, and how the 'opt' and 'optchain' +-- modifiers may also be used. If the Lua function has varargs, then +-- you may document an indefinite number of extra arguments! -- @string name person's name -- @int age +-- @string[opt] calender optional calendar +-- @int[optchain] offset optional offset -- @treturn string -function one (name,age) +function one (name,age,...) end diff --git a/tests/styles/func.lua b/tests/styles/func.lua new file mode 100644 index 0000000..d383d6e --- /dev/null +++ b/tests/styles/func.lua @@ -0,0 +1,15 @@ +------------ +-- Get length of string. +-- A (silly) module which returns a single function +-- +-- @module func +-- @string some text +-- @param opts multibyte encoding options +-- @string opts.charset encoding used +-- @bool opts.strict be very pedantic +-- @bool verbose tell the world about everything +-- @return its length +return function(s,opts,verbose) + return #s +end + diff --git a/tests/styles/subparams.lua b/tests/styles/subparams.lua new file mode 100644 index 0000000..e6cccfe --- /dev/null +++ b/tests/styles/subparams.lua @@ -0,0 +1,17 @@ +------------ +-- Parameters may have named subfields, if they are tables. +-- +-- @module subparams +module(...) + +------- +-- A function with subfield arguments. +-- @param s string +-- @param opts multibyte encoding options +-- @param opts.charset string +-- @param opts.strict bool +-- @param verbose bool +-- @return its length +function with_options (s,opts,verbose) +end + diff --git a/tests/styles/two.lua b/tests/styles/two.lua index 19b4b47..3976ed3 100644 --- a/tests/styles/two.lua +++ b/tests/styles/two.lua @@ -1,5 +1,5 @@ ------------ --- Classic Lua 5.1 module, +-- Classic Lua 5.1 module. -- Description here ----