Модуль:WDFormat
Для документации этого модуля может быть создана страница Модуль:WDFormat/doc
require('strict')
local p = {}
local wikidata = require('Модуль:WDCommon')
local function dump(obj, level)
if type(obj) ~= 'table' then
if type(obj) == 'string' then
return "'" .. obj .. "'"
else
return tostring(obj)
end
end
if not level then
level = 0
end
local indent = string.rep(' ', level)
local s = '{'
local isFirst = true
for i, v in pairs(obj) do
if not isFirst then
s = s .. ','
end
s = s .. '\n'
local currIndent = string.rep(' ', level + 4)
s = s .. currIndent
if type(i) == 'string' then
s = s .. i .. ' = '
end
s = s .. dump(v, level + 4)
isFirst = false
end
s = s .. '\n' .. indent .. '}'
return s
end
local Formatter = {
profile = nil,
processField = {},
}
function Formatter:tagToContainer(tag, parentContainer)
if not tag or not tag.name then
return parentContainer
end
local container = parentContainer
while tag do
if container then
container = container:tag(tag.name)
else
container = mw.html.create(tag.name)
end
if tag.classes then
for _, currClass in ipairs(tag.classes) do
container:addClass(currClass)
end
end
if tag.attr then
for attr, value in pairs(tag.attr) do
if type(value) == 'function' then
value = value(self.source)
end
container:attr(attr, value)
end
end
tag = tag.tag
end
return container
end
function Formatter:new(profile, source)
local obj = {}
setmetatable(obj, self)
self.__index = self
self.profile = profile
self.source = source
self.state = {
empty = true,
linkedEntities = {},
}
self.lastAddedText = ''
self.container = self:tagToContainer(profile.tag)
assert(self.container ~= nil, 'Not root container found. Use tag profile field.')
return obj
end
function Formatter.processField.numericalRanges(source, processedData, result)
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
local rangeSign
if defaultLang == 'ru' then
rangeSign = '—'
elseif defaultLang == 'ko' then
rangeSign = '~'
else
rangeSign = '–'
end
result.wikitext = mw.ustring.gsub(result.wikitext, '-', rangeSign)
end
function Formatter.processField.squareBrackets(source, processedData, result)
result.text = '[' .. result.text .. ']'
result.wikitext = '[' .. result.wikitext .. ']'
end
function Formatter.processField.dash(source, processedData, result)
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
local dashSign
if defaultLang == 'ru' then
dashSign = ' — '
end
result.wikitext = mw.ustring.gsub(result.wikitext, ' %- ', dashSign)
result.text = mw.ustring.gsub(result.text, ' %- ', dashSign)
end
function Formatter.processField.unit(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity then
return
end
local unit = wikidata.unit(entity, processedData.langCode)
if (unit and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
unit = mw.ustring.gsub(unit, '^%l', mw.ustring.upper)
end
result.text = unit
result.wikitext = unit
end
function Formatter.processField.abbr(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity or processedData.fieldTable.exact then
return
end
local abbr, _, ok = wikidata.abbrBiblio(entity, processedData.langCode)
if (abbr and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
abbr = mw.ustring.gsub(abbr, '^%l', mw.ustring.upper)
end
if abbr and ok then
abbr = mw.ustring.gsub(abbr, ' ', ' ')
end
result.text = abbr
result.wikitext = abbr
end
function Formatter.processField.short(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity or processedData.fieldTable.exact then
return
end
local short = wikidata.short(entity, processedData.langCode)
if (short and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
short = mw.ustring.gsub(short, '^%l', mw.ustring.upper)
end
if not short then
return
end
result.text = short
result.wikitext = short
end
function Formatter.processField.abbrWithHint(source, processedData, result, state)
local entity = processedData.fieldTable.entity
if not entity or processedData.fieldTable.exact then
return
end
local abbr, _, ok = wikidata.abbrBiblio(entity, processedData.langCode)
if (abbr and state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
abbr = mw.ustring.gsub(abbr, '^%l', mw.ustring.upper)
end
if abbr and ok then
abbr = mw.ustring.gsub(abbr, ' ', ' ')
end
if result.text ~= abbr then
result.wikitext = '<abbr title="' .. result.wikitext .. '">' .. abbr .. '</abbr>'
result.text = abbr
end
end
function Formatter.processField.date(source, processedData, result)
local langObj = mw.getLanguage(processedData.langCode)
result.text = langObj:formatDate('j xg Y', processedData.fieldTable.value.timestamp)
result.wikitext = result.text
end
function Formatter.processField.dateISO(source, processedData, result)
local langObj = mw.getLanguage(processedData.langCode)
result.text = langObj:formatDate('Y-m-d', processedData.fieldTable.value.timestamp)
result.wikitext = result.text
end
function Formatter.processField.uriScheme(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.text = wikidata.uriScheme(entity)
result.wikitext = result.text
end
function Formatter.processField.quantity(source, processedData, result)
local fieldTable = processedData.fieldTable
if not fieldTable.unitEntity then
return
end
local unit = wikidata.unit(fieldTable.unitEntity, processedData.langCode)
if unit then
result.text = result.text .. ' ' .. unit
result.wikitext = result.wikitext .. ' ' .. unit
end
end
local function nameFromFieldTable(nameTable)
local value = nameTable.value
local ok = true
if not value then
local label = mw.wikibase.getLabel(nameTable.entity)
if label then
value = mw.wikibase.getLabel(nameTable.entity) .. '<sup>[[d:' .. nameTable.entity .. '|?]]</sup>'
else
value = '?' .. '<sup>[[d:' .. nameTable.entity .. '|?]]</sup>'
end
ok = false
end
return value, ok
end
local function nameToInitial(nameTable)
local value = nameTable.value
local ok
if not value then
local label = mw.wikibase.getLabel(nameTable.entity)
if label then
value = label .. '<sup>[[d:' .. nameTable.entity .. '|?]]</sup>'
else
value = '?' .. '<sup>[[d:' .. nameTable.entity .. '|?]]</sup>'
end
ok = false
else
value = mw.ustring.sub(value, 1, 1) .. '.'
ok = true
end
return value, ok
end
local function namesToInitial(nameTables)
local value, ok
if table.getn(nameTables) == 0 then
value, ok = nameToInitial(nameTables)
else
value = ''
ok = true
for i, nameTable in ipairs(nameTables) do
if i > 1 then
value = value .. ' '
end
local currValue, currOk = nameToInitial(nameTable)
value = value .. currValue
ok = ok and currOk
end
end
return value, ok
end
function Formatter.processField.person(source, processedData, result)
-- currently supports only names with givenName and pathronym/mathronym,
-- middle names are not supported if they are a part of givenName
local fieldTable = processedData.fieldTable
local name
if fieldTable.exact then
name = result.text
if name:match(',') then
name = name:gsub('([^,]+), *(.+)', '%2 %1')
result.text = name
result.wikitext = name
end
return
end
if fieldTable.components and fieldTable.components.familyName and fieldTable.components.givenName then
local ok
name, ok = namesToInitial(fieldTable.components.givenName)
if fieldTable.components.ancestorName then
local ancestorName, ancestorNameOk = namesToInitial(fieldTable.components.ancestorName)
name = name .. ' ' .. ancestorName
ok = ok and ancestorNameOk
end
local familyName, familyNameOk = nameFromFieldTable(fieldTable.components.familyName)
name = name .. ' ' .. familyName
ok = ok and familyNameOk
if not ok then
result.linked = true
end
else
local entity = processedData.fieldTable.entity
if entity then
name = wikidata.abbr(entity, processedData.langCode)
else
name = processedData.value
end
if not name then
name = mw.wikibase.getLabel(entity) .. '<sup>[[d:' .. entity .. '|?]]</sup>'
end
end
result.text = name
result.wikitext = result.text
end
function Formatter.processField.personReversed(source, processedData, result)
-- currently supports only names with givenName and pathronym/mathronym,
-- middle names are not supported if they are a part of givenName
local fieldTable = processedData.fieldTable
local name
if fieldTable.exact then
name = result.text
if not name:match(',') then
local initials, familyName = mw.ustring.match(name, '^(.+%.) ([^%. ]+)$')
if not initials or not familyName then
return
end
name = familyName .. ', ' .. initials
result.text = name
result.wikitext = name
end
return
end
if fieldTable.components and fieldTable.components.familyName and fieldTable.components.givenName then
local ok
local familyName, ok = nameFromFieldTable(fieldTable.components.familyName)
local givenName, givenNameOk = namesToInitial(fieldTable.components.givenName)
ok = ok and givenNameOk
name = familyName .. ', ' .. givenName
if fieldTable.components.ancestorName then
local ancestorName, ancestorNameOk = namesToInitial(fieldTable.components.ancestorName)
ok = ok and ancestorNameOk
name = name .. ' ' .. ancestorName
end
if not ok then
result.linked = true
end
else
local entity = processedData.fieldTable.entity
if entity then
name = wikidata.abbr(entity, processedData.langCode)
else
return
end
if not name then
name = mw.wikibase.getLabel(entity) .. '<sup>[[d:' .. entity .. '|?]]</sup>'
end
end
result.text = name
result.wikitext = result.text
end
function Formatter.processField.personReversedNoComma(source, processedData, result)
-- currently supports only names with givenName and pathronym/mathronym,
-- middle names are not supported if they are a part of givenName
local fieldTable = processedData.fieldTable
local name
if fieldTable.exact then
name = result.text
if name:match(',') then
name = name:gsub('([^,]+), *(.+)', '%2 %1')
result.text = name
result.wikitext = name
else
local initials, familyName = mw.ustring.match(name, '^(.+%.) ([^%. ]+)$')
if not initials or not familyName then
return
end
name = familyName .. ' ' .. initials
result.text = name
result.wikitext = name
end
return
end
if fieldTable.exact then
name = result.text
if name:match(',') then
name = name:gsub('([^,]+), *(.+)', '%2 %1')
result.text = name
result.wikitext = name
end
return
end
if fieldTable.components and fieldTable.components.familyName and fieldTable.components.givenName then
local ok
local familyName, ok = nameFromFieldTable(fieldTable.components.familyName)
local givenName, givenNameOk = namesToInitial(fieldTable.components.givenName)
ok = ok and givenNameOk
name = familyName .. ' ' .. givenName
if fieldTable.components.ancestorName then
local ancestorName, ancestorNameOk = namesToInitial(fieldTable.components.ancestorName)
ok = ok and ancestorNameOk
name = name .. ' ' .. ancestorName
end
if not ok then
result.linked = true
end
else
local entity = processedData.fieldTable.entity
if entity then
name = wikidata.abbr(entity, processedData.langCode)
else
return
end
if not name then
name = mw.wikibase.getLabel(entity) .. '<sup>[[d:' .. entity .. '|?]]</sup>'
end
end
result.text = name
result.wikitext = result.text
end
function Formatter.processField.lowercase(source, processedData, result)
result.text = mw.ustring.lower(result.text)
result.wikitext = mw.ustring.lower(result.wikitext)
end
function Formatter.processField.wikisource(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
local title = mw.wikibase.getSitelink(entity, processedData.langCode .. 'wikisource')
if not title then
return
end
result.wikitext = '[[s:' .. title .. '|' .. result.text .. ']]'
result.linked = true
end
function Formatter.processField.link(source, processedData, result)
if result.linked or not processedData.url then
return
end
result.wikitext = '[' .. processedData.url .. ' ' .. result.wikitext .. ']'
result.linked = true
end
function Formatter.processField.wikilink(source, processedData, result)
if result.linked then
return
end
if processedData.wikilink then
result.wikitext = '[[' .. processedData.wikilink .. '|' .. result.wikitext .. ']]'
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
if result.state.linkedEntities[entity] then
return
end
local wikitext
wikitext, entity = wikidata.base.wikilink(entity, result.wikitext)
if result.state.linkedEntities[entity] or not entity then
return
end
result.wikitext = wikitext
result.state.linkedEntities[entity] = true
result.linked = true
end
function Formatter.processField.resolveWikilink(source, processedData, result)
if result.linked then
return
end
if processedData.wikilink then
result.wikitext = '[[' .. processedData.wikilink .. '|' .. result.wikitext .. ']]'
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
if result.state.linkedEntities[entity] then
return
end
local wikitext
wikitext, entity = wikidata.base.wikilink(entity, result.wikitext)
if not entity then
entity = wikidata.base.resolveParent(processedData.fieldTable.entity)
if not entity then
return
end
wikitext, entity = wikidata.base.wikilink(entity, result.wikitext)
end
if not entity or result.state.linkedEntities[entity] then
return
end
result.wikitext = wikitext
result.state.linkedEntities[entity] = true
result.linked = true
end
function Formatter.processField.valuableWikidata(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity or result.state.linkedEntities[entity] then
return
end
local entityObj = mw.wikibase.getEntity(entity)
if entityObj.sitelinks then
result.wikitext = result.wikitext .. '<sup>[[d:' .. entity .. '|[d]]]</sup>'
result.state.linkedEntities[entity] = true
result.linked = true
end
end
function Formatter.processField.wikidata(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity or result.state.linkedEntities[entity] then
return
end
result.wikitext = result.wikitext .. '<sup class="noprint">[[d:' .. entity .. '|[d]]]</sup>'
result.state.linkedEntities[entity] = true
result.linked = true
end
function Formatter.processField.forceWikidata(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.wikitext = result.wikitext .. '<sup>[[d:' .. entity .. '|[d]]]</sup>'
result.state.linkedEntities[entity] = true
result.linked = true
end
function Formatter.processField.wikidataLink(source, processedData, result)
if result.linked then
return
end
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.wikitext = '[[d:' .. entity .. '|' .. result.wikitext .. ']]'
result.linked = true
end
function Formatter.processField.forceWikidataLink(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.wikitext = '[[d:' .. entity .. '|' .. result.wikitext .. ']]'
result.linked = true
end
function Formatter.processField.entity(source, processedData, result)
local entity = processedData.fieldTable.entity
if not entity then
return
end
result.text = entity
result.wikitext = entity
end
local function fieldValueByPath(source, path)
if type(path) == 'table' then
local currField = source
for _, pathEntry in ipairs(path) do
currField = currField[pathEntry]
end
if not currField.value then
return nil
end
if path.sub then
return currField.value[path.sub]
end
return currField.value
end
local fieldValue = source[path]
if not fieldValue then
return nil
end
return fieldValue.value
end
local function fieldTableByPath(source, path)
if type(path) == 'table' then
local currField = source
for _, pathEntry in ipairs(path) do
currField = currField[pathEntry]
end
return currField
end
return source[path]
end
local function urlFromMaskByPart(part, source, fieldValue, langCode)
if part.urlMaskProp then
local urlMask = wikidata.urlMask(part.urlMaskProp, langCode)
if urlMask then
return urlMask:gsub('%$1', fieldValue)
end
elseif part.urlField then
local urlTable = source[part.urlField]
if urlTable then
if type(urlTable) == 'table' then
return urlTable.value
else
return urlTable
end
end
end
return nil
end
local function wikilinkFromMaskByPart(part, source, fieldValue)
if part.wikilinkMask then
return part.wikilinkMask:gsub('%$1', fieldValue)
end
return nil
end
local function forceLangByPart(part, source)
local langCode
if not part.forceLang then
langCode = source.langCode
if type(langCode) == 'table' then
langCode = langCode.value
end
end
if langCode then
return langCode
elseif part.forceLang == 'fallback' then
langCode = 'en'
elseif part.forceLang == 'default' or not part.forceLang then
local defaultLangObj = mw.getContentLanguage()
local defaultLang = defaultLangObj:getCode()
langCode = defaultLang
else
langCode = part.forceLang
end
return langCode
end
local function formatField(source, part, fieldTable, fieldValue, state)
local processedData = {
field = part.field,
wikilink = wikilinkFromMaskByPart(part, source, fieldValue),
langCode = forceLangByPart(part, source),
fieldTable = fieldTable,
capitalize = (part.capitalize or (part.capitalize == nil)) and (state.index == 1),
forceCapitalize = (part.capitalize == true and state.index == 1),
}
processedData.url = urlFromMaskByPart(part, source, fieldValue, processedData.langCode)
local value = fieldValue
local sourceLangCode
if source.langCode then
sourceLangCode = source.langCode.value
end
local linked = false
if fieldTable.entity and (processedData.langCode ~= sourceLangCode or not value) then
if processedData.langCode then
value = mw.wikibase.getLabelByLang(fieldTable.entity, processedData.langCode)
if not value then
value = mw.wikibase.getLabel(fieldTable.entity)
if value then
value = value .. '<sup>[[d:' .. fieldTable.entity .. '|?]]</sup>'
linked = true
end
end
else
value = mw.wikibase.getLabel(fieldTable.entity)
end
end
if value and type(value) == 'string' then
if (state.groupEmpty and processedData.capitalize) or processedData.forceCapitalize then
value = mw.ustring.gsub(value, '^%l', mw.ustring.upper)
end
end
if type(value) == 'table' then
value = dump(value)
elseif value == nil then
value = '<b><s>(nil)</s></b>'
end
processedData.value = value
local result = {
wikitext = value,
text = value,
state = state,
linked = linked,
}
if not part.format then
state.groupEmpty = false
return result
end
for _, formatFunc in ipairs(part.format) do
formatFunc(source, processedData, result, state)
end
state.groupEmpty = false
return result
end
local function formatFieldAsArray(source, part, fieldTable, state)
local text = ''
local wikitext = ''
local count = table.getn(fieldTable)
local cutCount = 0
if part.limits and count > part.limits.max then
cutCount = count - part.limits.cutTo
count = part.limits.cutTo
end
local delimiter = part.itemsDelimiter
if not delimiter then
delimiter = ', '
end
for i=1, count do
state.index = i
local currFieldTable = fieldTable[i]
local currResult = formatField(source, part, currFieldTable, currFieldTable.value, state)
wikitext = wikitext .. currResult.wikitext
text = text .. currResult.text
if i < count then
text = text .. delimiter
wikitext = wikitext .. delimiter
end
end
if cutCount > 0 then
local cutText = ''
for i=count + 1, count + cutCount do
state.index = i
local currFieldTable = fieldTable[i]
local currResult = formatField(source, part, currFieldTable, currFieldTable.value, state)
cutText = cutText .. currResult.text
if i < count + cutCount then
cutText = cutText .. delimiter
end
end
local processedData = {
field = part.field,
langCode = forceLangByPart(part, source),
fieldTable = fieldTable,
}
local othersText, othersWikitext = part.limits.replaceBy(source, processedData, cutText)
text = text .. othersText
wikitext = wikitext .. othersWikitext
end
return text, wikitext
end
local function fieldTableAndValueByPart(source, part)
if part.entity or part.value then
return { entity = part.entity, value = part.value }, part.value
else
return fieldTableByPath(source, part.field), fieldValueByPath(source, part.field)
end
end
local function formatFieldComponents(source, part, state)
local fieldTable, fieldValue = fieldTableAndValueByPart(source, part)
if type(fieldTable) ~= 'table' then
error('Field ' .. dump(part.field) .. ' is not a table. Its type is ' .. type(fieldTable))
end
local text
local wikitext
if table.getn(fieldTable) > 0 then
text, wikitext = formatFieldAsArray(source, part, fieldTable, state)
else
state.index = 1
local result = formatField(source, part, fieldTable, fieldValue, state)
text = result.text
wikitext = result.wikitext
end
return { text = text, wikitext = wikitext }
end
function Formatter:commonFormatField(part, parentContainer)
self:addPrefix(part)
local result = formatFieldComponents(self.source, part, self.state)
local container = self:tagToContainer(part.tag, parentContainer)
container:wikitext(result.wikitext)
self.lastAddedText = result.text
self.state.empty = false
self:addSuffix(part)
end
function Formatter:ensureEndsWith(endsText, parentContainer)
if self.lastAddedText and endsText then
local len = mw.ustring.len(endsText)
if mw.ustring.sub(self.lastAddedText, -len) ~= endsText then
parentContainer:wikitext(endsText)
end
end
end
function Formatter:ensureEndsAndAddDelimiter(group, part, parentContainer, groupEmpty)
if self.state.empty then
return
end
local delimiter = part.delimiter
local endsText = part.ensureEnds
if (delimiter == nil or groupEmpty) and not part.forceDelimiter then
if group.delimiter or group.ensureEnds then
delimiter = group.delimiter
endsText = group.ensureEnds
end
end
self:ensureEndsWith(endsText, parentContainer)
if type(delimiter) == 'function' then
delimiter = delimiter(self.source)
end
parentContainer:wikitext(delimiter)
end
function Formatter:addPrefix(part)
local prefix = part.prefix
if not prefix then
return
end
self.container:wikitext(prefix)
end
function Formatter:addSuffix(part)
local suffix = part.suffix
if not suffix then
return
end
self.container:wikitext(suffix)
self.lastAddedText = self.lastAddedText .. suffix
end
local function fieldExists(field, source)
if type(field) == 'table' then
local specified = false
local currField = source
for _, currFieldName in ipairs(field) do
currField = currField[currFieldName]
if not currField then
return false
else
specified = true
end
end
if field.sub then
if not currField.value then
return false
end
currField = currField.value[field.sub]
if not currField then
return false
end
end
return specified
end
return (source[field] ~= nil)
end
local fieldConflicts, fieldDepends
fieldConflicts = function(conflicts, source)
if not conflicts then
return false
end
if type(conflicts) == 'table' then
for _, currField in ipairs(conflicts) do
if type(currField) == 'string' then
if fieldExists(currField, source) then
return true
end
else
if fieldDepends(currField, source) then
return true
end
end
end
else
if fieldExists(conflicts, source) then
return true
end
end
return false
end
fieldDepends = function(depends, source)
if not depends then
return true
end
if type(depends) == 'table' then
for _, currField in ipairs(depends) do
if type(currField) == 'string' then
if not fieldExists(currField, source) then
return false
end
else
if not fieldConflicts(currField, source) then
return false
end
end
end
else
if not fieldExists(depends, source) then
return false
end
end
return true
end
local function partIsAvailable(part, source)
if part.parts then
for i, subpart in ipairs(part.parts) do
if partIsAvailable(subpart, source) then
return true
end
end
return false
end
if not fieldExists(part.field, source) and not part.entity and not part.value then
return false
end
if fieldConflicts(part.conflicts, source) then
return false
end
if not fieldDepends(part.depends, source) then
return false
end
if part.cond and not part.cond(source) then
return false
end
return true
end
function Formatter:formatGroup(group, parentContainer)
local state = self.state
state.groupEmpty = true
local groupContainer
for _, part in ipairs(group.parts) do
if partIsAvailable(part, self.source) then
if not groupContainer then
self:ensureEndsAndAddDelimiter(group, part, parentContainer, state.groupEmpty)
groupContainer = self:tagToContainer(group.tag, parentContainer) or parentContainer
local prefix = group.prefix
if prefix then
groupContainer:wikitext(prefix)
self.lastAddedText = self.lastAddedText .. prefix
end
else
self:ensureEndsAndAddDelimiter(group, part, groupContainer, state.groupEmpty)
end
if part.parts then
self:formatGroup(part, groupContainer)
else
self:commonFormatField(part, groupContainer)
end
end
end
if not state.groupEmpty then
local suffix = group.suffix
if suffix then
groupContainer:wikitext(suffix)
self.lastAddedText = self.lastAddedText .. suffix
end
end
end
function Formatter:formatGroups(groups, parentContainer)
for _, group in ipairs(groups) do
self:formatGroup(group, parentContainer)
end
end
function Formatter:format()
local groups = self.profile.groups
self:formatGroups(groups, self.container)
self:ensureEndsWith(self.profile.ensureEnds, self.container)
end
function Formatter:getAsText()
return tostring(self.container:allDone())
end
function p.format(profile, source)
local f = Formatter:new(profile, source)
f:format()
return f:getAsText()
end
p.f = Formatter.processField
return p