Module:TempScriptItem: Difference between revisions

From GTA Connected
Jump to navigation Jump to search
(temporary page for testing purposes)
 
No edit summary
 
(5 intermediate revisions by the same user not shown)
Line 491: Line 491:
local entries = p.getIndexedArgs(frame, 'desc')
local entries = p.getIndexedArgs(frame, 'desc')
local startTextFirstLine = "The <span style=\"font-family: 'Source Code Pro', monospace;\">"..p.getDisplayedNameColoured(frame).."</span> [[HowTo/Functions|"..args.type.."]] is "..(args.type == "event" and "invoked when" or "used to").." "
local startTextFirstLine = "The <span style=\"font-family: 'Source Code Pro', monospace;\">"..p.getDisplayedNameColoured(frame).."</span> "..args.type:lower().." is "..(args.type == "event" and "invoked when" or "used to").." "
for i,entry in ipairs(entries) do
for i,entry in ipairs(entries) do
Line 518: Line 518:
for i,arg in ipairs(entries) do
for i,arg in ipairs(entries) do
local tokens = p.split(arg, ' ')
local tokens = p.split(arg, ' ')
 
local isOptional = tokens[1]:sub(1, 1) == '['
if isOptional then
tokens[1] = tokens[1]:sub(2, tokens[1]:len())
tokens[4] = tokens[4]:sub(1, tokens[4]:len() - 1)
end
local defaultValue = isOptional and "<span style='font-family: \"Source Code Pro\", monospace;'>"..tokens[4].."</span>" or "n/a"
local description = p.formatDescription(table.concat(tokens, ' ', isOptional and 5 or 3))
if isEvent then
if isEvent then
rows[i] = { (i + 1)..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), description }
rows[i] = { (i + 1)..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), p.formatDescription(table.concat(tokens, ' ', 3, #tokens)) }
else
else
rows[i] = { i..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), (isOptional and ("Optional, defaults to "..defaultValue..". ") or "")..description }
local parts = p.getArgParts(arg)
 
if parts.optional then
if parts.defaultValueIsSpecified then
rows[i] = { i..") ", p.formatType(parts.type, frame), p.formatName(parts.name, frame), "Optional, defaults to "..p.formatDefaultValue(parts.defaultValue, frame)..". "..p.formatDescription(parts.description) }
else
rows[i] = { i..") ", p.formatType(parts.type, frame), p.formatName(parts.name, frame), "Optional, the default value has not been documented here yet. "..p.formatDescription(parts.description) }
end
else
rows[i] = { i..") ", p.formatType(parts.type, frame), p.formatName(parts.name, frame), p.formatDescription(parts.description) }
end
end
end
end
end
Line 1,151: Line 1,153:
function p.formatName(name, frame)
function p.formatName(name, frame)
return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getNameRgb()..";\"'>"..name.."</span>"
return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getNameRgb()..";\"'>"..name.."</span>"
end
function p.formatDefaultValue(value, frame)
return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getNameRgb()..";\"'>"..value.."</span>"
end
end


Line 1,168: Line 1,174:


function p.standardizeNullType(type)
function p.standardizeNullType(type)
type = type:lower()
local type2 = type:lower()
if type == 'void' or type == 'null' or type == 'undefined' or type == 'n/a' then
if type2 == 'void' or type2 == 'null' or type2 == 'undefined' or type2 == 'n/a' then
return 'void'
return 'void'
else
else

Latest revision as of 13:09, 29 February 2024

Documentation for this module may be created at Module:TempScriptItem/doc

local p = {}

-- settings
p.categoryItems = {
	-- Category Name		= table. (Fully resolved item name(s), case-sensitive.) (Event names must start with a capital letter "O", to match the dumpdoc XML.)
	['Audio']				= {'gta.addOneOffSound', 'gta.playFrontEndSound', 'gta.playSuspectLastSeen'},
	['Camera']				= {'gta.fading'},
	['Chatbox']				= {'message', 'messageClient', 'messageAllExcept', 'setChatWindowEnabled', 'OnChatOutput'},
	['Cursor']				= {'gta.getMouseSpeed'},
	['CustomText']			= {'gta.setCustomText', 'gta.setCustomText', 'gta.clearCustomText', 'gta.printBig'},
	['Drawing']				= {'gta.drawRectangle'},
	['Effect']				= {'gta.addParticleEffect', 'addParticleEffect', 'gta.addMovingParticleEffect', 'addMovingParticleEffect', 'gta.createSingleParticle', 'createSingleParticle'},
	['Font']				= {'font.render'},
	['GarbageCollection']	= {'collectAllGarbage'},
	['Game']				= {'gta.game', 'gta.standardControls', 'gta.gameSpeed', 'gta.loadDFF', 'gta.loadTXD', 'gta.loadCOL'},
	['GameStat']			= {'gta.setGameStat', 'gta.getGameStat'},
	['GUI']					= {'OnGUIClick'},
	['Key']					= {'isScancodePressed', 'OnCharacter'},
	['KillFrenzy']			= {'gta.startKillFrenzy'},
	['Message']				= {'gta.clearMessages', 'gta.smallMessage', 'gta.bigMessage', 'gta.pagerMessage'},
	['Miscellaneous']		= {'platform', 'toColour', 'gta.tickCount', 'getRandomSkin', 'gta.getRandomSkin', 'gta.processLineOfSight', 'processLineOfSight', 'gta.setIsland', 'gta.setDefaultInteriors'},
	['Network']				= {'isConnected', 'isConnecting', 'connect', 'disconnect', 'triggerNetworkEvent', 'OnDisconnect'},
	['Path']				= {'gta.trafficDensity', 'gta.setTrafficEnabled', 'setTrafficEnabled', 'gta.setCiviliansEnabled', 'setCiviliansEnabled'},
	['Ped']					= {'gta.tommyFatness'},
	['Radio']				= {'gta.setRadioChannel', 'gta.forceRadioChannel'},
	['Reflection']			= {'exportFunction', 'setErrorMode'},
	['Renderware']			= {'gta.rwRenderStateSet'},
	['SaveGame']			= {'gta.saveGame'},
	['Screen']				= {'gta.aspectRatio', 'gta.width', 'gta.height'},
	['Script']				= {'gta.terminateScript', 'gta.getActiveScripts', 'gta.scriptCommand'},
	['Timer']				= {'setImmediate', 'clearImmediate', 'setInterval', 'clearInterval', 'setTimeout', 'clearTimeout'},
	['Weather']				= {'snowing', 'forceSnowing'},
	['Window']				= {'OnFocus', 'OnLostFocus'},
	['World']				= {'gta.ssvBridgeEnabled', 'gta.planesEnabled', 'gta.trainsEnabled', 'setPlanesEnabled', 'setTrainsEnabled', 'world', 'gta.findGroundZForCoord', 'findGroundZForCoord', 'gta.setTrainsEnabled', 'gta.setPlanesEnabled'}
}

p.categoryWords = {
	-- Category Names. (String(s) to look for in an item name, case-INsensitive.)
	'Blip', 'Building', 'Camera', 'Chatbox', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo',
	'Element', 'Entity', 'Event', 'File', 'Font', 'Garage', 'HTTP', 'HUD', 'Key', 'Mission', 'Mouse', 'NetFlags', 'Network', 'Object',
	'Pause', 'Ped', 'Pickup', 'Player', 'Process', 'Render', 'Resource', 'Screen', 'Socket', 'Sphere', 'Time', 'Vehicle', 'Weather', 'World'
}

-- entry point
function p.main(frame)
	local args = p.getArgs(frame)
	
	if args.type == 'variable' then
		return p.showVariablePage(frame)
	elseif args.type == 'function' then
		return p.showFunctionPage(frame)
	elseif args.type == 'property' then
		return p.showPropertyPage(frame)
	elseif args.type == 'method' then
		return p.showMethodPage(frame)
	elseif args.type == 'event' then
		return p.showEventPage(frame)
	else
		return "This page has been documented incorrectly, '''type''' is missing."
	end
end

-- item page types
function p.showVariablePage(frame)
	return p.showPage(frame, { "Value" })
end

function p.showFunctionPage(frame)
	return p.showPage(frame, { "Parameters", "Return", "Callbacks" })
end

function p.showPropertyPage(frame)
	return p.showPage(frame, { "Value" })
end

function p.showMethodPage(frame)
	return p.showPage(frame, { "Parameters", "Return", "Callbacks" })
end

function p.showEventPage(frame)
	return p.showPage(frame, { "Parameters", "Attributes" })
end

-- item page
function p.showPage(frame, parts)
	local args = p.getArgs(frame)
	if args.type ~= 'event' then
		table.insert(parts, "Types")
	end
	--table.insert(parts, "Symbols")
	local lines = p.getPageTopLines(frame)
	for i,part in ipairs({"Notes", "Examples", "Compatibility", "Related"}) do
		table.insert(parts, part)
	end
	for i,part in ipairs(parts) do
		local data = p[part:lower() == 'return' and 'returns' or part:lower()](frame)
		if data and #data > 0 then
			if part == 'Settable' then
				part = args.readonly == 'true' and 'Read-Only' or 'Read and Set'
			end
			table.insert(lines, '== '..part..' ==')
			table.insert(lines, data)
		end
	end
	return table.concat(lines, "\n")
end

function p.getPageTopLines(frame)
	local lines = {}
	table.insert(lines, frame:preprocess("{{DISPLAYTITLE:"..p.getDisplayedName(frame).."}}"))
	table.insert(lines, "__NOTOC__"..p.getPageTopBoxes(frame).."\n")
	table.insert(lines, "<div style='margin-top: 20px;'>Available since "..p.since(frame).."</div>\n")
	table.insert(lines, "<div style='margin-top: 6px;'>\n"..p.syntax(frame).."</div>\n")
	table.insert(lines, "<div style='margin-top: 20px;'>\n"..p.description(frame).."</div>\n")
	return lines
end

function p.getPageTopBoxes(frame)
	local args = p.getArgs(frame)
	
	local topBoxes = { "type", "side", "games", "online" }
	if args.type == 'variable' or args.type == 'property' then
		table.insert(topBoxes, "settable")
	elseif args.type == 'event' then
		table.insert(topBoxes, "cancellable")
	end
	
	local outBoxes = {}
	for i,topBox in ipairs(topBoxes) do
		table.insert(outBoxes, p[topBox.."Box"](frame))
	end
	return table.concat(outBoxes, ' ').."<br>"
end

-- template arguments
function p.getArgs(frame)
	if p.argsCache then
		return p.argsCache
	else
		local args = frame:getParent().args
		p.copyOldToNewForTemplateArguments(frame, args)
		p.copyNonIndexedToIndexedTemplateArguments(frame, args)
		p.populateTypeSpecificTemplateArguments(frame, args)
		p.populateDefaultTemplateArguments(frame, args)
		p.argsCache = args
		return args
	end
end

function p.copyOldToNewForTemplateArguments(frame, args)
	--[[
	if args.parameters then
		args.syntax = args.parameters
	end
	if args.parametersSS then
		args.syntaxSS = args.parametersSS
	end
	if args.parametersCS then
		args.syntaxCS = args.parametersCS
	end
	
	if args.callbackParameters then
		args.callbackSyntax = args.callbackParameters
	end
	if args.callbackParametersSS then
		args.callbackSyntaxSS = args.callbackParametersSS
	end
	if args.callbackParametersCS then
		args.callbackSyntaxCS = args.callbackParametersCS
	end
	
	if args.type == 'event' then
		if args.callbackParameters then
			args.syntax = args.callbackParameters
		end
		if args.callbackParametersSS then
			args.syntaxSS = args.callbackParametersSS
		end
		if args.callbackParametersCS then
			args.syntaxCS = args.callbackParametersCS
		end
	end

	if args.returnType or args.returnTypes then
		local returnType = args.returnType and args.returnType or args.returnTypes
		if args.returnInfo then
			args.return1 = returnType.." "..args.returnInfo
			args.value = returnType.." "..args.returnInfo
		else
			args.return1 = returnType
			args.value = returnType
		end
	end
	]]

	if args['return'] then
		args.value = args['return']
	end
	
	if args.usage then
		args.desc = args.usage
	end
	
	if args.notes then
		local notes = p.splitLines(args.notes)
		for i,note in ipairs(notes) do
			args['note'..i] = note
		end
	end
	
	local exampleKeys = {
		'example', 'exampleSS', 'exampleCS', 'exampleJS', 'exampleLua', 'exampleSquirrel',
		'exampleJSSS', 'exampleJSCS', 'exampleLuaSS', 'exampleLuaCS', 'exampleSquirrelSS', 'exampleSquirrelCS'
	}
	for i,exampleKey in ipairs(exampleKeys) do
		if args[exampleKey] then
			local examples = p.split(args[exampleKey], '<br>')
			for i,example in ipairs(examples) do
				args[exampleKey..i] = example
			end
		end
	end
	
	if args.bcName and args.bcMaxVersion then
		args.previous1 = args.bcName.." "..args.bcMaxVersion
	end
end

function p.copyNonIndexedToIndexedTemplateArguments(frame, args)
	if args.description then
		args.description1 = args.description
	end
end

function p.populateTypeSpecificTemplateArguments(frame, args)
	if args.type == 'variable' or args.type == 'property' then
		if args.value then
			local tokens = p.split(args.value, ' ')
			local type = tokens[1]
			
			local name
			if args.type == 'property' then
				name = args.class.."."..args.name
			else
				name = args.name
			end
			
			args.syntax = type.." "..name
		end
	end
end

function p.populateDefaultTemplateArguments(frame, args)
	if not args.since then
		args.since = "1.0.0 1.0.0"
	end
	
	if args.syntax and args.syntax:lower() == "void" then
		args.parameter1 = "void"
	end
end

-- item top boxes
function p.typeBox(frame)
	local args = p.getArgs(frame)
	local typeText = p.getType(args.type)
	return p.loadTemplate(frame, 'GreenInformationBox', {typeText,id='type'});
end

function p.sideBox(frame)
	local args = p.getArgs(frame)
	local sideText, isShared = p.getSide(args.side)
	if isShared then
		return p.loadTemplate(frame, 'GreenInformationBox', {sideText,id='side'});
	else
		return p.loadTemplate(frame, 'RedInformationBox', {sideText,id='side'});
	end
end

function p.gamesBox(frame)
	local args = p.getArgs(frame)

	local gamesText = p.getGames(args.games)

	local games2 = { 'iii', 'vc', 'sa', 'iv' }
	local counter = 0
	for i2,game2 in ipairs(games2) do
		if gamesText:lower():find(game2) then
			counter = counter + 1
		end
	end

	if counter == #games2 or gamesText == "All Games" then
		return p.loadTemplate(frame, 'GreenInformationBox', {table.concat({
			p.loadTemplate(frame, 'icon-iii'),
			p.loadTemplate(frame, 'icon-vc'),
			p.loadTemplate(frame, 'icon-sa'),
			p.loadTemplate(frame, 'icon-iv')
		}, ' '), id='games'})
	else
		local games = { 'iii', 'vc', 'sa', 'iv' }
		local out = {}
		for i,game in ipairs(games) do
			if gamesText:lower():find(game) then
				table.insert(out, p.loadTemplate(frame, 'icon-'..game, {}))
			end
		end
		return p.loadTemplate(frame, 'RedInformationBox', {table.concat(out, ' '), id='games'})
	end
end

function p.onlineBox(frame)
	local args = p.getArgs(frame)
	if args.sp == 'true' or args.online == 'false' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Offline Only',id='online'});
	elseif args.sp == 'false' or args.offline == 'false' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Online Only',id='online'});
	else
		return p.loadTemplate(frame, 'GreenInformationBox', {'Online and Offline',id='online'});
	end
end

function p.settableBox(frame)
	local args = p.getArgs(frame)
	if args.readonly == 'true' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Read-Only',id='settable'});
	else
		return p.loadTemplate(frame, 'GreenInformationBox', {'Read and Set',id='settable'});
	end
end

function p.cancellableBox(frame)
	local args = p.getArgs(frame)
	if p.isCancellable(frame)then
		return p.loadTemplate(frame, 'GreenInformationBox', {'Cancellable',id='cancellable'});
	else
		return p.loadTemplate(frame, 'RedInformationBox', {'Not Cancellable',id='cancellable'});
	end
end

-- item parts
function p.since(frame)
	local args = p.getArgs(frame)
	if not args.since then
		return p.documentationMissing(frame, "Available since")
	else
		local sideVersions = p.getSideVersions(args.since, args.side)
		if #sideVersions == 2 then
			return "'''Server "..sideVersions[1].."''', '''Client "..sideVersions[2].."'''"
		else
			if args.side == 'server' then
				return "'''Server "..sideVersions[1].."'''"
			elseif args.side == 'client' then
				return "'''Client "..sideVersions[1].."'''"
			else
				return p.documentationMissing(frame, "Available since (Both server and client version is needed for shared items)")
			end
		end
	end
end

function p.syntax(frame)
	--[[
	local args = p.getArgs(frame)
	
	if args.syntax then
		return p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.syntax),id='syntax'})
	end
	
	if args.syntaxSS and args.syntaxCS then
		local rows = {}
		rows[1] = { "'''Server:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.syntaxSS),id='syntaxss'}) }
		rows[2] = { "'''Client:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.syntaxCS),id='syntaxcs'}) }
		return p.table(false, rows, { 100 }, false)
	end
	
	return p.documentationMissing(frame, "Syntax")
	]]

	local args = p.getArgs(frame)
	local isEvent = args.type == 'event'

	local returnType = 'void'
	local argsText = {}

	do
		local entries = p.getIndexedArgs(frame, 'return')

		if #entries == 0 then
		else
			local tokens = p.split(entries[1], ' ')
			
			returnType = tokens[1]
			returnType = p.standardizeNullType(returnType)	
		end
	end

	do
		local entries = p.getIndexedArgs(frame, 'arg')

		if isEvent then
			table.insert(entries, 1, 'Event event')
		end

		if isEvent and args.syntax and args.syntax:lower() == 'event event' then
		elseif #entries == 0 or entries[1] == "void" then
		else
			for i,arg in ipairs(entries) do
                table.insert(argsText, p.getArgSyntaxText(p.getArgParts(arg)))
			end
		end
	end

	if isEvent then
		return p.loadTemplate(frame, 'CodeSyntax', {
			p.getDisplayedName(frame)
			..'('..(#argsText == 0 and 'void' or table.concat(argsText, ', '))..')'
		})
	elseif args.type == 'property' or args.type == 'variable' then
		return p.loadTemplate(frame, 'CodeSyntax', {
			returnType..' '..p.getDisplayedName(frame)
		})
	else
		return p.loadTemplate(frame, 'CodeSyntax', {
			returnType
			..' '..p.getDisplayedName(frame)
			..'('..(#argsText == 0 and 'void' or table.concat(argsText, ', '))..')'
		})
	end
end

function p.getArgParts(text)
	--[[
        INPUT EXAMPLES (str)
        Vec3 pos The 3D position
        [Vec3 pos] The 3D position.
        [ Vec3 pos ] The 3D position
        [ Vec3 pos = new Vec3(0.0, 0.0, 0.0) ] The 3D position.

        OUTPUT (table)
        bool parts.optional
        bool parts.defaultValueIsSpecified
		str parts.type
		str parts.name
		str parts.defaultValue
		str parts.description
	]]

	local parts = {}

	text = p.trim(text)

    parts.optional = false
	local isProbablyOptional = text:sub(1, 1) == '['
	if isProbablyOptional then
		local optionalEndIndex = p.rfind(text, ']')
		if optionalEndIndex then
            parts.optional = true
            local syntaxPart = p.trim(text:sub(2, optionalEndIndex - 1))
            parts.description = p.trim(text:sub(optionalEndIndex + 1, #text))
            local tokens = p.split(syntaxPart, ' ')
            parts.type = tokens[1]
            parts.name = tokens[2]
            parts.defaultValueIsSpecified = tokens[3] == '=' and #tokens >= 4
            parts.defaultValue = table.concat(tokens, ' ', 4, #tokens)
            return parts
        end
    end

    local tokens = p.split(text, ' ')
    parts.type = tokens[1]
    parts.name = tokens[2]
    parts.description = p.trim(table.concat(tokens, ' ', 3, #tokens))
    return parts
end

function p.getArgSyntaxText(parts)
    if parts.optional then
        if parts.defaultValueIsSpecified then
            return '[ '..parts.type..' '..parts.name..' = '..parts.defaultValue..' ]'
        else
            return '[ '..parts.type..' '..parts.name..' ]'
        end
    else
        return parts.type..' '..parts.name
    end
end

function p.description(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'desc')
	
	local startTextFirstLine = "The <span style=\"font-family: 'Source Code Pro', monospace;\">"..p.getDisplayedNameColoured(frame).."</span> "..args.type:lower().." is "..(args.type == "event" and "invoked when" or "used to").." "
	
	for i,entry in ipairs(entries) do
		if i == 1 then
			entry = startTextFirstLine..entry
		end
		
		entries[i] = p.formatDescription(entry)
	end
	return table.concat(entries, '<br>')
end

function p.parameters(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'arg')
	local rows = {}
	local isEvent = args.type == 'event'
	
	if isEvent and args.syntax and args.syntax:lower() == 'event event' then
	elseif #entries == 0 or entries[1] == "void" then
		local headers = nil
		local rows = { { p.formatType("void", frame), "This "..args.type.." doesn't take any parameters." } }
		local widths = { 120, 400 }
		return p.table(headers, rows, widths, 'wikitable-returns wikitable')
	else
		for i,arg in ipairs(entries) do
			local tokens = p.split(arg, ' ')

			if isEvent then
				rows[i] = { (i + 1)..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), p.formatDescription(table.concat(tokens, ' ', 3, #tokens)) }
			else
				local parts = p.getArgParts(arg)

				if parts.optional then
					if parts.defaultValueIsSpecified then
						rows[i] = { i..") ", p.formatType(parts.type, frame), p.formatName(parts.name, frame), "Optional, defaults to "..p.formatDefaultValue(parts.defaultValue, frame)..". "..p.formatDescription(parts.description) }
					else
						rows[i] = { i..") ", p.formatType(parts.type, frame), p.formatName(parts.name, frame), "Optional, the default value has not been documented here yet. "..p.formatDescription(parts.description) }
					end
				else
					rows[i] = { i..") ", p.formatType(parts.type, frame), p.formatName(parts.name, frame), p.formatDescription(parts.description) }
				end
			end
		end
	end
	
	if isEvent then
		table.insert(rows, 1, { "1) ", p.formatType("Event", frame), p.formatName("event", frame), "The event object for this event." })
	end
	
	local headers, widths
	if isEvent then
		headers = nil--{ "Type", "Name", "Description" }
		widths = { 30, 80, 110, 400 }
	else
		headers = nil--{ "Type", "Name", "Presence", "Default Value", "Description" }
		widths = { 30, 80, 110, 400 }
	end
	return p.table(headers, rows, widths, 'wikitable-parameters wikitable')
end

function p.returns(frame)
	local args = p.getArgs(frame)
	local text = ""
	local entries
	local rows
	local headers
	local widths
	local isVoidReturn = false
	
	rows = {}
	entries = p.getIndexedArgs(frame, 'return')
	if #entries == 0 then
		text = text..p.documentationMissing(frame, "Returns").."<br><br>"
	else
		for i,arg in ipairs(entries) do
			local tokens = p.split(arg, ' ')
			
			local type = tokens[1]
			type = p.standardizeNullType(type)
			
			local description
			if type:lower() == "void" then
				isVoidReturn = true
				description = "This "..args.type.." doesn't return a value."
			elseif tokens[2] then
				description = p.formatDescription(table.concat(tokens, ' ', 2))
			else
				description = p.documentationMissing(frame, "Description for return value")
			end
			
			rows[i] = { p.formatType(tokens[1], frame), description }
		end
		headers = nil-- { "Type", "Description" }
		widths = { 120, 400 }
		text = text..p.table(headers, rows, widths, 'wikitable-returns wikitable')
	end
	
	--[[
	-- commented out as failure returns are not currently implemented in GTAC.
	if not isVoidReturn then
		rows = {}
		entries = p.getIndexedArgs(frame, 'freturn')
		text = text.."'''Failure Return:'''\n\n"
		if #entries == 0 then
			text = text..p.documentationMissing(frame, "Failure returns")
		else
			for i,arg in ipairs(entries) do
				local tokens = p.split(arg, ' ')
				
				local type = tokens[1]
				type = p.standardizeNullType(type)
				
				local value
				if type:lower() == 'void' then
					value = "n/a"
				elseif tokens[2] then
					value = p.formatSource(table.concat(tokens, ' ', 2), frame)
				else
					value = p.formatSource(p.documentationMissing(frame, "Description for failure return value"), frame)
				end
				
				rows[i] = { p.formatType(tokens[1], frame), value }
			end
			headers = nil--{ "Type", "Value" }
			widths = { 120, 400 }
			text = text..p.table(headers, rows, widths, 'wikitable-returns wikitable')
		end
	end
	]]
	
	return text
end

function p.callbacks(frame)
	local args = p.getArgs(frame)
	
	local suffixes = {
		{ '', '' },
		{ 'SS', 'Server-Side Callbacks' },
		{ 'CS', 'Client-Side Callbacks' }
	}
	
	local html = {}
	
	for i3,suffixData in ipairs(suffixes) do
		local suffix = suffixData[1]
		
		local parameters = p.getIndexedArgs(frame, 'arg')
		for i=1, #parameters do
			local callbackSyntax = {}
			local callbackText = args['cb'..i..'Text'..suffix]
			local callbackNParameters = p.getIndexedArgs(frame, 'cb'..i..'arg'..suffix)
			
			if #callbackNParameters > 0 then
				if suffixData[1] ~= '' then
					table.insert(html, "'''"..suffixData[2].."'''")
				end
				
				local parameterTokens = p.split(parameters[i], ' ')
				local rows = {}
				
				for i2,callbackNParameter in ipairs(callbackNParameters) do
					local callbackTokens = p.split(callbackNParameter, ' ')
					
					local parameterType = callbackTokens[1]
					local parameterName = callbackTokens[2]
					local description = p.formatDescription(table.concat(callbackTokens, ' ', 3))
					
					do
						if parameterType == "void" then
							table.insert(callbackSyntax, 'void')
						else
							table.insert(callbackSyntax, parameterType..' '..parameterName)
						end
					end

					rows[i2] = { i2..') ', p.formatType(parameterType, frame), p.formatName(parameterName, frame), description }
				end

				local callbackSyntax2 = #callbackSyntax == 0 and 'void' or table.concat(callbackSyntax, ', ')

				if #parameterTokens > 0 then
					local syntaxLine = parameterTokens[1]..' '..parameterTokens[2]..'('..callbackSyntax2..')'
					syntaxLine = p.loadTemplate(frame, 'CodeSyntax', {syntaxLine})
					table.insert(html, syntaxLine)
				end
				
				if callbackText and #callbackText > 0 then
					table.insert(html, callbackText)
				end
				
				local headers, widths
				headers = nil--{ "Type", "Name", "Description" }
				widths = { 30, 80, 110, 400 }
				local table2 = "\n"..p.table(headers, rows, widths, 'wikitable-callback wikitable')
				table.insert(html, table2)
				
				if i ~= #parameters then
					table.insert(html, "<br>")
				end
			end
		end
	end
	
	if args.callbackSyntax then
		table.concat(html, p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.callbackSyntax, true),id='callbacks'}))
	end
	
	if args.callbackSyntaxSS and args.callbackSyntaxCS then
		local rows = {}
		rows[1] = { "'''Server:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.callbackSyntaxSS, true),id='callbacks'}) }
		rows[2] = { "'''Client:'''", p.loadTemplate(frame, 'CodeSyntax', {p.buildSyntax(frame, args.callbackSyntaxCS, true),id='callbacks'}) }
		table.concat(html, p.table(false, rows, { 100 }, false, 'wikitable-callback wikitable'))
	end
	
	return #html > 0 and table.concat(html, '') or false
end

function p.attributes(frame)
	local args = p.getArgs(frame)
	local headers = nil--{ "Type", "Description" }
	local rows = {}
	if p.isCancellable(frame) then
		rows[1] = { 'cancellable', 'This '..args.type..' can be cancelled, by using [[event.preventDefault|event.preventDefault]].' }
	else
		rows[1] = { p.formatType('const', frame), 'This '..args.type..' cannot be cancelled.' }
	end
	local widths = { 120, 400 }
	return p.table(headers, rows, widths, 'wikitable-returns wikitable')
end

function p.value(frame)
	local args = p.getArgs(frame)
	
	if not args.value then
		return p.documentationMissing(frame, "Value type and description")
	end
	
	local arg = args.value
	local tokens = p.split(arg, ' ')
	local description = p.formatDescription(table.concat(tokens, ' ', 2))
	
	local headers = nil--{ "Type", "Description" }
	local rows = {}
	rows[1] = { p.formatType(tokens[1], frame), description }
	if args.readonly == 'true' then
		rows[2] = { p.formatType('readonly', frame), 'This '..args.type..' cannot be changed.' }
	else
		rows[2] = { 'read/set', 'This '..args.type..' can be changed, as well as read.' }
	end
	local widths = { 120, 400 }
	return p.table(headers, rows, widths, 'wikitable-returns wikitable')
end

function p.settable(frame)
	local args = p.getArgs(frame)
	
	if args.readonly == 'true' then
		return "This "..args.type.." is read-only."
	else
		return "This "..args.type.." can be read and set."
	end
end

function p.types(frame)
	local args = p.getArgs(frame)
	return p.getTypesText(frame, args.class)
end

function p.symbols(frame)
	local args = p.getArgs(frame)
	return p.getSymbolsText(frame, args.class)
end

function p.notes(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'note')
	if #entries == 0 then
		return "There aren't any notes for this "..args.type.."."
	end
	for i,entry in ipairs(entries) do
		entries[i] = p.dot(entry)
	end
	return "* "..table.concat(entries, "\n* ")
end

function p.examples(frame)
	local args = p.getArgs(frame)
	
	local data = {
		{ "example", "" },
		
		{ "exampleSS", "Server-Side" },
		{ "exampleCS", "Client-Side" },
		
		{ "exampleJS", "JavaScript" },
		{ "exampleLua", "Lua" },
		{ "exampleSquirrel", "Squirrel" },
		
		{ "exampleJSSS", "JavaScript - Server-Side" },
		{ "exampleJSCS", "JavaScript - Client-Side" },
		
		{ "exampleLuaSS", "Lua - Server-Side" },
		{ "exampleLuaCS", "Lua - Client-Side" },
		
		{ "exampleSquirrelSS", "Squirrel - Server-Side" },
		{ "exampleSquirrelCS", "Squirrel - Client-Side" }
	}
	
	local examples = {}
	local exampleIndex = 1
	for i,data2 in ipairs(data) do
		local entries = p.getIndexedArgs(frame, data2[1])
		for i2, entry in ipairs(entries) do
			local entry2 = "'''Example "..exampleIndex.." - "..data2[2]..":'''<br>"..p.loadTemplate(frame, 'CodeSyntax', {entry:gsub("\r\n", "\n"):gsub("\n", "<br>"):gsub("\t", "&nbsp;&nbsp;&nbsp;&nbsp;"),id='example_'..exampleIndex})
			table.insert(examples, entry2)
			exampleIndex = exampleIndex + 1
		end
	end
	
	if #examples == 0 then
		return "There aren't any examples for this "..args.type.."."
	end
	return table.concat(examples, "<br>")
end

function p.compatibility(frame)
	local args = p.getArgs(frame)
	local entries1 = p.getIndexedArgs(frame, 'previous')
	local entries2 = p.getIndexedArgs(frame, 'compat')
	if #entries1 == 0 and #entries2 == 0 then
		return "There isn't any compatibility information for this "..args.type.."."
	else
		local entriesOut = {}
		for i,arg in ipairs(entries1) do
			local versionsText, tokens = p.getVersionsText(arg, args.side, 2)
			entriesOut[i] = "This "..args.type.." was previously named <code>"..tokens[1].."</code> in "..versionsText.."."
		end
		local offset = #entriesOut
		for i,arg in ipairs(entries2) do
			entriesOut[i + offset] = p.dot(arg)
		end
		return "* "..table.concat(entriesOut, "\n* ")
	end
end

function p.related(frame)
	local args = p.getArgs(frame)
	local categoryTitle = p.getItemCategory(frame)
	if categoryTitle and categoryTitle:len() > 0 then
		local shared = args.side:lower() == 'shared'
		local server = args.side:lower() == 'shared' or args.side:lower() == 'server'
		local client = args.side:lower() == 'shared' or args.side:lower() == 'client'
		local pageName = args.type:lower() == 'event' and 'Event' or 'Function'
		
		local parts = {}
		
		local name
		if args.type == 'property' then
			name = args.class.."."..args.name
		else
			name = args.name
		end
		
		if server then
			local title = 'Server/'..pageName..'s/'..categoryTitle
			local t = p.loadTemplate(frame, title, args)
			
			--t = p.remove(t, "[["..name.."|"..name.."]]<br>\n")
			
			--[[
			t = p.replace(t, "  ", " ");
			t = p.remove(t, "\t");
			t = p.remove(t, "\r");
			t = p.remove(t, "\n");
			t = p.remove(t, "<a class=\"mw-selflink selflink\">"..name.."</a><br />")
			]]
			
			table.insert(parts, "'''Server Related'''<br><br>"..t)
		end
		
		if client then
			local title = 'Client/'..pageName..'s/'..categoryTitle
			local t = p.loadTemplate(frame, title, args)
			
			--t = p.remove(t, "[["..name.."|"..name.."]]<br>\n")
			
			--[[
			t = p.replace(t, "  ", " ");
			t = p.remove(t, "\t");
			t = p.remove(t, "\r");
			t = p.remove(t, "\n");
			t = p.remove(t, "<a class=\"mw-selflink selflink\">"..name.."</a><br />")
			]]
			
			table.insert(parts, "'''Client Related'''<br><br>"..t)
		end
		
		return table.concat(parts, '<br><br>')
	else
		return ''
	end
end

-- item category
function p.getItemCategory(frame)
	local args = p.getArgs(frame)
	local resolvedNameLower = p.getDisplayedName(frame):lower()
	
	for categoryTitle,categoryData in pairs(p.categoryItems) do
		for i2,resolvedItemName in ipairs(categoryData) do
			if resolvedNameLower == resolvedItemName:lower() then
				return categoryTitle
			end
		end
	end
	
	for i,categoryTitle in ipairs(p.categoryWords) do
		if resolvedNameLower:find(categoryTitle:lower(), 1, true) then
			return categoryTitle
		end
	end
	
	return false
end

-- item part utility
function p.getType(text)
	local finds = { "variable", "function", "property", "method", "event", "define" }
	local replaces = { "Variable", "Function", "Property", "Method", "Event", "Define" }
	local defaults = {}
	return p.findText(text, finds, replaces, defaults, "'''undocumented type'''")
end

function p.getGames(text)
	local finds = { "iii", "vc", "sa", "iv", "ug" }
	local replaces = { "GTA III", "GTA VC", "GTA SA", "GTA IV", "GTA UG" }
	local defaults = { "all", "all games" }
	return p.findText(text, finds, replaces, defaults, "All Games")
end

function p.getSide(text)
	local finds = { "shared", "server", "client" }
	local replaces = { "Server and Client", "Server Only", "Client Only" }
	local defaults = {}
	local result = p.findText(text, finds, replaces, defaults, "'''undocumented side'''")
	local isShared = result == replaces[1]
	return result, isShared
end

function p.getDisplayedName(frame)
	local args = p.getArgs(frame)
	if p.isOOP(frame) then
		return p.lowerFirstCharCase(args.class).."."..args.name
	else
		return args.name
	end
end

function p.getTypeRgb()
	return '#009106'
end

function p.getNameRgb()
	return '#0645ad'
end

function p.getDisplayedNameColoured(frame)
	local typeRgb = p.getTypeRgb()
	local nameRgb = p.getNameRgb()
	local args = p.getArgs(frame)
	if p.isOOP(frame) then
		return "<span style='color:"..typeRgb..";'>"..p.lowerFirstCharCase(args.class).."</span>.<span style='color:"..nameRgb.."';>"..args.name.."</span>"
	else
		return "<span style='color:"..nameRgb.."';>"..args.name.."</span>"
	end
end

function p.getSuccessReturnTypes(frame)
	local entries = p.getIndexedArgs(frame, 'return')
	if #entries == 0 then
		return "unknown"
	end
	for i,entry in ipairs(entries) do
		local tokens = p.split(entry, ' ')
		entries[i] = tokens[1]
	end
	return table.concat(entries, ", ")
end

function p.isOOP(frame)
	local args = p.getArgs(frame)
	return args.type == "property" or args.type == "method"
end

function p.warning(frame, text)
	return p.loadTemplate(frame, 'BlueInformationBox', {text})..'\n'
end

function p.documentationMissing(frame, text)
	return p.warning(frame, "Documentation Missing: "..text)
end

function p.buildSyntax(frame, parameters, isCallback)
	if isCallback == nil then
		isCallback = false;
	end
	local args = p.getArgs(frame)
	if not isCallback and (args.type == 'variable' or args.type == 'property') then
		return p.getSuccessReturnTypes(frame).." "..p.getDisplayedName(frame)
	elseif isCallback or args.type == 'event' then
		return "function("..parameters..")"
	else
		return p.getSuccessReturnTypes(frame).." "..p.getDisplayedName(frame).."("..parameters..")"
	end
end

function p.getTypesText(frame, baseType)
	local args = p.getArgs(frame)
	
	local derivedTypes = {
		ped			    = {'Player'},
		physical		= {'Object', 'Ped', 'Vehicle', 'Player'},
		entity			= {'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Player'},
		element			= {'Blip', 'Entity', 'Marker', 'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Player'},
		vehicle			= {'Train'},
		event			= {'CancellableEvent', 'KeyEvent'},
		surface			= {'RenderTarget', 'Texture'}
	}
	
	local serverTypes = {}
	local sharedTypes = {
		'Building', 'Client', 'Effect', 'Event', 'Timer', 'ReflectedFunction', 'Resource', 'Stream', 'Vec2', 'Vec3', 'Matrix4x4',
		'Element', 'Blip', 'Pickup', 'Entity', 'Physical','Vehicle', 'Train', 'XmlDocument', 'XmlElement',
		'CancellableEvent', 'KeyEvent', 'Marker', 'Object', 'Ped', 'Player'
	}
	
	local out = {}
	
	local getTypeSide = function(type)
		for i2,type2 in ipairs(serverTypes) do
			if type:lower() == type2:lower() then
				return 'server'
			end
		end
		
		for i2,type2 in ipairs(sharedTypes) do
			if type:lower() == type2:lower() then
				return 'shared'
			end
		end
		
		return 'client'
	end
	
	if baseType then
		local baseTypeLower = baseType:lower()
		local allTypes
		if derivedTypes[baseTypeLower] then
			allTypes = p.copyTable(derivedTypes[baseTypeLower])
			table.insert(allTypes, 1, baseType)
		else
			allTypes = { baseType }
		end
		local types = {}
		for i,type in ipairs(allTypes) do
			types[i] = type
		end
		p.sortTable(types)
		for i,type in ipairs(types) do
			types[i] = p.loadTemplate(frame, 'Side', {[getTypeSide(type)]='1'}).." ".."<span style=\"font-family: 'Source Code Pro', monospace;\">"..type.."</span>"
		end
		--table.insert(out, "<div style='margin-top: 20px;'></div>")
		table.insert(out, "\n"..table.concat(types, "<br>"))
	end
	
	return table.concat(out, "")
end

function p.getSymbolsText(frame, baseType)
	local args = p.getArgs(frame)
	
	local out = {}
	
	local rows = {}
	
	if args.type == 'function' or args.type == 'variable' then
		local parts = p.split(p.getDisplayedName(frame), ".")
		for i=1,(#parts)-1,1 do
			rows[i] = {}
			rows[i][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[i].."</span>"
			rows[i][2] = 'Namespace'
			rows[i][3] = i == 1 and 'Global' or 'Namespace: '..parts[i-1]
			rows[i][4] = i == 1 and parts[i]..' is a namespace, which exists in the global namespace.' or parts[i]..' is a namespace, which exists in the '..parts[i-1]..' namespace.'
		end
		rows[#parts] = {}
		rows[#parts][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[#parts].."</span>"
		rows[#parts][2] = p.properCase(args.type)
		rows[#parts][3] = #parts == 1 and 'Global' or 'Namespace: '..parts[#parts-1]
		rows[#parts][4] = #parts == 1 and parts[1]..' is a '..p.properCase(args.type)..', which exists in the global namespace.' or parts[#parts]..' is a '..p.properCase(args.type)..', which exists in the '..parts[#parts-1]..' namespace.'
	elseif args.type == 'method' or args.type == 'property' then
		local parts = p.split(p.getDisplayedName(frame), ".")
		rows[1] = {}
		rows[1][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[1].."</span>"
		rows[1][2] = 'Object'
		rows[1][3] = 'n/a'
		rows[1][4] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[1].."</span>"..' is an object.'
		
		rows[2] = {}
		rows[2][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[2].."</span>"
		rows[2][2] = p.properCase(args.type)
		rows[2][3] = #parts == 1 and 'Global' or 'Namespace: '..parts[#parts-1]
		rows[2][4] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..parts[2].."</span>"..' is a '..p.properCase(args.type)..', which is used on objects of type '..parts[1]
	elseif args.type == 'event' then
		rows[1] = {}
		rows[1][1] = "<span style=\"font-family: 'Source Code Pro', monospace; color: "..p.getNameRgb()..";\">"..p.getDisplayedName(frame).."</span>"
		rows[1][2] = 'String'
		rows[1][3] = 'n/a'
		rows[1][4] = 'Events are referenced by name. Event names use data type String.'
	end
	
	--table.insert(out, "<div style='margin-top: 20px;'></div>")
	local headers = { 'Symbol Name', 'Symbol Type', 'Container', 'Description' }
	table.insert(out, "\n"..p.table(headers, rows, { 120, 120, 400 }, false))
	
	if args.type ~= 'event' then
		local languages = { 'JS', 'Lua', 'Squirrel' }
		local languageSymbols = {
			['JS']			= { ['function'] = 'dot',	['variable'] = 'dot',	['method'] = 'dot',		['property'] = 'dot' },
			['Lua']			= { ['function'] = 'dot',	['variable'] = 'dot',	['method'] = 'colon',	['property'] = 'dot' },
			['Squirrel']	= { ['function'] = 'dot',	['variable'] = 'dot',	['method'] = 'dot',		['property'] = 'dot' }
		}
		for i,language in ipairs(languages) do
			local line
			if args.type == 'function' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to call a function.'
			elseif args.type == 'variable' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to get or set a variable.'
			elseif args.type == 'method' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to call a method.'
			elseif args.type == 'property' then
				line = language..' uses the '..languageSymbols[language][args.type]..' symbol to get or set a property.'
			end
			table.insert(out, "<br>"..line)
		end
	end
	
	return table.concat(out, "")
end

function p.isCancellable(frame)
	local args = p.getArgs(frame)
	return (args.cancel and args.cancel == 'true') or (args.cancel and args.cancel == 'true')
end

function p.formatType(type, frame)
	return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getTypeRgb()..";\"'>"..type.."</span>"
end

function p.formatName(name, frame)
	return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getNameRgb()..";\"'>"..name.."</span>"
end

function p.formatDefaultValue(value, frame)
	return "<span style='font-family: \"Source Code Pro\", monospace; color: "..p.getNameRgb()..";\"'>"..value.."</span>"
end

function p.formatSource(type, frame)
	return "<code>"..type.."</code>"
end

function p.formatCode(code)
	return "<code>"..code.."</code>"
end

function p.formatDescription(description)
	description = p.capital(description)
	description = p.dot(description)
	return description
end

function p.standardizeNullType(type)
	local type2 = type:lower()
	if type2 == 'void' or type2 == 'null' or type2 == 'undefined' or type2 == 'n/a' then
		return 'void'
	else
		return type
	end
end

-- general utility
function p.split(str, sep, limit)
   if not sep or sep == "" then
      return false
   end
   if not str then
      return false
   end
   limit = limit or math.huge
   if limit == 0 or limit == 1 then
      return {str}, 1
   end

   local r = {}
   local n, init = 0, 1

   while true do
      local s,e = str:find(sep, init, true)
      if not s then
         break
      end
      r[#r+1] = str:sub(init, s - 1)
      init = e + 1
      n = n + 1
      if n == limit - 1 then
         break
      end
   end

   if init <= str:len() then
      r[#r+1] = str:sub(init)
   else
      r[#r+1] = ""
   end
   n = n + 1

   if limit < 0 then
      for i=n, n + limit + 1, -1 do r[i] = nil end
      n = n + limit
   end

   return r, n
end

function p.rfind(s, find)
	local index = s:reverse():find(find)
	if not index then return end
	return #s - index + 1
end

function p.trim(s)
	return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end

function p.splitLines(text)
	text = p.remove(text, "\n")
	text = p.remove(text, "\r")
	return p.split(text, "<br>")
end

function p.remove(text, remove)
	return p.replace(text, remove, "")
end

function p.replace(text, find, replace)
	return text:gsub(find, replace)
end

function p.getIndexedArgs(frame, name)
	local args = p.getArgs(frame)
	local i = 1
	local out = {}
	if args[name] then
		out[i] = args[name]
		i = i + 1
	end
	while args[name..i] do
		out[i] = args[name..i]
		i = i + 1
	end
	return out
end

function p.loadTemplate(frame, title, args)
	return frame:expandTemplate{ title = title, args = args }
end

function p.getSideVersions(arg, side, start)
	if not start then start = 1 end
	local tokens = p.split(arg, ' ')
	local sideVersions = {}
	if side == 'shared' then
		if tokens[start+1] then
			sideVersions[1] = tokens[start]
			sideVersions[2] = tokens[start+1]
		else
			sideVersions[1] = tokens[start]
		end
	elseif side == 'server' then
		sideVersions[1] = tokens[start]
	elseif side == 'client' then
		sideVersions[1] = tokens[start]
	end
	return sideVersions, tokens
end

function p.getVersionsText(arg, side, start)
	if not start then start = 1 end
	local tokens = p.split(arg, ' ')
	local text
	if side == 'shared' then
		if tokens[start+1] then
			text = "server version "..tokens[start].." and client version "..tokens[start+1]
		else
			text = "version "..tokens[start]
		end
	elseif side == 'server' then
		text = "server version "..tokens[start]
	elseif side == 'client' then
		text = "client version "..tokens[start]
	end
	return text, tokens
end

function p.properCase(text)
	return text:sub(1,1):upper()..text:sub(2):lower()
end

function p.lowerFirstCharCase(text)
	return text:sub(1,1):lower()..text:sub(2)
end

function p.findText(text, finds, replaces, defaults, default)
	if not text then return default end
	local textLower = text:lower()
	
	for i,default2 in ipairs(defaults) do
		if textLower == default2 then
			return default
		end
	end
	
	local items = {}
	for i,find in ipairs(finds) do
		if textLower:find(find, 1, true) then
			table.insert(items, replaces[i])
		end
	end
	return p.getListText(items)
end

function p.getListText(items)
	local count = #items
	
	if count == 1 then
		return items[1]
	elseif count == 2 then
		return items[1].." and "..items[2]
	else
		return table.concat(items, ", ", 1, count - 1).." and "..items[count]
	end
end

function p.dot(text)
	local i = #text
	while i > 0 and text:sub(i, i) == "." do
		i = i - 1
	end
	return text:sub(1,i).."."
end

function p.capital(text)
	return text:sub(1,1):upper()..text:sub(2)
end

function p.copyTable(t)
	local out = {}
	for k,v in pairs(t) do
		out[k] = v
	end
	return out
end

function p.sortTable(t)
	table.sort(t, function(a,b)
		return a:lower() < b:lower()
	end)
end

function p.table(headers, rows, widths, tableCssClass)
	if tableCssClass == nil then tableCssClass = 'wikitable' end
	rows = p.copyTable(rows)
	if not widths then widths = {} end
	
	if headers then
		headers = p.copyTable(headers)
		
		for i,header in ipairs(headers) do
			local width = widths[i]
			if width and width > 0 then
				headers[i] = "id='th_"..i.."' style='text-align:left;"..(i==#headers and 'min-width' or 'width')..":"..width.."px;'| "..header
			end
		end
	else
		for i,cell in ipairs(rows[1]) do
			local width = widths[i]
			if width and width > 0 then
				rows[1][i] = "id='td_"..i.."' style='text-align:left;"..(i==#rows[1] and 'min-width' or 'width')..":"..width.."px;'| "..cell
			end
		end
	end
	
	for i,row in ipairs(rows) do
		rows[i] = '|'..table.concat(row, '\n|')
	end
	
	return '{|'..(tableCssClass and ' class="'..tableCssClass..'"' or '')..(headers and " id='headers' " and '\n!'..table.concat(headers, '\n!')..'\n|-' or '')..'\n'..table.concat(rows, '\n|-\n')..'\n|}'
end

-- return the module
return p