Module:ScriptItem: Difference between revisions

From GTA Connected
Jump to navigation Jump to search
No edit summary
No edit summary
Line 38: Line 38:
-- Category Names. (String(s) to look for in an item name, case-INsensitive.)
-- Category Names. (String(s) to look for in an item name, case-INsensitive.)
'Blip', 'Building', 'Camera', 'Chatbox', 'Civilian', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo',
'Blip', 'Building', 'Camera', 'Chatbox', 'Civilian', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo',
'Element', 'Entity', 'Event', 'File', 'Garage', 'HTTP', 'HUD', 'Key', 'Mission', 'Mouse', 'Network', 'Object',
'Element', 'Entity', 'Event', 'File', 'Font', 'Garage', 'HTTP', 'HUD', 'Key', 'Mission', 'Mouse', 'Network', 'Object',
'Pause', 'Ped', 'Pickup', 'Player', 'Process', 'Render', 'Resource', 'Screen', 'Socket', 'Sphere', 'Time', 'Transformable', 'Vehicle', 'Weather', 'World'
'Pause', 'Ped', 'Pickup', 'Player', 'Process', 'Render', 'Resource', 'Screen', 'Socket', 'Sphere', 'Time', 'Transformable', 'Vehicle', 'Weather', 'World'
}
}

Revision as of 21:07, 20 April 2021

Documentation for this module may be created at Module:ScriptItem/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', 'Civilian', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo',
	'Element', 'Entity', 'Event', 'File', 'Font', 'Garage', 'HTTP', 'HUD', 'Key', 'Mission', 'Mouse', 'Network', 'Object',
	'Pause', 'Ped', 'Pickup', 'Player', 'Process', 'Render', 'Resource', 'Screen', 'Socket', 'Sphere', 'Time', 'Transformable', '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)
	local parts = { "Value", "Settable" }
	return p.showPage(frame, parts)
end

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

function p.showPropertyPage(frame)
	local parts = { "Value", "Settable", "Types" }
	return p.showPage(frame, parts)
end

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

function p.showEventPage(frame)
	local parts = { "Parameters", "Cancellable" }
	return p.showPage(frame, parts)
end

-- item page
function p.showPage(frame, parts)
	local args = p.getArgs(frame)
	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;'>"..p.syntax(frame).."</div>\n")
	table.insert(lines, "<div style='margin-top: 20px;'>"..p.description(frame).."</div>\n")
	return lines
end

function p.getPageTopBoxes(frame)
	local args = p.getArgs(frame)
	
	local topBoxes = { "type", "endpoint", "games", "online" }
	if args.type == 'variable' or args.type == 'property' then
		table.insert(topBoxes, "settable")
	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.usage then
		args.description = 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.endpointBox(frame)
	local args = p.getArgs(frame)
	local endpointText, isShared = p.getEndpoint(args.endpoint)
	if isShared then
		return p.loadTemplate(frame, 'GreenInformationBox', {endpointText,id='endpoint'});
	else
		return p.loadTemplate(frame, 'RedInformationBox', {endpointText,id='endpoint'});
	end
end

function p.gamesBox(frame)
	local args = p.getArgs(frame)
	local gamesText = p.getGames(args.games)
	if gamesText == "All Games" then
		return p.loadTemplate(frame, 'GreenInformationBox', {gamesText,id='games'});
	else
		return p.loadTemplate(frame, 'RedInformationBox', {gamesText..' Only',id='games'});
	end
end

function p.onlineBox(frame)
	local args = p.getArgs(frame)
	if args.sp == 'true' then
		return p.loadTemplate(frame, 'RedInformationBox', {'Offline 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

-- item parts
function p.since(frame)
	local args = p.getArgs(frame)
	if not args.since then
		return p.documentationMissing(frame, "Available since")
	else
		local endpointVersions = p.getEndpointVersions(args.since, args.endpoint)
		if #endpointVersions == 2 then
			return "'''Server "..endpointVersions[1].."''', '''Client "..endpointVersions[2].."'''"
		else
			if args.endpoint == 'server' then
				return "'''Server "..endpointVersions[1].."'''"
			elseif args.endpoint == 'client' then
				return "'''Client "..endpointVersions[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")
end

function p.description(frame)
	local args = p.getArgs(frame)
	local entries = p.getIndexedArgs(frame, 'description')
	
	local startTextFirstLine = "The <span style=\"font-family: 'Source Code Pro', monospace;\">"..p.getDisplayedName(frame).."</span> "..args.type.." 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, 'parameter')
	local rows = {}
	local isEvent = args.type == 'event'
	
	if isEvent and args.syntax and args.syntax:lower() == 'event event' then
	elseif #entries == 0 then
		return p.documentationMissing(frame, "Parameters")
	elseif entries[1] == "void" then
		local headers = nil
		local rows = { { "void", "This "..args.type.." doesn't take any parameters." } }
		local widths = { 30, 80, 510 }
		return p.table(headers, rows, widths, 'wikitable-parameters wikitable')
	else
		for i,arg in ipairs(entries) do
			local tokens = p.split(arg, ' ')
			
			local isOptional = tokens[3] == 'optional'
			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
				rows[i] = { i..") ", p.formatType(tokens[1], frame), p.formatSource(tokens[2], frame), description }
			else
				rows[i] = { i..") ", p.formatType(tokens[1], frame), p.formatSource(tokens[2], frame), (isOptional and ("Optional, defaults to "..defaultValue..". ") or "")..description }
			end
		end
	end
	
	if isEvent then
		table.insert(rows, 1, { "1) ", p.formatType("Event", frame), "event", "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, "Success 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 any values on success."
			elseif tokens[2] then
				description = p.formatDescription(table.concat(tokens, ' ', 2))
			else
				description = p.documentationMissing(frame, "Description for success return value")
			end
			
			rows[i] = { '', p.formatType(tokens[1], frame), description }
		end
		headers = nil-- { "Type", "Description" }
		widths = { 30, 80, 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, 'returnFail')
		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 = { 30, 80, 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, 'parameter')
		for i=1, #parameters do
			local callbackSyntax = args['callback'..i..'Syntax'..suffix]
			local callbackText = args['callback'..i..'Text'..suffix]
			local callbackNParameters = p.getIndexedArgs(frame, 'callback'..i..'Parameter'..suffix)
			
			if #callbackNParameters > 0 then
				if suffixData[1] ~= '' then
					table.insert(html, "'''"..suffixData[2].."'''")
				end
				
				local parameterTokens = p.split(parameters[i], ' ')
				local rows = {}
				
				if #parameterTokens > 0 then
					local syntaxLine = parameterTokens[1]..' '..parameterTokens[2]..'('..callbackSyntax..')'
					syntaxLine = p.loadTemplate(frame, 'CodeSyntax', {syntaxLine})
					table.insert(html, syntaxLine)
				end
				
				if callbackText and #callbackText > 0 then
					table.insert(html, callbackText)
				end
				
				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))
					
					rows[i2] = { i2..') ', p.formatType(parameterType, frame), p.formatSource(parameterName, frame), description }
				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.cancellable(frame)
	local args = p.getArgs(frame)
	
	if p.isCancellable(frame) then
		return "This "..args.type.." can be cancelled using [[event.preventDefault|event.preventDefault]]."
	else
		return "This "..args.type.." can't be cancelled."
	end
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 = { { '', p.formatType(tokens[1], frame), description } }
	local widths = { 30, 80, 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.getDerivedClassesText(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 entry = "'''Example "..exampleIndex.." - "..data2[2]..":'''<br>"..p.loadTemplate(frame, 'CodeSyntax', {entry,id='example_'..exampleIndex})
			table.insert(examples, entry)
			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.endpoint, 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.endpoint:lower() == 'shared'
		local server = args.endpoint:lower() == 'shared' or args.endpoint:lower() == 'server'
		local client = args.endpoint:lower() == 'shared' or args.endpoint: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.getEndpoint(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 endpoint'''")
	local isShared = result == replaces[1]
	return result, isShared
end

function p.getDisplayedName(frame)
	local args = p.getArgs(frame)
	if p.isOOP(frame) then
		return args.class:lower().."."..args.name
	else
		return args.name
	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.getDerivedClassesText(frame, baseType)
	local args = p.getArgs(frame)
	
	local derivedTypes = {
		ped			    = {'Civilian', 'Player'},
		physical		= {'Object', 'Ped', 'Vehicle', 'Civilian', 'Player'},
		entity			= {'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Civilian', 'Player'},
		transformable	= {'Blip', 'Entity', 'Marker', 'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Civilian', 'Player'},
		element			= {'Transformable', 'Blip', 'Entity', 'Marker', 'Building', 'Physical', 'Object', 'Ped', 'Vehicle', 'Civilian', 'Player'}
	}
	
	local baseTypeLower = baseType:lower()
	
	if derivedTypes[baseTypeLower] then
		local types = p.copyTable(derivedTypes[baseTypeLower])
		table.insert(types, baseType)
		for i,type in ipairs(types) do
			types[i] = type
		end
		p.sortTable(types)
		return "This "..args.type.." can be used on types: "..p.formatSource(table.concat(types, " "), frame)
	else
		return "This "..args.type.." can only be used on type: "..p.formatType(baseType, frame)
	end
end

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

function p.formatType(type, frame)
	return p.formatSource(type, frame)
end

function p.formatSource(type, frame)
	return frame:preprocess('<source>'..type..'</source>')
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)
	type = type:lower()
	if type == 'void' or type == 'null' or type == 'undefined' or type == '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.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 = {}
	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.getEndpointVersions(arg, endpoint, start)
	if not start then start = 1 end
	local tokens = p.split(arg, ' ')
	local endpointVersions = {}
	if endpoint == 'shared' then
		if tokens[start+1] then
			endpointVersions[1] = tokens[start]
			endpointVersions[2] = tokens[start+1]
		else
			endpointVersions[1] = tokens[start]
		end
	elseif endpoint == 'server' then
		endpointVersions[1] = tokens[start]
	elseif endpoint == 'client' then
		endpointVersions[1] = tokens[start]
	end
	return endpointVersions, tokens
end

function p.getVersionsText(arg, endpoint, start)
	if not start then start = 1 end
	local tokens = p.split(arg, ' ')
	local text
	if endpoint == '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 endpoint == 'server' then
		text = "server version "..tokens[start]
	elseif endpoint == '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.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