Difference between revisions of "Module:Track gauge"
Jump to navigation
Jump to search
blackwiki>DePiep m (DePiep moved page Module:RailGauge to Module:Track gauge without leaving a redirect: follow template name change) |
blackwiki>DePiep (Apply name change into "module:Track gauge") |
||
| Line 1: | Line 1: | ||
| − | -- This module implements the {{ | + | -- This module implements the {{Track gauge}} template. |
| − | -- Data is in Module: | + | -- Data is in Module:Track gauge/data and in Module:Track gauge/extra |
local p = {} | local p = {} | ||
local gaugeDataAll = nil | local gaugeDataAll = nil | ||
local gaugeDataExtra = nil | local gaugeDataExtra = nil | ||
| − | local dataPageName = 'Module: | + | local dataPageName = 'Module:Track gauge/data' -- sandbox here |
| − | local dataPageExtra = 'Module: | + | local dataPageExtra = 'Module:Track gauge/extra' -- sandbox here |
----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ||
-- prepareArgs -- Normalise Arguments coming from an #invoke or from a module | -- prepareArgs -- Normalise Arguments coming from an #invoke or from a module | ||
| Line 93: | Line 93: | ||
return nil | return nil | ||
end | end | ||
| − | local | + | local tgEntry = nil |
| − | for i, | + | for i, tgEntry in ipairs(gaugeDataAll) do |
| − | for j, alias in ipairs( | + | for j, alias in ipairs(tgEntry.aliases) do |
if alias == searchAlias then | if alias == searchAlias then | ||
| − | return | + | return tgEntry |
end | end | ||
end | end | ||
end | end | ||
-- No hit, so seach in datapage /extra | -- No hit, so seach in datapage /extra | ||
| − | + | tgEntry = nil | |
gaugeDataExtra = mw.loadData(dataPageExtra) | gaugeDataExtra = mw.loadData(dataPageExtra) | ||
| − | for i, | + | for i, tgEntry in ipairs(gaugeDataExtra) do |
| − | for j, alias in ipairs( | + | for j, alias in ipairs(tgEntry.aliases) do |
if alias == searchAlias then | if alias == searchAlias then | ||
| − | return | + | return tgEntry |
end | end | ||
end | end | ||
| Line 130: | Line 130: | ||
-- catMentions -- Wikicode for "article mentions gauge" categories | -- catMentions -- Wikicode for "article mentions gauge" categories | ||
----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ||
| − | function p.catMentions( | + | function p.catMentions(tgEntry, sortlabel, doReturn) |
local ns = 'Category:' | local ns = 'Category:' | ||
local cat | local cat | ||
| − | if | + | if tgEntry == nil then |
| − | cat = 'Articles that mention a specific | + | cat = 'Articles that mention a specific track gauge' -- Parent, the container cat |
else | else | ||
| − | cat = 'Articles that mention | + | cat = 'Articles that mention track gauge ' .. tgEntry.id .. ' mm' |
end | end | ||
-- Argument 'label' can be used to add a catsort. Catsort is not used (as of 20 May 2014) | -- Argument 'label' can be used to add a catsort. Catsort is not used (as of 20 May 2014) | ||
| Line 162: | Line 162: | ||
-- formatImp -- Formats imperial units size into a single text element | -- formatImp -- Formats imperial units size into a single text element | ||
----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ||
| − | function p.formatImp( | + | function p.formatImp(tgEntry, measurementToLink, setNowrap, addUnitlink) |
local ret = {} | local ret = {} | ||
| − | local ft = | + | local ft = tgEntry.ft |
if ft then | if ft then | ||
local ftlink = addUnitlink and measurementToLink ~= 'imp' and '[[Foot (unit)|ft]]' or 'ft' | local ftlink = addUnitlink and measurementToLink ~= 'imp' and '[[Foot (unit)|ft]]' or 'ft' | ||
table.insert(ret, mw.ustring.format('%s %s', ft, ftlink)) | table.insert(ret, mw.ustring.format('%s %s', ft, ftlink)) | ||
end | end | ||
| − | local inches = | + | local inches = tgEntry['in'] |
| − | local num = | + | local num = tgEntry.num |
| − | local den = | + | local den = tgEntry.den |
if inches and not num and not den then | if inches and not num and not den then | ||
table.insert(ret, inches) | table.insert(ret, inches) | ||
| Line 187: | Line 187: | ||
gaugeSize = table.concat(ret, ' ') | gaugeSize = table.concat(ret, ' ') | ||
end | end | ||
| − | if measurementToLink == 'imp' and | + | if measurementToLink == 'imp' and tgEntry.pagename ~= nil then |
| − | return mw.ustring.format('[[%s|%s]]', | + | return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize) |
else | else | ||
return gaugeSize | return gaugeSize | ||
| Line 196: | Line 196: | ||
-- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument | -- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument | ||
----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ||
| − | function p.formatMet( | + | function p.formatMet(tgEntry, measurementToLink, setNowrap, addUnitlink) |
| − | local m = | + | local m = tgEntry.m |
local gaugeSize | local gaugeSize | ||
if m then | if m then | ||
| Line 203: | Line 203: | ||
gaugeSize = mw.ustring.format('%s %s', m, mUnit) | gaugeSize = mw.ustring.format('%s %s', m, mUnit) | ||
else | else | ||
| − | local mm = | + | local mm = tgEntry.mm |
mm = tonumber(mm) | mm = tonumber(mm) | ||
if mm then | if mm then | ||
| Line 214: | Line 214: | ||
gaugeSize = noWrap(gaugeSize) | gaugeSize = noWrap(gaugeSize) | ||
end | end | ||
| − | if measurementToLink == 'met' and | + | if measurementToLink == 'met' and tgEntry.pagename ~= nil then |
| − | return mw.ustring.format('[[%s|%s]]', | + | return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize) |
else | else | ||
return gaugeSize | return gaugeSize | ||
| Line 223: | Line 223: | ||
-- formatAltName | -- formatAltName | ||
----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ||
| − | function formatAltName( | + | function formatAltName(tgEntry, addArticleLink, disp, setNowrap) |
-- Asserted: al=on | -- Asserted: al=on | ||
| − | if | + | if tgEntry.name == nil then |
return '' | return '' | ||
end | end | ||
| Line 237: | Line 237: | ||
table.insert(retAlt, '<span class="nowrap">') | table.insert(retAlt, '<span class="nowrap">') | ||
end | end | ||
| − | if addArticleLink and | + | if addArticleLink and tgEntry.link then -- If not defined then fallback to unlinked name |
| − | table.insert(retAlt, | + | table.insert(retAlt, tgEntry.link) |
else | else | ||
| − | table.insert(retAlt, | + | table.insert(retAlt, tgEntry.name) |
end | end | ||
if setNowrap then --close tag | if setNowrap then --close tag | ||
| Line 251: | Line 251: | ||
----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ||
function p.main(frame) | function p.main(frame) | ||
| − | -- In general: | + | -- In general: tgEntry (from TG/data) is passed to the functions, arguments are processed here. |
local title = mw.title.getCurrentTitle() | local title = mw.title.getCurrentTitle() | ||
local args = prepareArgs(frame) | local args = prepareArgs(frame) | ||
| − | local | + | local tgEntry = p.getRgEntry(args.searchAlias) |
-- Categorise the page if no gauge information was found. | -- Categorise the page if no gauge information was found. | ||
| − | if | + | if tgEntry == nil then |
local category = '' | local category = '' | ||
if title:inNamespaces(0, 14) then | if title:inNamespaces(0, 14) then | ||
| Line 267: | Line 267: | ||
end | end | ||
category = mw.ustring.format( | category = mw.ustring.format( | ||
| − | + | "[[Category:Articles using template 'Track gauge' with unrecognized input|%s%s]]", | |
sort1, title.text) | sort1, title.text) | ||
end | end | ||
return (args[1] or '') .. category | return (args[1] or '') .. category | ||
end | end | ||
| − | -- sandbox here: option to show "( | + | -- sandbox here: option to show "(Track gauge/sandbox is currently under construction)" message. (Comment out to make it idle) |
| − | -- return | + | -- return tgEntry.id .. ' <span style="font-size:85%; font-style:italic; font-family=courier">(Track gauge/sandbox is currently under construction)</span>' |
| − | -- Check and set args & | + | -- Check and set args & tgEntry props: disp, first, nowrap, first |
local disp = args.disp or '' | local disp = args.disp or '' | ||
| − | local first = args.first or | + | local first = args.first or tgEntry.def1 |
local unitlink = args.unitlink or '' | local unitlink = args.unitlink or '' | ||
local nowrap = args.nowrap or '' | local nowrap = args.nowrap or '' | ||
| Line 285: | Line 285: | ||
measurementToLink = first -- Can make metric text links to the imp linked page | measurementToLink = first -- Can make metric text links to the imp linked page | ||
else | else | ||
| − | measurementToLink = | + | measurementToLink = tgEntry.def1 -- When first=swapped, this could link 2nd measure. |
end | end | ||
end | end | ||
| Line 297: | Line 297: | ||
if first == 'met' then | if first == 'met' then | ||
table.insert(ret, | table.insert(ret, | ||
| − | p.formatMet( | + | p.formatMet(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on')) |
else | else | ||
table.insert(ret, | table.insert(ret, | ||
| − | p.formatImp( | + | p.formatImp(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on')) |
end | end | ||
-- The joint and the second measure | -- The joint and the second measure | ||
| Line 324: | Line 324: | ||
if first ~= 'met' then | if first ~= 'met' then | ||
table.insert(ret, | table.insert(ret, | ||
| − | p.formatMet( | + | p.formatMet(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on')) |
else | else | ||
table.insert(ret, | table.insert(ret, | ||
| − | p.formatImp( | + | p.formatImp(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on')) |
end | end | ||
table.insert(ret, closeDisp) -- Could be '' | table.insert(ret, closeDisp) -- Could be '' | ||
| Line 337: | Line 337: | ||
if args.al or args.allk ~= nil then | if args.al or args.allk ~= nil then | ||
local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags | local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags | ||
| − | table.insert(ret, formatAltName( | + | table.insert(ret, formatAltName(tgEntry, args.allk == 'on', disp, setNowrapAltname)) |
end | end | ||
-- Closing nowrap tag | -- Closing nowrap tag | ||
| Line 344: | Line 344: | ||
end | end | ||
-- Category mentionings (maintenance) | -- Category mentionings (maintenance) | ||
| − | if | + | if tgEntry.id == '1435' or (args.addcat or '') == 'no' then |
-- No categorization | -- No categorization | ||
elseif title:inNamespaces(0) then | elseif title:inNamespaces(0) then | ||
| − | table.insert(ret, p.catMentions( | + | table.insert(ret, p.catMentions(tgEntry)) |
end | end | ||
-- Now sting the table together | -- Now sting the table together | ||
Revision as of 16:49, 3 September 2014
| 30px | This module depends on the following other modules: |
| 40x40px | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
This module implements the {{Track gauge}} template. Please see the template page for documentation on how to use the main TrackGauge function.
Gauge data
The gauge information is stored at Module:Track gauge/data; to add new gauges, see the instructions there.
Data checks
This module includes a function that checks the data page for errors. It is used with the following code:
{{#invoke:Track gauge/autodocument|checkData|name of data page}}
The first positional parameter is the name of the data page that you want to be checked. If this is omitted, the module checks Module:Track gauge/data.
Tracking/maintenance category
-- This module implements the {{Track gauge}} template.
-- Data is in Module:Track gauge/data and in Module:Track gauge/extra
local p = {}
local gaugeDataAll = nil
local gaugeDataExtra = nil
local dataPageName = 'Module:Track gauge/data' -- sandbox here
local dataPageExtra = 'Module:Track gauge/extra' -- sandbox here
-----------------------------------------------------------------------------------
-- prepareArgs -- Normalise Arguments coming from an #invoke or from a module
-----------------------------------------------------------------------------------
local function prepareArgs(frame)
local origArgs
if frame == mw.getCurrentFrame() then
origArgs = frame:getParent().args
for k, v in pairs(frame.args) do
origArgs = frame.args
break
end
else
origArgs = frame
end
local args = {}
-- searchAlias is the cleaned value of args[1]. args[1] is kept as rawInput for error message
local searchAlias = ''
local rawDisp
for k, v in pairs(origArgs) do
if tonumber(k) == nil then -- Named argment
if k == 'disp' then
rawDisp = v -- Keep raw disp input to pass through plain (wiki)text
args[k] = mw.ustring.lower(v)
elseif k == 'first' then
v = mw.ustring.lower(v)
if v == 'met' or v == 'metric' then
v = 'met'
elseif v == 'imp' or v == 'imperial' then
v = 'imp'
else k = 'trashparam_first' end
args[k] = v
elseif k == 'nowrap' or k == 'wrap' then -- wrap=y deprecated; reading: nowrap=off
v = mw.ustring.lower(v)
if v == '' or v == 'off' or v == 'on' or v == 'all' then
elseif v == 'inline' or (k == 'wrap' and v == 'y') then
v = 'off'
else v = '' end
args['nowrap'] = v
else
args[k] = mw.ustring.lower(v)
end
else
args[k] = v -- Keep rawInput in [1] for error message
if k == 1 then
-- Unnamed argument, the alias to be searched
-- Cleanup
searchAlias = p.normaliseAliasInput(v)
end
end
end
args['searchAlias'] = searchAlias
if rawDisp then args['rawDisp'] = rawDisp end
return args
end
-----------------------------------------------------------------------------------
-- normaliseAliasInput
-----------------------------------------------------------------------------------
function p.normaliseAliasInput(aliasIn)
local a
a = mw.ustring.lower(mw.ustring.gsub(aliasIn, '[%s%,]', ''))
a = mw.ustring.gsub(a, ' ', '')
a = mw.ustring.gsub(a, 'gauge$', '')
a = mw.ustring.gsub(a, "'", "ft")
a = mw.ustring.gsub(a, '"', 'in')
a = mw.ustring.gsub(a, '⁄', '/')
a = mw.ustring.gsub(a, '⁄', '/')
return a
end
-----------------------------------------------------------------------------------
-- debugReturnArgs -- Debug function.
-----------------------------------------------------------------------------------
function p.debugReturnArgs(frame)
local args = prepareArgs(frame)
local retArgs = {}
for k, a in pairs(args) do
table.insert(retArgs, k .. '=' .. a)
end
return 'Args: ' .. table.concat(retArgs, '; ')
end
-----------------------------------------------------------------------------------
-- getRgEntry -- Find entry data for a single gauge (alias)
-----------------------------------------------------------------------------------
function p.getRgEntry(searchAlias)
gaugeDataAll = mw.loadData(dataPageName)
if searchAlias == '' then
return nil
end
local tgEntry = nil
for i, tgEntry in ipairs(gaugeDataAll) do
for j, alias in ipairs(tgEntry.aliases) do
if alias == searchAlias then
return tgEntry
end
end
end
-- No hit, so seach in datapage /extra
tgEntry = nil
gaugeDataExtra = mw.loadData(dataPageExtra)
for i, tgEntry in ipairs(gaugeDataExtra) do
for j, alias in ipairs(tgEntry.aliases) do
if alias == searchAlias then
return tgEntry
end
end
end
end
-----------------------------------------------------------------------------------
-- noWrap -- Add span tags to prevent a string from wrapping.
-----------------------------------------------------------------------------------
local function noWrap(s)
return mw.ustring.format('<span class="nowrap">%s</span>', s)
end
-----------------------------------------------------------------------------------
-- frac -- A slimmed-down version of the {{frac}} template (a nowrap is to be added with the unit)
-----------------------------------------------------------------------------------
local function frac(whole, num, den)
return mw.ustring.format(
'<span class="frac">%s%s<sup>%s</sup>⁄<sub>%s</sub></span>',
whole or '', whole and '<span class="visualhide"> </span>' or '',
num, den)
end
-----------------------------------------------------------------------------------
-- catMentions -- Wikicode for "article mentions gauge" categories
-----------------------------------------------------------------------------------
function p.catMentions(tgEntry, sortlabel, doReturn)
local ns = 'Category:'
local cat
if tgEntry == nil then
cat = 'Articles that mention a specific track gauge' -- Parent, the container cat
else
cat = 'Articles that mention track gauge ' .. tgEntry.id .. ' mm'
end
-- Argument 'label' can be used to add a catsort. Catsort is not used (as of 20 May 2014)
if sortlabel ~= nil then
sortlabel = '|' .. sortlabel
else
sortlabel = ''
end
if doReturn ~= nil then
if doReturn == 'fullpagename' then
return ns .. cat
elseif doReturn == 'pagename' then -- plaintext, no namespace
return cat
elseif doReturn == 'show' then -- colontrick
return '[[:' .. ns .. cat .. sortlabel .. ']]'
else -- unknown arg value
return ns .. cat
end
else -- Returns straight categorisation (wikitext)
return '[[' .. ns .. cat .. sortlabel .. ']]'
end
end
-----------------------------------------------------------------------------------
-- formatImp -- Formats imperial units size into a single text element
-----------------------------------------------------------------------------------
function p.formatImp(tgEntry, measurementToLink, setNowrap, addUnitlink)
local ret = {}
local ft = tgEntry.ft
if ft then
local ftlink = addUnitlink and measurementToLink ~= 'imp' and '[[Foot (unit)|ft]]' or 'ft'
table.insert(ret, mw.ustring.format('%s %s', ft, ftlink))
end
local inches = tgEntry['in']
local num = tgEntry.num
local den = tgEntry.den
if inches and not num and not den then
table.insert(ret, inches)
elseif num and den then
table.insert(ret, frac(inches, num, den))
end
if inches or num and den then
local incheslink = addUnitlink and measurementToLink ~= 'imp' and '[[inch|in]]' or 'in'
table.insert(ret, incheslink)
end
local gaugeSize
if setNowrap then
gaugeSize = noWrap(table.concat(ret, ' '))
else
gaugeSize = table.concat(ret, ' ')
end
if measurementToLink == 'imp' and tgEntry.pagename ~= nil then
return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize)
else
return gaugeSize
end
end
-----------------------------------------------------------------------------------
-- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument
-----------------------------------------------------------------------------------
function p.formatMet(tgEntry, measurementToLink, setNowrap, addUnitlink)
local m = tgEntry.m
local gaugeSize
if m then
local mUnit = addUnitlink and measurementToLink ~= 'met' and '[[metre|m]]' or 'm'
gaugeSize = mw.ustring.format('%s %s', m, mUnit)
else
local mm = tgEntry.mm
mm = tonumber(mm)
if mm then
mm = mw.getContentLanguage():formatNum(mm)
end
local mmUnit = addUnitlink and measurementToLink ~= 'met' and '[[millimetre|mm]]' or 'mm'
gaugeSize = mw.ustring.format('%s %s', mm, mmUnit)
end
if setNowrap then
gaugeSize = noWrap(gaugeSize)
end
if measurementToLink == 'met' and tgEntry.pagename ~= nil then
return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize)
else
return gaugeSize
end
end
-----------------------------------------------------------------------------------
-- formatAltName
-----------------------------------------------------------------------------------
function formatAltName(tgEntry, addArticleLink, disp, setNowrap)
-- Asserted: al=on
if tgEntry.name == nil then
return ''
end
local retAlt = {}
if disp == 'br' then
table.insert(retAlt, '<br>')
else
table.insert(retAlt, ' ')
end
if setNowrap then
table.insert(retAlt, '<span class="nowrap">')
end
if addArticleLink and tgEntry.link then -- If not defined then fallback to unlinked name
table.insert(retAlt, tgEntry.link)
else
table.insert(retAlt, tgEntry.name)
end
if setNowrap then --close tag
table.insert(retAlt, '</span>')
end
return table.concat(retAlt, '')
end
-----------------------------------------------------------------------------------
-- main -- The basic module
-----------------------------------------------------------------------------------
function p.main(frame)
-- In general: tgEntry (from TG/data) is passed to the functions, arguments are processed here.
local title = mw.title.getCurrentTitle()
local args = prepareArgs(frame)
local tgEntry = p.getRgEntry(args.searchAlias)
-- Categorise the page if no gauge information was found.
if tgEntry == nil then
local category = ''
if title:inNamespaces(0, 14) then
local sort1
if (args[1] or '') == '' then -- Blank input, sort top.
sort1 = ' '
else
sort1 = args[1] .. ', '
end
category = mw.ustring.format(
"[[Category:Articles using template 'Track gauge' with unrecognized input|%s%s]]",
sort1, title.text)
end
return (args[1] or '') .. category
end
-- sandbox here: option to show "(Track gauge/sandbox is currently under construction)" message. (Comment out to make it idle)
-- return tgEntry.id .. ' <span style="font-size:85%; font-style:italic; font-family=courier">(Track gauge/sandbox is currently under construction)</span>'
-- Check and set args & tgEntry props: disp, first, nowrap, first
local disp = args.disp or ''
local first = args.first or tgEntry.def1
local unitlink = args.unitlink or ''
local nowrap = args.nowrap or ''
local setNowrapElement = (nowrap == '' or nowrap == 'off') -- To prevent nested nowrap tags
local measurementToLink
if args.lk == 'on' then
if disp == '1' then
measurementToLink = first -- Can make metric text links to the imp linked page
else
measurementToLink = tgEntry.def1 -- When first=swapped, this could link 2nd measure.
end
end
-- String the text elements together (compose the return table)
local ret = {}
-- nowrap opening tag
if nowrap == 'all' or nowrap == 'on' then
table.insert(ret, '<span class="nowrap">')
end
-- First measure
if first == 'met' then
table.insert(ret,
p.formatMet(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
else
table.insert(ret,
p.formatImp(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
end
-- The joint and the second measure
if disp == '1' then
else
local joinText = ''
local closeDisp = ''
if disp == 's' or disp == '/' then
joinText = '/​'
elseif disp == 'br' then
joinText = '<br>('
closeDisp = ')'
elseif disp == '[' or disp == '[]' then
joinText = ' ['
closeDisp = ']'
elseif disp ~= '' then -- Is anytext
joinText = ' ' .. args['rawDisp'] .. ' '
else
joinText = ' ('
closeDisp = ')'
end
table.insert(ret, joinText)
if first ~= 'met' then
table.insert(ret,
p.formatMet(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
else
table.insert(ret,
p.formatImp(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
end
table.insert(ret, closeDisp) -- Could be ''
end
if nowrap == 'on' then -- Closing tag
table.insert(ret, '</span>')
end
-- Alternative name
if args.al or args.allk ~= nil then
local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags
table.insert(ret, formatAltName(tgEntry, args.allk == 'on', disp, setNowrapAltname))
end
-- Closing nowrap tag
if nowrap == 'all' then
table.insert(ret, '</span>')
end
-- Category mentionings (maintenance)
if tgEntry.id == '1435' or (args.addcat or '') == 'no' then
-- No categorization
elseif title:inNamespaces(0) then
table.insert(ret, p.catMentions(tgEntry))
end
-- Now sting the table together
return table.concat(ret, '')
end
return p