local function defaultTableDebug(buffer, input)
buffer:writeRaw("{")
for key, value in pairs(input) do
buffer:write("[{:?}] = {:?}", key, value)
if next(input, key) ~= nil then
buffer:writeRaw(", ")
end
end
buffer:writeRaw("}")
end
local function defaultTableDebugExtended(buffer, input)
if next(input) == nil then
buffer:writeRaw("{}")
return
end
buffer:writeLineRaw("{")
buffer:indent()
for key, value in pairs(input) do
buffer:writeLine("[{:?}] = {:#?},", key, value)
end
buffer:unindent()
buffer:writeRaw("}")
end
local function debugImpl(buffer, value, extendedForm)
local valueType = typeof(value)
if valueType == "string" then
local formatted = string.format("%q", value)
buffer:writeRaw(formatted)
elseif valueType == "table" then
local valueMeta = getmetatable(value)
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
valueMeta.__fmtDebug(value, buffer, extendedForm)
else
if extendedForm then
defaultTableDebugExtended(buffer, value)
else
defaultTableDebug(buffer, value)
end
end
elseif valueType == "Instance" then
buffer:writeRaw(value:GetFullName())
else
buffer:writeRaw(tostring(value))
end
end
local function writeFmt(buffer, template, ...)
local currentArg = 0
local i = 1
local len = #template
while i <= len do
local openBrace = template:find("{", i)
if openBrace == nil then
buffer:writeRaw(template:sub(i))
break
else
local charAfterBrace = template:sub(openBrace + 1, openBrace + 1)
if charAfterBrace == "{" then
buffer:writeRaw(template:sub(i, openBrace))
i = openBrace + 2
else
if openBrace - i > 0 then
buffer:writeRaw(template:sub(i, openBrace - 1))
end
local closeBrace = template:find("}", openBrace + 1)
assert(closeBrace ~= nil, "Unclosed formatting specifier. Use '{{' to write an open brace.")
local formatSpecifier = template:sub(openBrace + 1, closeBrace - 1)
currentArg = currentArg + 1
local arg = select(currentArg, ...)
if formatSpecifier == "" then
buffer:writeRaw(tostring(arg))
elseif formatSpecifier == ":?" then
debugImpl(buffer, arg, false)
elseif formatSpecifier == ":#?" then
debugImpl(buffer, arg, true)
else
error("unsupported format specifier " .. formatSpecifier, 2)
end
i = closeBrace + 1
end
end
end
end
local function debugOutputBuffer()
local buffer = {}
local startOfLine = true
local indentLevel = 0
local indentation = ""
function buffer:writeLine(template, ...)
writeFmt(self, template, ...)
self:nextLine()
end
function buffer:writeLineRaw(value)
self:writeRaw(value)
self:nextLine()
end
function buffer:write(template, ...)
return writeFmt(self, template, ...)
end
function buffer:writeRaw(value)
if #value > 0 then
if startOfLine and #indentation > 0 then
startOfLine = false
table.insert(self, indentation)
end
table.insert(self, value)
startOfLine = false
end
end
function buffer:nextLine()
table.insert(self, "\n")
startOfLine = true
end
function buffer:indent()
indentLevel = indentLevel + 1
indentation = string.rep(" ", indentLevel)
end
function buffer:unindent()
indentLevel = math.max(0, indentLevel - 1)
indentation = string.rep(" ", indentLevel)
end
function buffer:finish()
return table.concat(self, "")
end
return buffer
end
local function fmt(template, ...)
local buffer = debugOutputBuffer()
writeFmt(buffer, template, ...)
return buffer:finish()
end
local function debugify(object, fmtFunc)
return setmetatable({}, {
__fmtDebug = function(_, ...)
return fmtFunc(object, ...)
end,
__tostring = function()
return tostring(object)
end,
})
end
return {
debugOutputBuffer = debugOutputBuffer,
fmt = fmt,
debugify = debugify,
}