jeffrafter-marvin 0.1.20081115 → 0.1.20081120
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/README.textile +105 -32
- data/VERSION.yml +1 -1
- data/bin/marvin +10 -6
- data/config/connections.yml.sample +5 -0
- data/config/settings.yml.sample +2 -7
- data/config/setup.rb +6 -1
- data/handlers/debug_handler.rb +5 -0
- data/handlers/hello_world.rb +1 -1
- data/lib/marvin.rb +13 -9
- data/lib/marvin/abstract_client.rb +88 -43
- data/lib/marvin/abstract_parser.rb +14 -2
- data/lib/marvin/base.rb +44 -6
- data/lib/marvin/dispatchable.rb +9 -4
- data/lib/marvin/exception_tracker.rb +1 -1
- data/lib/marvin/exceptions.rb +3 -0
- data/lib/marvin/irc.rb +4 -5
- data/lib/marvin/irc/client.rb +39 -7
- data/lib/marvin/irc/event.rb +9 -4
- data/lib/marvin/irc/replies.rb +154 -0
- data/lib/marvin/loader.rb +26 -8
- data/lib/marvin/logger.rb +66 -3
- data/lib/marvin/options.rb +33 -0
- data/lib/marvin/parsers.rb +3 -0
- data/lib/marvin/parsers/command.rb +105 -0
- data/lib/marvin/parsers/prefixes.rb +8 -0
- data/lib/marvin/parsers/prefixes/host_mask.rb +30 -0
- data/lib/marvin/parsers/prefixes/server.rb +24 -0
- data/lib/marvin/parsers/ragel_parser.rb +713 -0
- data/lib/marvin/parsers/ragel_parser.rl +144 -0
- data/lib/marvin/parsers/regexp_parser.rb +0 -3
- data/lib/marvin/parsers/simple_parser.rb +20 -81
- data/lib/marvin/settings.rb +9 -9
- data/lib/marvin/test_client.rb +5 -1
- data/lib/marvin/util.rb +20 -3
- data/script/{run → client} +0 -0
- data/script/daemon-runner +1 -1
- data/script/install +3 -0
- data/test/parser_comparison.rb +62 -0
- data/test/parser_test.rb +264 -0
- data/test/test_helper.rb +10 -0
- metadata +19 -9
- data/lib/marvin/drb_handler.rb +0 -7
- data/lib/marvin/irc/abstract_server.rb +0 -4
- data/lib/marvin/irc/base_server.rb +0 -11
- data/lib/marvin/irc/socket_client.rb +0 -69
- data/lib/marvin/parsers/simple_parser/default_events.rb +0 -37
- data/lib/marvin/parsers/simple_parser/event_extensions.rb +0 -14
- data/lib/marvin/parsers/simple_parser/prefixes.rb +0 -34
@@ -7,12 +7,24 @@ module Marvin
|
|
7
7
|
return self.new(line.strip).to_event
|
8
8
|
end
|
9
9
|
|
10
|
+
attr_accessor :line, :command, :event
|
11
|
+
|
12
|
+
# Instantiates a parser instance, attempts to
|
13
|
+
# parse it for it's command and it's event.
|
10
14
|
def initialize(line)
|
11
|
-
|
15
|
+
self.line = line
|
16
|
+
self.command = self.class.parse!(line)
|
17
|
+
self.event = self.command.to_event unless self.command.blank?
|
12
18
|
end
|
13
19
|
|
14
20
|
def to_event
|
15
|
-
|
21
|
+
self.event
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.parse!(line)
|
27
|
+
raise NotImplementedError, "Must be implemented in a subclass"
|
16
28
|
end
|
17
29
|
|
18
30
|
end
|
data/lib/marvin/base.rb
CHANGED
@@ -33,10 +33,42 @@ module Marvin
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
# Registers a block or method to be called when
|
37
|
+
# a given event is occured.
|
38
|
+
# == Examples
|
39
|
+
#
|
40
|
+
# on_event :incoming_message, :process_commands
|
41
|
+
#
|
42
|
+
# Will call process_commands the current object
|
43
|
+
# every time incoming message is called.
|
44
|
+
#
|
45
|
+
# on_event :incoming_message do
|
46
|
+
# Marvin::Logger.debug ">> #{options.inspect}"
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# Will invoke the block every time an incoming message
|
50
|
+
# is processed.
|
51
|
+
def on_event(name, method_name = nil, &blk)
|
52
|
+
# If the name is set and it responds to :to_sym
|
53
|
+
# and no block was passed in.
|
54
|
+
blk = proc { self.send(method_name.to_sym) } if method_name.respond_to?(:to_sym) && blk.blank?
|
37
55
|
self.event_handlers_for(name, false) << blk
|
38
56
|
end
|
39
57
|
|
58
|
+
def on_numeric(value, method_name = nil, &blk)
|
59
|
+
if value.is_a?(Numeric)
|
60
|
+
new_value = "%03d" % value
|
61
|
+
else
|
62
|
+
new_value = Marvin::IRC::Replies[value]
|
63
|
+
end
|
64
|
+
if new_value.nil?
|
65
|
+
logger.error "The numeric '#{value}' was undefined"
|
66
|
+
else
|
67
|
+
blk = proc { self.send(method_name.to_sym) } if method_name.respond_to?(:to_sym) && blk.blank?
|
68
|
+
self.event_handlers_for(:"incoming_numeric_#{new_value}", false) << blk
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
40
72
|
# Register's in the IRC Client callback chain.
|
41
73
|
def register!(parent = Marvin::Settings.default_client)
|
42
74
|
return if self == Marvin::Base # Only do it for sub-classes.
|
@@ -57,15 +89,21 @@ module Marvin
|
|
57
89
|
begin
|
58
90
|
self.setup_defaults(options)
|
59
91
|
h = self.class.event_handlers_for(message)
|
60
|
-
h.each
|
61
|
-
self.instance_eval &handle
|
62
|
-
end
|
92
|
+
h.each { |handle| self.instance_eval(&handle) }
|
63
93
|
rescue Exception => e
|
64
94
|
logger.fatal "Exception processing handle #{message}"
|
65
95
|
Marvin::ExceptionTracker.log(e)
|
66
96
|
end
|
67
97
|
end
|
68
98
|
|
99
|
+
def handle_incoming_numeric(opts)
|
100
|
+
self.handle(:incoming_numeric, opts)
|
101
|
+
name = :"incoming_numeric_#{options.code}"
|
102
|
+
events = self.class.event_handlers_for(name)
|
103
|
+
logger.debug "Dispatching #{events.size} events for #{name}"
|
104
|
+
events.each { |eh| self.instance_eval(&eh) }
|
105
|
+
end
|
106
|
+
|
69
107
|
def say(message, target = self.target)
|
70
108
|
client.msg target, message
|
71
109
|
end
|
@@ -97,11 +135,11 @@ module Marvin
|
|
97
135
|
|
98
136
|
# Determines whether the previous message was inside a channel.
|
99
137
|
def from_channel?
|
100
|
-
self.target && self.target[0]
|
138
|
+
self.target && [?#, ?&].include?(self.target[0])
|
101
139
|
end
|
102
140
|
|
103
141
|
def addressed?
|
104
|
-
self.from_user? || options.message
|
142
|
+
self.from_user? || options.message =~ /^#{self.client.nickname.downcase}:\s+/i
|
105
143
|
end
|
106
144
|
|
107
145
|
def setup_defaults(options)
|
data/lib/marvin/dispatchable.rb
CHANGED
@@ -26,9 +26,11 @@ module Marvin
|
|
26
26
|
module InstanceMethods
|
27
27
|
|
28
28
|
# Returns the handlers registered on this class,
|
29
|
-
# used inside +dispatch+.
|
29
|
+
# used inside +dispatch+. Note that it will call
|
30
|
+
# dup on each of the objects to get a new instance.
|
31
|
+
# please ensure your object acts accordingly.
|
30
32
|
def handlers
|
31
|
-
self.class.handlers
|
33
|
+
@handlers ||= self.class.handlers.map { |h| h.dup }
|
32
34
|
end
|
33
35
|
|
34
36
|
# Dispatch an 'event' with a given name to the handlers
|
@@ -53,7 +55,7 @@ module Marvin
|
|
53
55
|
# call.
|
54
56
|
self.handlers.each do |handler|
|
55
57
|
if handler.respond_to?(full_handler_name)
|
56
|
-
handler.
|
58
|
+
handler.send(full_handler_name, opts)
|
57
59
|
else
|
58
60
|
handler.handle name, opts
|
59
61
|
end
|
@@ -62,7 +64,10 @@ module Marvin
|
|
62
64
|
# catch it and continue on our way. In essence, we
|
63
65
|
# stop the dispatch of events to the next set of the
|
64
66
|
# handlers.
|
65
|
-
rescue HaltHandlerProcessing
|
67
|
+
rescue HaltHandlerProcessing => e
|
68
|
+
Marvin::Logger.info "Halting processing chain"
|
69
|
+
rescue Exception => e
|
70
|
+
Marvin::ExceptionTracker.log(e)
|
66
71
|
end
|
67
72
|
|
68
73
|
end
|
data/lib/marvin/exceptions.rb
CHANGED
data/lib/marvin/irc.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
module Marvin
|
2
2
|
module IRC
|
3
|
-
autoload :Client,
|
4
|
-
autoload :Event,
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :BaseServer, 'marvin/irc/base_server'
|
3
|
+
autoload :Client, 'marvin/irc/client'
|
4
|
+
autoload :Event, 'marvin/irc/event'
|
5
|
+
autoload :Server, 'marvin/irc/server'
|
6
|
+
autoload :Replies, 'marvin/irc/replies'
|
8
7
|
end
|
9
8
|
end
|
data/lib/marvin/irc/client.rb
CHANGED
@@ -45,32 +45,38 @@ module Marvin::IRC
|
|
45
45
|
# the class method, register_handler with the
|
46
46
|
# handler as the only argument.
|
47
47
|
class Client < Marvin::AbstractClient
|
48
|
+
cattr_accessor :stopped
|
48
49
|
attr_accessor :em_connection
|
49
50
|
|
50
51
|
class EMConnection < EventMachine::Protocols::LineAndTextProtocol
|
51
|
-
attr_accessor :client
|
52
|
+
attr_accessor :client, :server, :port
|
52
53
|
|
53
|
-
def initialize
|
54
|
-
|
55
|
-
|
54
|
+
def initialize(*args)
|
55
|
+
opts = args.extract_options!
|
56
|
+
super(*args)
|
57
|
+
self.client = Marvin::IRC::Client.new(opts)
|
56
58
|
self.client.em_connection = self
|
57
59
|
end
|
58
60
|
|
59
61
|
def post_init
|
60
62
|
client.process_connect
|
63
|
+
super
|
61
64
|
end
|
62
65
|
|
63
66
|
def unbind
|
64
67
|
client.process_disconnect
|
68
|
+
super
|
65
69
|
end
|
66
70
|
|
67
71
|
def receive_line(line)
|
72
|
+
Marvin::Logger.debug "<< #{line.strip}"
|
68
73
|
self.client.receive_line(line)
|
69
74
|
end
|
70
75
|
|
71
76
|
end
|
72
77
|
|
73
78
|
def send_line(*args)
|
79
|
+
args.each { |line| Marvin::Logger.debug ">> #{line.strip}" }
|
74
80
|
em_connection.send_data *args
|
75
81
|
end
|
76
82
|
|
@@ -80,18 +86,44 @@ module Marvin::IRC
|
|
80
86
|
# networking portion of the IRC Client.
|
81
87
|
def self.run
|
82
88
|
self.setup # So we have options etc
|
83
|
-
|
84
|
-
|
85
|
-
EventMachine
|
89
|
+
settings = YAML.load_file(Marvin::Settings.root / "config/connections.yml")
|
90
|
+
if settings.is_a?(Hash)
|
91
|
+
EventMachine.epoll
|
92
|
+
EventMachine::run do
|
93
|
+
settings.each do |name, options|
|
94
|
+
settings = options.symbolize_keys!
|
95
|
+
settings[:server] ||= name
|
96
|
+
settings.reverse_merge!(:port => 6667, :channels => [])
|
97
|
+
connect settings
|
98
|
+
end
|
99
|
+
end
|
100
|
+
else
|
101
|
+
logger.fatal "config/connections.yml couldn't be loaded. Exiting"
|
86
102
|
end
|
87
103
|
end
|
88
104
|
|
105
|
+
def self.connect(opts = {})
|
106
|
+
logger.info "Connecting to #{opts[:server]}:#{opts[:port]} - Channels: #{opts[:channels].join(", ")}"
|
107
|
+
EventMachine::connect(opts[:server], opts[:port], EMConnection, opts)
|
108
|
+
logger.info "Connection created for #{opts[:server]}:#{opts[:port]}"
|
109
|
+
end
|
110
|
+
|
89
111
|
def self.stop
|
112
|
+
return if self.stopped
|
90
113
|
logger.debug "Telling all connections to quit"
|
91
114
|
self.connections.dup.each { |connection| connection.quit }
|
92
115
|
logger.debug "Telling Event Machine to Stop"
|
93
116
|
EventMachine::stop_event_loop
|
94
117
|
logger.debug "Stopped."
|
118
|
+
self.stopped = true
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.add_reconnect(opts = {})
|
122
|
+
Marvin::Logger.warn "Adding entry to reconnect to #{opts[:server]}:#{opts[:port]} in 15 seconds"
|
123
|
+
EventMachine::add_timer(15) do
|
124
|
+
Marvin::Logger.warn "Attempting to reconnect to #{opts[:server]}:#{opts[:port]}"
|
125
|
+
Marvin::IRC::Client.connect(opts)
|
126
|
+
end
|
95
127
|
end
|
96
128
|
|
97
129
|
# Registers a callback handle that will be periodically run.
|
data/lib/marvin/irc/event.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Marvin::IRC
|
2
2
|
class Event
|
3
|
-
attr_accessor :keys, :name, :raw_arguments
|
3
|
+
attr_accessor :keys, :name, :raw_arguments, :prefix
|
4
4
|
|
5
5
|
def initialize(name, *args)
|
6
6
|
self.name = name.to_sym
|
@@ -8,23 +8,28 @@ module Marvin::IRC
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def to_hash
|
11
|
-
return
|
11
|
+
return @hash_value unless @hash_value.blank?
|
12
12
|
results = {}
|
13
13
|
values = self.raw_arguments.to_a
|
14
14
|
last_index = self.keys.size - 1
|
15
15
|
self.keys.each_with_index do |key, i|
|
16
16
|
results[key] = (i == last_index ? values.join(" ").strip : values.shift)
|
17
17
|
end
|
18
|
-
|
18
|
+
results.merge!(prefix.to_hash) unless prefix.blank?
|
19
|
+
return (@hash_value = results)
|
19
20
|
end
|
20
21
|
|
21
22
|
def inspect
|
22
|
-
"#<Marvin::IRC::Event name=#{self.name} attributes
|
23
|
+
"#<Marvin::IRC::Event name=#{self.name} attributes=#{self.to_hash.inspect} >"
|
23
24
|
end
|
24
25
|
|
25
26
|
def to_incoming_event_name
|
26
27
|
:"incoming_#{self.name}"
|
27
28
|
end
|
28
29
|
|
30
|
+
def to_outgoing_event_name
|
31
|
+
:"outgoing_#{self.name}"
|
32
|
+
end
|
33
|
+
|
29
34
|
end
|
30
35
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Marvin
|
2
|
+
module IRC
|
3
|
+
module Replies
|
4
|
+
|
5
|
+
def self.[](key)
|
6
|
+
key = key.to_s.upcase
|
7
|
+
if const_defined?(key)
|
8
|
+
value = const_get(key)
|
9
|
+
return "%03d" % value
|
10
|
+
else
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Common response codes.
|
16
|
+
RPL_WELCOME = 001
|
17
|
+
RPL_YOURHOST = 002
|
18
|
+
RPL_CREATED = 003
|
19
|
+
RPL_MYINFO = 004
|
20
|
+
RPL_BOUNCE = 005
|
21
|
+
RPL_TRACELINK = 200
|
22
|
+
RPL_TRACECONNECTING = 201
|
23
|
+
RPL_TRACEHANDSHAKE = 202
|
24
|
+
RPL_TRACEUNKNOWN = 203
|
25
|
+
RPL_TRACEOPERATOR = 204
|
26
|
+
RPL_TRACEUSER = 205
|
27
|
+
RPL_TRACESERVER = 206
|
28
|
+
RPL_TRACESERVICE = 207
|
29
|
+
RPL_TRACENEWTYPE = 208
|
30
|
+
RPL_TRACECLASS = 209
|
31
|
+
RPL_TRACERECONNECT = 210
|
32
|
+
RPL_TRACELOG = 261
|
33
|
+
RPL_TRACEEND = 262
|
34
|
+
RPL_STATSLINKINFO = 211
|
35
|
+
RPL_STATSCOMMANDS = 212
|
36
|
+
RPL_ENDOFSTATS = 219
|
37
|
+
RPL_STATSUPTIME = 242
|
38
|
+
RPL_STATSOLINE = 243
|
39
|
+
RPL_UMODEIS = 221
|
40
|
+
RPL_SERVLIST = 234
|
41
|
+
RPL_SERVLISTEND = 235
|
42
|
+
RPL_LUSERCLIENT = 251
|
43
|
+
RPL_LUSEROP = 252
|
44
|
+
RPL_LUSERUNKNOWN = 253
|
45
|
+
RPL_LUSERCHANNELS = 254
|
46
|
+
RPL_LUSERME = 255
|
47
|
+
RPL_ADMINME = 256
|
48
|
+
RPL_ADMINEMAIL = 259
|
49
|
+
RPL_TRYAGAIN = 263
|
50
|
+
RPL_USERHOST = 302
|
51
|
+
RPL_ISON = 303
|
52
|
+
RPL_AWAY = 301
|
53
|
+
RPL_UNAWAY = 305
|
54
|
+
RPL_NOWAWAY = 306
|
55
|
+
RPL_WHOISUSER = 311
|
56
|
+
RPL_WHOISSERVER = 312
|
57
|
+
RPL_WHOISOPERATOR = 313
|
58
|
+
RPL_WHOISIDLE = 317
|
59
|
+
RPL_ENDOFWHOIS = 318
|
60
|
+
RPL_WHOISCHANNELS = 319
|
61
|
+
RPL_WHOWASUSER = 314
|
62
|
+
RPL_ENDOFWHOWAS = 369
|
63
|
+
RPL_LISTSTART = 321
|
64
|
+
RPL_LIST = 322
|
65
|
+
RPL_LISTEND = 323
|
66
|
+
RPL_UNIQOPIS = 325
|
67
|
+
RPL_CHANNELMODEIS = 324
|
68
|
+
RPL_NOTOPIC = 331
|
69
|
+
RPL_TOPIC = 332
|
70
|
+
RPL_INVITING = 341
|
71
|
+
RPL_SUMMONING = 342
|
72
|
+
RPL_INVITELIST = 346
|
73
|
+
RPL_ENDOFINVITELIST = 347
|
74
|
+
RPL_EXCEPTLIST = 348
|
75
|
+
RPL_ENDOFEXCEPTLIST = 349
|
76
|
+
RPL_VERSION = 351
|
77
|
+
RPL_WHOREPLY = 352
|
78
|
+
RPL_ENDOFWHO = 315
|
79
|
+
RPL_NAMREPLY = 353
|
80
|
+
RPL_ENDOFNAMES = 366
|
81
|
+
RPL_LINKS = 364
|
82
|
+
RPL_ENDOFLINKS = 365
|
83
|
+
RPL_BANLIST = 367
|
84
|
+
RPL_ENDOFBANLIST = 368
|
85
|
+
RPL_INFO = 371
|
86
|
+
RPL_ENDOFINFO = 374
|
87
|
+
RPL_MOTDSTART = 375
|
88
|
+
RPL_MOTD = 372
|
89
|
+
RPL_ENDOFMOTD = 376
|
90
|
+
RPL_YOUREOPER = 381
|
91
|
+
RPL_REHASHING = 382
|
92
|
+
RPL_YOURESERVICE = 383
|
93
|
+
RPL_TIME = 391
|
94
|
+
RPL_USERSSTART = 392
|
95
|
+
RPL_USERS = 393
|
96
|
+
RPL_ENDOFUSERS = 394
|
97
|
+
RPL_NOUSERS = 395
|
98
|
+
ERR_NOSUCHNICK = 401
|
99
|
+
ERR_NOSUCHSERVER = 402
|
100
|
+
ERR_NOSUCHCHANNEL = 403
|
101
|
+
ERR_CANNOTSENDTOCHAN = 404
|
102
|
+
ERR_TOOMANYCHANNELS = 405
|
103
|
+
ERR_WASNOSUCHNICK = 406
|
104
|
+
ERR_TOOMANYTARGETS = 407
|
105
|
+
ERR_NOSUCHSERVICE = 408
|
106
|
+
ERR_NOORIGIN = 409
|
107
|
+
ERR_NORECIPIENT = 411
|
108
|
+
ERR_NOTEXTTOSEND = 412
|
109
|
+
ERR_NOTOPLEVEL = 413
|
110
|
+
ERR_WILDTOPLEVEL = 414
|
111
|
+
ERR_BADMASK = 415
|
112
|
+
ERR_UNKNOWNCOMMAND = 421
|
113
|
+
ERR_NOMOTD = 422
|
114
|
+
ERR_NOADMININFO = 423
|
115
|
+
ERR_FILEERROR = 424
|
116
|
+
ERR_NONICKNAMEGIVEN = 431
|
117
|
+
ERR_ERRONEUSNICKNAME = 432
|
118
|
+
ERR_NICKNAMEINUSE = 433
|
119
|
+
ERR_NICKCOLLISION = 436
|
120
|
+
ERR_UNAVAILRESOURCE = 437
|
121
|
+
ERR_USERNOTINCHANNEL = 441
|
122
|
+
ERR_NOTONCHANNEL = 442
|
123
|
+
ERR_USERONCHANNEL = 443
|
124
|
+
ERR_NOLOGIN = 444
|
125
|
+
ERR_SUMMONDISABLED = 445
|
126
|
+
ERR_USERSDISABLED = 446
|
127
|
+
ERR_NOTREGISTERED = 451
|
128
|
+
ERR_NEEDMOREPARAMS = 461
|
129
|
+
ERR_ALREADYREGISTRED = 462
|
130
|
+
ERR_NOPERMFORHOST = 463
|
131
|
+
ERR_PASSWDMISMATCH = 464
|
132
|
+
ERR_YOUREBANNEDCREEP = 465
|
133
|
+
ERR_YOUWILLBEBANNED = 466
|
134
|
+
ERR_KEYSET = 467
|
135
|
+
ERR_CHANNELISFULL = 471
|
136
|
+
ERR_UNKNOWNMODE = 472
|
137
|
+
ERR_INVITEONLYCHAN = 473
|
138
|
+
ERR_BANNEDFROMCHAN = 474
|
139
|
+
ERR_BADCHANNELKEY = 475
|
140
|
+
ERR_BADCHANMASK = 476
|
141
|
+
ERR_NOCHANMODES = 477
|
142
|
+
ERR_BANLISTFULL = 478
|
143
|
+
ERR_NOPRIVILEGES = 481
|
144
|
+
ERR_CHANOPRIVSNEEDED = 482
|
145
|
+
ERR_CANTKILLSERVER = 483
|
146
|
+
ERR_RESTRICTED = 484
|
147
|
+
ERR_UNIQOPPRIVSNEEDED = 485
|
148
|
+
ERR_NOOPERHOST = 491
|
149
|
+
ERR_UMODEUNKNOWNFLAG = 501
|
150
|
+
ERR_USERSDONTMATCH = 502
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|