]> git.lizzy.rs Git - luairc.git/blob - src/irc/misc.lua
9f122803789dd451be509d9bbe03bea7bf72b5be
[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 -- split {{{
68 --
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
72 --              delimiter
73 -- @param end_delim String of characters to use as the end of substring
74 --                  delimiter
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 .. "?"
86     -- }}}
87
88     local ret = {}
89     local instring = false
90     while str:len() > 0 do
91         -- handle case for not currently in a string {{{
92         if not instring then
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)
103                 break
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))
108                 else
109                     table.insert(ret, str:sub(1, next_ind - 1))
110                     table.insert(ret, str:sub(next_ind + 2))
111                 end
112                 break
113             elseif next_ind == lquote_ind then
114                 table.insert(ret, str:sub(1, next_ind - 1))
115                 str = str:sub(next_ind + 2)
116                 instring = true
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)
120             end
121         -- }}}
122         -- handle case for currently in a string {{{
123         else
124             local endstr = str:find(rquotes..optdelim)
125             table.insert(ret, str:sub(1, endstr - 1))
126             str = str:sub(endstr + 2)
127             instring = false
128         end
129         -- }}}
130     end
131     return ret
132 end
133 -- }}}
134
135 -- basename {{{
136 --
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 .. "(.*)"))
145 end
146 -- }}}
147
148 -- dirname {{{
149 --
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 .. ".*"))
158 end
159 -- }}}
160
161 -- str_to_int {{{
162 --
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
167 --               to ENDIANNESS)
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
172     local ret = ""
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
177         end
178     end
179     return ret
180 end
181 -- }}}
182
183 -- int_to_str {{{
184 --
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
191     local ret = 0
192     for i = 1, int:len() do
193         if endian == "big" or endian == "network" then ind = int:len() - i + 1
194         else ind = i
195         end
196         ret = ret + string.byte(int:sub(ind, ind)) * 2^(8 * (i - 1))
197     end
198     return ret
199 end
200 -- }}}
201
202 -- ip_str_to_int {{{
203 --
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)
208     local i = 3
209     local ret = 0
210     for num in ip_str:gmatch("%d+") do
211         ret = ret + num * 2^(i * 8)                  
212         i = i - 1
213     end
214     return ret
215 end
216 -- }}}
217
218 -- ip_int_to_str {{{
219 --
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)
224     local ip = {}
225     for i = 3, 0, -1 do
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)
229     end 
230     return table.concat(ip, ".")
231 end
232 -- }}}
233
234 -- get_unique_filename {{{
235 --
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
242
243     local count = 1
244     while true do
245         if not exists(filename .. "." .. count) then
246             return filename .. "." .. count
247         end
248         count = count + 1
249     end
250 end
251 -- }}}
252
253 -- try_call {{{
254 --
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
261         return fn(...)
262     end
263 end
264 -- }}}
265
266 -- try_call_warn {{{
267 --
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
275         return fn(...)
276     else
277         irc_debug.warn(msg)
278     end
279 end
280 -- }}}
281
282 -- value_iter {{{
283 --
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
288     end
289     local key, val = base.next(state, arg)
290     if not key then return end
291
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
296         end
297     end
298     return val
299 end
300 -- }}}
301 -- }}}