Module:ScriptItem2: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
(8 intermediate revisions by the same user not shown) | |||
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', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo', | 'Blip', 'Building', 'Camera', 'Chatbox', 'Cheat', 'Client', 'Command', 'Cursor', 'Debug', 'Dodo', | ||
'Element', 'Entity', 'Event', 'File', 'Font', 'Garage', 'HTTP', 'HUD', 'Key', 'Mission', 'Mouse', 'Network', 'Object', | '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' | 'Pause', 'Ped', 'Pickup', 'Player', 'Process', 'Render', 'Resource', 'Screen', 'Socket', 'Sphere', 'Time', 'Vehicle', 'Weather', 'World' | ||
} | } | ||
Line 407: | Line 407: | ||
else | else | ||
for i,arg in ipairs(entries) do | for i,arg in ipairs(entries) do | ||
table.insert(argsText, p.getArgSyntaxText(p.getArgParts(arg))) | |||
end | end | ||
end | end | ||
Line 441: | Line 428: | ||
}) | }) | ||
end | 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 | end | ||
Line 447: | 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> | 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 474: | 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, ' ') | ||
if isEvent then | if isEvent then | ||
rows[i] = { (i + 1)..") ", p.formatType(tokens[1], frame), p.formatName(tokens[2], frame), | 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( | 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 760: | Line 806: | ||
local entries = p.getIndexedArgs(frame, data2[1]) | local entries = p.getIndexedArgs(frame, data2[1]) | ||
for i2, entry in ipairs(entries) do | for i2, entry in ipairs(entries) do | ||
local entry2 = "'''Example "..exampleIndex.." - "..data2[2]..":'''<br>"..p.loadTemplate(frame, 'CodeSyntax', {entry,id='example_'..exampleIndex}) | local entry2 = "'''Example "..exampleIndex.." - "..data2[2]..":'''<br>"..p.loadTemplate(frame, 'CodeSyntax', {entry:gsub("\r\n", "\n"):gsub("\n", "<br>"):gsub("\t", " "),id='example_'..exampleIndex}) | ||
table.insert(examples, entry2) | table.insert(examples, entry2) | ||
exampleIndex = exampleIndex + 1 | exampleIndex = exampleIndex + 1 | ||
Line 899: | Line 945: | ||
local args = p.getArgs(frame) | local args = p.getArgs(frame) | ||
if p.isOOP(frame) then | if p.isOOP(frame) then | ||
return args.class | return p.lowerFirstCharCase(args.class).."."..args.name | ||
else | else | ||
return args.name | return args.name | ||
Line 918: | Line 964: | ||
local args = p.getArgs(frame) | local args = p.getArgs(frame) | ||
if p.isOOP(frame) then | if p.isOOP(frame) then | ||
return "<span style='color:"..typeRgb..";'>"..args.class | return "<span style='color:"..typeRgb..";'>"..p.lowerFirstCharCase(args.class).."</span>.<span style='color:"..nameRgb.."';>"..args.name.."</span>" | ||
else | else | ||
return "<span style='color:"..nameRgb.."';>"..args.name.."</span>" | return "<span style='color:"..nameRgb.."';>"..args.name.."</span>" | ||
Line 1,107: | 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 | ||
function p.formatSource(type, frame) | function p.formatSource(type, frame) | ||
return | return "<code>"..type.."</code>" | ||
end | end | ||
Line 1,124: | Line 1,174: | ||
function p.standardizeNullType(type) | function p.standardizeNullType(type) | ||
local type2 = type:lower() | |||
if | if type2 == 'void' or type2 == 'null' or type2 == 'undefined' or type2 == 'n/a' then | ||
return 'void' | return 'void' | ||
else | else | ||
Line 1,174: | Line 1,224: | ||
return r, n | 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 | end | ||
Line 1,248: | Line 1,308: | ||
function p.properCase(text) | function p.properCase(text) | ||
return text:sub(1,1):upper()..text:sub(2):lower() | 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 | end | ||
Latest revision as of 13:09, 29 February 2024
Documentation for this module may be created at Module:ScriptItem2/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", " "),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