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