2 -- Various useful functions that didn't fit anywhere else
5 local irc_debug = require 'irc.debug'
6 local socket = require 'socket'
7 local math = require 'math'
8 local os = require 'os'
9 local string = require 'string'
10 local table = require 'table'
14 -- This module contains various useful functions which didn't fit in any of the
25 -- private functions {{{
27 -- Check for existence of a file. This returns true if renaming a file to
28 -- itself succeeds. This isn't ideal (I think anyway) but it works here, and
29 -- lets me not have to bring in LFS as a dependency.
30 -- @param filename File to check for existence
31 -- @return True if the file exists, false otherwise
32 local function exists(filename)
33 local _, err = os.rename(filename, filename)
34 if not err then return true end
35 return not err:find("No such file or directory")
39 -- public functions {{{
42 -- Gets the various parts of a full username.
43 -- @param user A usermask (i.e. returned in the from field of a callback)
45 -- @return username (if it exists)
46 -- @return hostname (if it exists)
47 function parse_user(user)
48 local found, bang, nick = user:find("^([^!]*)!")
50 user = user:sub(bang + 1)
54 local found, equals = user:find("^.=")
58 local found, at, username = user:find("^([^@]*)@")
60 return nick, username, user:sub(at + 1)
69 -- Splits str into substrings based on several options.
70 -- @param str String to split
71 -- @param delim String of characters to use as the beginning of substring
73 -- @param end_delim String of characters to use as the end of substring
75 -- @param lquotes String of characters to use as opening quotes (quoted strings
76 -- in str will be considered one substring)
77 -- @param rquotes String of characters to use as closing quotes
78 -- @return Array of strings, one for each substring that was separated out
79 function split(str, delim, end_delim, lquotes, rquotes)
80 -- handle arguments {{{
81 delim = "["..(delim or DELIM).."]"
82 if end_delim then end_delim = "["..end_delim.."]" end
83 if lquotes then lquotes = "["..lquotes.."]" end
84 if rquotes then rquotes = "["..rquotes.."]" end
85 local optdelim = delim .. "?"
89 local instring = false
90 while str:len() > 0 do
91 -- handle case for not currently in a string {{{
93 local end_delim_ind, lquote_ind, delim_ind
94 if end_delim then end_delim_ind = str:find(optdelim..end_delim) end
95 if lquotes then lquote_ind = str:find(optdelim..lquotes) end
96 local delim_ind = str:find(delim)
97 if not end_delim_ind then end_delim_ind = str:len() + 1 end
98 if not lquote_ind then lquote_ind = str:len() + 1 end
99 if not delim_ind then delim_ind = str:len() + 1 end
100 local next_ind = math.min(end_delim_ind, lquote_ind, delim_ind)
101 if next_ind == str:len() + 1 then
102 table.insert(ret, str)
104 elseif next_ind == end_delim_ind then
105 -- TODO: hackish here
106 if str:sub(next_ind, next_ind) == end_delim:gsub('[%[%]]', '') then
107 table.insert(ret, str:sub(next_ind + 1))
109 table.insert(ret, str:sub(1, next_ind - 1))
110 table.insert(ret, str:sub(next_ind + 2))
113 elseif next_ind == lquote_ind then
114 table.insert(ret, str:sub(1, next_ind - 1))
115 str = str:sub(next_ind + 2)
117 else -- last because the top two contain it
118 table.insert(ret, str:sub(1, next_ind - 1))
119 str = str:sub(next_ind + 1)
122 -- handle case for currently in a string {{{
124 local endstr = str:find(rquotes..optdelim)
125 table.insert(ret, str:sub(1, endstr - 1))
126 str = str:sub(endstr + 2)
137 -- Returns the basename of a file (the part after the last directory separator).
138 -- @param path Path to the file
139 -- @param sep Directory separator (optional, defaults to PATH_SEP)
140 -- @return The basename of the file
141 function basename(path, sep)
142 sep = sep or PATH_SEP
143 if not path:find(sep) then return path end
144 return socket.skip(2, path:find(".*" .. sep .. "(.*)"))
150 -- Returns the dirname of a file (the part before the last directory separator).
151 -- @param path Path to the file
152 -- @param sep Directory separator (optional, defaults to PATH_SEP)
153 -- @return The dirname of the file
154 function dirname(path, sep)
155 sep = sep or PATH_SEP
156 if not path:find(sep) then return "." end
157 return socket.skip(2, path:find("(.*)" .. sep .. ".*"))
163 -- Converts a number to a low-level int.
164 -- @param str String representation of the int
165 -- @param bytes Number of bytes in an int (defaults to INT_BYTES)
166 -- @param endian Which endianness to use (big, little, host, network) (defaultsi
168 -- @return A string whose first INT_BYTES characters make a low-level int
169 function str_to_int(str, bytes, endian)
170 bytes = bytes or INT_BYTES
171 endian = endian or ENDIANNESS
173 for i = 0, bytes - 1 do
174 local new_byte = string.char(math.fmod(str / (2^(8 * i)), 256))
175 if endian == "big" or endian == "network" then ret = new_byte .. ret
176 else ret = ret .. new_byte
185 -- Converts a low-level int to a number.
186 -- @param int String whose bytes correspond to the bytes of a low-level int
187 -- @param endian Endianness of the int argument (defaults to ENDIANNESS)
188 -- @return String representation of the low-level int argument
189 function int_to_str(int, endian)
190 endian = endian or ENDIANNESS
192 for i = 1, int:len() do
193 if endian == "big" or endian == "network" then ind = int:len() - i + 1
196 ret = ret + string.byte(int:sub(ind, ind)) * 2^(8 * (i - 1))
204 -- Converts a string IP address to a low-level int.
205 -- @param ip_str String representation of an IP address
206 -- @return Low-level int representation of that IP address
207 function ip_str_to_int(ip_str)
210 for num in ip_str:gmatch("%d+") do
211 ret = ret + num * 2^(i * 8)
220 -- Converts an int to a string IP address.
221 -- @param ip_int Low-level int representation of an IP address
222 -- @return String representation of that IP address
223 function ip_int_to_str(ip_int)
226 local new_num = math.floor(ip_int / 2^(i * 8))
227 table.insert(ip, new_num)
228 ip_int = ip_int - new_num * 2^(i * 8)
230 return table.concat(ip, ".")
234 -- get_unique_filename {{{
236 -- Returns a unique filename.
237 -- @param filename Filename to start with
238 -- @return Filename (same as the one we started with, except possibly with some
239 -- numbers appended) which does not currently exist on the filesystem
240 function get_unique_filename(filename)
241 if not exists(filename) then return filename end
245 if not exists(filename .. "." .. count) then
246 return filename .. "." .. count
255 -- Call a function, if it exists.
256 -- @param fn Function to try to call
257 -- @param ... Arguments to fn
258 -- @return The return values of fn, if it was successfully called
259 function try_call(fn, ...)
260 if base.type(fn) == "function" then
268 -- Same as try_call, but complain if the function doesn't exist.
269 -- @param msg Warning message to use if the function doesn't exist
270 -- @param fn Function to try to call
271 -- @param ... Arguments to fn
272 -- @return The return values of fn, if it was successfully called
273 function try_call_warn(msg, fn, ...)
274 if base.type(fn) == "function" then
284 -- Iterator to iterate over just the values of a table.
285 function value_iter(state, arg, pred)
286 for k, v in base.pairs(state) do
287 if arg == v then arg = k end
289 local key, val = base.next(state, arg)
290 if not key then return end
292 if base.type(pred) == "function" then
293 while not pred(val) do
294 key, val = base.next(state, key)
295 if not key then return end