296 lines
7.4 KiB
Lua
296 lines
7.4 KiB
Lua
-- ---------------------------------------------------------------------------
|
|
-- Itte Util
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
--[[
|
|
-- A collection of helper functions used by Itte.
|
|
--]]
|
|
|
|
local itteutil = {}
|
|
itteutil.docs = {}
|
|
|
|
|
|
itteutil.docs.get_docs = [===[ (docstring_table [, func_str [, printd_bool]])
|
|
Given a table of docstrings and a function name, return the description for
|
|
the function name. If `name` is unspecified, return descriptions for all
|
|
functions. If `printd` is unspecified, print to stdout, or if set to false,
|
|
return results as a string.
|
|
]===]
|
|
function itteutil.get_docs(docstr, name, printd)
|
|
local docs = ""
|
|
if name ~= nil then
|
|
-- 2nd-level nested objects, e.g. x.y.func()
|
|
if itteutil.is_substr(name, "%.") then
|
|
local sep, _ = string.find(name, "%.")
|
|
local dk = name:sub(1, sep - 1)
|
|
local nk = name:sub(sep + 1)
|
|
docs = "\n" .. name .. " " .. string.gsub(docstr[dk][nk]:sub(3),
|
|
" ", "") .. "\n"
|
|
else
|
|
docs = "\n" .. name .. " " .. string.gsub(docstr[name]:sub(3), " ", "")
|
|
.. "\n"
|
|
end
|
|
else
|
|
-- Sort on-site since associative arrays have no fixed order
|
|
local dk = itteutil.table_keys(docstr)
|
|
docs = "\n"
|
|
for c = 1, #dk do
|
|
-- 2nd-level nested objects, e.g. x.y.func()
|
|
if type(docstr[dk[c]]) == "table" then
|
|
local n_dk = itteutil.table_keys(docstr[dk[c]])
|
|
for nc = 1, #n_dk do
|
|
docs = docs .. dk[c] .. "." .. n_dk[nc] .. " " ..
|
|
string.gsub(docstr[dk[c]][n_dk[nc]]:sub(3), " ", "") .. "\n"
|
|
end
|
|
else
|
|
docs = docs .. dk[c] .. " " .. string.gsub(docstr[dk[c]]:sub(3),
|
|
" ", "") .. "\n"
|
|
end
|
|
end
|
|
end
|
|
|
|
if printd == false then
|
|
return docs
|
|
else
|
|
print(docs)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
itteutil.docs.help = [[ ([func_str])
|
|
Given a function name, print a corresponding description.
|
|
]]
|
|
function itteutil.help(name)
|
|
itteutil.get_docs(itteutil.docs, name)
|
|
end
|
|
|
|
|
|
itteutil.docs.is_substr = [[ (str, find_str)
|
|
Check if a string is a substring of another string. Return true if it is a
|
|
substring, or false otherwise.
|
|
]]
|
|
function itteutil.is_substr(str, search)
|
|
if (string.find(str, search) ~= nil) then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
itteutil.docs.first_upper = [[ (str)
|
|
Return a string with the first character in uppercase.
|
|
]]
|
|
function itteutil.first_upper(str)
|
|
return str:sub(1, 1):upper() .. str:sub(2)
|
|
end
|
|
|
|
|
|
itteutil.docs.first_lower = [[ (str)
|
|
Return a string with the first character in lowercase.
|
|
]]
|
|
function itteutil.first_lower(str)
|
|
return str:sub(1, 1):lower() .. str:sub(2)
|
|
end
|
|
|
|
|
|
itteutil.docs.split_str = [[ (str, [, pattern_str])
|
|
Split a string on a pattern separator and return a table of string values,
|
|
e.g. split_str("Hello world", "%S+"). If no pattern is specified, split
|
|
string on the space character.
|
|
]]
|
|
function itteutil.split_str(str, sep)
|
|
local tbl = {}
|
|
local n = 0
|
|
-- Other patterns: https://www.lua.org/pil/20.2.html
|
|
if sep == nil then sep = "%S+" end
|
|
for sub in string.gmatch(str, sep) do
|
|
n = n + 1
|
|
tbl[n] = sub
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
|
|
itteutil.docs.a_or_an = [[ (str)
|
|
Return the corresponding indefinite article for a word.
|
|
Does *not* apply to acronyms, e.g. FTP.
|
|
]]
|
|
function itteutil.a_or_an(str)
|
|
local article = "a"
|
|
if itteutil.is_substr("a e i o u", str:sub(1, 1)) or
|
|
itteutil.is_substr("ho", str:sub(1, 2)) then
|
|
article = "an"
|
|
end
|
|
if (str == "one") or (itteutil.is_substr("eu", str:sub(1, 2))) or
|
|
(itteutil.is_substr("uni usa use usi usu", str:sub(1, 3))) then
|
|
article = "a"
|
|
end
|
|
return article
|
|
end
|
|
|
|
|
|
itteutil.docs.table_keys = [[ (table)
|
|
Return a sorted table of keys for an associative table.
|
|
]]
|
|
function itteutil.table_keys(tbl)
|
|
local keys = {}
|
|
local n = 0
|
|
for k, v in pairs(tbl) do
|
|
n = n + 1
|
|
keys[n] = k
|
|
end
|
|
table.sort(keys)
|
|
return keys
|
|
end
|
|
|
|
|
|
itteutil.docs.has_key = [[ (table, find_str)
|
|
Given a table and string, check whether the string is a key in the table.
|
|
Return true if found, or false otherwise.
|
|
]]
|
|
function itteutil.has_key(tbl, str)
|
|
local keys = itteutil.table_keys(tbl)
|
|
-- Primitive check for array-like table with numerical keys
|
|
if (keys[1] == 1) and (keys[#keys] == #keys) then
|
|
for k = 1, #keys do
|
|
if str == tbl[k] then
|
|
do return true end
|
|
end
|
|
end
|
|
-- Associative table
|
|
else
|
|
for k = 1, #keys do
|
|
if str == keys[k] then
|
|
do return true end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
itteutil.docs.find_key = [[ (table, find_str)
|
|
Given a table and string, check whether the string is a key in the table.
|
|
Return the key if found, or nil otherwise.
|
|
]]
|
|
function itteutil.find_key(tbl, str)
|
|
local keys = itteutil.table_keys(tbl)
|
|
-- Primitive check for array-like table with numerical keys
|
|
if (keys[1] == 1) and (keys[#keys] == #keys) then
|
|
for k = 1, #keys do
|
|
if str == tbl[k] then
|
|
do return k end
|
|
end
|
|
end
|
|
-- Associative table
|
|
else
|
|
for k = 1, #keys do
|
|
if str == keys[k] then
|
|
do return k end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
|
|
|
|
itteutil.docs.is_entry = [[ (table, key_str, val_str)
|
|
Given an associative table, a key and value pair as strings, check whether
|
|
the pair is in the table. Return true if it exists in the table, or false
|
|
otherwise.
|
|
]]
|
|
function itteutil.is_entry(tbl, key, val)
|
|
for k, v in pairs(tbl) do
|
|
if (key == k) and (val == v) then
|
|
do return true end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
itteutil.docs.pick = [===[ (table [, num_int [, unique_bool ]])
|
|
Pseudo-randomly pick entries from a non-associative table and return the
|
|
entries in a table. If `num` is unspecified, it will return one entry.
|
|
If `unique` is unspecified, it will return non-recurring values.
|
|
]===]
|
|
function itteutil.pick(tbl, num, unique)
|
|
local picks = {}
|
|
if num == nil then num = 1 end
|
|
if unique == nil then unique = true end
|
|
picks[1] = tbl[math.random(1, #tbl)]
|
|
|
|
if (num > 1) and (num <= #tbl) then
|
|
for n = 2, num do
|
|
local p = tbl[math.random(1, #tbl)]
|
|
-- Check for duplicate
|
|
if unique then
|
|
local c = 1
|
|
-- Avoid endless loop if all values in the table are identical
|
|
while (itteutil.has_key(picks, p)) and (c <= #tbl) do
|
|
p = tbl[math.random(1, #tbl)]
|
|
c = c + 1
|
|
end
|
|
end
|
|
picks[n] = p
|
|
end
|
|
end
|
|
return picks
|
|
end
|
|
|
|
|
|
itteutil.docs.sleep = [[ (seconds_int)
|
|
Set a delay in seconds.
|
|
]]
|
|
function itteutil.sleep(s)
|
|
local timer = os.clock()
|
|
while (os.clock() - timer < s) do end
|
|
end
|
|
|
|
|
|
itteutil.docs.debug = [[ (tag_str, debug_str [, enabled_bool])
|
|
Format and print debug output if `enabled` is true.
|
|
]]
|
|
function itteutil.debug(tag, str, enabled)
|
|
if enabled then
|
|
local ts = os.date("%Y-%m-%d %H:%M:%S", os.time())
|
|
print("[" .. ts .. "][" .. tag .. "] " .. str)
|
|
end
|
|
end
|
|
|
|
|
|
itteutil.docs.source_file = [[ (filename_str)
|
|
Load a source file.
|
|
]]
|
|
function itteutil.source_file(str)
|
|
local func, err = loadfile(str)
|
|
if func then
|
|
return func()
|
|
else
|
|
itteutil.debug("source_file", "Error: " .. err, true)
|
|
do return end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
itteutil.docs.read_file = [[ (filename_str)
|
|
Open a file and return the contents as a string.
|
|
]]
|
|
function itteutil.read_file(str)
|
|
local file = io.open(str)
|
|
if io.type(file) == "file" then
|
|
local contents = file:read("*a")
|
|
file:close()
|
|
return contents
|
|
else
|
|
itteutil.debug("read_file", "Error: cannot read file.", true)
|
|
end
|
|
end
|
|
|
|
|
|
return itteutil
|