]> git.lizzy.rs Git - luairc.git/blob - src/irc/misc.lua
Port to Lua 5.3
[luairc.git] / src / irc / misc.lua
1 ---
2 -- Various useful functions that didn't fit anywhere else
3 -- initialization {{{
4 local irc_debug = libs.debug
5 local socket =    libs.socket
6 -- }}}
7
8 ---
9 -- This module contains various useful functions which didn't fit in any of the
10 -- other modules.
11 local misc = {}
12
13 -- defaults {{{
14 misc.DELIM = ' '
15 misc.PATH_SEP = '/'
16 misc.ENDIANNESS = "big"
17 misc.INT_BYTES = 4
18 -- }}}
19
20 -- private functions {{{
21 --
22 -- Check for existence of a file. This returns true if renaming a file to
23 -- itself succeeds. This isn't ideal (I think anyway) but it works here, and
24 -- lets me not have to bring in LFS as a dependency.
25 -- @param filename File to check for existence
26 -- @return True if the file exists, false otherwise
27 local function exists(filename)
28     local _, err = os.rename(filename, filename)
29     if not err then return true end
30     return not err:find("No such file or directory")
31 end
32 -- }}}
33
34 -- internal functions {{{
35 -- _split {{{
36 --
37 -- Splits str into substrings based on several options.
38 -- @param str String to split
39 -- @param delim String of characters to use as the beginning of substring
40 --              delimiter
41 -- @param end_delim String of characters to use as the end of substring
42 --                  delimiter
43 -- @param lquotes String of characters to use as opening quotes (quoted strings
44 --                in str will be considered one substring)
45 -- @param rquotes String of characters to use as closing quotes
46 -- @return Array of strings, one for each substring that was separated out
47 function misc._split(str, delim, end_delim, lquotes, rquotes)
48     -- handle arguments {{{
49     delim = "["..(delim or misc.DELIM).."]"
50     if end_delim then end_delim = "["..end_delim.."]" end
51     if lquotes then lquotes = "["..lquotes.."]" end
52     if rquotes then rquotes = "["..rquotes.."]" end
53     local optdelim = delim .. "?"
54     -- }}}
55
56     local ret = {}
57     local instring = false
58     while str:len() > 0 do
59         -- handle case for not currently in a string {{{
60         if not instring then
61             local end_delim_ind, lquote_ind, delim_ind
62             if end_delim then end_delim_ind = str:find(optdelim..end_delim) end
63             if lquotes then lquote_ind = str:find(optdelim..lquotes) end
64             local delim_ind = str:find(delim)
65             if not end_delim_ind then end_delim_ind = str:len() + 1 end
66             if not lquote_ind then lquote_ind = str:len() + 1 end
67             if not delim_ind then delim_ind = str:len() + 1 end
68             local next_ind = math.min(end_delim_ind, lquote_ind, delim_ind)
69             if next_ind == str:len() + 1 then
70                 table.insert(ret, str)
71                 break
72             elseif next_ind == end_delim_ind then
73                 -- TODO: hackish here
74                 if str:sub(next_ind, next_ind) == end_delim:gsub('[%[%]]', '') then
75                     table.insert(ret, str:sub(next_ind + 1))
76                 else
77                     table.insert(ret, str:sub(1, next_ind - 1))
78                     table.insert(ret, str:sub(next_ind + 2))
79                 end
80                 break
81             elseif next_ind == lquote_ind then
82                 table.insert(ret, str:sub(1, next_ind - 1))
83                 str = str:sub(next_ind + 2)
84                 instring = true
85             else -- last because the top two contain it
86                 table.insert(ret, str:sub(1, next_ind - 1))
87                 str = str:sub(next_ind + 1)
88             end
89         -- }}}
90         -- handle case for currently in a string {{{
91         else
92             local endstr = str:find(rquotes..optdelim)
93             table.insert(ret, str:sub(1, endstr - 1))
94             str = str:sub(endstr + 2)
95             instring = false
96         end
97         -- }}}
98     end
99     return ret
100 end
101 -- }}}
102
103 -- _basename {{{
104 --
105 -- Returns the basename of a file (the part after the last directory separator).
106 -- @param path Path to the file
107 -- @param sep Directory separator (optional, defaults to PATH_SEP)
108 -- @return The basename of the file
109 function misc._basename(path, sep)
110     sep = sep or misc.PATH_SEP
111     if not path:find(sep) then return path end
112     return socket.skip(2, path:find(".*" .. sep .. "(.*)"))
113 end
114 -- }}}
115
116 -- _dirname {{{
117 --
118 -- Returns the dirname of a file (the part before the last directory separator).
119 -- @param path Path to the file
120 -- @param sep Directory separator (optional, defaults to PATH_SEP)
121 -- @return The dirname of the file
122 function misc._dirname(path, sep)
123     sep = sep or misc.PATH_SEP
124     if not path:find(sep) then return "." end
125     return socket.skip(2, path:find("(.*)" .. sep .. ".*"))
126 end
127 -- }}}
128
129 -- _str_to_int {{{
130 --
131 -- Converts a number to a low-level int.
132 -- @param str String representation of the int
133 -- @param bytes Number of bytes in an int (defaults to INT_BYTES)
134 -- @param endian Which endianness to use (big, little, host, network) (defaultsi
135 --               to ENDIANNESS)
136 -- @return A string whose first INT_BYTES characters make a low-level int
137 function misc._str_to_int(str, bytes, endian)
138     bytes = bytes or misc.INT_BYTES
139     endian = endian or misc.ENDIANNESS
140     local ret = ""
141     for i = 0, bytes - 1 do 
142         local new_byte = string.char(math.fmod(str / (2^(8 * i)), 256))
143         if endian == "big" or endian == "network" then ret = new_byte .. ret
144         else ret = ret .. new_byte
145         end
146     end
147     return ret
148 end
149 -- }}}
150
151 -- _int_to_str {{{
152 --
153 -- Converts a low-level int to a number.
154 -- @param int String whose bytes correspond to the bytes of a low-level int
155 -- @param endian Endianness of the int argument (defaults to ENDIANNESS)
156 -- @return String representation of the low-level int argument
157 function misc._int_to_str(int, endian)
158     endian = endian or misc.ENDIANNESS
159     local ret = 0
160     for i = 1, int:len() do
161         if endian == "big" or endian == "network" then ind = int:len() - i + 1
162         else ind = i
163         end
164         ret = ret + string.byte(int:sub(ind, ind)) * 2^(8 * (i - 1))
165     end
166     return ret
167 end
168 -- }}}
169
170 -- _ip_str_to_int {{{
171 -- TODO: handle endianness here
172 --
173 -- Converts a string IP address to a low-level int.
174 -- @param ip_str String representation of an IP address
175 -- @return Low-level int representation of that IP address
176 function misc._ip_str_to_int(ip_str)
177     local i = 3
178     local ret = 0
179     for num in ip_str:gmatch("%d+") do
180         ret = ret + num * 2^(i * 8)                  
181         i = i - 1
182     end
183     return ret
184 end
185 -- }}}
186
187 -- _ip_int_to_str {{{
188 -- TODO: handle endianness here
189 --
190 -- Converts an int to a string IP address.
191 -- @param ip_int Low-level int representation of an IP address
192 -- @return String representation of that IP address
193 function misc._ip_int_to_str(ip_int)
194     local ip = {}
195     for i = 3, 0, -1 do
196         local new_num = math.floor(ip_int / 2^(i * 8))
197         table.insert(ip, new_num)
198         ip_int = ip_int - new_num * 2^(i * 8)
199     end 
200     return table.concat(ip, ".")
201 end
202 -- }}}
203
204 -- _get_unique_filename {{{
205 --
206 -- Returns a unique filename.
207 -- @param filename Filename to start with
208 -- @return Filename (same as the one we started with, except possibly with some
209 --         numbers appended) which does not currently exist on the filesystem
210 function misc._get_unique_filename(filename)
211     if not exists(filename) then return filename end
212
213     local count = 1
214     while true do
215         if not exists(filename .. "." .. count) then
216             return filename .. "." .. count
217         end
218         count = count + 1
219     end
220 end
221 -- }}}
222
223 -- _try_call {{{
224 --
225 -- Call a function, if it exists.
226 -- @param fn Function to try to call
227 -- @param ... Arguments to fn
228 -- @return The return values of fn, if it was successfully called
229 function misc._try_call(fn, ...)
230     if type(fn) == "function" then
231         return fn(...)
232     end
233 end
234 -- }}}
235
236 -- _try_call_warn {{{
237 --
238 -- Same as try_call, but complain if the function doesn't exist.
239 -- @param msg Warning message to use if the function doesn't exist
240 -- @param fn Function to try to call
241 -- @param ... Arguments to fn
242 -- @return The return values of fn, if it was successfully called
243 function misc._try_call_warn(msg, fn, ...)
244     if type(fn) == "function" then
245         return fn(...)
246     else
247         irc_debug._warn(msg)
248     end
249 end
250 -- }}}
251
252 -- _value_iter {{{
253 --
254 -- Iterator to iterate over just the values of a table.
255 function misc._value_iter(state, arg, pred)
256     for k, v in pairs(state) do
257         if arg == v then arg = k end
258     end
259     local key, val = next(state, arg)
260     if not key then return end
261
262     if type(pred) == "function" then
263         while not pred(val) do
264             key, val = next(state, key)
265             if not key then return end
266         end
267     end
268     return val
269 end
270 -- }}}
271
272 -- _parse_user {{{
273 --
274 -- Gets the various parts of a full username.
275 -- @param user A usermask (i.e. returned in the from field of a callback)
276 -- @return nick
277 -- @return username (if it exists)
278 -- @return hostname (if it exists)
279 function misc._parse_user(user)
280     local found, bang, nick = user:find("^([^!]*)!")
281     if found then
282         user = user:sub(bang + 1)
283     else
284         return user
285     end
286     local found, equals = user:find("^.=")
287     if found then
288         user = user:sub(3)
289     end
290     local found, at, username = user:find("^([^@]*)@")
291     if found then
292         return nick, username, user:sub(at + 1)
293     else
294         return nick, user
295     end
296 end
297 -- }}}
298 -- }}}
299
300 return misc