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.
Files changed (48) hide show
  1. data/README.textile +105 -32
  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 +6 -1
  7. data/handlers/debug_handler.rb +5 -0
  8. data/handlers/hello_world.rb +1 -1
  9. data/lib/marvin.rb +13 -9
  10. data/lib/marvin/abstract_client.rb +88 -43
  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/exception_tracker.rb +1 -1
  15. data/lib/marvin/exceptions.rb +3 -0
  16. data/lib/marvin/irc.rb +4 -5
  17. data/lib/marvin/irc/client.rb +39 -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 +26 -8
  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 +105 -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 +9 -9
  33. data/lib/marvin/test_client.rb +5 -1
  34. data/lib/marvin/util.rb +20 -3
  35. data/script/{run → client} +0 -0
  36. data/script/daemon-runner +1 -1
  37. data/script/install +3 -0
  38. data/test/parser_comparison.rb +62 -0
  39. data/test/parser_test.rb +264 -0
  40. data/test/test_helper.rb +10 -0
  41. metadata +19 -9
  42. data/lib/marvin/drb_handler.rb +0 -7
  43. data/lib/marvin/irc/abstract_server.rb +0 -4
  44. data/lib/marvin/irc/base_server.rb +0 -11
  45. data/lib/marvin/irc/socket_client.rb +0 -69
  46. data/lib/marvin/parsers/simple_parser/default_events.rb +0 -37
  47. data/lib/marvin/parsers/simple_parser/event_extensions.rb +0 -14
  48. 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
@@ -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
@@ -2,7 +2,7 @@ module Marvin
2
2
  class ExceptionTracker
3
3
 
4
4
  cattr_accessor :logger
5
- self.logger = Marvin::Logger.logger
5
+ self.logger = Marvin::Logger
6
6
 
7
7
  def self.log(e)
8
8
  logger.fatal "Exception raised inside Marvin Instance."
@@ -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
@@ -1,9 +1,8 @@
1
1
  module Marvin
2
2
  module IRC
3
- autoload :Client, 'marvin/irc/client'
4
- autoload :Event, 'marvin/irc/event'
5
- autoload :SocketClient, 'marvin/irc/socket_client'
6
- autoload :AbstractServer, 'marvin/irc/abstract_server'
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
@@ -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,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
- 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
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.
@@ -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