Sutto-marvin 0.1.20081115 → 0.1.20081120
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +70 -50
- 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 +1 -0
- data/handlers/debug_handler.rb +5 -0
- data/handlers/hello_world.rb +1 -1
- data/lib/marvin.rb +1 -0
- data/lib/marvin/abstract_client.rb +79 -42
- 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/drb_handler.rb +7 -2
- data/lib/marvin/exceptions.rb +3 -0
- data/lib/marvin/irc.rb +1 -1
- data/lib/marvin/irc/client.rb +31 -7
- data/lib/marvin/irc/event.rb +9 -4
- data/lib/marvin/irc/replies.rb +154 -0
- data/lib/marvin/loader.rb +1 -0
- 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 +73 -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 +8 -8
- data/lib/marvin/util.rb +2 -2
- data/script/{run → client} +0 -0
- data/script/daemon-runner +1 -1
- data/script/install +3 -0
- data/test/parser_comparison.rb +36 -0
- data/test/parser_test.rb +20 -0
- data/test/test_helper.rb +10 -0
- metadata +19 -9
- 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/drb_handler.rb
CHANGED
data/lib/marvin/exceptions.rb
CHANGED
data/lib/marvin/irc.rb
CHANGED
@@ -2,8 +2,8 @@ module Marvin
|
|
2
2
|
module IRC
|
3
3
|
autoload :Client, 'marvin/irc/client'
|
4
4
|
autoload :Event, 'marvin/irc/event'
|
5
|
-
autoload :SocketClient, 'marvin/irc/socket_client'
|
6
5
|
autoload :AbstractServer, 'marvin/irc/abstract_server'
|
7
6
|
autoload :BaseServer, 'marvin/irc/base_server'
|
7
|
+
autoload :Replies, 'marvin/irc/replies'
|
8
8
|
end
|
9
9
|
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,36 @@ 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
|
95
119
|
end
|
96
120
|
|
97
121
|
# 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
|