Editing Module:DPLlua

Warning: You are not logged in. Once you make an edit, a temporary account will be created for you. Learn more. Log in or create an account to continue receiving notifications after this account expires, and to access other features.
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision Your text
Line 1: Line 1:
-- <nowiki>
-- <nowiki>
local dpl = {}
local dpl = {}
local libraryUtil = require( 'libraryUtil' )
local hasContent = require( 'Module:Paramtest' ).has_content
local checkType = libraryUtil.checkType
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg


dpl.pipe = '¦'
dpl.pipe = '¦'
local dataContentMarker = '`#@@#`'
local dataContentMarker = '`#@@#`'
local allIncludedParamNames = {}
local usesInclude = {}


-- Custom function for splitting a string because mw.text.split() is waaay too slow
-- Custom function for splitting a string because mw.text.split() is waaay too slow
local function split( str, pattern, plain )
local function split( str, pattern, plain )
local res = {}
local res = {}
local continue = true
continue = true
local startIndex = 1
local startIndex = 1


Line 32: Line 28:
-- Also custom function for speed
-- Also custom function for speed
local function trim( str )
local function trim( str )
return (string.gsub( str, '^%s+', '' ):gsub( '%s+$', '' ))
return string.match( str, '^%s*(.-)%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 = {
['{'] = '&#123;',
['{'] = '&#123;',
['\180'] = '&#123;', -- Wtf dpl...
['}'] = '&#125;',
['}'] = '&#125;',
['\181'] = '&#125;',
['['] = '&#91;',
['['] = '&#91;',
[']'] = '&#93;',
[']'] = '&#93;',
['|'] = '&#124;',
['|'] = '&#124;',
['-'] = '&#8208;'
['-'] = '&#8208;',
['\226'] = '',
['\157'] = '',
}
}
local function escape( str )
local function escape( str )
return (string.gsub( str, '[{}%[%]|%-]', escapeChars ))
-- the \226\157\180\181 are used to match ❴ (U+E29DB4) and ❵ (U+E29DB5) wich are 3 bytes long so
-- we can't use them directly inside [] patterns. Ustring would fix this but it's way too slow.
str = string.gsub( str, '[{}%[%]|%-\226\157\180\181]', escapeChars )
return str
end
end


Line 68: Line 59:
}
}
local function unEscape( str )
local function unEscape( str )
return (string.gsub( str, '&#%d+;', unEscapeChars ))
str = string.gsub( str, '&#%d+;', unEscapeChars )
end
return str
 
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 105: Line 91:
'suppresserrors',
'suppresserrors',
'noresultsfooter',
'noresultsfooter',
'format',
'format'
'groupMultiTemplateResults'
}
}


Line 115: Line 100:


local function formatInclude( query )
local function formatInclude( query )
checkTypeForNamedArg( 'Module:DPLlua.ask', 'include', query, 'string' )
query = split( query, ',', true )
query = split( query, ',', true )
local includedParamNames = {}
local count = #query
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, extra = query[i]:match( '{(.-)[¦|}](.*)' )
local templateName, params = query[i]:match( '{(.-)[¦|}]([^,]*)' )
if hasContent( extra ) then
if params:find( '%S' ) then
local phantomTemplateName = extra:match( '^(.-)}' ) or extra:match( '^[./].+' )
params:gsub( '^:%-', '' )
local phantomTemplatePrefix = extra:match( '^(.-)}' ) and '' or templateName
query[i] = string.format( '{%s}%s', templateName, params )
local params = extra:gsub( '^.-}', '' ):gsub( '^[./].+', '' ):gsub( ':%-', '' )
for _ in params:gmatch( ':' ) do
local sur = hasContent( phantomTemplateName ) and ('¦' .. phantomTemplatePrefix .. phantomTemplateName) or ''
count = count + 1
query[i] = string.format( '{%s%s}%s', templateName, sur, params )
 
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( sectionAttributes, { hasPhantomTemplate=false } )
end
end
else
table.insert( includedParamNames, { name=trim( query[i] ) } )
table.insert( sectionAttributes, { hasPhantomTemplate=false } )
end
end
end
end


return table.concat( query, ',' ), includedParamNames, sectionAttributes
return table.concat( query, ',' ), count
end
end


Line 158: Line 125:
local count = query.count or 500
local count = query.count or 500
local offset = query.offset or 0
local offset = query.offset or 0
local usesInclude = false
local _usesInclude = false
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
-- We use table format when the include parameter is used to make sure we can
-- is needed to add dataContentMarkers when a phantom template is used
-- differentiate between the results in case more than one item is included
local dplStringInclude =
local dplStringInclude =
[=[
[=[
{{#dpl:
{{#dpl:
|noresultsheader=@@
|noresultsheader = @@
|count=%s
|count=%s
|offset=%s
|offset=%s
|%s
|%s
|table=,
|table=,
|listseparators=,\n¦-\n¦[[%%PAGE%%¦]],,
|tablerow=%s
|tablerow=%s
|secseparators=%s
}}]=]
}}]=]


Line 184: Line 147:
[=[
[=[
{{#dpl:
{{#dpl:
|noresultsheader=@@
|noresultsheader = @@
|count=%s
|count=%s
|offset=%s
|offset=%s
Line 195: Line 158:
for i = 1, math.ceil( count / 500 ) do
for i = 1, math.ceil( count / 500 ) do
local params = {}
local params = {}
local includeCount = 0


for k, v in pairs( query ) do
for k, v in pairs( query ) do
if k == 'include' then
if k == 'include' then
v, includedParamNames, sectionAttributes = formatInclude( v )
v, includeCount = formatInclude( v )
usesInclude =  true
_usesInclude =  true
end
 
if type( v ) == 'table' then
for _, x in ipairs( v ) do
table.insert( params, k .. '=' .. tostring( x ):gsub( '|', '¦' ) )
end
else
table.insert( params, k .. '=' .. tostring( v ):gsub( '|', '¦' ) )
end
end
table.insert( params, k .. '=' .. tostring( v ):gsub( '|', '¦' ) )
end
end


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 227: Line 174:
offset,
offset,
table.concat( params, '\n|' ),
table.concat( params, '\n|' ),
string.rep( dataContentMarker..'%%'..dataContentMarker..',', #includedParamNames ),
string.rep( dataContentMarker..'%%'..dataContentMarker..',', includeCount )
secseparators
) )
) )
else
else
Line 243: Line 189:
end
end


table.insert( allIncludedParamNames, includedParamNames )
table.insert( usesInclude, _usesInclude )
 
return table.concat( queries )
return table.concat( queries )
end
end


local function toTable( query, groupMultiTemplateResults )
local function toTable( query )
local includedParamNames = table.remove( allIncludedParamNames, 1 )
local _usesInclude = table.remove( usesInclude, 1 )
local usesInclude = #includedParamNames > 0
local res = {}
local res = {}
 
query = query:gsub( '<p>Extension:DynamicPageList .-</p>', function(item) res.error = item; return '' end )
if not query:match( '^@@' ) then -- @@ is used when no result is found
 
if _usesInclude then
if query:find( '^@@' ) then -- @@ is used when no result is found
query = query:gsub( dataContentMarker..'(.-)'..dataContentMarker, escape )
return res
query = query:gsub( '{|.-|%-', '') -- Remove the header of the table
end
-- Replace the footer of the table width a row indicator. This effectively
 
-- combines the output of multiple dpl queries when count > 500
if usesInclude then
query = query:gsub( '|}', '|-' )
query = query:gsub( dataContentMarker..'(.-)'..dataContentMarker, escape )
end
end
 
query = trim( query )
query = trim( query )
query = split( query, '|-', true ) -- Results of the returned pages are separated by |-
query = split( query, '|-', true ) -- Results of the returned pages are separated by |-
 
for _, v in ipairs( query ) do
for _, v in ipairs( query ) do
if v:match( '%S' ) then
if hasContent( v ) and not v:find( '^@@' ) then
v = trim( v )
v = trim( v )
local title = v:match( '^|%[%[(.-)|' )
local title = v:match( '^|%[%[(.-)|' )
local dataList = v:match( '^|.-|.-|(.*)' ) -- This is everything after the title
local rawDataList = v:match( '^|.-|.-|(.*)' ) -- This is everything after the title
 
if not _usesInclude then
if not usesInclude then
if title and title ~= '' then
if title and title ~= '' then
table.insert( res, title )
table.insert( res, title )
end
end
else
else
-- When multiple includes are used (e.g. include={Template1},{Template2}) its results are separated by a pipe
-- When multiple includes are used (e.g. include={Template1},{Template2} or include={Template}:1:2) their results are separated by a pipe
dataList = split( dataList, '|', true )
rawDataList = split( rawDataList, '|', true )
local _dataList = {}
local cleanedDataList = {}
 
for _, dataItem in ipairs( dataList ) do
for _incIndex, dataItem in ipairs( rawDataList ) do
dataItem = unEscape( dataItem )
local incIndex = ((_incIndex - 1) % #includedParamNames) + 1 -- Needed in case the same template appears multiple times on the same page
dataItem = unEscape( dataItem )
dataItem = trim( dataItem )
 
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.
-- When we include an entire template we use the %ARGS% parameter supplied by dpl.
-- However all | characters are repaced with §, e.g.:
-- However all | characters are repaced with §, e.g.:
-- §namelessParam
-- §nameLessParam
-- §param = text [[wowee§link text]]
-- §param = text [[wowee§link text]]
-- §param2 = text {{something§something else}}
-- §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 ) -- Restore pipe characters inside links and templates
dataItem = dataItem:gsub( '%b[]', function(x) return x:gsub( '§', '|' ) end )
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
dataItem = trim( dataItem )
local _dataItem = {}
 
if dataItem:match( '§' ) then -- Check if we included a template
if dataItem ~= '' then
dataItem = split( dataItem, '§', true )
dataItem = split( dataItem:sub( 3 ), '§' ) -- The sub(3) removes the first § at the start. § is 2 bytes wide so start at index 3
local _dataItem = {}
 
for i, item in ipairs( dataItem ) do
for i, item in ipairs( dataItem ) do
if item:find( '=' ) then -- Check if the parameter is named or unnamed
if i ~= 1 then -- skip first item as it is a false empty string created by splitting on § when the string started with a §
local param, value = item:match( '^%s*(.-)%s*=%s*(.-)%s*$' )
if item:find( '=' ) then -- Check if the parameter is named or unnamed
_dataItem[ param ] = value
local param, value = item:match( '^%s*(.-)%s*=%s*(.-)%s*$' )
else
_dataItem[ param ] = value
table.insert( _dataItem, trim( item ) )
else
table.insert( _dataItem, trim( item ) )
end
end
end
end
end
dataItem = _dataItem
end
end
 
dataItem = _dataItem
table.insert( _dataList, dataItem )
end
end
 
local dataListIndex = groupMultiTemplateResults and 1 or math.ceil( _incIndex / #includedParamNames )
if title and title ~= '' then
if
table.insert( res, { title=title, include=_dataList } )
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 paramName = includedParamNames[ incIndex ].param
paramName = tonumber( paramName ) or paramName -- Keep as string if tonumber fails
cleanedDataList[ dataListIndex ] = cleanedDataList[ dataListIndex ] or {}
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
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
 
if title and title ~= '' then
for _, v in ipairs( cleanedDataList ) do
table.insert( res, { title=title, include=v } )
end
end
end
end
Line 359: Line 271:
-- Combinig multiple dpl queries yields better performance than doing them sequentially
-- Combinig multiple dpl queries yields better performance than doing them sequentially
function dpl.ask( ... )
function dpl.ask( ... )
local formatTime = os.clock()
local queries = { ... }
local queries = { ... }
local wantsGrouping = {}


for i = 1, #queries do
for i = 1, #queries do
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] )
end
end
formatTime = os.clock() - formatTime


local DPLtime = os.clock()
queries = table.concat( queries, '$@µ@$' )
queries = table.concat( queries, '$@µ@$' )
local time = os.clock()
queries = mw.getCurrentFrame():preprocess( queries )
queries = mw.getCurrentFrame():preprocess( queries )
time = os.clock() - time
queries = split( queries, '$@µ@$', true )
queries = split( queries, '$@µ@$', true )
DPLtime = os.clock() - DPLtime


for i = 1, #queries do
for i = 1, #queries do
local parseTime = os.clock()
queries[i] = toTable( queries[i] )
queries[i] = toTable( queries[i], wantsGrouping[i] )
queries[i].time = time
parseTime = os.clock() - parseTime
queries[i]['DPL time'] = DPLtime
queries[i]['Parse time'] = math.floor( (formatTime + parseTime) * 1e5 ) / 1e5 -- os.clock() has a resolution of 10µs
end
end


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 = 50,
-- 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 = 50,
-- 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 = 100
-- }
-- mw.logObject(q)
-- local q = dpl.ask{
-- namespace = "",
-- uses = "Template:Infobox Spell",
-- notcategory = {"Removed content", "Removed spells"},
-- nottitlematch = {"Enchant Crossbow Bolt", "Storm of Armadyl"},
-- include = "{Infobox Spell}",
-- }
-- 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>
Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see Deadlock:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template

Page included on this page: