Module:UtilsPage: Difference between revisions

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search
(attempted performance improvement. Didn't achieve the desired result, but keeping here for the record)
No edit summary
 
(27 intermediate revisions by 4 users not shown)
Line 2: Line 2:
local h = {}
local h = {}


local utilsCargo = require("Module:UtilsCargo")
local utilsString = require("Module:UtilsString")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")
local utilsTable = require("Module:UtilsTable")
function p.exists(fullPageName, noRedirect)
local title = mw.title.new(fullPageName)
if not title then
return false
end
local isMainNamespace = utilsString.isEmpty(title.nsText)
local result = p.dpl({
title = title.text,
namespace = title.nsText,
skipthispage = "no",
notnamespace = (not isMainNamespace) and "" or nil, -- For some reason it'll return results for the main namespace without this line
redirects = noRedirect and "exclude" or "include"
})
return #result > 0
end


local SEPARATOR = "#"
local SEPARATOR = "#"
function p.dpl(args)
function p.dpl(args)
local dplArgs = {}
local dplArgs = ""
for k, v in pairs(args) do
for k, v in pairs(args) do
if k == "format" or type(v) == "table" and v.value == "format" then
if k == "format" or type(v) == "table" and v.value == "format" then
mw.addWarning("<code>format</code> argument cannot be used here. Format the resulting Lua table instead.")
mw.addWarning("<code>format</code> argument cannot be used here. Format the resulting Lua table instead.")
elseif type(v) == "table" then
elseif type(v) == "table" then
dplArgs = h.appendArg(dplArgs, v.param, v.value)
for _, andedValue in ipairs(v) do
dplArgs = dplArgs .. h.appendArg(k, andedValue)
end
else
else
dplArgs = h.appendArg(dplArgs, k, v)
dplArgs = dplArgs .. h.appendArg(k, v)
end
end
end
end
dplArgs = h.appendArg(dplArgs, "format", SEPARATOR..",,%PAGE%" .. SEPARATOR .. ",")
dplArgs = dplArgs .. h.appendArg("format", SEPARATOR..",,%PAGE%" .. SEPARATOR .. ",")
local result = h.evaluate(dplArgs)
local result = mw.getCurrentFrame():preprocess("{{#dpl:" .. dplArgs .. "}}")
if not utilsString.endsWith(result, SEPARATOR) then
if not utilsString.endsWith(result, SEPARATOR) then
return {}
return {}
Line 43: Line 30:
return result
return result
end
end
function h.appendArg(args, param, value)
function h.appendArg(param, value)
value = tostring(value)
value = tostring(value)
if not param or not value then
value = string.gsub(value, "\|", "{{!}}")
return args
if param and value then
elseif type(args) == "table" and args[param] then
return "|" .. param .. "=" .. value .. "\n"
-- DPL allows for repeated arguments but this is incompatible with frame:callParserFunction.  
else
-- In this case we must convert the argument set to string and use the more expensive frame:preprocess
return ""
args = h.argsToString(args)
end
end
end
if type(args) == "table" then
 
args[param] = value
function p.exists(fullPageName, noRedirect)
local anchorStart = string.find(fullPageName, "#")
if anchorStart then
fullPageName = string.sub(fullPageName, 1, anchorStart - 1)
end
end
if type(args) == "string" then
local queryResults = utilsCargo.query("_pageData", "_pageName, _isRedirect", {
args = args .. h.argToString(param, value)
where = utilsCargo.allOf({
end
_pageName = fullPageName
return args
})
})
return #queryResults > 0 and (not noRedirect or queryResults[1]._isRedirect == "0")
end
end
function h.argsToString(args)
 
local result = ""
function p.fullUrl(page, queryParams)
for k, v in pairs(args) do
local baseUrl = mw.site.server .. "/"
result = result .. h.argToString(k, v)
local pageUrl = baseUrl ..  mw.uri.encode(page, "WIKI")
if queryParams then
local encodedParams = {}
for k, v in pairs(queryParams) do
local param = k .. "=" .. mw.uri.encode(tostring(v), "QUERY")
table.insert(encodedParams, param)
end
local queryStr = "?" .. table.concat(encodedParams, "&")
pageUrl = pageUrl .. queryStr
end
end
return result
return pageUrl
end
end
function h.argToString(param, value)
 
value = string.gsub(value, "\|", "{{!}}")
function p.getSubpages(fullPageName)
return "|" .. param .. "=" .. value .. "\n"
local title = fullPageName and mw.title.new(fullPageName) or mw.title.getCurrentTitle()
end
local pages = p.dpl({
function h.evaluate(args)
namespace= title.nsText,
if type(args) == "string" then
titlematch= '%' .. title.text .. '/%',
return mw.getCurrentFrame():preprocess("{{#dpl:" .. args .. "}}")
})
else
table.sort(pages)
return mw.getCurrentFrame():callParserFunction({ name = "#dpl:", args = args })
return pages
end
end
end


Line 89: Line 87:
namespace= title.nsText,
namespace= title.nsText,
title= title.text,
title= title.text,
skipthispage= "no",
})
})
return #dplResult ~= 0
return #dplResult ~= 0
Line 99: Line 98:
local title = fullPageName and mw.title.new(fullPageName) or mw.title.getCurrentTitle()
local title = fullPageName and mw.title.new(fullPageName) or mw.title.getCurrentTitle()
return utilsTable.includes(namespaces, title.nsText)
return utilsTable.includes(namespaces, title.nsText)
end
function p.isRedirect(page)
local anchorStart = string.find(page, "#")
if anchorStart then
page = string.sub(page, 1, anchorStart - 1)
end
local queryResults = utilsCargo.query("_pageData", "_pageName, _isRedirect", {
where = utilsCargo.allOf({
_pageName = page
})
})
return #queryResults > 0 and queryResults[1]._isRedirect == "1"
end
end


Line 108: Line 120:
end
end


p.Schemas = {
function p.Schemas()
exists = {
return {
fullPageName = {
exists = {
type = "string",
fullPageName = {
required = true,
type = "string",
desc = "Full page name with namespace prefix.",
required = true,
desc = "Full page name with namespace prefix.",
},
noRedirect = {
type = "boolean",
desc = "If true, redirects are not considered."
},
},
},
noRedirect = {
fullUrl = {
type = "boolean",
fullPageName = {
desc = "If true, redirects are not considered."
type = "string",
required = true,
desc = "Full page name with namespace prefix.",
},
queryParams = {
type = "map",
keys = { type = "string" },
values = {
oneOf = {
{ type = "string" },
{ type = "number" },
},
},
desc = "{{Wp|Query string|Query parameters}}.",
},
},
},
},
getSubpages = {
inCategory = {
fullPageName = {
category = {
type = "string",
type = "string",
desc = "A full page name with namespace prefix. If nil, <code>mw.title.getCurrentTitle()</code> is used.",
required = true,
},
desc = "Category name with or without namespace prefix.",
},
},
fullPageName = {
inCategory = {
type = "string",
category = {
required = true,
type = "string",
desc = "Full page name with namespace prefix."
required = true,
desc = "Category name with or without namespace prefix.",
},
fullPageName = {
type = "string",
required = true,
desc = "Full page name with namespace prefix."
},
},
},
},
inNamespace = {
inNamespace = {
namespaces = {
namespaces = {
desc = "A namespace or array of namespaces.",
desc = "A namespace or array of namespaces.",
required = true,
required = true,
oneOf = {
oneOf = {
{ type = "string" },
{ type = "string" },
{ type = "array", items = { type = "string" } }
{ type = "array", items = { type = "string" } }
},
},
},
fullPageName = {
type = "string",
desc = "Full pagename. Defaults to the name of the current page."
}
},
},
fullPageName = {
stripNamespace = {
type = "string",
page = {
desc = "Full pagename. Defaults to the name of the current page."
required = true,
}
type = "string",
},
desc = "Pagename to strip namespace prefix from.",
stripNamespace = {
},
page = {
namespace = {
required = true,
type = "string",
type = "string",
desc = "Namespace to strip. If nil, any namespace will be stripped."
desc = "Pagename to strip namespace prefix from.",
},
},
},
namespace = {
}
type = "string",
end
desc = "Namespace to strip. If nil, any namespace will be stripped."
},
},
}


p.Documentation = {
function p.Documentation()
exists = {
return {
desc = 'Check whether a page exists. Unlike {{Scribunto Manual|lib=mw.title}}, this function does not register a link in [[Special:WantedPages]]. It also does not count as an "expensive parser function."',
exists = {
params = {"fullPageName", "noRedirect"},
desc = 'Checks whether a page exists using the [[Special:CargoTables/_pageData|_pageData]] Cargo table. Unlike {{Scribunto Manual|lib=mw.title}} this function does not register a link in [[Special:WantedPages]] or [[Special:WhatLinksHere]], nor does it count as an "expensive parser function."',
returns = "Boolean indicating whether the page exists.",
params = {"fullPageName", "noRedirect"},
cases = {
returns = "Boolean indicating whether the page exists.",
{
cases = {
args = {"OoT"},
{
expect = true,
args = {"OoT"},
expect = true,
},
{
args = {"OoT", true},
expect = false,
},
{
desc = "Works for files and file redirects too",
args = {"File:OoT Bomb Bag Model.png"},
expect = true,
},
{
args = {"File:MM Bomb Bag Model.png"},
expect = true,
},
{
args = {"File:MM Bomb Bag Model.png", true},
expect = false,
},
{
desc = "Ignores section anchors",
args = {"Impa#Biography"},
expect = true,
},
},
},
{
},
args = {"OoT", true},
getSubpages = {
expect = false,
params = {"fullPageName"},
returns = "A list of subpages",
cases = {
outputOnly = true,
{
args = {"Module:Constants"},
},
{
args = {"Module:UtilsPage"},
},
},
},
},
},
},
fullUrl = {
dpl = {
desc = "A performant alternative to {{Scribunto Manual|lib=mw.uri.fullUrl}}. Unlike <code>mw.uri.fullUrl</code>, it cannot translate interwiki links. To format the link as an internal link, see [[Module:UtilsMarkup#link]].",
desc = "This function is wrapper for the [[gphelp:Extension:DPL3/Manual|DPL]] parser function.",
params = {"fullPageName", "queryParams"},
params = {"args"},
returns = "The url for the specified wiki page.",
returns = "Array of results. '''Results are limited to a 500 maximum.'''",
cases = {
cases = {
{
{
args = {"Mipha's Grace"},
args = { {titlematch = "Link|Zelda", namespace = "Category"} },
expect = "//zeldawiki.wiki/Mipha%27s_Grace",
expect = {"Category:Link", "Category:Zelda"}
},
{
args = {"Special:Upload", { wpDestFile = "TWWHD Great Fairy Figurine Model.png" } },
expect = "//zeldawiki.wiki/Special:Upload?wpDestFile=TWWHD+Great+Fairy+Figurine+Model.png"
},
{
args = {"New Page", {
action = "edit",
redlink = 1,
}},
expect = "//zeldawiki.wiki/New_Page?action=edit&redlink=1",
},
},
},
{
},
desc = "A special array format exists for specifying repeated arguments",
dpl = {
args = {
desc = "<p>This function is wrapper for the [[gphelp:Extension:DPL3/Manual|DPL]] parser function.</p>"
{
.. "<p>When constructing queries, keep in mind DPL's [https://www.mediawiki.org/wiki/Extension:DynamicPageList3#Configuration configured limits].</p>"
{
.. "<ul>"
param = "category",
..  "<li>A single query can return no more than 500 results. (<code>maxResultCount</code>)</li>"
value = "Lynels",
..  "<li>A single query using <code>category</code> selection may contain no more than 4 categories. (<code>maxCategoryCount</code>)</li>"
},
.. "</ul>",
{
params = {"args"},
param = "notcategory",
returns = "Array of results.",
value = "Enemies in Breath of the Wild",
cases = {
},
{
args = { {titlematch = "Link|Zelda", namespace = "Category"} },
expect = {"Category:Link", "Category:Zelda"}
},
{
desc = "Repeating arguments",
args = {
{
{
param = "notcategory",
category = "Lynels",
value = "Enemies in A Link Between Worlds",
notcategory = {
"Sub-Bosses",
"Enemies in Hyrule Warriors: Age of Calamity",
},
},
},
}
},
expect = {"Blue Lynel", "Red Lynel"},
},
},
expect = {"Purple Lynel", "Blue Lynel"},
},
},
},
},
},
inCategory = {
inCategory = {
params = {"category", "fullPageName"},
params = {"category", "fullPageName"},
returns = "A boolean indicating whether the given page is a member of the given category.",
returns = "A boolean indicating whether the given page is a member of the given category.",
cases = {
cases = {
{
{
desc = "Works with or without the namespace prefix.",
desc = "Works with or without the namespace prefix.",
args = {"Characters in Breath of the Wild", "Link"},
args = {"Characters in Breath of the Wild", "Link"},
expect = true,
expect = true,
},
{
args = {"Category:Characters", "Link"},
expect = true,
},
{
args = {"Items", "Link"},
expect = false,
},
{
args = {"Fakecategory", "Link"},
expect = false,
},
{
desc = "For pages not in the main namespace, the namespace prefix is required.",
args = {"Characters by Game", "Characters in Breath of the Wild"},
expect = false,
},
{
args = {"Characters by Game", "Category:Characters in Breath of the Wild"},
expect = true,
},
},
},
{
},
args = {"Category:Characters", "Link"},
inNamespace = {
expect = true,
params = {"namespaces", "fullPageName"},
},
returns = "<code>true</code> if and only if <code>fullPageName</code> (or the current page) has a namespace prefix that is one of <code>namespaces</code>, regardless of whether the page actually exists.",
{
cases = {
args = {"Items", "Link"},
{
expect = false,
desc = "Main namespace is the empty string.",
},
args = {"", "Link"},
{
expect = true,
args = {"Fakecategory", "Link"},
},
expect = false,
{
},
args = {"Category", "Link"},
{
expect = false,
desc = "For pages not in the main namespace, the namespace prefix is required.",
},
args = {"Characters by Game", "Characters in Breath of the Wild"},
{
expect = false,
args = {"Category", "Category:Link"},
},
expect = true,
{
},
args = {"Characters by Game", "Category:Characters in Breath of the Wild"},
{
expect = true,
desc = "Can evaluate to true even when page does not exist.",
args = {"Category", "Category:Flippityfloppityfloo"},
expect = true,
},
{
desc = "Current page",
args = {"Module"},
expect = true,
},
{
desc = "Multiple namespaces",
args = {{"User", "MediaWiki"}, "Princess Zelda"},
expect = false,
},
{
args = {{"User", "MediaWiki"}, "User:Abdullah"},
expect = true,
},
},
},
},
},
},
isRedirect = {
inNamespace = {
desc = 'Checks whether a page is a redirect using the [[Special:CargoTables/_pageData|_pageData]] Cargo table. Unlike {{Scribunto Manual|lib=mw.title}} this function does not register a link in [[Special:WantedPages]] or [[Special:WhatLinksHere]], nor does it count as an "expensive parser function."',
params = {"namespaces", "fullPageName"},
params = {"page"},
returns = "<code>true</code> if and only if <code>fullPageName</code> (or the current page) has a namespace prefix that is one of <code>namespaces</code>, regardless of whether the page actually exists.",
returns = "<code>true</code> if the page is a redirect, <code>false</code> otherwise.",
cases = {
cases = {
{
{
desc = "Main namespace is the empty string.",
args = {"OoT"},
args = {"", "Link"},
expect = true,
expect = true,
},
},
{
{
args = {"The Legend of Zelda: Ocarina of Time"},
args = {"Category", "Link"},
expect = false,
expect = false,
},
},
{
{
args = {"Notapage"},
args = {"Category", "Category:Link"},
expect = false,
expect = true,
},
},
{
desc = "Can evaluate to true even when page does not exist.",
args = {"Category", "Category:Flippityfloppityfloo"},
expect = true,
},
{
desc = "Current page",
args = {"Module"},
expect = true,
},
{
desc = "Multiple namespaces",
args = {{"User", "MediaWiki"}, "Princess Zelda"},
expect = false,
},
{
args = {{"User", "MediaWiki"}, "User:Abdullah"},
expect = true,
},
},
},
},
},
stripNamespace = {
stripNamespace = {
params = {"page", "namespace"},
params = {"page", "namespace"},
returns = "<code>page</code> with namespace prefix stripped off.",
returns = "<code>page</code> with namespace prefix stripped off.",
cases = {
cases = {
outputOnly = true,
outputOnly = true,
{
{
args = {"Category:Items in Breath of the Wild", "Category"},
args = {"Category:Items in Breath of the Wild", "Category"},
expect = "Items in Breath of the Wild",
expect = "Items in Breath of the Wild",
},
},
{
{
args = {"Items in Breath of the Wild", "Category"},
args = {"Items in Breath of the Wild", "Category"},
expect = "Items in Breath of the Wild",
expect = "Items in Breath of the Wild",
},
},
{
{
args = {"Category:Items in Breath of the Wild", "File"},
args = {"Category:Items in Breath of the Wild", "File"},
expect = "Category:Items in Breath of the Wild",
expect = "Category:Items in Breath of the Wild",
},
},
{
{
args = {"File:TWWHD Tingle Model.png", "File"},
args = {"File:TWWHD Tingle Model.png", "File"},
expect = "TWWHD Tingle Model.png",
expect = "TWWHD Tingle Model.png",
},
},
{
{
args = {"File:TWWHD Tingle Model.png"},
args = {"File:TWWHD Tingle Model.png"},
expect = "TWWHD Tingle Model.png",
expect = "TWWHD Tingle Model.png",
},
},
{
{
args = {":Category:Items in Breath of the Wild"},
args = {":Category:Items in Breath of the Wild"},
expect = "Items in Breath of the Wild",
expect = "Items in Breath of the Wild",
}
}
}
}
}
}
}
}
end


return p
return p

Latest revision as of 01:53, 19 August 2023

This module exports the following functions.

dpl

dpl(args)

This function is wrapper for the DPL parser function.

When constructing queries, keep in mind DPL's configured limits.

  • A single query can return no more than 500 results. (maxResultCount)
  • A single query using category selection may contain no more than 4 categories. (maxCategoryCount)

Returns

  • Array of results.

Examples

#InputOutputResult
1
dpl({
  namespace = "Category",
  titlematch = "Link|Zelda",
})
{"Category:Link", "Category:Zelda"}
Repeating arguments
2
dpl({
  category = "Lynels",
  notcategory = {
    "Sub-Bosses",
    "Enemies in Hyrule Warriors: Age of Calamity",
  },
})
{"Blue Lynel", "Red Lynel"}

exists

exists(fullPageName, [noRedirect])

Checks whether a page exists using the _pageData Cargo table. Unlike mw.title this function does not register a link in Special:WantedPages or Special:WhatLinksHere, nor does it count as an "expensive parser function."

Parameters

Returns

  • Boolean indicating whether the page exists.

Examples

#InputOutputResult
3
exists("OoT")
true
4
exists("OoT", true)
false
Works for files and file redirects too
5
exists("File:OoT Bomb Bag Model.png")
true
6
exists("File:MM Bomb Bag Model.png")
true
7
exists("File:MM Bomb Bag Model.png", true)
false
Ignores section anchors
8
exists("Impa#Biography")
true

fullUrl

fullUrl(fullPageName, [queryParams])

A performant alternative to mw.uri.fullUrl. Unlike mw.uri.fullUrl, it cannot translate interwiki links. To format the link as an internal link, see Module:UtilsMarkup#link.

Parameters

Returns

  • The url for the specified wiki page.

Examples

#InputOutputResultStatus
9
fullUrl("Mipha's Grace")
"//zeldawiki.wiki/Mipha%27s_Grace"
//zeldawiki.wiki/Mipha%27s_Grace
10
fullUrl(
  "Special:Upload",
  {
    wpDestFile = "TWWHD Great Fairy Figurine Model.png",
  }
)
"//zeldawiki.wiki/Special:Upload?wpDestFile=TWWHD+Great+Fairy+Figurine+Model.png"
//zeldawiki.wiki/Special:Upload?wpDestFile=TWWHD+Great+Fairy+Figurine+Model.png
11
fullUrl(
  "New Page",
  {
    action = "edit",
    redlink = 1,
  }
)
"//zeldawiki.wiki/New_Page?action=edit&redlink=1"
//zeldawiki.wiki/New_Page?action=edit&redlink=1

getSubpages

getSubpages([fullPageName])

Parameters

  • [fullPageName]
    A full page name with namespace prefix. If nil, mw.title.getCurrentTitle() is used.

Returns

  • A list of subpages

Examples

#InputOutput
12
getSubpages("Module:Constants")
{
  "Module:Constants/Data",
  "Module:Constants/Documentation",
  "Module:Constants/category/deprecatedParams",
  "Module:Constants/category/deprecatedParams/Documentation",
  "Module:Constants/category/invalidArgs",
  "Module:Constants/category/invalidArgs/Documentation",
  "Module:Constants/category/templateErrors",
  "Module:Constants/class/noexcerpt",
  "Module:Constants/class/noexcerpt/Documentation",
  "Module:Constants/class/pixelArt",
  "Module:Constants/class/pixelArt/Documentation",
  "Module:Constants/class/tooltip",
  "Module:Constants/class/tooltip/Documentation",
  "Module:Constants/number/maxImageArea",
  "Module:Constants/number/maxImageArea/Documentation",
  "Module:Constants/number/maxNavboxPartitionSize",
  "Module:Constants/number/maxNavboxPartitionSize/Documentation",
  "Module:Constants/url/discord",
  "Module:Constants/url/discord/Documentation",
}
13
getSubpages("Module:UtilsPage")
{"Module:UtilsPage/Documentation"}

inCategory

inCategory(category, fullPageName)

Parameters

  • category
    Category name with or without namespace prefix.
  • fullPageName
    Full page name with namespace prefix.

Returns

  • A boolean indicating whether the given page is a member of the given category.

Examples

#InputOutputResult
Works with or without the namespace prefix.
14
inCategory("Characters in Breath of the Wild", "Link")
true
15
inCategory("Category:Characters", "Link")
true
16
inCategory("Items", "Link")
false
17
inCategory("Fakecategory", "Link")
false
For pages not in the main namespace, the namespace prefix is required.
18
inCategory(
  "Characters by Game",
  "Characters in Breath of the Wild"
)
false
19
inCategory(
  "Characters by Game",
  "Category:Characters in Breath of the Wild"
)
true

inNamespace

inNamespace(namespaces, [fullPageName])

Parameters

Returns

  • true if and only if fullPageName (or the current page) has a namespace prefix that is one of namespaces, regardless of whether the page actually exists.

Examples

#InputOutputResult
Main namespace is the empty string.
20
inNamespace("", "Link")
true
21
inNamespace("Category", "Link")
false
22
inNamespace("Category", "Category:Link")
true
Can evaluate to true even when page does not exist.
23
inNamespace("Category", "Category:Flippityfloppityfloo")
true
Current page
24
inNamespace("Module")
true
Multiple namespaces
25
inNamespace({"User", "MediaWiki"}, "Princess Zelda")
false
26
inNamespace({"User", "MediaWiki"}, "User:Abdullah")
true

isRedirect

isRedirect(page)

Checks whether a page is a redirect using the _pageData Cargo table. Unlike mw.title this function does not register a link in Special:WantedPages or Special:WhatLinksHere, nor does it count as an "expensive parser function."

Returns

  • true if the page is a redirect, false otherwise.

Examples

#InputOutputResult
27
isRedirect("OoT")
true
28
isRedirect("The Legend of Zelda: Ocarina of Time")
false
29
isRedirect("Notapage")
false

stripNamespace

stripNamespace(page, [namespace])

Parameters

  • page
    Pagename to strip namespace prefix from.
  • [namespace]
    Namespace to strip. If nil, any namespace will be stripped.

Returns

  • page with namespace prefix stripped off.

Examples

#InputOutputStatus
30
stripNamespace("Category:Items in Breath of the Wild", "Category")
"Items in Breath of the Wild"
31
stripNamespace("Items in Breath of the Wild", "Category")
"Items in Breath of the Wild"
32
stripNamespace("Category:Items in Breath of the Wild", "File")
"Category:Items in Breath of the Wild"
33
stripNamespace("File:TWWHD Tingle Model.png", "File")
"TWWHD Tingle Model.png"
34
stripNamespace("File:TWWHD Tingle Model.png")
"TWWHD Tingle Model.png"
35
stripNamespace(":Category:Items in Breath of the Wild")
"Items in Breath of the Wild"

local p = {}
local h = {}

local utilsCargo = require("Module:UtilsCargo")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")

local SEPARATOR = "#"
function p.dpl(args)
	local dplArgs = ""
	for k, v in pairs(args) do
		if k == "format" or type(v) == "table" and v.value == "format" then
			mw.addWarning("<code>format</code> argument cannot be used here. Format the resulting Lua table instead.")
		elseif type(v) == "table" then
			for _, andedValue in ipairs(v) do
				dplArgs = dplArgs .. h.appendArg(k, andedValue)
			end
		else
			dplArgs = dplArgs .. h.appendArg(k, v)
		end
	end
	dplArgs = dplArgs .. h.appendArg("format", SEPARATOR..",,%PAGE%" .. SEPARATOR .. ",")
	local result = mw.getCurrentFrame():preprocess("{{#dpl:" .. dplArgs .. "}}")
	if not utilsString.endsWith(result, SEPARATOR) then
		return {}
	end
	result = string.gsub(result, SEPARATOR .. ":", SEPARATOR) -- strip : prefix from Category results
	result = utilsString.trim(result, SEPARATOR)
	result = utilsString.split(result, SEPARATOR)
	return result
end
function h.appendArg(param, value)
	value = tostring(value)
	value = string.gsub(value, "\|", "{{!}}")
	if param and value then
		return "|" .. param .. "=" .. value .. "\n"
	else
		return ""
	end
end

function p.exists(fullPageName, noRedirect)
	local anchorStart = string.find(fullPageName, "#")
	if anchorStart then
		fullPageName = string.sub(fullPageName, 1, anchorStart - 1)
	end
	local queryResults = utilsCargo.query("_pageData", "_pageName, _isRedirect", {
		where = utilsCargo.allOf({
			_pageName = fullPageName
		})	
	})
	return #queryResults > 0 and (not noRedirect or queryResults[1]._isRedirect == "0")
end

function p.fullUrl(page, queryParams)
	local baseUrl = mw.site.server .. "/"
	local pageUrl = baseUrl ..  mw.uri.encode(page, "WIKI")
	if queryParams then
		local encodedParams = {}
		for k, v in pairs(queryParams) do
			local param = k .. "=" .. mw.uri.encode(tostring(v), "QUERY")
			table.insert(encodedParams, param) 
		end
		local queryStr = "?" .. table.concat(encodedParams, "&")
		pageUrl = pageUrl .. queryStr
	end
	return pageUrl
end

function p.getSubpages(fullPageName)
	local title = fullPageName and mw.title.new(fullPageName) or mw.title.getCurrentTitle()
	local pages = p.dpl({
		namespace= title.nsText,
		titlematch= '%' .. title.text .. '/%',
	})
	table.sort(pages)
	return pages
end

function p.inCategory(category, fullPageName)
	if (not category) or (not fullPageName) then
		return false
	end
	local title = mw.title.new(fullPageName)
	local dplResult = p.dpl({
		category= p.stripNamespace(category),
		namespace= title.nsText,
		title= title.text,
		skipthispage= "no",
	})
	return #dplResult ~= 0
end

function p.inNamespace(namespaces, fullPageName)
	if type(namespaces) == "string" then
		namespaces = {namespaces}
	end
	local title = fullPageName and mw.title.new(fullPageName) or mw.title.getCurrentTitle()
	return utilsTable.includes(namespaces, title.nsText)
end

function p.isRedirect(page)
	local anchorStart = string.find(page, "#")
	if anchorStart then
		page = string.sub(page, 1, anchorStart - 1)
	end
	local queryResults = utilsCargo.query("_pageData", "_pageName, _isRedirect", {
		where = utilsCargo.allOf({
			_pageName = page
		})	
	})
	return #queryResults > 0 and queryResults[1]._isRedirect == "1"
end

function p.stripNamespace(page, namespace)
	if not namespace then
		namespace = "[^:]*"
	end
	return string.gsub(page, ":?".. namespace..":", "")
end

function p.Schemas() 
	return {
		exists = {
			fullPageName = {
				type = "string",
				required = true,
				desc = "Full page name with namespace prefix.",
			},
			noRedirect = {
				type = "boolean",
				desc = "If true, redirects are not considered."
			},
		},
		fullUrl = {
			fullPageName = {
				type = "string",
				required = true,
				desc = "Full page name with namespace prefix.",
			},
			queryParams = {
				type = "map",
				keys = { type = "string" },
				values = {
					oneOf = {
						{ type = "string" },
						{ type = "number" },
					},
				},
				desc = "{{Wp|Query string|Query parameters}}.",
			},
		},
		getSubpages = {
			fullPageName = {
				type = "string",
				desc = "A full page name with namespace prefix. If nil, <code>mw.title.getCurrentTitle()</code> is used.",
			},
		},
		inCategory = {
			category = {
				type = "string",
				required = true,
				desc = "Category name with or without namespace prefix.",
			},
			fullPageName = {
				type = "string",
				required = true,
				desc = "Full page name with namespace prefix."
			},
		},
		inNamespace = {
			namespaces = {
				desc = "A namespace or array of namespaces.",
				required = true,
				oneOf = {
					{ type = "string" },
					{ type = "array", items = { type = "string" } }
				},
			},
			fullPageName = {
				type = "string",
				desc = "Full pagename. Defaults to the name of the current page."
			}
		},
		stripNamespace = {
			page = {
				required = true,
				type = "string",
				desc = "Pagename to strip namespace prefix from.",
			},
			namespace = {
				type = "string",
				desc = "Namespace to strip. If nil, any namespace will be stripped."
			},
		},
	}
end

function p.Documentation()
	return {
		exists = {
			desc = 'Checks whether a page exists using the [[Special:CargoTables/_pageData|_pageData]] Cargo table. Unlike {{Scribunto Manual|lib=mw.title}} this function does not register a link in [[Special:WantedPages]] or [[Special:WhatLinksHere]], nor does it count as an "expensive parser function."',
			params = {"fullPageName", "noRedirect"},
			returns = "Boolean indicating whether the page exists.",
			cases = {
				{
					args = {"OoT"},
					expect = true,
				},
				{
					args = {"OoT", true},
					expect = false,
				},
				{
					desc = "Works for files and file redirects too",
					args = {"File:OoT Bomb Bag Model.png"},
					expect = true,
				},
				{
					args = {"File:MM Bomb Bag Model.png"},
					expect = true,
				},
				{
					args = {"File:MM Bomb Bag Model.png", true},
					expect = false,
				},
				{
					desc = "Ignores section anchors",
					args = {"Impa#Biography"},
					expect = true,
				},
			},
		},
		getSubpages = {
			params = {"fullPageName"},
			returns = "A list of subpages",
			cases = {
				outputOnly = true,
				{
					args = {"Module:Constants"},
				},
				{
					args = {"Module:UtilsPage"},
				},
			},
		},
		fullUrl = {
			desc = "A performant alternative to {{Scribunto Manual|lib=mw.uri.fullUrl}}. Unlike <code>mw.uri.fullUrl</code>, it cannot translate interwiki links. To format the link as an internal link, see [[Module:UtilsMarkup#link]].",
			params = {"fullPageName", "queryParams"},
			returns = "The url for the specified wiki page.",
			cases = {
				{
					args = {"Mipha's Grace"},
					expect = "//zeldawiki.wiki/Mipha%27s_Grace",
				},
				{
					args = {"Special:Upload", { wpDestFile = "TWWHD Great Fairy Figurine Model.png" } },
					expect = "//zeldawiki.wiki/Special:Upload?wpDestFile=TWWHD+Great+Fairy+Figurine+Model.png"
				},
				{
					args = {"New Page", {
						action = "edit",
						redlink = 1,
					}},
					expect = "//zeldawiki.wiki/New_Page?action=edit&redlink=1",
				},
			},
		},
		dpl = {
			desc = "<p>This function is wrapper for the [[gphelp:Extension:DPL3/Manual|DPL]] parser function.</p>"
				.. "<p>When constructing queries, keep in mind DPL's [https://www.mediawiki.org/wiki/Extension:DynamicPageList3#Configuration configured limits].</p>"
				.. "<ul>"
				..   "<li>A single query can return no more than 500 results. (<code>maxResultCount</code>)</li>"
				..   "<li>A single query using <code>category</code> selection may contain no more than 4 categories. (<code>maxCategoryCount</code>)</li>"
				.. "</ul>",
			params = {"args"},
			returns = "Array of results.",
			cases = {
				{
					args = { {titlematch = "Link|Zelda", namespace = "Category"} },
					expect = {"Category:Link", "Category:Zelda"}
				},
				{
					desc = "Repeating arguments",
					args = {
						{
							category = "Lynels",
							notcategory = {
								"Sub-Bosses",
								"Enemies in Hyrule Warriors: Age of Calamity",
							},
						},
					},
					expect = {"Blue Lynel", "Red Lynel"},
				},
			},
		},
		inCategory = {
			params = {"category", "fullPageName"},
			returns = "A boolean indicating whether the given page is a member of the given category.",
			cases = {
				{
					desc = "Works with or without the namespace prefix.",
					args = {"Characters in Breath of the Wild", "Link"},
					expect = true,
				},
				{
					args = {"Category:Characters", "Link"},
					expect = true,
				},
				{
					args = {"Items", "Link"},
					expect = false,
				},
				{
					args = {"Fakecategory", "Link"},
					expect = false,
				},
				{
					desc = "For pages not in the main namespace, the namespace prefix is required.",
					args = {"Characters by Game", "Characters in Breath of the Wild"},
					expect = false,
				},
				{
					args = {"Characters by Game", "Category:Characters in Breath of the Wild"},
					expect = true,
				},
			},
		},
		inNamespace = {
			params = {"namespaces", "fullPageName"},
			returns = "<code>true</code> if and only if <code>fullPageName</code> (or the current page) has a namespace prefix that is one of <code>namespaces</code>, regardless of whether the page actually exists.",
			cases = {
				{
					desc = "Main namespace is the empty string.",
					args = {"", "Link"},
					expect = true,
				},
				{
					args = {"Category", "Link"},
					expect = false,
				},
				{
					args = {"Category", "Category:Link"},
					expect = true,
				},
				{
					desc = "Can evaluate to true even when page does not exist.",
					args = {"Category", "Category:Flippityfloppityfloo"},
					expect = true,
				},
				{
					desc = "Current page",
					args = {"Module"},
					expect = true,
				},
				{
					desc = "Multiple namespaces",
					args = {{"User", "MediaWiki"}, "Princess Zelda"},
					expect = false,
				},
				{
					args = {{"User", "MediaWiki"}, "User:Abdullah"},
					expect = true,
				},
			},
		},
		isRedirect = {
			desc = 'Checks whether a page is a redirect using the [[Special:CargoTables/_pageData|_pageData]] Cargo table. Unlike {{Scribunto Manual|lib=mw.title}} this function does not register a link in [[Special:WantedPages]] or [[Special:WhatLinksHere]], nor does it count as an "expensive parser function."',
			params = {"page"},
			returns = "<code>true</code> if the page is a redirect, <code>false</code> otherwise.",
			cases = {
				{
					args = {"OoT"},
					expect = true,
				},
				{
					args = {"The Legend of Zelda: Ocarina of Time"},
					expect = false,
				},
				{
					args = {"Notapage"},
					expect = false,
				},
			},
		},
		stripNamespace = {
			params = {"page", "namespace"},
			returns = "<code>page</code> with namespace prefix stripped off.",
			cases = {
				outputOnly = true,
				{
					args = {"Category:Items in Breath of the Wild", "Category"},
					expect = "Items in Breath of the Wild",
				},
				{
					args = {"Items in Breath of the Wild", "Category"},
					expect = "Items in Breath of the Wild",
				},
				{
					args = {"Category:Items in Breath of the Wild", "File"},
					expect = "Category:Items in Breath of the Wild",
				},
				{
					args = {"File:TWWHD Tingle Model.png", "File"},
					expect = "TWWHD Tingle Model.png",
				},
				{
					args = {"File:TWWHD Tingle Model.png"},
					expect = "TWWHD Tingle Model.png",
				},
				{
					args = {":Category:Items in Breath of the Wild"},
					expect = "Items in Breath of the Wild",
				}
			}
		}
	}
end

return p