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