blob: bf9f9e08559c137be3d8a7fa0b6266ffc467e7f3 [file] [log] [blame]
--------------------------------------------------------------------------------
-- 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&lt;%s&gt;', 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&lt;%s,%s&gt;', 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>') => '&lt;markup&gt;'
-- @return #string Converted string.
function M.securechevrons( str )
if not str then return nil, 'String expected.' end
return string.gsub(str:gsub('<', '&lt;'), '>', '&gt;')
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