ftext.lua
local ftext = {}
local dec = {"d", "decimal", "dec"}
local hex = {"h", "hexidecimal", "hex"}
local col = {"c", "color", "colour", "col", "name"}
function ftext.wordWrap(str, limit, indent, indent1)
indent = indent or ""
indent1 = indent1 or indent
limit = limit or 72
local here = 1 - #indent1
local function check(sp, st, word, fi)
if fi - here > limit then
here = st - #indent
return "\n" .. indent .. word
end
end
return indent1 .. str:gsub("(%s+)()(%S+)()", check)
end
function ftext.xwrap(text, limit, type)
local colorPattern
if table.contains(dec, type) then
colorPattern = _Echos.Patterns.Decimal[1]
elseif table.contains(hex, type) then
colorPattern = _Echos.Patterns.Hex[1]
elseif table.contains(col, type) then
colorPattern = _Echos.Patterns.Color[1]
else
return ftext.wordWrap(text, limit)
end
local strippedString = rex.gsub(text, colorPattern, "")
local strippedLines = ftext.wordWrap(strippedString, limit):split("\n")
local lineIndex = 1
local line = ""
local strLine = ""
local lines = {}
local strLines = {}
local workingLine = strippedLines[lineIndex]:split("")
local workingLineLength = #workingLine
local lineColumn = 0
for str, color, res in rex.split(text, colorPattern) do
if res then
if type == "Hex" then
color = "#r"
elseif type == "Dec" then
color = "<r>"
elseif type == "Color" then
color = "<reset>"
end
end
color = color or ""
local strLen = str:len()
if lineColumn + strLen <= workingLineLength then
strLine = strLine .. str
line = line .. str .. color
lineColumn = lineColumn + strLen
else
local neededChars = workingLineLength - lineColumn
local take = str:sub(1, neededChars)
local leave = str:sub(neededChars + 1, -1)
strLine = strLine .. take
line = line .. take
table.insert(lines, line)
table.insert(strLines, strLine)
line = ""
strLine = ""
lineIndex = lineIndex + 1
workingLine = strippedLines[lineIndex]:split("")
workingLineLength = #workingLine
lineColumn = 0
if leave:sub(1, 1) == " " then
leave = leave:sub(2, -1)
end
while leave ~= "" do
take = leave:sub(1, workingLineLength)
leave = leave:sub(workingLineLength + 1, -1)
if leave:sub(1, 1) == " " then
leave = leave:sub(2, -1)
end
if take:len() < workingLineLength then
lineColumn = take:len()
line = line .. take .. color
strLine = strLine .. take
else
lineIndex = lineIndex + 1
workingLine = strippedLines[lineIndex]
if workingLine then
workingLine = strippedLines[lineIndex]:split("")
workingLineLength = #workingLine
end
table.insert(lines, take)
table.insert(strLines, take)
end
if leave == "\n" then
table.insert(lines, leave)
table.insert(strLines, leave)
leave = ""
end
end
end
end
if line ~= "" then
table.insert(lines, line)
end
return table.concat(lines, "\n")
end
function ftext.fText(str, opts)
local options = ftext.fixFormatOptions(str, opts)
if options.wrap and (options.strLen > options.effWidth) then
local wrapped = ""
if str:find("\n") then
for _,line in ipairs(str:split("\n")) do
local newline = "\n"
if _ == 1 then newline = "" end
wrapped = wrapped .. newline .. ftext.xwrap(line, options.effWidth, options.formatType)
end
else
wrapped = ftext.xwrap(str, options.effWidth, options.formatType)
end
local lines = wrapped:split("\n")
local formatted = {}
options.fixed = false
for _, line in ipairs(lines) do
table.insert(formatted, ftext.fLine(line, options))
end
return table.concat(formatted, "\n")
else
return ftext.fLine(str, options)
end
end
function ftext.fixFormatOptions(str, opts)
if opts.fixed then
return table.deepcopy(opts)
end
if opts == nil then
opts = {}
end if type(opts) ~= "table" then
error("Improper argument: options expected to be passed as table")
end
local options = table.deepcopy(opts)
if options.wrap == nil then
options.wrap = true
end if options.truncate == nil then
options.truncate = false
end options.formatType = options.formatType or "" options.width = options.width or 80 options.cap = options.cap or "" options.spacer = options.spacer or " " options.alignment = options.alignment or "center" if options.nogap == nil then
options.nogap = false
end
if options.inside == nil then
options.inside = false
end if not options.mirror == false then
options.mirror = options.mirror or true
end if table.contains(dec, options.formatType) then
options.capColor = options.capColor or "<255,255,255>"
options.spacerColor = options.spacerColor or "<255,255,255>"
options.textColor = options.textColor or "<255,255,255>"
options.colorReset = "<r>"
options.colorPattern = _Echos.Patterns.Decimal[1]
elseif table.contains(hex, options.formatType) then
options.capColor = options.capColor or "#FFFFFF"
options.spacerColor = options.spacerColor or "#FFFFFF"
options.textColor = options.textColor or "#FFFFFF"
options.colorReset = "#r"
options.colorPattern = _Echos.Patterns.Hex[1]
elseif table.contains(col, options.formatType) then
options.capColor = options.capColor or "<white>"
options.spacerColor = options.spacerColor or "<white>"
options.textColor = options.textColor or "<white>"
options.colorReset = "<reset>"
options.colorPattern = _Echos.Patterns.Color[1]
else
options.capColor = ""
options.spacerColor = ""
options.textColor = ""
options.colorReset = ""
options.colorPattern = ""
end
options.originalString = str
options.strippedString = rex.gsub(tostring(str), options.colorPattern, "")
options.strLen = string.len(options.strippedString)
options.leftCap = options.cap
options.rightCap = options.cap
options.capLen = string.len(options.cap)
local gapSpaces = 0
if not options.nogap then
if options.alignment == "center" then
gapSpaces = 2
else
gapSpaces = 1
end
end
options.nontextlength = options.width - options.strLen - gapSpaces
options.leftPadLen = math.floor(options.nontextlength / 2)
options.rightPadLen = options.nontextlength - options.leftPadLen
options.effWidth = options.width - ((options.capLen * gapSpaces) + gapSpaces)
if options.capLen > options.leftPadLen then
options.cap = options.cap:sub(1, options.leftPadLen)
options.capLen = string.len(options.cap)
end
options.fixed = true
return options
end
function ftext.fLine(str, opts)
local options = ftext.fixFormatOptions(str, opts)
local truncate, strLen, width = options.truncate, options.strLen, options.width
if truncate and strLen > width then
local wrapped = ftext.xwrap(str, options.effWidth, options.formatType)
local lines = wrapped:split("\n")
str = lines[1]
end
local leftCap = options.leftCap
local rightCap = options.rightCap
local leftPadLen = options.leftPadLen
local rightPadLen = options.rightPadLen
local capLen = options.capLen
if options.alignment == "center" then if options.mirror then rightCap = string.gsub(rightCap, "<", ">")
rightCap = string.gsub(rightCap, "%[", "%]")
rightCap = string.gsub(rightCap, "{", "}")
rightCap = string.gsub(rightCap, "%(", "%)")
rightCap = string.reverse(rightCap)
end if not options.nogap then
str = string.format(" %s ", str)
end
elseif options.alignment == "right" then leftPadLen = leftPadLen + rightPadLen
rightPadLen = 0
rightCap = ""
if not options.nogap then
str = string.format(" %s", str)
end
else rightPadLen = rightPadLen + leftPadLen
leftPadLen = 0
leftCap = ""
if not options.nogap then
str = string.format("%s ", str)
end
end local fullLeftCap = string.format("%s%s%s", options.capColor, leftCap, options.colorReset)
local fullLeftSpacer = string.format("%s%s%s", options.spacerColor, string.rep(options.spacer, (leftPadLen - capLen)), options.colorReset)
local fullText = string.format("%s%s%s", options.textColor, str, options.colorReset)
local fullRightSpacer = string.format("%s%s%s", options.spacerColor, string.rep(options.spacer, (rightPadLen - capLen)), options.colorReset)
local fullRightCap = string.format("%s%s%s", options.capColor, rightCap, options.colorReset)
if options.inside then
local finalString = string.format("%s%s%s%s%s", fullLeftCap, fullLeftSpacer, fullText, fullRightSpacer, fullRightCap)
return finalString
else
local finalString = string.format("%s%s%s%s%s", fullLeftSpacer, fullLeftCap, fullText, fullRightCap, fullRightSpacer)
return finalString
end
end
function ftext.align(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = ""
options.wrap = false
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fLine(str, options)
end
function ftext.dalign(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = "d"
options.wrap = false
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fLine(str, options)
end
function ftext.calign(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = "c"
options.wrap = false
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fLine(str, options)
end
function ftext.halign(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = "h"
options.wrap = false
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fLine(str, options)
end
function ftext.cfText(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = "c"
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fText(str, options)
end
function ftext.dfText(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = "d"
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fText(str, options)
end
function ftext.hfText(str, opts)
local options = {}
if opts == nil then
opts = {}
end
if type(opts) == "table" then
options = table.deepcopy(opts)
options.formatType = "h"
else
error("Improper argument: options expected to be passed as table")
end
options = ftext.fixFormatOptions(str, options)
return ftext.fText(str, options)
end
local TextFormatter = {}
TextFormatter.validFormatTypes = {'d', 'dec', 'decimal', 'h', 'hex', 'hexidecimal', 'c', 'color', 'colour', 'col', 'name', 'none', 'e', 'plain', ''}
function TextFormatter:setType(typeToSet)
local isNotValid = not table.contains(self.validFormatTypes, typeToSet)
if isNotValid then
error("TextFormatter:setType: Invalid argument, valid types are:" .. table.concat(self.validFormatTypes, ", "))
end
self.options.formatType = typeToSet
end
function TextFormatter:toBoolean(thing)
if type(thing) ~= "boolean" then
if thing == "true" then
thing = true
elseif thing == "false" then
thing = false
else
return nil
end
end
return thing
end
function TextFormatter:checkString(str)
if type(str) ~= "string" then
if tostring(str) then
str = tostring(str)
else
return nil
end
end
return str
end
function TextFormatter:setWrap(shouldWrap)
local argumentType = type(shouldWrap)
shouldWrap = self:toBoolean(shouldWrap)
if shouldWrap == nil then
error("TextFormatter:setWrap(shouldWrap) Argument error, boolean expected, got " .. argumentType ..
", if you want to set the number of characters wide to format for, use setWidth()")
end
self.options.wrap = shouldWrap
end
function TextFormatter:setWidth(width)
if type(width) ~= "number" then
if tonumber(width) then
width = tonumber(width)
else
error("TextFormatter:setWidth(width): Argument error, number expected, got " .. type(width))
end
end
self.options.width = width
end
function TextFormatter:setCap(cap)
local argumentType = type(cap)
local cap = self:checkString(cap)
if cap == nil then
error("TextFormatter:setCap(cap): Argument error, string expect, got " .. argumentType)
end
self.options.cap = cap
end
function TextFormatter:setCapColor(capColor)
local argumentType = type(capColor)
local capColor = self:checkString(capColor)
if capColor == nil then
error("TextFormatter:setCapColor(capColor): Argument error, string expected, got " .. argumentType)
end
self.options.capColor = capColor
end
function TextFormatter:setSpacerColor(spacerColor)
local argumentType = type(spacerColor)
local spacerColor = self:checkString(spacerColor)
if spacerColor == nil then
error("TextFormatter:setSpacerColor(spacerColor): Argument error, string expected, got " .. argumentType)
end
self.options.spacerColor = spacerColor
end
function TextFormatter:setTextColor(textColor)
local argumentType = type(textColor)
local textColor = self:checkString(textColor)
if textColor == nil then
error("TextFormatter:setTextColor(textColor): Argument error, string expected, got " .. argumentType)
end
self.options.textColor = textColor
end
function TextFormatter:setSpacer(spacer)
local argumentType = type(spacer)
local spacer = self:checkString(spacer)
if spacer == nil then
error("TextFormatter:setSpacer(spacer): Argument error, string expect, got " .. argumentType)
end
self.options.spacer = spacer
end
function TextFormatter:setAlignment(alignment)
local validAlignments = {"left", "right", "center"}
if not table.contains(validAlignments, alignment) then
error("TextFormatter:setAlignment(alignment): Argument error: Only valid arguments for setAlignment are 'left', 'right', or 'center'. You sent" ..
alignment)
end
self.options.alignment = alignment
end
function TextFormatter:setInside(spacerInside)
local argumentType = type(spacerInside)
spacerInside = self:toBoolean(spacerInside)
if spacerInside == nil then
error("TextFormatter:setInside(spacerInside) Argument error, boolean expected, got " .. argumentType)
end
self.options.inside = spacerInside
end
function TextFormatter:setMirror(shouldMirror)
local argumentType = type(shouldMirror)
shouldMirror = self:toBoolean(shouldMirror)
if shouldMirror == nil then
error("TextFormatter:setMirror(shouldMirror): Argument error, boolean expected, got " .. argumentType)
end
self.options.mirror = shouldMirror
end
function TextFormatter:setNoGap(noGap)
local argumentType = type(noGap)
noGap = self:toBoolean(noGap)
if noGap == nil then
error("TextFormatter:setNoGap(noGap): Argument error, boolean expected, got " .. argumentType)
end
self.options.noGap = noGap
end
function TextFormatter:enableTruncate()
self.options.truncate = true
end
function TextFormatter:disableTruncate()
self.options.truncate = false
end
function TextFormatter:format(str)
return ftext.fText(str, self.options)
end
function TextFormatter:new(options)
if options == nil then
options = {}
end
if options and type(options) ~= "table" then
error("TextFormatter:new(options): Argument error, table expected, got " .. type(options))
end
local me = {}
me.options = {formatType = "c", wrap = true, width = 80, cap = "", spacer = " ", alignment = "center", inside = true, mirror = false}
for option, value in pairs(options) do
me.options[option] = value
end
setmetatable(me, self)
self.__index = self
return me
end
ftext.TextFormatter = TextFormatter
local TableMaker = {
headCharacter = "*",
footCharacter = "*",
edgeCharacter = "*",
rowSeparator = "-",
separator = "|",
separateRows = true,
colorReset = "<reset>",
formatType = "c",
printHeaders = true,
autoEcho = false,
title = "",
printTitle = false,
headerTitle = false,
forceHeaderSeparator = false,
autoEchoConsole = "main",
}
function TableMaker:checkPosition(position, func)
if position == nil then
position = 0
end
if type(position) ~= "number" then
if tonumber(position) then
position = tonumber(position)
else
error(func .. ": Argument error: position expected as number, got " .. type(position))
end
end
return position
end
function TableMaker:insert(tbl, pos, item)
if pos ~= 0 then
table.insert(tbl, pos, item)
else
table.insert(tbl, item)
end
end
function TableMaker:getColumn(position)
position = position or #self.columns
position = self:checkPosition(position, "TableMaker:getColumn(position)")
return self.columns[position]
end
function TableMaker:addColumn(options, position)
if options == nil then
options = {}
end
if not type(options) == "table" then
error("TableMaker:addColumn(options, position): Argument error: options expected as table, got " .. type(options))
end
local options = table.deepcopy(options)
position = self:checkPosition(position, "TableMaker:addColumn(options, position)")
options.width = options.width or 20
options.name = options.name or ""
local formatter = TextFormatter:new(options)
self:insert(self.columns, position, formatter)
end
function TableMaker:deleteColumn(position)
if position == nil then
error("TableMaker:deleteColumn(position): Argument Error: position as number expected, got nil")
end
position = self:checkPosition(position)
local maxColumn = #self.columns
if position > maxColumn then
error(
"TableMaker:deleteColumn(position): Argument Error: position provided was larger than number of columns in the table. Number of columns: " ..
#self.columns)
end
table.remove(self.columns, position)
end
function TableMaker:replaceColumn(options, position)
if position == nil then
error("TableMaker:replaceColumn(options, position): Argument error: position as number expected, got nil")
end
position = self:checkPosition(position)
if type(options) ~= "table" then
error("TableMaker:replaceColumn(options, position): Argument error: options as table expected, got " .. type(options))
end
if #self.columns < position then
error(
"TableMaker:replaceColumn(options, position): you cannot specify a position higher than the number of columns currently in the TableMaker. You sent:" ..
position .. " and there are: " .. #self.columns .. "columns in the TableMaker")
end
options.width = options.width or 20
options.name = options.name or ""
local formatter = TextFormatter:new(options)
self.columns[position] = formatter
end
function TableMaker:getRow(position)
position = position or #self.rows
position = self:checkPosition(position, "TableMaker:getRow(position)")
return self.rows[position]
end
function TableMaker:addRow(columnEntries, position)
local columnEntriesType = type(columnEntries)
if columnEntriesType ~= "table" then
error("TableMaker:addRow(columnEntries, position): Argument error, columnEntries expected as table, got " .. columnEntriesType)
end
for _, entry in ipairs(columnEntries) do
local entryCheck = self:checkEntry(entry)
if entryCheck == 0 then
if type(entry) == "function" then
error(
"TableMaker:addRow(columnEntries, position): Argument Error, you provided a function for a columnEntry but it does not return a string. We need a string. It was entry number " ..
_ .. "in columnEntries")
else
error("TableMaker:addRow(columnEntries, position): Argument error, columnEntries items expected as string, got:" .. type(entry))
end
end
end
position = self:checkPosition(position, "TableMaker:addRow(columnEntries, position)")
self:insert(self.rows, position, columnEntries)
end
function TableMaker:deleteRow(position)
if position == nil then
error("TableMaker:deleteRow(position): Argument Error: position as number expected, got nil")
end
position = self:checkPosition(position, "TableMaker:deleteRow(position)")
local maxRow = #self.rows
if position > maxRow then
error("TableMaker:deleteRow(position): Argument Error: position given was > the number of rows we have # of rows is:" .. maxRow)
end
table.remove(self.rows, position)
end
function TableMaker:replaceRow(columnEntries, position)
if position == nil then
error("TableMaker:replaceRow(columnEntries, position): ArgumentError: position expected as number, received nil")
end
position = self:checkPosition(position, "TableMaker:replaceRow(columnEntries, position)")
if #self.rows < position then
error(
"TableMaker:replaceRow(columnEntries, position): position cannot be greater than the number of rows already in the tablemaker. You provided: " ..
position .. " and there are " .. #self.rows .. "rows in the TableMaker")
end
for _, entry in ipairs(columnEntries) do
local entryCheck = self:checkEntry(entry)
if entryCheck == 0 then
if type(entry) == "function" then
error(
"TableMaker:replaceRow(columnEntries, position): Argument Error: you provided a function for a columnEntry but it does not return a string. We need a string. It was entry number " ..
_ .. "in columnEntries")
else
error("TableMaker:replaceRow(columnEntries, position): Argument error: columnEntries items expected as string, got:" .. type(entry))
end
end
end
self.rows[position] = columnEntries
end
function TableMaker:checkEntry(entry)
local allowedTypes = {"string"}
if self.allowPopups then
table.insert(allowedTypes, "table")
end
local entryType = type(entry)
if entryType == "function" then
entryType = type(entry())
end
if table.contains(allowedTypes, entryType) then
return entry
else
return 0
end
end
function TableMaker:checkNumber(num)
if num == nil then
num = 0
end
if not tonumber(num) then
num = 0
end
return tonumber(num)
end
function TableMaker:getCell(row, column)
local rowType = type(row)
local columnType = type(column)
local maxRow = #self.rows
local maxColumn = #self.columns
local ae = "TableMaker:getCell(row, column): Argument error:"
row = self:checkNumber(row)
column = self:checkNumber(column)
if row == 0 then
if rowType ~= "number" then
printError(f"{ae} row as number expected, got {rowType}", true, true)
else
printError(f"{ae} rows start at 1, and you asked for row 0", true, true)
end
elseif column == 0 then
if columnType ~= "number" then
printError(f"{ae} column as number expected, got {columnType}", true, true)
else
printError(f"{ae} columns start at 1, and you asked for column 0", true, true)
end
elseif row > maxRow then
printError(f"{ae} row exceeds number of rows in table ({maxRow})")
elseif column > maxColumn then
printError(f"{ae} column exceeds number of columns in table ({maxColumn})", true, true)
end
return self.rows[row][column], self.columns[column]
end
function TableMaker:setCell(row, column, entry)
local maxRow = #self.rows
local maxColumn = #self.columns
local ae = "TableMaker:setCell(row, column, entry): Argument Error:"
row = self:checkNumber(row)
if row == 0 then
error(ae .. " row must be a number, you provided " .. type(row))
end
column = self:checkNumber(column)
if column == 0 then
error(ae .. " column must be a number, you provided " .. type(column))
end
if row > maxRow then
error(ae .. " row is higher than the number of rows in the table. Highest row:" .. maxRow)
end
if column > maxColumn then
error(ae .. " column is higher than the number of columns in the table. Highest column:" .. maxColumn)
end
local entryType = type(entry)
entry = self:checkEntry(entry)
if entry == 0 then
if entryType == "function" then
error(ae .. " entry was provided as a function, but does not return a string. We need a string in the end")
else
error("TableMaker:setCell(row, column, entry): Argument Error: entry must be a string, or a function which returns a string. You provided a " .. entryType)
end
end
self.rows[row][column] = entry
end
function TableMaker:totalWidth()
local width = 0
local numberOfColumns = #self.columns
local separatorWidth = string.len(self.separator)
local edgeWidth = string.len(self.edgeCharacter) * 2
for _, column in ipairs(self.columns) do
width = width + column.options.width
end
separatorWidth = separatorWidth * (numberOfColumns - 1)
width = width + edgeWidth + separatorWidth
return width
end
function TableMaker:getType()
local dec = {"d", "decimal", "dec"}
local hex = {"h", "hexidecimal", "hex"}
local col = {"c", "color", "colour", "col", "name"}
if table.contains(dec, self.formatType) then
return 'd'
elseif table.contains(hex, self.formatType) then
return 'h'
elseif table.contains(col, self.formatType) then
return 'c'
else
return ''
end
end
function TableMaker:echo(message, echoType, ...)
local fType = self:getType()
local consoleType = type(self.autoEchoConsole)
local console = ""
if echoType == nil then
echoType = ""
end
if consoleType == "string" then
console = self.autoEchoConsole
elseif consoleType == "nil" then
console = "main"
else
console = self.autoEchoConsole.name
end
local functionName = string.format("%secho%s", fType, echoType)
local func = _G[functionName]
if echoType == "" then
func(console, message)
else
func(console, message, ...)
end
end
function TableMaker:scanRow(rowToScan)
local row = table.deepcopy(rowToScan)
local rowEntries = #row
local numberOfColumns = #self.columns
local columns = {}
local linesInRow = 0
local rowText = ""
local ec = self.frameColor .. self.edgeCharacter .. self.colorReset
local sep = self.separatorColor .. self.separator .. self.colorReset
if rowEntries < numberOfColumns then
local entriesNeeded = numberOfColumns - rowEntries
for i = 1, entriesNeeded do
table.insert(row, "")
end
end
for index, formatter in ipairs(self.columns) do
local str = row[index]
local column = ""
if type(str) == "function" then
str = str()
end
column = formatter:format(str)
table.insert(columns, column:split("\n"))
end
for _, rowLines in ipairs(columns) do
if linesInRow < #rowLines then
linesInRow = #rowLines
end
end
for index, rowLines in ipairs(columns) do
if #rowLines < linesInRow then
local neededLines = linesInRow - #rowLines
for i = 1, neededLines do
table.insert(rowLines, self.columns[index]:format(""))
end
end
end
for i = 1, linesInRow do
local thisLine = ec
for index, column in ipairs(columns) do
if index == 1 then
thisLine = string.format("%s%s", thisLine, column[i])
else
thisLine = string.format("%s%s%s", thisLine, sep, column[i])
end
end
thisLine = string.format("%s%s", thisLine, ec)
if rowText == "" then
rowText = thisLine
else
rowText = string.format("%s\n%s", rowText, thisLine)
end
end
return rowText
end
function TableMaker:echoRow(rowToScan)
local row = table.deepcopy(rowToScan)
local rowEntries = #row
local numberOfColumns = #self.columns
local columns = {}
local linesInRow = 0
local ec = self.frameColor .. self.edgeCharacter .. self.colorReset
local sep = self.separatorColor .. self.separator .. self.colorReset
if rowEntries < numberOfColumns then
local entriesNeeded = numberOfColumns - rowEntries
for i = 1, entriesNeeded do
table.insert(row, "")
end
end
for index, formatter in ipairs(self.columns) do
local str = row[index]
local column = ""
if type(str) == "function" then
str = str()
end
if type(str) == "table" then
str = str[1]
end
column = formatter:format(str)
table.insert(columns, column:split("\n"))
end
for _, rowLines in ipairs(columns) do
if linesInRow < #rowLines then
linesInRow = #rowLines
end
end
for index, rowLines in ipairs(columns) do
if #rowLines < linesInRow then
local neededLines = linesInRow - #rowLines
for i = 1, neededLines do
table.insert(rowLines, self.columns[index]:format(""))
end
end
end
for i = 1, linesInRow do
self:echo(ec)
for index, column in ipairs(columns) do
local message = column[i]
if index ~= 1 then
self:echo(sep)
end
if type(row[index]) == "string" then
self:echo(message)
elseif type(row[index]) == "table" then
local rowEntry = row[index]
local echoType = ""
if type(rowEntry[2]) == "string" then
echoType = "Link"
elseif type(rowEntry[2]) == "table" then
echoType = "Popup"
end
self:echo(message, echoType, rowEntry[2], rowEntry[3], rowEntry[4] or true)
end
end
self:echo(ec)
self:echo("\n")
end
end
function TableMaker:makeHeader()
local totalWidth = self:totalWidth()
local ec = self.frameColor .. self.edgeCharacter .. self.colorReset
local sep = self.separatorColor .. self.separator .. self.colorReset
local header = self.frameColor .. string.rep(self.headCharacter, totalWidth) .. self.colorReset
local columnHeaders = ""
if self.printHeaders then
local columnEntries = {}
for _, v in ipairs(self.columns) do
table.insert(columnEntries, v:format(v.options.name))
end
local divWithNewlines = self.headerTitle and header or self:createRowDivider()
divWithNewlines = "\n" .. divWithNewlines
columnHeaders = string.format("\n%s%s%s%s", ec, table.concat(columnEntries, sep), ec, (self.separateRows or self.forceHeaderSeparator) and divWithNewlines or '')
end
local title = self:makeTitle(totalWidth, header)
header = string.format("%s%s%s", header, title, columnHeaders)
return header
end
function TableMaker:makeTitle(totalWidth, header)
if not self.printTitle then
return ""
end
local title = ftext.fText(self.title, {width = totalWidth, alignment = "center", cap = self.headCharacter, capColor = self.frameColor, inside = true, textColor = self.titleColor, formatType = self.formatType})
title = string.format("\n%s\n%s", title, header)
return title
end
function TableMaker:createRowDivider()
local columnPieces = {}
for _, v in ipairs(self.columns) do
local piece = string.format("%s%s%s", self.separatorColor, string.rep(self.rowSeparator, v.options.width), self.colorReset)
table.insert(columnPieces, piece)
end
local ec = self.frameColor .. self.edgeCharacter .. self.colorReset
local sep = self.separatorColor .. self.separator .. self.colorReset
return string.format("%s%s%s", ec, table.concat(columnPieces, sep), ec)
end
function TableMaker:setTitle(title)
self.title = title
if self.autoEcho then self:assemble() end
end
function TableMaker:setRowSeparator(char)
self.rowSeparator = char
if self.autoEcho then self:assemble() end
end
function TableMaker:setEdgeCharacter(char)
self.edgeCharacter = char
if self.autoEcho then self:assemble() end
end
function TableMaker:setFootCharacter(char)
self.footCharacter = char
if self.autoEcho then self:assemble() end
end
function TableMaker:setHeadCharacter(char)
self.headCharacter = char
if self.autoEcho then self:assemble() end
end
function TableMaker:setSeparator(char)
self.separator = char
if self.autoEcho then self:assemble() end
end
function TableMaker:setTitleColor(color)
self.titleColor = color
if self.autoEcho then self:assemble() end
end
function TableMaker:setSeparatorColor(color)
self.separatorColor = color
if self.autoEcho then self:assemble() end
end
function TableMaker:setFrameColor(color)
self.frameColor = color
if self.autoEcho then self:assemble() end
end
function TableMaker:enableForceHeaderSeparator()
self.forceHeaderSeparator = true
if self.autoEcho then self:assemble() end
end
function TableMaker:disableForceHeaderSeparator()
self.forceHeaderSeparator = false
if self.autoEcho then self:assemble() end
end
function TableMaker:enableHeaderTitle()
self.headerTitle = true
if self.autoEcho then self:assemble() end
end
function TableMaker:disableHeaderTitle()
self.headerTitle = false
if self.autoEcho then self:assemble() end
end
function TableMaker:enablePrintTitle()
self.printTitle = true
if self.autoEcho then self:assemble() end
end
function TableMaker:disablePrintTitle()
self.printTitle = false
if self.autoEcho then self:assemble() end
end
function TableMaker:enablePrintHeaders()
self.printHeaders = true
if self.autoEcho then self:assemble() end
end
function TableMaker:disablePrintHeaders()
self.printHeaders = false
if self.autoEcho then self:assemble() end
end
function TableMaker:enableRowSeparator()
self.separateRows = true
if self.autoEcho then self:assemble() end
end
function TableMaker:disableRowSeparator()
self.separateRows = false
if self.autoEcho then self:assemble() end
end
function TableMaker:enablePopups()
self.autoEcho = true
self.allowPopups = true
if self.autoEcho then self:assemble() end
end
function TableMaker:enableAutoEcho()
self.autoEcho = true
self:assemble()
end
function TableMaker:disableAutoEcho()
if self.allowPopups then
error("TableMaker:disableAutoEcho(): you cannot disable autoEcho once you have enabled popups.")
else
self.autoEcho = false
end
end
function TableMaker:enableAutoClear()
self.autoClear = true
if self.autoEcho then self:assemble() end
end
function TableMaker:disableAutoClear()
self.autoClear = false
end
function TableMaker:setAutoEchoConsole(console)
local funcName = "TableMaker:setAutoEchoConsole(console)"
if console == nil then
console = "main"
end
local consoleType = type(console)
if consoleType ~= "string" and consoleType ~= "table" then
error(funcName .. " ArgumentError: console as string or a Geyser MiniConsole or UserWindow expected, got " .. consoleType)
elseif consoleType == "table" and not (console.type == "miniConsole" or console.type == "userwindow") then
error(funcName .. " ArgumentError: console received was a table and may be a Geyser object, but console.type is not miniConsole, it is " ..
console.type)
end
self.autoEchoConsole = console
if self.autoEcho then self:assemble() end
end
function TableMaker:assemble()
if self.allowPopups and self.autoEcho then
self:popupAssemble()
else
return self:textAssemble()
end
end
function TableMaker:popupAssemble()
if self.autoClear then
local console = self.autoEchoConsole
if console and console ~= "main" then
if type(console) == "table" then
console = console.name
end
clearWindow(console)
end
end
local divWithNewLines = string.format("%s\n", self:createRowDivider())
local header = self:makeHeader() .. "\n"
local footer = string.format("%s%s%s\n", self.frameColor, string.rep(self.footCharacter, self:totalWidth()), self.colorReset)
self:echo(header)
for _, row in ipairs(self.rows) do
if _ ~= 1 and self.separateRows then
self:echo(divWithNewLines)
end
self:echoRow(row)
end
self:echo(footer)
end
function TableMaker:textAssemble()
local sheet = ""
local rows = {}
for _, row in ipairs(self.rows) do
table.insert(rows, self:scanRow(row))
end
local divWithNewlines = string.format("\n%s\n", self:createRowDivider())
local footer = string.format("%s%s%s", self.frameColor, string.rep(self.footCharacter, self:totalWidth()), self.colorReset)
sheet = string.format("%s\n%s\n%s\n", self:makeHeader(), table.concat(rows, self.separateRows and divWithNewlines or "\n"), footer)
if self.autoEcho then
local console = self.autoEchoConsole or "main"
if type(console) == "table" then
console = console.name
end
if self.autoClear and console ~= "main" then
clearWindow(console)
end
self:echo(sheet)
end
return sheet
end
function TableMaker:new(options)
local funcName = "TableMaker:new(options)"
local me = {}
setmetatable(me, self)
self.__index = self
if options == nil then
options = {}
end
if type(options) ~= "table" then
error("TableMaker:new(options): ArgumentError: options expected as table, got " .. type(options))
end
local options = table.deepcopy(options)
if options.allowPopups == true then
options.autoEcho = true
else
options.allowPopups = false
end
local columns = false
if options.columns then
if type(options.columns) ~= "table" then
error("TableMaker:new(options): option error: You provided an options.columns entry of type " .. type(options.columns) ..
" and columns must a table with entries suitable for TableFormatter:addColumn().")
end
columns = table.deepcopy(options.columns)
options.columns = nil
end
local rows = false
if options.rows then
if type(options.rows) ~= "table" then
error("TableMaker:new(options): option error: You provided an options.rows entry of type " .. type(options.rows) ..
" and rows must be a table with entrys suitable for TableFormatter:addRow()")
end
rows = table.deepcopy(options.rows)
options.rows = nil
end
for option, value in pairs(options) do
me[option] = value
end
local dec = {"d", "decimal", "dec"}
local hex = {"h", "hexidecimal", "hex"}
local col = {"c", "color", "colour", "col", "name"}
if table.contains(dec, me.formatType) then
me.frameColor = me.frameColor or "<255,255,255>"
me.separatorColor = me.separatorColor or me.frameColor
me.titleColor = me.titleColor or me.frameColor
me.colorReset = "<r>"
elseif table.contains(hex, me.formatType) then
me.frameColor = me.frameColor or "#ffffff"
me.separatorColor = me.separatorColor or me.frameColor
me.titleColor = me.titleColor or me.frameColor
me.colorReset = "#r"
elseif table.contains(col, me.formatType) then
me.frameColor = me.frameColor or "<white>"
me.separatorColor = me.separatorColor or me.frameColor
me.titleColor = me.titleColor or me.frameColor
me.colorReset = "<reset>"
else
me.frameColor = ""
me.separatorColor = ""
me.titleColor = ""
me.colorReset = ""
end
me.columns = {}
me.rows = {}
if columns then
for _, column in ipairs(columns) do
me:addColumn(column)
end
end
if rows then
for _, row in ipairs(rows) do
me:addRow(row)
end
end
return me
end
ftext.TableMaker = TableMaker
return ftext