newton 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +22 -0
- data/README.md +189 -0
- data/Rakefile +6 -0
- data/examples/autovoice.rb +45 -0
- data/examples/echo_bot.rb +22 -0
- data/examples/excess_flood.rb +23 -0
- data/examples/memo.rb +50 -0
- data/examples/schema.rb +41 -0
- data/examples/secure_eval.rb +46 -0
- data/lib/newton/ban.rb +40 -0
- data/lib/newton/bot.rb +361 -0
- data/lib/newton/callback.rb +24 -0
- data/lib/newton/channel.rb +362 -0
- data/lib/newton/constants.rb +123 -0
- data/lib/newton/exceptions.rb +25 -0
- data/lib/newton/formatted_logger.rb +64 -0
- data/lib/newton/irc.rb +261 -0
- data/lib/newton/isupport.rb +96 -0
- data/lib/newton/mask.rb +46 -0
- data/lib/newton/message.rb +162 -0
- data/lib/newton/message_queue.rb +62 -0
- data/lib/newton/rubyext/infinity.rb +1 -0
- data/lib/newton/rubyext/module.rb +18 -0
- data/lib/newton/rubyext/queue.rb +19 -0
- data/lib/newton/rubyext/string.rb +24 -0
- data/lib/newton/syncable.rb +55 -0
- data/lib/newton/user.rb +226 -0
- data/lib/newton.rb +1 -0
- data/test/helper.rb +60 -0
- data/test/test_commands.rb +85 -0
- data/test/test_events.rb +89 -0
- data/test/test_helpers.rb +14 -0
- data/test/test_irc.rb +38 -0
- data/test/test_message.rb +117 -0
- data/test/test_parse.rb +153 -0
- data/test/test_queue.rb +49 -0
- data/test/tests.rb +9 -0
- metadata +100 -0
@@ -0,0 +1,362 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module Newton
|
5
|
+
class Channel
|
6
|
+
include Syncable
|
7
|
+
@channels = {}
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Finds or creates a channel.
|
11
|
+
#
|
12
|
+
# @param [String] name name of a channel
|
13
|
+
# @param [Bot] bot a bot
|
14
|
+
# @return [Channel]
|
15
|
+
# @see Bot#Channel
|
16
|
+
def find_ensured(name, bot)
|
17
|
+
downcased_name = name.irc_downcase(bot.irc.isupport["CASEMAPPING"])
|
18
|
+
@channels[downcased_name] ||= new(name, bot)
|
19
|
+
@channels[downcased_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Finds a channel.
|
23
|
+
#
|
24
|
+
# @param [String] name name of a channel
|
25
|
+
# @return [Channel, nil]
|
26
|
+
def find(name)
|
27
|
+
@channels[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Array<Channel>] Returns all channels
|
31
|
+
def all
|
32
|
+
@channels.values
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Bot]
|
37
|
+
attr_reader :bot
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
attr_reader :name
|
41
|
+
|
42
|
+
# @return [Array<User>]
|
43
|
+
attr_reader :users
|
44
|
+
synced_attr_reader :users
|
45
|
+
|
46
|
+
# @return [String]
|
47
|
+
attr_accessor :topic
|
48
|
+
synced_attr_reader :topic
|
49
|
+
|
50
|
+
# @return [Array<Ban>]
|
51
|
+
attr_reader :bans
|
52
|
+
synced_attr_reader :bans
|
53
|
+
|
54
|
+
# @return [Array<String>]
|
55
|
+
attr_reader :modes
|
56
|
+
synced_attr_reader :modes
|
57
|
+
def initialize(name, bot)
|
58
|
+
@bot = bot
|
59
|
+
@name = name
|
60
|
+
@users = {}
|
61
|
+
@bans = []
|
62
|
+
|
63
|
+
@modes = {}
|
64
|
+
# TODO raise if not a channel
|
65
|
+
|
66
|
+
@topic = nil
|
67
|
+
|
68
|
+
@in_channel = false
|
69
|
+
|
70
|
+
@synced_attributes = Set.new
|
71
|
+
@when_requesting_synced_attribute = lambda {|attr|
|
72
|
+
unless @in_channel
|
73
|
+
unsync(attr)
|
74
|
+
case attr
|
75
|
+
when :users
|
76
|
+
@bot.raw "NAMES #@name"
|
77
|
+
when :topic
|
78
|
+
@bot.raw "TOPIC #@name"
|
79
|
+
when :bans
|
80
|
+
@bot.raw "MODE #@name +b"
|
81
|
+
when :modes
|
82
|
+
@bot.raw "MODE #@name"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_accessor :limit
|
89
|
+
# @return [Number]
|
90
|
+
def limit
|
91
|
+
@modes["l"].to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
def limit=(val)
|
95
|
+
if val == -1 or val.nil?
|
96
|
+
mode "-l"
|
97
|
+
else
|
98
|
+
mode "+l #{val}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
attr_accessor :secret
|
103
|
+
# @return [Boolean] true if the channel is secret (+s)
|
104
|
+
def secret
|
105
|
+
@modes["s"]
|
106
|
+
end
|
107
|
+
alias_method :secret?, :secret
|
108
|
+
|
109
|
+
def secret=(bool)
|
110
|
+
if bool
|
111
|
+
mode "+s"
|
112
|
+
else
|
113
|
+
mode "-s"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
attr_accessor :moderated # documentation only
|
118
|
+
# @return [Boolean] true if the channel is moderated (only users
|
119
|
+
# with +o and +v are able to send messages)
|
120
|
+
def moderated
|
121
|
+
@modes["m"]
|
122
|
+
end
|
123
|
+
alias_method :moderated?, :moderated
|
124
|
+
|
125
|
+
def moderated=(val)
|
126
|
+
if bool
|
127
|
+
mode "+m"
|
128
|
+
else
|
129
|
+
mode "-m"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
attr_accessor :invite_only
|
134
|
+
# @return [Boolean] true if the channel is invite only (+i)
|
135
|
+
def invite_only
|
136
|
+
@modes["i"]
|
137
|
+
end
|
138
|
+
alias_method :invite_only?, :invite_only
|
139
|
+
|
140
|
+
def invite_only=(bool)
|
141
|
+
if bool
|
142
|
+
mode "+i"
|
143
|
+
else
|
144
|
+
mode "-i"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
attr_accessor :key
|
149
|
+
# @return [String, nil]
|
150
|
+
def key
|
151
|
+
@modes["k"]
|
152
|
+
end
|
153
|
+
|
154
|
+
def key=(new_key)
|
155
|
+
if new_key.nil?
|
156
|
+
mode "-k #{key}"
|
157
|
+
else
|
158
|
+
mode "+k #{new_key}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets or unsets modes. Most of the time you won't need this but
|
163
|
+
# use setter methods like {Channel#invite_only=}.
|
164
|
+
# @param [String] s a mode string
|
165
|
+
# @return [void]
|
166
|
+
# @example
|
167
|
+
# channel.mode "+n"
|
168
|
+
def mode(s)
|
169
|
+
@bot.raw "MODE #@name #{s}"
|
170
|
+
end
|
171
|
+
|
172
|
+
# @api private
|
173
|
+
# @return [void]
|
174
|
+
def sync_modes(all = true)
|
175
|
+
unsync :users
|
176
|
+
unsync :bans
|
177
|
+
unsync :modes
|
178
|
+
@bot.raw "NAMES #@name" if all
|
179
|
+
@bot.raw "MODE #@name +b" # bans
|
180
|
+
@bot.raw "MODE #@name"
|
181
|
+
end
|
182
|
+
|
183
|
+
# @return [Boolean] true if `user` is opped in the channel
|
184
|
+
def opped?(user)
|
185
|
+
user = User.find_ensured(user, @bot) unless user.is_a?(User)
|
186
|
+
@users[user] == "@"
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [Boolean] true if `user` is voiced in the channel
|
190
|
+
def voiced?(user)
|
191
|
+
user = User.find_ensured(user, @bot) unless user.is_a?(User)
|
192
|
+
@users[user] == "+"
|
193
|
+
end
|
194
|
+
|
195
|
+
# Bans someone from the channel.
|
196
|
+
#
|
197
|
+
# @param [Ban, Mask, User, String] target the mask to ban
|
198
|
+
# @return [Mask] the mask used for banning
|
199
|
+
def ban(target)
|
200
|
+
mask = Mask.from(target)
|
201
|
+
|
202
|
+
@bot.raw "MODE #@name +b #{mask}"
|
203
|
+
mask
|
204
|
+
end
|
205
|
+
|
206
|
+
# Unbans someone from the channel.
|
207
|
+
#
|
208
|
+
# @param [Ban, Mask, User, String] target the mask to unban
|
209
|
+
# @return [Mask] the mask used for unbanning
|
210
|
+
def unban(target)
|
211
|
+
mask = Mask.from(target)
|
212
|
+
|
213
|
+
@bot.raw "MODE #@name -b #{mask}"
|
214
|
+
mask
|
215
|
+
end
|
216
|
+
|
217
|
+
# @param [String, User] user the user to op
|
218
|
+
# @return [void]
|
219
|
+
def op(user)
|
220
|
+
@bot.raw "MODE #@name +o #{user}"
|
221
|
+
end
|
222
|
+
|
223
|
+
# @param [String, User] user the user to deop
|
224
|
+
# @return [void]
|
225
|
+
def deop(user)
|
226
|
+
@bot.raw "MODE #@name -o #{user}"
|
227
|
+
end
|
228
|
+
|
229
|
+
# @param [String, User] user the user to voice
|
230
|
+
# @return [void]
|
231
|
+
def voice(user)
|
232
|
+
@bot.raw "MODE #@name +v #{user}"
|
233
|
+
end
|
234
|
+
|
235
|
+
# @param [String, User] user the user to devoice
|
236
|
+
# @return [void]
|
237
|
+
def devoice(user)
|
238
|
+
@bot.raw "MODE #@name -v #{user}"
|
239
|
+
end
|
240
|
+
|
241
|
+
# @api private
|
242
|
+
# @return [void]
|
243
|
+
def add_user(user, mode = nil)
|
244
|
+
@in_channel = true if user == @bot
|
245
|
+
@users[user] = mode # TODO can a user have more than one mode?
|
246
|
+
end
|
247
|
+
|
248
|
+
# @api private
|
249
|
+
# @return [void]
|
250
|
+
def remove_user(user)
|
251
|
+
@in_channel = false if user == @bot
|
252
|
+
@users.delete(user)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Removes all users
|
256
|
+
#
|
257
|
+
# @api private
|
258
|
+
# @return [void]
|
259
|
+
def clear_users
|
260
|
+
@users.clear
|
261
|
+
end
|
262
|
+
|
263
|
+
# Send a message to the channel.
|
264
|
+
#
|
265
|
+
# @param [String] message the message
|
266
|
+
# @return [void]
|
267
|
+
def send(message)
|
268
|
+
@bot.msg(@name, message)
|
269
|
+
end
|
270
|
+
alias_method :privmsg, :send
|
271
|
+
|
272
|
+
|
273
|
+
# Send a CTCP to the channel.
|
274
|
+
#
|
275
|
+
# @param [String] message the ctcp message
|
276
|
+
# @return [void]
|
277
|
+
def ctcp(message)
|
278
|
+
send "\001#{message}\001"
|
279
|
+
end
|
280
|
+
|
281
|
+
# Invoke an action (/me) in the channel.
|
282
|
+
#
|
283
|
+
# @param [String] message the message
|
284
|
+
# @return [void]
|
285
|
+
def action(message)
|
286
|
+
@bot.action(@name, message)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Invites a user to the channel.
|
290
|
+
#
|
291
|
+
# @param [String, User] user the user to invite
|
292
|
+
# @return [void]
|
293
|
+
def invite(user)
|
294
|
+
@bot.raw("INVITE #{user} #@name")
|
295
|
+
end
|
296
|
+
|
297
|
+
# Sets the topic.
|
298
|
+
#
|
299
|
+
# @param [String] new_topic the new topic
|
300
|
+
# @raise [Exceptions::TopicTooLong]
|
301
|
+
def topic=(new_topic)
|
302
|
+
if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
|
303
|
+
raise Exceptions::TopicTooLong, new_topic
|
304
|
+
end
|
305
|
+
|
306
|
+
@bot.raw "TOPIC #@name :#{new_topic}"
|
307
|
+
end
|
308
|
+
|
309
|
+
# Kicks a user from the channel.
|
310
|
+
#
|
311
|
+
# @param [String, User] user the user to kick
|
312
|
+
# @param [String] a reason for the kick
|
313
|
+
# @raise [Exceptions::KickReasonTooLong]
|
314
|
+
# @return [void]
|
315
|
+
def kick(user, reason = nil)
|
316
|
+
if reason.to_s.size > @bot.irc.isupport["KICKLEN"] && @bot.strict?
|
317
|
+
raise Exceptions::KickReasonTooLong, reason
|
318
|
+
end
|
319
|
+
|
320
|
+
@bot.raw("KICK #@name #{user} :#{reason}")
|
321
|
+
end
|
322
|
+
|
323
|
+
# Invites a user to the channel.
|
324
|
+
#
|
325
|
+
# @param [String, User] user the user to invite
|
326
|
+
# @return [void]
|
327
|
+
def invite(user)
|
328
|
+
@bot.raw "INVITE #{user} #@name"
|
329
|
+
end
|
330
|
+
|
331
|
+
# Causes the bot to part from the channel.
|
332
|
+
#
|
333
|
+
# @param [String] message the part message.
|
334
|
+
# @return [void]
|
335
|
+
def part(message = nil)
|
336
|
+
@bot.raw "PART #@name :#{message}"
|
337
|
+
end
|
338
|
+
|
339
|
+
# Joins the channel
|
340
|
+
#
|
341
|
+
# @param [String] key the channel key, if any. If none is
|
342
|
+
# specified but @key is set, @key will be used
|
343
|
+
# @return [void]
|
344
|
+
def join(key = nil)
|
345
|
+
if key.nil? and modes["k"] != true
|
346
|
+
key = modes["k"]
|
347
|
+
end
|
348
|
+
@bot.raw "JOIN #{[@name, key].compact.join(" ")}"
|
349
|
+
end
|
350
|
+
|
351
|
+
# @return [String]
|
352
|
+
def to_s
|
353
|
+
@name
|
354
|
+
end
|
355
|
+
alias_method :to_str, :to_s
|
356
|
+
|
357
|
+
# @return [String]
|
358
|
+
def inspect
|
359
|
+
"#<Channel name=#{@name.inspect}>"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Newton
|
2
|
+
ERR_NOSUCHNICK = 401
|
3
|
+
ERR_NOSUCHSERVER = 402
|
4
|
+
ERR_NOSUCHCHANNEL = 403
|
5
|
+
ERR_CANNOTSENDTOCHAN = 404
|
6
|
+
ERR_TOOMANYCHANNELS = 405
|
7
|
+
ERR_WASNOSUCHNICK = 406
|
8
|
+
ERR_TOOMANYTARGETS = 407
|
9
|
+
ERR_NOORIGIN = 409
|
10
|
+
ERR_NORECIPIENT = 411
|
11
|
+
ERR_NOTEXTTOSEND = 412
|
12
|
+
ERR_NOTOPLEVEL = 413
|
13
|
+
ERR_WILDTOPLEVEL = 414
|
14
|
+
ERR_UNKNOWNCOMMAND = 421
|
15
|
+
ERR_NOMOTD = 422
|
16
|
+
ERR_NOADMININFO = 423
|
17
|
+
ERR_FILEERROR = 424
|
18
|
+
ERR_NONICKNAMEGIVEN = 431
|
19
|
+
ERR_ERRONEUSNICKNAME = 432
|
20
|
+
ERR_NICKNAMEINUSE = 433
|
21
|
+
ERR_NICKCOLLISION = 436
|
22
|
+
ERR_USERNOTINCHANNEL = 441
|
23
|
+
ERR_NOTONCHANNEL = 442
|
24
|
+
ERR_USERONCHANNEL = 443
|
25
|
+
ERR_NOLOGIN = 444
|
26
|
+
ERR_SUMMONDISABLED = 445
|
27
|
+
ERR_USERSDISABLED = 446
|
28
|
+
ERR_NOTREGISTERED = 451
|
29
|
+
ERR_NEEDMOREPARAMS = 461
|
30
|
+
ERR_ALREADYREGISTRED = 462
|
31
|
+
ERR_NOPERMFORHOST = 463
|
32
|
+
ERR_PASSWDMISMATCH = 464
|
33
|
+
ERR_YOUREBANNEDCREEP = 465
|
34
|
+
ERR_KEYSET = 467
|
35
|
+
ERR_CHANNELISFULL = 471
|
36
|
+
ERR_UNKNOWNMODE = 472
|
37
|
+
ERR_INVITEONLYCHAN = 473
|
38
|
+
ERR_BANNEDFROMCHAN = 474
|
39
|
+
ERR_BADCHANNELKEY = 475
|
40
|
+
ERR_NOPRIVILEGES = 481
|
41
|
+
ERR_CHANOPRIVSNEEDED = 482
|
42
|
+
ERR_CANTKILLSERVER = 483
|
43
|
+
ERR_NOOPERHOST = 491
|
44
|
+
ERR_UMODEUNKNOWNFLAG = 501
|
45
|
+
ERR_USERSDONTMATCH = 502
|
46
|
+
|
47
|
+
RPL_NONE = 300
|
48
|
+
RPL_USERHOST = 302
|
49
|
+
RPL_ISON = 303
|
50
|
+
RPL_AWAY = 301
|
51
|
+
RPL_UNAWAY = 305
|
52
|
+
RPL_NOWAWAY = 306
|
53
|
+
RPL_WHOISUSER = 311
|
54
|
+
RPL_WHOISSERVER = 312
|
55
|
+
RPL_WHOISOPERATOR = 313
|
56
|
+
RPL_WHOISIDLE = 317
|
57
|
+
RPL_ENDOFWHOIS = 318
|
58
|
+
RPL_WHOISCHANNELS = 319
|
59
|
+
RPL_WHOWASUSER = 314
|
60
|
+
RPL_ENDOFWHOWAS = 369
|
61
|
+
RPL_LISTSTART = 321
|
62
|
+
RPL_LIST = 322
|
63
|
+
RPL_LISTEND = 323
|
64
|
+
RPL_CHANNELMODEIS = 324
|
65
|
+
RPL_WHOISACCOUNT = 330
|
66
|
+
RPL_NOTOPIC = 331
|
67
|
+
RPL_TOPIC = 332
|
68
|
+
RPL_INVITING = 341
|
69
|
+
RPL_SUMMONING = 342
|
70
|
+
RPL_VERSION = 351
|
71
|
+
RPL_WHOREPLY = 352
|
72
|
+
RPL_ENDOFWHO = 315
|
73
|
+
RPL_NAMEREPLY = 353
|
74
|
+
RPL_NAMREPLY = 353
|
75
|
+
RPL_ENDOFNAMES = 366
|
76
|
+
RPL_LINKS = 364
|
77
|
+
RPL_ENDOFLINKS = 365
|
78
|
+
RPL_BANLIST = 367
|
79
|
+
RPL_ENDOFBANLIST = 368
|
80
|
+
RPL_INFO = 371
|
81
|
+
RPL_ENDOFINFO = 374
|
82
|
+
RPL_MOTDSTART = 375
|
83
|
+
RPL_MOTD = 372
|
84
|
+
RPL_ENDOFMOTD = 376
|
85
|
+
RPL_YOUREOPER = 381
|
86
|
+
RPL_REHASHING = 382
|
87
|
+
RPL_TIME = 391
|
88
|
+
RPL_USERSSTART = 392
|
89
|
+
RPL_USERS = 393
|
90
|
+
RPL_ENDOFUSERS = 394
|
91
|
+
RPL_NOUSERS = 395
|
92
|
+
RPL_TRACELINK = 200
|
93
|
+
RPL_TRACECONNECTING = 201
|
94
|
+
RPL_TRACEHANDSHAKE = 202
|
95
|
+
RPL_TRACEUNKNOWN = 203
|
96
|
+
RPL_TRACEOPERATOR = 204
|
97
|
+
RPL_TRACEUSER = 205
|
98
|
+
RPL_TRACESERVER = 206
|
99
|
+
RPL_TRACENEWTYPE = 208
|
100
|
+
RPL_TRACELOG = 261
|
101
|
+
RPL_STATSLINKINFO = 211
|
102
|
+
RPL_STATSCOMMANDS = 212
|
103
|
+
RPL_STATSCLINE = 213
|
104
|
+
RPL_STATSNLINE = 214
|
105
|
+
RPL_STATSILINE = 215
|
106
|
+
RPL_STATSKLINE = 216
|
107
|
+
RPL_STATSYLINE = 218
|
108
|
+
RPL_ENDOFSTATS = 219
|
109
|
+
RPL_STATSLLINE = 241
|
110
|
+
RPL_STATSUPTIME = 242
|
111
|
+
RPL_STATSOLINE = 243
|
112
|
+
RPL_STATSHLINE = 244
|
113
|
+
RPL_UMODEIS = 221
|
114
|
+
RPL_LUSERCLIENT = 251
|
115
|
+
RPL_LUSEROP = 252
|
116
|
+
RPL_LUSERUNKNOWN = 253
|
117
|
+
RPL_LUSERCHANNELS = 254
|
118
|
+
RPL_LUSERME = 255
|
119
|
+
RPL_ADMINME = 256
|
120
|
+
RPL_ADMINLOC1 = 257
|
121
|
+
RPL_ADMINLOC2 = 258
|
122
|
+
RPL_ADMINEMAIL = 259
|
123
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Newton
|
2
|
+
module Exceptions
|
3
|
+
# Generic error. Superclass for all Newton-specific errors.
|
4
|
+
class Generic < ::StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class ArgumentTooLong < Generic
|
8
|
+
end
|
9
|
+
|
10
|
+
# Error that is raised when a topic is too long to be set.
|
11
|
+
class TopicTooLong < ArgumentTooLong
|
12
|
+
end
|
13
|
+
|
14
|
+
# Error that is raised when a nick is too long to be used.
|
15
|
+
class NickTooLong < ArgumentTooLong
|
16
|
+
end
|
17
|
+
|
18
|
+
# Error that is raised when a kick reason is too long.
|
19
|
+
class KickReasonTooLong < ArgumentTooLong
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnsupportedFeature < Generic
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Newton
|
2
|
+
class FormattedLogger
|
3
|
+
COLORS = {
|
4
|
+
:reset => "\e[0m",
|
5
|
+
:bold => "\e[1m",
|
6
|
+
:red => "\e[31m",
|
7
|
+
:green => "\e[32m",
|
8
|
+
:yellow => "\e[33m",
|
9
|
+
:blue => "\e[34m",
|
10
|
+
}
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @return [void]
|
14
|
+
def debug(message)
|
15
|
+
log(message, :debug)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
# @return [void]
|
20
|
+
def log(message, kind = :generic)
|
21
|
+
message = message.to_s.chomp # don't want to tinker with the original string
|
22
|
+
unless $stdout.tty?
|
23
|
+
$stderr.puts message
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
if kind == :debug
|
28
|
+
prefix = colorize("!! ", :yellow)
|
29
|
+
message = prefix + message
|
30
|
+
else
|
31
|
+
pre, msg = message.split(" :", 2)
|
32
|
+
pre_parts = pre.split(" ")
|
33
|
+
|
34
|
+
if kind == :incoming
|
35
|
+
prefix = colorize(">> ", :green)
|
36
|
+
|
37
|
+
if pre_parts.size == 1
|
38
|
+
pre_parts[0] = colorize(pre_parts[0], :bold)
|
39
|
+
else
|
40
|
+
pre_parts[0] = colorize(pre_parts[0], :blue)
|
41
|
+
pre_parts[1] = colorize(pre_parts[1], :bold)
|
42
|
+
end
|
43
|
+
|
44
|
+
elsif kind == :outgoing
|
45
|
+
prefix = colorize("<< ", :red)
|
46
|
+
pre_parts[0] = colorize(pre_parts[0], :bold)
|
47
|
+
end
|
48
|
+
|
49
|
+
message = prefix + pre_parts.join(" ")
|
50
|
+
message << colorize(" :#{msg}", :yellow) if msg
|
51
|
+
end
|
52
|
+
$stderr.puts message
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
# @param [String] text text to colorize
|
57
|
+
# @param [Array<Symbol>] codes array of colors to apply
|
58
|
+
# @return [String] colorized string
|
59
|
+
def colorize(text, *codes)
|
60
|
+
COLORS.values_at(*codes).join + text + COLORS[:reset]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|