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.
Files changed (44) hide show
  1. data/README.textile +70 -50
  2. data/VERSION.yml +1 -1
  3. data/bin/marvin +10 -6
  4. data/config/connections.yml.sample +5 -0
  5. data/config/settings.yml.sample +2 -7
  6. data/config/setup.rb +1 -0
  7. data/handlers/debug_handler.rb +5 -0
  8. data/handlers/hello_world.rb +1 -1
  9. data/lib/marvin.rb +1 -0
  10. data/lib/marvin/abstract_client.rb +79 -42
  11. data/lib/marvin/abstract_parser.rb +14 -2
  12. data/lib/marvin/base.rb +44 -6
  13. data/lib/marvin/dispatchable.rb +9 -4
  14. data/lib/marvin/drb_handler.rb +7 -2
  15. data/lib/marvin/exceptions.rb +3 -0
  16. data/lib/marvin/irc.rb +1 -1
  17. data/lib/marvin/irc/client.rb +31 -7
  18. data/lib/marvin/irc/event.rb +9 -4
  19. data/lib/marvin/irc/replies.rb +154 -0
  20. data/lib/marvin/loader.rb +1 -0
  21. data/lib/marvin/logger.rb +66 -3
  22. data/lib/marvin/options.rb +33 -0
  23. data/lib/marvin/parsers.rb +3 -0
  24. data/lib/marvin/parsers/command.rb +73 -0
  25. data/lib/marvin/parsers/prefixes.rb +8 -0
  26. data/lib/marvin/parsers/prefixes/host_mask.rb +30 -0
  27. data/lib/marvin/parsers/prefixes/server.rb +24 -0
  28. data/lib/marvin/parsers/ragel_parser.rb +713 -0
  29. data/lib/marvin/parsers/ragel_parser.rl +144 -0
  30. data/lib/marvin/parsers/regexp_parser.rb +0 -3
  31. data/lib/marvin/parsers/simple_parser.rb +20 -81
  32. data/lib/marvin/settings.rb +8 -8
  33. data/lib/marvin/util.rb +2 -2
  34. data/script/{run → client} +0 -0
  35. data/script/daemon-runner +1 -1
  36. data/script/install +3 -0
  37. data/test/parser_comparison.rb +36 -0
  38. data/test/parser_test.rb +20 -0
  39. data/test/test_helper.rb +10 -0
  40. metadata +19 -9
  41. data/lib/marvin/irc/socket_client.rb +0 -69
  42. data/lib/marvin/parsers/simple_parser/default_events.rb +0 -37
  43. data/lib/marvin/parsers/simple_parser/event_extensions.rb +0 -14
  44. 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
- raise NotImplementedError, "Not implemented in an abstract parser"
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
- raise NotImplementedError, "Not implemented in an abstract parser"
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
- def on_event(name, &blk)
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 do |handle|
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.split(" ").first.downcase == "#{self.client.nickname.downcase}:"
142
+ self.from_user? || options.message =~ /^#{self.client.nickname.downcase}:\s+/i
105
143
  end
106
144
 
107
145
  def setup_defaults(options)
@@ -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.sent(full_handler_name, opts)
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
@@ -1,7 +1,12 @@
1
1
  module Marvin
2
+ class DRBHandler
2
3
 
3
- def handle(message, options)
4
- end
4
+ attr_accessor :port
5
5
 
6
+ # Will do the appropriate things to dispatch
7
+ # a message to the different DRB clients.
8
+ def handle(message, opts = {})
9
+ end
10
+
6
11
  end
7
12
  end
@@ -5,4 +5,7 @@ module Marvin
5
5
  # Used to stop the flow of handler chains.
6
6
  class HaltHandlerProcessing < Error; end
7
7
 
8
+ # Used for when an expression can't be parsed
9
+ class UnparseableMessage < Error; end
10
+
8
11
  end
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
@@ -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
- super
55
- self.client = Marvin::IRC::Client.new
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
- EventMachine::run do
84
- logger.debug "Connecting to #{self.configuration.server}:#{self.configuration.port}"
85
- EventMachine::connect self.configuration.server, self.configuration.port, Marvin::IRC::Client::EMConnection
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.
@@ -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 {} unless self.raw_arguments
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
- return results
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=[#{keys * ","}] >"
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