303 lines
9.2 KiB
Lua
303 lines
9.2 KiB
Lua
---
|
|
-- Various useful functions that didn't fit anywhere else
|
|
-- initialization {{{
|
|
local base = _G
|
|
local irc_debug = require 'irc.debug'
|
|
local socket = require 'socket'
|
|
local math = require 'math'
|
|
local os = require 'os'
|
|
local string = require 'string'
|
|
local table = require 'table'
|
|
-- }}}
|
|
|
|
---
|
|
-- This module contains various useful functions which didn't fit in any of the
|
|
-- other modules.
|
|
module 'irc.misc'
|
|
|
|
-- defaults {{{
|
|
DELIM = ' '
|
|
PATH_SEP = '/'
|
|
ENDIANNESS = "big"
|
|
INT_BYTES = 4
|
|
-- }}}
|
|
|
|
-- private functions {{{
|
|
--
|
|
-- Check for existence of a file. This returns true if renaming a file to
|
|
-- itself succeeds. This isn't ideal (I think anyway) but it works here, and
|
|
-- lets me not have to bring in LFS as a dependency.
|
|
-- @param filename File to check for existence
|
|
-- @return True if the file exists, false otherwise
|
|
local function exists(filename)
|
|
local _, err = os.rename(filename, filename)
|
|
if not err then return true end
|
|
return not err:find("No such file or directory")
|
|
end
|
|
-- }}}
|
|
|
|
-- internal functions {{{
|
|
-- _split {{{
|
|
--
|
|
-- Splits str into substrings based on several options.
|
|
-- @param str String to split
|
|
-- @param delim String of characters to use as the beginning of substring
|
|
-- delimiter
|
|
-- @param end_delim String of characters to use as the end of substring
|
|
-- delimiter
|
|
-- @param lquotes String of characters to use as opening quotes (quoted strings
|
|
-- in str will be considered one substring)
|
|
-- @param rquotes String of characters to use as closing quotes
|
|
-- @return Array of strings, one for each substring that was separated out
|
|
function _split(str, delim, end_delim, lquotes, rquotes)
|
|
-- handle arguments {{{
|
|
delim = "["..(delim or DELIM).."]"
|
|
if end_delim then end_delim = "["..end_delim.."]" end
|
|
if lquotes then lquotes = "["..lquotes.."]" end
|
|
if rquotes then rquotes = "["..rquotes.."]" end
|
|
local optdelim = delim .. "?"
|
|
-- }}}
|
|
|
|
local ret = {}
|
|
local instring = false
|
|
while str:len() > 0 do
|
|
-- handle case for not currently in a string {{{
|
|
if not instring then
|
|
local end_delim_ind, lquote_ind, delim_ind
|
|
if end_delim then end_delim_ind = str:find(optdelim..end_delim) end
|
|
if lquotes then lquote_ind = str:find(optdelim..lquotes) end
|
|
local delim_ind = str:find(delim)
|
|
if not end_delim_ind then end_delim_ind = str:len() + 1 end
|
|
if not lquote_ind then lquote_ind = str:len() + 1 end
|
|
if not delim_ind then delim_ind = str:len() + 1 end
|
|
local next_ind = math.min(end_delim_ind, lquote_ind, delim_ind)
|
|
if next_ind == str:len() + 1 then
|
|
table.insert(ret, str)
|
|
break
|
|
elseif next_ind == end_delim_ind then
|
|
-- TODO: hackish here
|
|
if str:sub(next_ind, next_ind) == end_delim:gsub('[%[%]]', '') then
|
|
table.insert(ret, str:sub(next_ind + 1))
|
|
else
|
|
table.insert(ret, str:sub(1, next_ind - 1))
|
|
table.insert(ret, str:sub(next_ind + 2))
|
|
end
|
|
break
|
|
elseif next_ind == lquote_ind then
|
|
table.insert(ret, str:sub(1, next_ind - 1))
|
|
str = str:sub(next_ind + 2)
|
|
instring = true
|
|
else -- last because the top two contain it
|
|
table.insert(ret, str:sub(1, next_ind - 1))
|
|
str = str:sub(next_ind + 1)
|
|
end
|
|
-- }}}
|
|
-- handle case for currently in a string {{{
|
|
else
|
|
local endstr = str:find(rquotes..optdelim)
|
|
table.insert(ret, str:sub(1, endstr - 1))
|
|
str = str:sub(endstr + 2)
|
|
instring = false
|
|
end
|
|
-- }}}
|
|
end
|
|
return ret
|
|
end
|
|
-- }}}
|
|
|
|
-- _basename {{{
|
|
--
|
|
-- Returns the basename of a file (the part after the last directory separator).
|
|
-- @param path Path to the file
|
|
-- @param sep Directory separator (optional, defaults to PATH_SEP)
|
|
-- @return The basename of the file
|
|
function _basename(path, sep)
|
|
sep = sep or PATH_SEP
|
|
if not path:find(sep) then return path end
|
|
return socket.skip(2, path:find(".*" .. sep .. "(.*)"))
|
|
end
|
|
-- }}}
|
|
|
|
-- _dirname {{{
|
|
--
|
|
-- Returns the dirname of a file (the part before the last directory separator).
|
|
-- @param path Path to the file
|
|
-- @param sep Directory separator (optional, defaults to PATH_SEP)
|
|
-- @return The dirname of the file
|
|
function _dirname(path, sep)
|
|
sep = sep or PATH_SEP
|
|
if not path:find(sep) then return "." end
|
|
return socket.skip(2, path:find("(.*)" .. sep .. ".*"))
|
|
end
|
|
-- }}}
|
|
|
|
-- _str_to_int {{{
|
|
--
|
|
-- Converts a number to a low-level int.
|
|
-- @param str String representation of the int
|
|
-- @param bytes Number of bytes in an int (defaults to INT_BYTES)
|
|
-- @param endian Which endianness to use (big, little, host, network) (defaultsi
|
|
-- to ENDIANNESS)
|
|
-- @return A string whose first INT_BYTES characters make a low-level int
|
|
function _str_to_int(str, bytes, endian)
|
|
bytes = bytes or INT_BYTES
|
|
endian = endian or ENDIANNESS
|
|
local ret = ""
|
|
for i = 0, bytes - 1 do
|
|
local new_byte = string.char(math.fmod(str / (2^(8 * i)), 256))
|
|
if endian == "big" or endian == "network" then ret = new_byte .. ret
|
|
else ret = ret .. new_byte
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
-- }}}
|
|
|
|
-- _int_to_str {{{
|
|
--
|
|
-- Converts a low-level int to a number.
|
|
-- @param int String whose bytes correspond to the bytes of a low-level int
|
|
-- @param endian Endianness of the int argument (defaults to ENDIANNESS)
|
|
-- @return String representation of the low-level int argument
|
|
function _int_to_str(int, endian)
|
|
endian = endian or ENDIANNESS
|
|
local ret = 0
|
|
for i = 1, int:len() do
|
|
if endian == "big" or endian == "network" then ind = int:len() - i + 1
|
|
else ind = i
|
|
end
|
|
ret = ret + string.byte(int:sub(ind, ind)) * 2^(8 * (i - 1))
|
|
end
|
|
return ret
|
|
end
|
|
-- }}}
|
|
|
|
-- _ip_str_to_int {{{
|
|
-- TODO: handle endianness here
|
|
--
|
|
-- Converts a string IP address to a low-level int.
|
|
-- @param ip_str String representation of an IP address
|
|
-- @return Low-level int representation of that IP address
|
|
function _ip_str_to_int(ip_str)
|
|
local i = 3
|
|
local ret = 0
|
|
for num in ip_str:gmatch("%d+") do
|
|
ret = ret + num * 2^(i * 8)
|
|
i = i - 1
|
|
end
|
|
return ret
|
|
end
|
|
-- }}}
|
|
|
|
-- _ip_int_to_str {{{
|
|
-- TODO: handle endianness here
|
|
--
|
|
-- Converts an int to a string IP address.
|
|
-- @param ip_int Low-level int representation of an IP address
|
|
-- @return String representation of that IP address
|
|
function _ip_int_to_str(ip_int)
|
|
local ip = {}
|
|
for i = 3, 0, -1 do
|
|
local new_num = math.floor(ip_int / 2^(i * 8))
|
|
table.insert(ip, new_num)
|
|
ip_int = ip_int - new_num * 2^(i * 8)
|
|
end
|
|
return table.concat(ip, ".")
|
|
end
|
|
-- }}}
|
|
|
|
-- _get_unique_filename {{{
|
|
--
|
|
-- Returns a unique filename.
|
|
-- @param filename Filename to start with
|
|
-- @return Filename (same as the one we started with, except possibly with some
|
|
-- numbers appended) which does not currently exist on the filesystem
|
|
function _get_unique_filename(filename)
|
|
if not exists(filename) then return filename end
|
|
|
|
local count = 1
|
|
while true do
|
|
if not exists(filename .. "." .. count) then
|
|
return filename .. "." .. count
|
|
end
|
|
count = count + 1
|
|
end
|
|
end
|
|
-- }}}
|
|
|
|
-- _try_call {{{
|
|
--
|
|
-- Call a function, if it exists.
|
|
-- @param fn Function to try to call
|
|
-- @param ... Arguments to fn
|
|
-- @return The return values of fn, if it was successfully called
|
|
function _try_call(fn, ...)
|
|
if base.type(fn) == "function" then
|
|
return fn(...)
|
|
end
|
|
end
|
|
-- }}}
|
|
|
|
-- _try_call_warn {{{
|
|
--
|
|
-- Same as try_call, but complain if the function doesn't exist.
|
|
-- @param msg Warning message to use if the function doesn't exist
|
|
-- @param fn Function to try to call
|
|
-- @param ... Arguments to fn
|
|
-- @return The return values of fn, if it was successfully called
|
|
function _try_call_warn(msg, fn, ...)
|
|
if base.type(fn) == "function" then
|
|
return fn(...)
|
|
else
|
|
irc_debug._warn(msg)
|
|
end
|
|
end
|
|
-- }}}
|
|
|
|
-- _value_iter {{{
|
|
--
|
|
-- Iterator to iterate over just the values of a table.
|
|
function _value_iter(state, arg, pred)
|
|
for k, v in base.pairs(state) do
|
|
if arg == v then arg = k end
|
|
end
|
|
local key, val = base.next(state, arg)
|
|
if not key then return end
|
|
|
|
if base.type(pred) == "function" then
|
|
while not pred(val) do
|
|
key, val = base.next(state, key)
|
|
if not key then return end
|
|
end
|
|
end
|
|
return val
|
|
end
|
|
-- }}}
|
|
|
|
-- _parse_user {{{
|
|
--
|
|
-- Gets the various parts of a full username.
|
|
-- @param user A usermask (i.e. returned in the from field of a callback)
|
|
-- @return nick
|
|
-- @return username (if it exists)
|
|
-- @return hostname (if it exists)
|
|
function _parse_user(user)
|
|
local found, bang, nick = user:find("^([^!]*)!")
|
|
if found then
|
|
user = user:sub(bang + 1)
|
|
else
|
|
return user
|
|
end
|
|
local found, equals = user:find("^.=")
|
|
if found then
|
|
user = user:sub(3)
|
|
end
|
|
local found, at, username = user:find("^([^@]*)@")
|
|
if found then
|
|
return nick, username, user:sub(at + 1)
|
|
else
|
|
return nick, user
|
|
end
|
|
end
|
|
-- }}}
|
|
-- }}}
|