Last active
May 4, 2017 06:17
-
-
Save Necktrox/2387ac6aea23bbff7b71 to your computer and use it in GitHub Desktop.
Debug function for formatted output with variable names
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DEBUG_MODE = true | |
DEBUG_PIPE = print | |
SHOW_ADDRESS = true | |
MTA_COMPATIBLE = false | |
local function getVariableValue(level, variable) | |
local index, name, value, skip = 1 | |
-- Check if variable name contains a dot (table access) | |
if variable:find("%.") ~= nil then | |
-- Get the table name | |
local vtable = variable:sub(1, variable:find("%.") - 1) | |
-- Get the table reference | |
local vt = getVariableValue(level, vtable) | |
-- Abort if reference is not a table | |
if type(vt) ~= "table" then | |
return nil | |
end | |
-- Remove the table name from the variable name | |
variable = variable:sub(variable:find("%.") + 1, variable:len()) | |
-- Dig into the table to get the requested field | |
for var in variable:gmatch("[^%.]+") do | |
if type(vt) ~= "table" then | |
return nil | |
else | |
vt = vt[var] | |
end | |
end | |
-- Return found value | |
return vt | |
end | |
-- Search for variable in local environment | |
repeat | |
name, value = debug.getlocal(level + 1, index) | |
if name ~= nil and name == variable then | |
return value | |
else | |
index = index + 1 | |
end | |
until name == nil | |
index = 1 | |
local ref = debug.getinfo(level + 1, "f").func | |
-- Check if ref points to a valid function | |
while type(ref) ~= "function" and level > 0 do | |
ref = debug.getinfo(level, "f").func | |
level = level - 1 | |
end | |
-- Search for variable in global environment | |
if type(ref) == "function" then | |
repeat | |
name, value = debug.getupvalue(ref, index) | |
if name ~= nil and name == variable then | |
return value | |
else | |
index = index + 1 | |
end | |
until name == nil | |
end | |
if MTA_COMPATIBLE then | |
-- Last attempt is to return a value from _G | |
return _G[variable] | |
else | |
-- Last attempt is to return a value from function environment | |
return getfenv(ref)[variable] | |
end | |
end | |
local function replace(where, what, with) | |
-- Replace each finding | |
repeat | |
local start, stop = where:find(what, 1, true) | |
if start ~= nil then | |
where = where:sub(1, start - 1) .. with .. where:sub(stop + 1, where:len()) | |
end | |
until start == nil | |
-- Return the replaced string | |
return where | |
end | |
local function size(tbl) | |
-- Verify that we handle only tables | |
if type(tbl) ~= "table" then | |
return 0 | |
end | |
local size = 0 | |
-- Calculate size | |
for index in pairs(tbl) do | |
size = size + 1 | |
end | |
return size | |
end | |
local function trimAddress(value) | |
-- Convert value to string | |
if type(value) ~= "string" then | |
value = tostring(value) | |
end | |
-- Remove any description and leading zeros from address | |
return value:gsub(".-: [0]*", "") | |
end | |
local function trimPlayerName(name) | |
-- Convert value to string | |
if type(name) ~= "string" then | |
name = tostring(name) | |
end | |
-- Check if name contains only color codes | |
if name:gsub("#%x%x%x%x%x%x", ""):len() == 0 then | |
-- Add invisible character to show color codes | |
return name:gsub("#(%x%x%x%x%x%x)", "#\1%1") | |
else | |
-- Remove color codes | |
return name:gsub("#%x%x%x%x%x%x", "") | |
end | |
end | |
local function format(name, value, modifier) | |
-- Verify that modifier is a string | |
modifier = type(modifier) == "string" and modifier or "" | |
-- Cache the value type | |
local vtype = type(value) | |
-- Number (rounded) | |
if (vtype == "number" and modifier == "") or modifier == "number" then | |
if vtype ~= "number" then | |
value = tonumber(value) or 0 | |
end | |
local integral, fractional = math.modf(value) | |
local formatter = (fractional ~= 0) and "%.3f" or "%d" | |
return formatter:format(value) | |
-- Natural (not rounded) | |
elseif modifier == "natural" then | |
if vtype ~= "number" then | |
value = tonumber(value) or 0 | |
end | |
return tostring(value) | |
-- Nil | |
elseif (vtype == "nil" and modifier == "") or modifier == "nil" then | |
return "nil" | |
-- String | |
elseif (vtype == "string" and modifier == "") or modifier == "string" then | |
return tostring(value) | |
-- Boolean | |
elseif (vtype == "boolean" and modifier == "") or modifier == "boolean" then | |
if vtype ~= "boolean" then | |
if vtype == "string" then | |
return (value ~= "false") and "true" or "false" | |
elseif vtype == "number" then | |
return (value ~= 0) and "true" or "false" | |
end | |
end | |
return (value and "true" or "false") | |
-- Table | |
elseif (vtype == "table" and modifier == "") or modifier == "table" then | |
if vtype ~= "table" then | |
value = {value} | |
end | |
if SHOW_ADDRESS then | |
return name .. " [table(" .. size(value) .."), 0x".. trimAddress(value) .."]" | |
else | |
return name .. " [table(" .. size(value) ..")]" | |
end | |
-- Function | |
elseif (vtype == "function" and modifier == "") or modifier == "function" then | |
if vtype ~= "function" then | |
return "non-function" | |
end | |
if SHOW_ADDRESS then | |
return name .. " [function, 0x".. trimAddress(value) .."]" | |
else | |
return name .. " [function]" | |
end | |
-- Function (execute) | |
elseif modifier == "execute" then | |
if vtype ~= "function" then | |
return "nil" | |
end | |
local result = value() | |
return format(name, result) | |
end | |
if MTA_COMPATIBLE then | |
-- Element | |
if (isElement(value) and modifier == "") or modifier == "element" then | |
if not isElement(value) then | |
return "non-element" | |
end | |
local address = SHOW_ADDRESS and (" [0x".. trimAddress(value) .."]") or "" | |
if value.type == "player" then | |
return trimPlayerName(value:getName()) .. address | |
elseif value.type == "vehicle" then | |
if isElement(value:getOccupant()) then | |
return value.type .. " (driver: " .. trimPlayerName(value:getOccupant():getName()) .. ")" .. address | |
else | |
return value.type .. address | |
end | |
else | |
return value.type .. address | |
end | |
-- Resource | |
elseif (vtype == "userdata" and getResourceName(value) and modifier == "") or modifier == "resource" then | |
if vtype ~= "userdata" or not getResourceName(value) then | |
return "non-resource" | |
end | |
if SHOW_ADDRESS then | |
return getResourceName(value) .. " [resource, 0x".. trimAddress(value) .."]" | |
else | |
return getResourceName(value) .. " [resource]" | |
end | |
end | |
-- Userdata | |
if (vtype == "userdata" and modifier == "") or modifier == "userdata" then | |
if vtype ~= "userdata" then | |
return "non-userdata" | |
end | |
if SHOW_ADDRESS then | |
return name .. " [userdata, 0x".. trimAddress(value) .."]" | |
else | |
return name .. " [userdata]" | |
end | |
end | |
return tostring(value) | |
end | |
function debugf(text) | |
if not DEBUG_MODE or type(DEBUG_PIPE) ~= "function" then | |
-- Do not parse if debug is disabled | |
return | |
end | |
-- Modify always a copy of the original | |
local copy = text | |
-- Match every bracket in the text | |
for brackets in text:gmatch("{(.-)}") do | |
-- Check if brackets contain a modifier | |
if brackets:find("|") ~= nil then | |
local index, args = 1, {} | |
-- Get each 'argument' (including modifier) | |
for value in brackets:gmatch("[^|]+") do | |
args[index] = value | |
index = index + 1 | |
end | |
-- Replace the bracket with the value using a modifier | |
copy = replace(copy, "{".. brackets .."}", format(args[1], getVariableValue(2, args[1]), args[2])) | |
else | |
-- Replace the bracket with the value | |
copy = replace(copy, "{".. brackets .."}", format(brackets, getVariableValue(2, brackets))) | |
end | |
end | |
-- Forward result to debug pipe function | |
DEBUG_PIPE(copy) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage:
Output:
Notes:
The code includes optional useful code for the Multi Theft Auto modification.