Module:DPLlua: Difference between revisions
Created page with "-- <nowiki> local dpl = {} local libraryUtil = require( 'libraryUtil' ) local checkType = libraryUtil.checkType local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg..." |
mNo edit summary |
||
| Line 2: | Line 2: | ||
local dpl = {} | local dpl = {} | ||
local libraryUtil = require( 'libraryUtil' ) | local libraryUtil = require( 'libraryUtil' ) | ||
local hasContent = require( 'Module:Paramtest' ).has_content | |||
local checkType = libraryUtil.checkType | local checkType = libraryUtil.checkType | ||
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg | local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg | ||
| Line 12: | Line 13: | ||
local function split( str, pattern, plain ) | local function split( str, pattern, plain ) | ||
local res = {} | local res = {} | ||
continue = true | local continue = true | ||
local startIndex = 1 | local startIndex = 1 | ||
| Line 31: | Line 32: | ||
-- Also custom function for speed | -- Also custom function for speed | ||
local function trim( str ) | local function trim( str ) | ||
return string. | return (string.gsub( str, '^%s+', '' ):gsub( '%s+$', '' )) | ||
end | |||
local function mergeItem( tbl, key, item ) | |||
if type( tbl[key] ) == 'table' and type( item ) == 'table' then | |||
for k in pairs( tbl[key] ) do | |||
mergeItem( tbl[key], k, item[k] ) | |||
end | |||
elseif type( tbl[key] ) == 'table' then | |||
table.insert( tbl[key], item ) | |||
else | |||
tbl[key] = { tbl[key], item } | |||
end | |||
end | end | ||
local escapeChars = { | local escapeChars = { | ||
['{'] = '{', | ['{'] = '{', | ||
['}'] = '}', | ['}'] = '}', | ||
['['] = '[', | ['['] = '[', | ||
[']'] = ']', | [']'] = ']', | ||
| Line 45: | Line 56: | ||
} | } | ||
local function escape( str ) | local function escape( str ) | ||
return (string.gsub( str, '[{}%[%]|%-]', escapeChars )) | |||
end | end | ||
| Line 60: | Line 68: | ||
} | } | ||
local function unEscape( str ) | local function unEscape( str ) | ||
return (string.gsub( str, '&#%d+;', unEscapeChars )) | |||
return str | end | ||
local function fixCurlyBrackets( str ) | |||
-- the \226\157\180\181 are used to match ❴ (U+2774) and ❵ (U+2775) wich are 3 bytes long (UTF-8) so | |||
-- we can't use them directly inside [] patterns. Ustring would fix this but it's way too slow. | |||
return (string.gsub( str, '\226\157[\180\181]', { ['❴'] = '{', ['❵'] = '}' } )) | |||
end | end | ||
| Line 92: | Line 105: | ||
'suppresserrors', | 'suppresserrors', | ||
'noresultsfooter', | 'noresultsfooter', | ||
'format' | 'format', | ||
'groupMultiTemplateResults' | |||
} | } | ||
| Line 104: | Line 118: | ||
query = split( query, ',', true ) | query = split( query, ',', true ) | ||
local includedParamNames = {} | local includedParamNames = {} | ||
local sectionAttributes = {} | |||
for i = 1, #query do | for i = 1, #query do | ||
if query[i]:match( '%b{}' ) then -- Check if we are including a template | if query[i]:match( '%b{}' ) then -- Check if we are including a template | ||
local templateName, | local templateName, extra = query[i]:match( '{(.-)[¦|}](.*)' ) | ||
if | if hasContent( extra ) then | ||
params:gsub( '^:%-', '' ) | local phantomTemplateName = extra:match( '^(.-)}' ) or extra:match( '^[./].+' ) | ||
query[i] = string.format( '{%s}%s', templateName, params ) | local phantomTemplatePrefix = extra:match( '^(.-)}' ) and '' or templateName | ||
local params = extra:gsub( '^.-}', '' ):gsub( '^[./].+', '' ):gsub( ':%-', '' ) | |||
local sur = hasContent( phantomTemplateName ) and ('¦' .. phantomTemplatePrefix .. phantomTemplateName) or '' | |||
query[i] = string.format( '{%s%s}%s', templateName, sur, params ) | |||
for param in params:gmatch( ':([^:]*)' ) do | if hasContent( phantomTemplateName ) then | ||
table.insert( includedParamNames, { name=phantomTemplatePrefix..phantomTemplateName, isTemplate=true, hasPhantomTemplate=true } ) | |||
table.insert( sectionAttributes, { hasPhantomTemplate=true } ) | |||
else | |||
for param in params:gmatch( ':([^:]*)' ) do | |||
param = trim( param ) | |||
table.insert( includedParamNames, { name=templateName, isTemplate=true, param=param } ) | |||
end | |||
table.insert( sectionAttributes, { hasPhantomTemplate=false } ) | |||
end | end | ||
else | else | ||
query[i] = string.format( '{%s¦DPLlua helper}', templateName ) -- Use a helper template to get all the parameters of our included template | query[i] = string.format( '{%s¦DPLlua helper}', templateName ) -- Use a helper template to get all the parameters of our included template | ||
table.insert( includedParamNames, { name=templateName, isTemplate=true, includeAll=true } ) | table.insert( includedParamNames, { name=templateName, isTemplate=true, includeAll=true } ) | ||
table.insert( sectionAttributes, { hasPhantomTemplate=false } ) | |||
end | end | ||
else | else | ||
table.insert( includedParamNames, { name=trim( query[i] ) } ) | table.insert( includedParamNames, { name=trim( query[i] ) } ) | ||
table.insert( sectionAttributes, { hasPhantomTemplate=false } ) | |||
end | end | ||
end | end | ||
return table.concat( query, ',' ), includedParamNames | return table.concat( query, ',' ), includedParamNames, sectionAttributes | ||
end | end | ||
| Line 135: | Line 160: | ||
local usesInclude = false | local usesInclude = false | ||
local includedParamNames = {} | local includedParamNames = {} | ||
local sectionAttributes | |||
query.count = nil | query.count = nil | ||
query.offset = nil | query.offset = nil | ||
-- | -- Use table format so we can place dataContentMarkers around each included parameter. The secseparator | ||
-- | -- is needed to add dataContentMarkers when a phantom template is used | ||
local dplStringInclude = | local dplStringInclude = | ||
[=[ | [=[ | ||
| Line 148: | Line 174: | ||
|%s | |%s | ||
|table=, | |table=, | ||
|listseparators=,\n¦-\n¦[[%%PAGE%%¦]],, | |||
|tablerow=%s | |tablerow=%s | ||
|secseparators=%s | |||
}}]=] | }}]=] | ||
| Line 170: | Line 198: | ||
for k, v in pairs( query ) do | for k, v in pairs( query ) do | ||
if k == 'include' then | if k == 'include' then | ||
v, includedParamNames = formatInclude( v ) | v, includedParamNames, sectionAttributes = formatInclude( v ) | ||
usesInclude = true | usesInclude = true | ||
end | end | ||
| Line 184: | Line 212: | ||
if usesInclude then | if usesInclude then | ||
local secseparators = '' | |||
for _, v in ipairs( sectionAttributes ) do | |||
if v.hasPhantomTemplate then | |||
-- Phantom templates need this because they ignore tablerow formatting | |||
secseparators = secseparators .. '¶¦' .. dataContentMarker .. ',' .. dataContentMarker .. ',' | |||
else | |||
secseparators = secseparators .. '¶¦,,' | |||
end | |||
end | |||
table.insert( queries, string.format( | table.insert( queries, string.format( | ||
dplStringInclude, | dplStringInclude, | ||
| Line 189: | Line 227: | ||
offset, | offset, | ||
table.concat( params, '\n|' ), | table.concat( params, '\n|' ), | ||
string.rep( dataContentMarker..'%%'..dataContentMarker..',', #includedParamNames ) | string.rep( dataContentMarker..'%%'..dataContentMarker..',', #includedParamNames ), | ||
secseparators | |||
) ) | ) ) | ||
else | else | ||
| Line 205: | Line 244: | ||
table.insert( allIncludedParamNames, includedParamNames ) | table.insert( allIncludedParamNames, includedParamNames ) | ||
return table.concat( queries ) | return table.concat( queries ) | ||
end | end | ||
local function toTable( query ) | local function toTable( query, groupMultiTemplateResults ) | ||
local includedParamNames = table.remove( allIncludedParamNames, 1 ) | local includedParamNames = table.remove( allIncludedParamNames, 1 ) | ||
local usesInclude = #includedParamNames > 0 | local usesInclude = #includedParamNames > 0 | ||
| Line 216: | Line 254: | ||
query = query:gsub( '<p>Extension:DynamicPageList .-</p>', function(item) res.error = item; return '' end ) | query = query:gsub( '<p>Extension:DynamicPageList .-</p>', function(item) res.error = item; return '' end ) | ||
if query: | if query:find( '^@@' ) then -- @@ is used when no result is found | ||
return res | return res | ||
end | end | ||
if usesInclude then | if usesInclude then | ||
query = query:gsub( dataContentMarker..'(.-)'..dataContentMarker, escape ) | query = query:gsub( dataContentMarker..'(.-)'..dataContentMarker, escape ) | ||
end | end | ||
| Line 234: | Line 266: | ||
for _, v in ipairs( query ) do | for _, v in ipairs( query ) do | ||
if v: | if hasContent( v ) and not v:find( '^@@' ) then | ||
v = trim( v ) | v = trim( v ) | ||
local title = v:match( '^|%[%[(.-)|' ) | local title = v:match( '^|%[%[(.-)|' ) | ||
| Line 244: | Line 276: | ||
end | end | ||
else | else | ||
-- When multiple includes are used (e.g. include={Template1},{Template2} or include={Template}:1:2) | -- When multiple includes are used (e.g. include={Template1},{Template2} or include={Template}:1:2) their results are separated by a pipe | ||
rawDataList = split( rawDataList, '|', true ) | rawDataList = split( rawDataList, '|', true ) | ||
local cleanedDataList = {} | local cleanedDataList = {} | ||
for | for _incIndex, dataItem in ipairs( rawDataList ) do | ||
local incIndex = ((_incIndex - 1) % #includedParamNames) + 1 -- Needed in case the same template appears multiple times on the same page | |||
dataItem = unEscape( dataItem ) | dataItem = unEscape( dataItem ) | ||
dataItem = trim( dataItem ) | dataItem = trim( dataItem ) | ||
if includedParamNames[ incIndex ].isTemplate and includedParamNames[ incIndex ].includeAll then -- Check if we included a full template | if includedParamNames[ incIndex ].isTemplate and includedParamNames[ incIndex ].includeAll then -- Check if we included a full template | ||
-- When we include an entire template we use the %ARGS% parameter supplied by dpl. | |||
-- However all | characters are repaced with §, e.g.: | |||
-- §namelessParam | |||
-- §param = text [[wowee§link text]] | |||
-- §param2 = text {{something§something else}} | |||
dataItem = dataItem:gsub( '\127\'"`UNIQ%-%-nowiki%-%x+%-QINU`"\'\127', function(item) return '<nowiki>' .. item .. '</nowiki>' end ) | |||
dataItem = mw.text.unstripNoWiki( dataItem ) -- Unstrip nowiki so we can clean their content | |||
dataItem = fixCurlyBrackets( dataItem ) -- When using the %ARGS% dpl parameter, curly brackets are replaced with ❴ (U+2774) and ❵ (U+2775) | |||
dataItem = dataItem:gsub( '%b{}', function(x) return x:gsub( '§', '|' ) end ) -- Restore pipe characters inside links and templates | |||
dataItem = dataItem:gsub( '%b[]', function(x) return x:gsub( '§', '|' ) end ) | |||
dataItem = dataItem:gsub( '<nowiki>(.-)</nowiki>', function(x) return mw.getCurrentFrame():extensionTag( 'nowiki', x ) end ) -- Restrip nowiki | |||
local _dataItem = {} | local _dataItem = {} | ||
| Line 279: | Line 315: | ||
end | end | ||
if includedParamNames[ incIndex ].isTemplate and not includedParamNames[ incIndex ].includeAll then -- This means there was an include in the form 'include = {template}:param' | local dataListIndex = groupMultiTemplateResults and 1 or math.ceil( _incIndex / #includedParamNames ) | ||
if | |||
includedParamNames[ incIndex ].isTemplate and | |||
not includedParamNames[ incIndex ].includeAll and | |||
not includedParamNames[ incIndex ].hasPhantomTemplate | |||
then -- This means there was an include in the form 'include = {template}:param' | |||
local templateName = includedParamNames[ incIndex ].name | local templateName = includedParamNames[ incIndex ].name | ||
local paramName = includedParamNames[ incIndex ].param | local paramName = includedParamNames[ incIndex ].param | ||
paramName = tonumber( paramName ) or paramName -- Keep as string if tonumber fails | paramName = tonumber( paramName ) or paramName -- Keep as string if tonumber fails | ||
cleanedDataList[ templateName ] = cleanedDataList[ templateName ] or {} | cleanedDataList[ dataListIndex ] = cleanedDataList[ dataListIndex ] or {} | ||
cleanedDataList[ templateName ][ paramName ] = dataItem | cleanedDataList[ dataListIndex ][ templateName ] = cleanedDataList[ dataListIndex ][ templateName ] or {} | ||
if groupMultiTemplateResults and _incIndex > #includedParamNames then | |||
mergeItem( cleanedDataList[ dataListIndex ][ templateName ], paramName, dataItem ) | |||
else | |||
cleanedDataList[ dataListIndex ][ templateName ][ paramName ] = dataItem | |||
end | |||
else | else | ||
local templateName = includedParamNames[ incIndex ].name | |||
cleanedDataList[ dataListIndex ] = cleanedDataList[ dataListIndex ] or {} | |||
if groupMultiTemplateResults and _incIndex > #includedParamNames then | |||
mergeItem( cleanedDataList[ dataListIndex ], templateName, dataItem ) | |||
else | |||
cleanedDataList[ dataListIndex ][ templateName ] = dataItem | |||
end | |||
end | end | ||
end | end | ||
if title and title ~= '' then | if title and title ~= '' then | ||
table.insert( res, { title=title, include= | for _, v in ipairs( cleanedDataList ) do | ||
table.insert( res, { title=title, include=v } ) | |||
end | |||
end | end | ||
end | end | ||
| Line 304: | Line 360: | ||
function dpl.ask( ... ) | function dpl.ask( ... ) | ||
local queries = { ... } | local queries = { ... } | ||
local wantsGrouping = {} | |||
for i = 1, #queries do | for i = 1, #queries do | ||
checkType( 'Module:DPLlua.ask', i, queries[i], 'table' ) | checkType( 'Module:DPLlua.ask', i, queries[i], 'table' ) | ||
table.insert( wantsGrouping, queries[i].groupMultiTemplateResults or false ) | |||
removeFormattingSettings( queries[i] ) | removeFormattingSettings( queries[i] ) | ||
queries[i] = formatDpl( queries[i] ) | queries[i] = formatDpl( queries[i] ) | ||
| Line 318: | Line 376: | ||
for i = 1, #queries do | for i = 1, #queries do | ||
queries[i] = toTable( queries[i] ) | queries[i] = toTable( queries[i], wantsGrouping[i] ) | ||
queries[i].time = time | queries[i].time = time | ||
end | end | ||
| Line 324: | Line 382: | ||
return unpack( queries ) | return unpack( queries ) | ||
end | end | ||
-- function dpl.test() | |||
-- local time = os.clock() | |||
-- local a, b = dpl.ask({ | |||
-- namespace = 'Module', | |||
-- linksto = 'Module:Chart data', | |||
-- distinct = 'strict', | |||
-- ordermethod = 'title', | |||
-- nottitlematch = '%/doc¦%sandbox%¦Exchange/%¦Exchange historical/%¦Chart data', | |||
-- ignorecase = 'true', | |||
-- allowcachedresults = false | |||
-- },{ | |||
-- namespace = 'Module', | |||
-- linksto = 'Module:Enum', | |||
-- distinct = 'strict', | |||
-- ordermethod = 'title', | |||
-- nottitlematch = '%/doc¦%sandbox%¦Exchange/%¦Exchange historical/%¦Enum', | |||
-- ignorecase = 'true', | |||
-- allowcachedresults = false | |||
-- }) | |||
-- mw.logObject(a) | |||
-- mw.logObject(b) | |||
-- local a, b = dpl.ask({ | |||
-- namespace = 'Module', | |||
-- linksto = 'Module:Chart data', | |||
-- distinct = 'strict', | |||
-- ordermethod = 'title', | |||
-- nottitlematch = '%/doc¦%sandbox%¦Exchange/%¦Exchange historical/%¦Chart data', | |||
-- ignorecase = 'true', | |||
-- allowcachedresults = false | |||
-- },{ | |||
-- namespace = '', | |||
-- ignorecase = 'true', | |||
-- uses = 'Template:Infobox Recipe', | |||
-- count = 1, | |||
-- include = '{Infobox Recipe},{Infobox Item}', | |||
-- allowcachedresults = false | |||
-- }) | |||
-- mw.logObject(a) | |||
-- mw.logObject(b) | |||
-- local a = dpl.ask{ | |||
-- namespace = '', | |||
-- uses = 'Template:Infobox Recipe', | |||
-- include = '{Infobox Recipe}:skill:name,{Infobox Item}:update,{Infobox Item|test}', | |||
-- count = 1, | |||
-- ordermethod = 'title', | |||
-- } | |||
-- mw.logObject(a) | |||
-- local q = dpl.ask{ | |||
-- uses = "Template:Collections table", | |||
-- category = "Archaeology collections", | |||
-- -- include = "{Infobox collection}:reward,{Collections table}:1:2:3:4:5:6:7:8:9:10:11:12:13:14:15", | |||
-- include = "{Infobox collection}:reward,{Collections table}", | |||
-- count = 1 | |||
-- } | |||
-- mw.logObject(q) | |||
-- local list = dpl.ask{ | |||
-- namespace = 'Template', | |||
-- uses = 'Template:Navbox', | |||
-- ordermethod = 'title', | |||
-- include = '{Navbox}:gtitle1:gtitle2', | |||
-- count = 1, | |||
-- offset = 3 | |||
-- } | |||
-- mw.logObject(list) | |||
-- local list = dpl.ask{ | |||
-- namespace = 'User', | |||
-- titlematch = 'CephHunter/Sandbox/test1', | |||
-- include = '{User:CephHunter/Sandbox/test2|User:CephHunter/Sandbox/test3},{User:CephHunter/Sandbox/test3}:1', | |||
-- } | |||
-- mw.logObject(list) | |||
-- mw.logObject(dpl.ask{ | |||
-- namespace = 'User', | |||
-- ignorecase = 'true', | |||
-- titlematch = 'CephHunter/Sandbox/test1', | |||
-- include = '{User:CephHunter/Sandbox/test2}' | |||
-- }) | |||
-- mw.logObject(dpl.ask{ | |||
-- namespace = 'Module', | |||
-- uses = 'Template:Helper module', | |||
-- titlematch = '%/doc', | |||
-- nottitlematch = 'Exchange/%|Exchange historical/%|Sandbox/%', | |||
-- ordermethod = 'title', | |||
-- include = '{Helper module}, {Helper module}:example', | |||
-- count = 1, | |||
-- offset = 13 | |||
-- }) | |||
-- mw.logObject(dpl.ask{ | |||
-- namespace = 'Module', | |||
-- titlematch = 'Chart data|Absorbative calculator', | |||
-- nottitlematch = 'Exchange/%|Exchange historical/%|Sandbox/%|%/doc|DPLlua%', | |||
-- ordermethod = 'title', | |||
-- include = '%0' | |||
-- }) | |||
-- mw.logObject(dpl.ask{ | |||
-- uses = 'Template:Collections table', | |||
-- include = '{Collections table}', | |||
-- count = 5 | |||
-- }) | |||
-- mw.log(os.clock()-time) | |||
-- end | |||
return dpl | return dpl | ||
-- </nowiki> | -- </nowiki> | ||