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