| -------------------------------------------------------------------------------- |
| -- Copyright (c) 2012 Sierra Wireless. |
| -- All rights reserved. This program and the accompanying materials |
| -- are made available under the terms of the Eclipse Public License v1.0 |
| -- which accompanies this distribution, and is available at |
| -- http://www.eclipse.org/legal/epl-v10.html |
| -- |
| -- Contributors: |
| -- Kevin KIN-FOO <kkinfoo@sierrawireless.com> |
| -- - initial API and implementation and initial documentation |
| -------------------------------------------------------------------------------- |
| local apimodel = require 'models.apimodel' |
| |
| --- |
| -- @module docutils |
| -- Handles link generation, node quick description. |
| -- |
| -- Provides: |
| -- * link generation |
| -- * anchor generation |
| -- * node quick description |
| local M = {} |
| |
| function M.isempty(map) |
| local f = pairs(map) |
| return f(map) == nil |
| end |
| |
| --- |
| -- Provide a handling function for all supported anchor types |
| -- recordtypedef => #(typename) |
| -- item (field of recordtypedef) => #(typename).itemname |
| -- item (global) => itemname |
| --functiontypedef callof given type => ##(giventypename)__call |
| |
| M.anchortypes = { |
| recordtypedef = function (o) return string.format('#(%s)', o.name) end, |
| functiontypedef = function (o,t) return string.format('#(%s)__call', t.name) end, |
| item = function(o) |
| if not o.parent or o.parent.tag == 'file' then |
| -- Handle items referencing globals |
| return o.name |
| elseif o.parent and o.parent.tag == 'recordtypedef' then |
| -- Handle items included in recordtypedef |
| local recordtypedef = o.parent |
| local recordtypedefanchor = M.anchor(recordtypedef) |
| if not recordtypedefanchor then |
| return nil, 'Unable to generate anchor for `recordtypedef parent.' |
| end |
| return string.format('%s.%s', recordtypedefanchor, o.name) |
| end |
| return nil, 'Unable to generate anchor for `item' |
| end |
| } |
| |
| --- |
| -- Provides anchor string for an object of API mode |
| -- |
| -- @function [parent = #docutils] anchor |
| -- @param modelobject Object form API model |
| -- @result #string Anchor for an API model object, this function __may rise an error__ |
| -- @usage # -- In a template |
| -- # local anchorname = anchor(someobject) |
| -- <a id="$(anchorname)" /> |
| function M.anchor( modelobject,... ) |
| local tag = modelobject.tag |
| if M.anchortypes[ tag ] then |
| return M.anchortypes[ tag ](modelobject,...) |
| end |
| return nil, string.format('No anchor available for `%s', tag) |
| end |
| |
| local function getexternalmodule( item ) |
| -- Get file which contains this item |
| local file |
| if item.parent then |
| if item.parent.tag =='recordtypedef' then |
| local recordtypedefparent = item.parent.parent |
| if recordtypedefparent and recordtypedefparent.tag =='file'then |
| file = recordtypedefparent |
| end |
| elseif item.parent.tag =='file' then |
| file = item.parent |
| else |
| return nil, 'Unable to fetch item parent' |
| end |
| end |
| return file |
| end |
| |
| --- |
| -- Provide a handling function for all supported link types |
| -- |
| -- internaltyperef => ##(typename) |
| -- => #anchor(recordtyperef) |
| -- externaltyperef => modulename.html##(typename) |
| -- => linkto(file)#anchor(recordtyperef) |
| -- file(module) => modulename.html |
| -- index => index.html |
| -- functiontypedef callof given type => ##(giventypename)__call |
| -- recordtypedef => ##(typename) |
| -- => #anchor(recordtyperef) |
| -- item (internal field of recordtypedef) => ##(typename).itemname |
| -- => #anchor(item) |
| -- item (internal global) => #itemname |
| -- => #anchor(item) |
| -- item (external field of recordtypedef) => modulename.html##(typename).itemname |
| -- => linkto(file)#anchor(item) |
| -- item (externalglobal) => modulename.html#itemname |
| -- => linkto(file)#anchor(item) |
| M.linktypes = { |
| internaltyperef = function(o) return string.format('##(%s)', o.typename) end, |
| externaltyperef = function(o) return string.format('%s.html##(%s)', o.modulename, o.typename) end, |
| file = function(o) return string.format('%s.html', o.name) end, |
| index = function() return 'index.html' end, |
| recordtypedef = function(o) |
| local anchor = M.anchor(o) |
| if not anchor then |
| return nil, 'Unable to generate anchor for `recordtypedef.' |
| end |
| return string.format('#%s', anchor) |
| end, |
| functiontypedef = function(o,...) |
| local anchor = M.anchor(o,...) |
| if not anchor then |
| return nil, 'Unable to generate anchor for `functiontypedef.' |
| end |
| return string.format('#%s', anchor) |
| end, |
| item = function(o) |
| |
| -- For every item get anchor |
| local anchor = M.anchor(o) |
| if not anchor then |
| return nil, 'Unable to generate anchor for `item.' |
| end |
| |
| -- Built local link to item |
| local linktoitem = string.format('#%s', anchor) |
| |
| -- |
| -- For external item, prefix with the link to the module. |
| -- |
| -- The "external item" concept is used only here for short/embedded |
| -- notation purposed. This concept and the `.external` field SHALL NOT |
| -- be used elsewhere. |
| -- |
| if o.external then |
| |
| -- Get link to file which contains this item |
| local file = getexternalmodule( o ) |
| local linktofile = file and M.linkto( file ) |
| if not linktofile then |
| return nil, 'Unable to generate link for external `item.' |
| end |
| |
| -- Built external link to item |
| linktoitem = string.format("%s%s", linktofile, linktoitem) |
| end |
| |
| return linktoitem |
| end |
| } |
| |
| --- |
| -- Generates text for HTML links from API model element |
| -- |
| -- @function [parent = #docutils] |
| -- @param modelobject Object form API model |
| -- @result #string Links text for an API model element, this function __may rise an error__. |
| -- @usage # -- In a template |
| -- <a href="$( linkto(api) )">Some text</a> |
| function M.linkto( apiobject,...) |
| local tag = apiobject.tag |
| if M.linktypes[ tag ] then |
| return M.linktypes[tag](apiobject,...) |
| end |
| if not tag then |
| return nil, 'Link generation is impossible as no tag has been provided.' |
| end |
| return nil, string.format('No link generation available for `%s.', tag) |
| end |
| |
| --- |
| -- Provide a handling function for all supported pretty name types |
| -- primitivetyperef => #typename |
| -- internaltyperef => #typename |
| -- inlinetyperef => #def.typename |
| -- externaltyperef => modulename#typename |
| -- file(module) => modulename |
| -- index => index |
| -- functiontypedef callof given type => giventypename(param1,param2, ...) |
| -- recordtypedef => typename |
| -- item (internal function of recordtypedef) => typename.itemname(param1, param2,...) |
| -- item (internal func with self of recordtypedef) => typename:itemname(param2) |
| -- item (internal non func field of recordtypedef) => typename.itemname |
| -- item (internal func global) => functionname(param1, param2,...) |
| -- item (internal non func global) => itemname |
| -- item (external function of recordtypedef) => modulename#typename.itemname(param1, param2,...) |
| -- item (external func with self of recordtypedef) => modulename#typename:itemname(param2) |
| -- item (external non func field of recordtypedef) => modulename#typename.itemname |
| -- item (external func global) => functionname(param1, param2,...) |
| -- item (external non func global) => itemname |
| M.prettynametypes = { |
| primitivetyperef = function(o) return string.format('#%s', o.typename) end, |
| externaltyperef = function(o) return string.format('%s#%s', o.modulename, o.typename) end, |
| inlinetyperef = function(o) |
| if not(o.def and o.def.tag == "recordtypedef" and o.def.name) then |
| return nil |
| end |
| if o.def.name == "list" then |
| local valuetypename = M.prettyname(o.def.defaultvaluetyperef) |
| return valuetypename and string.format('#list<%s>', valuetypename) or nil |
| elseif o.def.name == "map" then |
| local keytypename = M.prettyname(o.def.defaultkeytyperef) |
| local valuetypename = M.prettyname(o.def.defaultvaluetyperef) |
| return keytypename and valuetypename and string.format('#map<%s,%s>', keytypename, valuetypename) or nil |
| else |
| return string.format('#%s',o.def.name) |
| end |
| end, |
| index = function(o) return "index" end, |
| file = function(o) return o.name end, |
| recordtypedef = function(o) return o.name end, |
| functiontypedef = function(o,t) |
| if t and t.tag == 'recordtypedef' and t.name then |
| local paramlist = {} |
| for position, param in ipairs(o.params) do |
| -- we ignore the first param |
| if not (position == 1) then |
| table.insert(paramlist, param.name) |
| end |
| end |
| return string.format('%s(%s)',t.name, table.concat(paramlist, ", ")) |
| end |
| end, |
| item = function( o ) |
| |
| -- Determine item name |
| -- ---------------------- |
| local itemname = o.name |
| |
| -- Determine scope |
| -- ---------------------- |
| local parent = o.parent |
| local isglobal = parent and parent.tag == 'file' |
| local isfield = parent and parent.tag == 'recordtypedef' |
| |
| -- Determine type name |
| -- ---------------------- |
| |
| local typename = isfield and parent.name |
| |
| -- Fetch item definition |
| -- ---------------------- |
| -- Get file object |
| local file |
| if isglobal then |
| file = parent |
| elseif isfield then |
| file = parent.parent |
| end |
| -- Get definition |
| local definition = o:resolvetype (file) |
| |
| |
| |
| -- Build prettyname |
| -- ---------------------- |
| local prettyname |
| if not definition or definition.tag ~= 'functiontypedef' then |
| -- Fields |
| if isglobal or not typename then |
| prettyname = itemname |
| else |
| prettyname = string.format('%s.%s', typename, itemname) |
| end |
| else |
| -- Functions |
| -- Build parameter list |
| local paramlist = {} |
| local isinvokable = M.isinvokable(o) |
| for position, param in ipairs(definition.params) do |
| -- For non global function, when first parameter is 'self', |
| -- it will not be part of listed parameters |
| if not (position == 1 and isinvokable and isfield) then |
| table.insert(paramlist, param.name) |
| if position ~= #definition.params then |
| table.insert(paramlist, ', ') |
| end |
| end |
| end |
| |
| if isglobal or not typename then |
| prettyname = string.format('%s(%s)',itemname, table.concat(paramlist)) |
| else |
| -- Determine function prefix operator, |
| -- ':' if 'self' is first parameter, '.' else way |
| local operator = isinvokable and ':' or '.' |
| |
| -- Append function parameters |
| prettyname = string.format('%s%s%s(%s)',typename, operator, itemname, table.concat(paramlist)) |
| end |
| end |
| |
| -- Manage external Item prettyname |
| -- ---------------------- |
| local externalmodule = o.external and getexternalmodule( o ) |
| local externalmodulename = externalmodule and externalmodule.name |
| |
| if externalmodulename then |
| return string.format('%s#%s',externalmodulename,prettyname) |
| else |
| return prettyname |
| end |
| end |
| } |
| M.prettynametypes.internaltyperef = M.prettynametypes.primitivetyperef |
| |
| --- |
| -- Check if the given item is a function that can be invoked |
| function M.isinvokable(item) |
| --test if the item is global |
| if item.parent and item.parent.tag == 'file' then |
| return false |
| end |
| -- check first param |
| local definition = item:resolvetype() |
| if definition and definition.tag == 'functiontypedef' then |
| if (#definition.params > 0) then |
| return definition.params[1].name == 'self' |
| end |
| end |
| end |
| |
| --- |
| -- Disable Markdown processing on a specific string |
| -- @param #string s Content to browse |
| -- @param #string escaped What Markdown should not process |
| -- @return #string Original `s` #string with `escaped` #string backslashed |
| function M.escape(s, escaped) |
| return string.gsub(s, escaped, '\\'..escaped) |
| end |
| |
| --- |
| -- Provide human readable overview from an API model element |
| -- |
| -- Resolve all element needed to summurize nicely an element form API model. |
| -- @usage $ print( prettyname(item) ) |
| -- module:somefunction(secondparameter) |
| -- @function [parent = #docutils] |
| -- @param apiobject Object form API model |
| -- @result #string Human readable description of given element. |
| -- @result #nil, #string In case of error. |
| function M.prettyname( apiobject, ... ) |
| local tag = apiobject.tag |
| if M.prettynametypes[tag] then |
| local prettyname = M.prettynametypes[tag](apiobject,...) |
| return M.escape(prettyname,'_') |
| elseif not tag then |
| return nil, 'No pretty name available as no tag has been provided.' |
| end |
| return nil, string.format('No pretty name for `%s.', tag) |
| end |
| |
| --- |
| -- Just make a string print table in HTML. |
| -- @function [parent = #docutils] securechevrons |
| -- @param #string String to convert. |
| -- @usage securechevrons('<markup>') => '<markup>' |
| -- @return #string Converted string. |
| function M.securechevrons( str ) |
| if not str then return nil, 'String expected.' end |
| return string.gsub(str:gsub('<', '<'), '>', '>') |
| end |
| |
| ------------------------------------------------------------------------------- |
| -- Handling format of @{some#type} tag. |
| -- Following functions enable to recognize several type of references between |
| -- "{}". |
| ------------------------------------------------------------------------------- |
| |
| --- |
| -- Build a global var from string such as: |
| -- * `global#foo` |
| -- * `foo#global.bar` |
| -- @param #string str |
| local globals = function(str) |
| -- Handling globals from modules |
| local modulename, fieldname = str:gmatch('([%a%.%d_]+)#global%.([%a%.%d_]+)')() |
| if modulename and fieldname then |
| local item = apimodel._item(fieldname) |
| local file = apimodel._file() |
| file.name = modulename |
| file:addglobalvar( item ) |
| return item |
| end |
| -- Handling other globals |
| local name = str:gmatch('global#([%a%.%d_]+)')() |
| if name then |
| return apimodel._externaltypref('global', name) |
| end |
| return nil |
| end |
| |
| --- |
| -- Build an external field from string like `module#(type).field` |
| -- @param #string str |
| local field = function( str ) |
| |
| -- Match `module#type.field` |
| local mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#([%a%.%d_]+)%.([%a%.%d_]+)')() |
| |
| -- Try matching `module#(type).field` |
| if not mod then |
| mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#%(([%a%.%d_]+)%)%.([%a%.%d_]+)')() |
| if not mod then |
| -- No match |
| return nil |
| end |
| end |
| |
| -- Build according `item |
| local modulefielditem = apimodel._item( fieldname ) |
| local moduletype = apimodel._recordtypedef(typename) |
| moduletype:addfield( modulefielditem ) |
| local typeref |
| if #mod > 0 then |
| local modulefile = apimodel._file() |
| modulefile.name = mod |
| modulefile:addtype( moduletype ) |
| typeref = apimodel._externaltypref(mod, typename) |
| modulefielditem.external = true |
| else |
| typeref = apimodel._internaltyperef(typename) |
| end |
| modulefielditem.type = typeref |
| return modulefielditem |
| end |
| |
| --- |
| -- Build an API internal reference from a string like: `#typeref` or #(type.ref) |
| -- @param #string str |
| local internal = function ( str ) |
| local typename = str:gmatch('#([%a%.%d_]+)')() |
| if not typename then |
| typename = str:gmatch('#%(([%a%.%d_]+)%)')() |
| if not typename then |
| -- No match |
| return nil |
| end |
| end |
| |
| -- Do not handle this name is it starts with reserved name "global" |
| if typename:find("global.") == 1 then return nil end |
| return apimodel._internaltyperef(typename) |
| end |
| |
| --- |
| -- Build an API external reference from a string like: `mod.ule#type` |
| -- @param #string str |
| local extern = function (str) |
| |
| -- Match `mod.ule#ty.pe` |
| local modulename, typename = str:gmatch('([%a%.%d_]+)#([%a%.%d_]+)')() |
| |
| -- Trying `mod.ule#(ty.pe)` |
| if not modulename then |
| modulename, typename = str:gmatch('([%a%.%d_]+)#%(([%a%.%d_]+)%)')() |
| |
| -- No match at all |
| if not modulename then |
| return nil |
| end |
| end |
| return apimodel._externaltypref(modulename, typename) |
| end |
| |
| --- |
| -- Build an API module(file) from a string like: `mod.ule` |
| -- @param #string str |
| local file = function (str) |
| local modulename = str:gmatch('([%a%.%d_]+)')() |
| if modulename then |
| local file = apimodel._file() |
| file.name = modulename |
| return file |
| end |
| return nil |
| end |
| |
| |
| --- |
| -- Provide API Model element from a string |
| -- @usage local externaltyperef = getelement("somemodule#somefield") |
| function M.getelement( str ) |
| |
| -- Order matters, more restrictive are at begin of table |
| local extractors = { |
| globals, |
| field, |
| extern, |
| internal, |
| file |
| } |
| -- Loop over extractors. |
| -- First valid result is used |
| for _, extractor in ipairs( extractors ) do |
| local result = extractor( str ) |
| if result then return result end |
| end |
| return nil |
| end |
| |
| -------------------------------------------------------------------------------- |
| -- Iterator that iterates on the table in key ascending order. |
| -- |
| -- @function [parent=#utils.table] sortedPairs |
| -- @param t table to iterate. |
| -- @return iterator function. |
| function M.sortedpairs(t) |
| local a = {} |
| local insert = table.insert |
| for n in pairs(t) do insert(a, n) end |
| table.sort(a) |
| local i = 0 |
| return function() |
| i = i + 1 |
| return a[i], t[a[i]] |
| end |
| end |
| return M |