marvin 0.8.0.0

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 (54) hide show
  1. data/bin/marvin +33 -0
  2. data/handlers/debug_handler.rb +5 -0
  3. data/handlers/hello_world.rb +9 -0
  4. data/handlers/keiki_thwopper.rb +21 -0
  5. data/handlers/simple_logger.rb +24 -0
  6. data/handlers/tweet_tweet.rb +19 -0
  7. data/lib/marvin.rb +56 -0
  8. data/lib/marvin/abstract_client.rb +146 -0
  9. data/lib/marvin/abstract_parser.rb +29 -0
  10. data/lib/marvin/base.rb +195 -0
  11. data/lib/marvin/client/actions.rb +104 -0
  12. data/lib/marvin/client/default_handlers.rb +97 -0
  13. data/lib/marvin/command_handler.rb +91 -0
  14. data/lib/marvin/console.rb +50 -0
  15. data/lib/marvin/core_commands.rb +49 -0
  16. data/lib/marvin/distributed.rb +8 -0
  17. data/lib/marvin/distributed/client.rb +225 -0
  18. data/lib/marvin/distributed/handler.rb +85 -0
  19. data/lib/marvin/distributed/protocol.rb +88 -0
  20. data/lib/marvin/distributed/server.rb +154 -0
  21. data/lib/marvin/dsl.rb +103 -0
  22. data/lib/marvin/exception_tracker.rb +19 -0
  23. data/lib/marvin/exceptions.rb +11 -0
  24. data/lib/marvin/irc.rb +7 -0
  25. data/lib/marvin/irc/client.rb +168 -0
  26. data/lib/marvin/irc/event.rb +39 -0
  27. data/lib/marvin/irc/replies.rb +154 -0
  28. data/lib/marvin/logging_handler.rb +76 -0
  29. data/lib/marvin/middle_man.rb +103 -0
  30. data/lib/marvin/parsers.rb +9 -0
  31. data/lib/marvin/parsers/command.rb +107 -0
  32. data/lib/marvin/parsers/prefixes.rb +8 -0
  33. data/lib/marvin/parsers/prefixes/host_mask.rb +35 -0
  34. data/lib/marvin/parsers/prefixes/server.rb +24 -0
  35. data/lib/marvin/parsers/ragel_parser.rb +720 -0
  36. data/lib/marvin/parsers/ragel_parser.rl +143 -0
  37. data/lib/marvin/parsers/simple_parser.rb +35 -0
  38. data/lib/marvin/settings.rb +31 -0
  39. data/lib/marvin/test_client.rb +58 -0
  40. data/lib/marvin/util.rb +54 -0
  41. data/templates/boot.erb +3 -0
  42. data/templates/connections.yml.erb +10 -0
  43. data/templates/debug_handler.erb +5 -0
  44. data/templates/hello_world.erb +10 -0
  45. data/templates/rakefile.erb +15 -0
  46. data/templates/settings.yml.erb +8 -0
  47. data/templates/setup.erb +31 -0
  48. data/templates/test_helper.erb +17 -0
  49. data/test/abstract_client_test.rb +63 -0
  50. data/test/parser_comparison.rb +62 -0
  51. data/test/parser_test.rb +266 -0
  52. data/test/test_helper.rb +62 -0
  53. data/test/util_test.rb +57 -0
  54. metadata +136 -0
@@ -0,0 +1,11 @@
1
+ module Marvin
2
+
3
+ class Error < StandardError; end
4
+
5
+ # Used to stop the flow of handler chains.
6
+ class HaltHandlerProcessing < Error; end
7
+
8
+ # Used for when an expression can't be parsed
9
+ class UnparseableMessage < Error; end
10
+
11
+ end
data/lib/marvin/irc.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Marvin
2
+ module IRC
3
+ autoload :Client, 'marvin/irc/client'
4
+ autoload :Event, 'marvin/irc/event'
5
+ autoload :Replies, 'marvin/irc/replies'
6
+ end
7
+ end
@@ -0,0 +1,168 @@
1
+ require 'eventmachine'
2
+
3
+ module Marvin::IRC
4
+ class Client < Marvin::AbstractClient
5
+
6
+ @@stopped = false
7
+
8
+ attr_accessor :em_connection
9
+
10
+ class EMConnection < EventMachine::Protocols::LineAndTextProtocol
11
+ is :loggable
12
+
13
+ attr_accessor :client, :port, :configuration
14
+
15
+ def initialize(*args)
16
+ @configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::Nash.new
17
+ super(*args)
18
+ @client = Marvin::IRC::Client.new(@configuration)
19
+ @client.em_connection = self
20
+ @connected = false
21
+ rescue Exception => e
22
+ Marvin::ExceptionTracker.log(e)
23
+ Marvin::IRC::Client.stop
24
+ end
25
+
26
+ def post_init
27
+ super
28
+ if should_use_ssl?
29
+ logger.info "Starting SSL for #{host_with_port}"
30
+ start_tls
31
+ else
32
+ connected!
33
+ end
34
+ rescue Exception => e
35
+ Marvin::ExceptionTracker.log(e)
36
+ Marvin::IRC::Client.stop
37
+ end
38
+
39
+ def ssl_handshake_completed
40
+ logger.info "SSL handshake completed for #{host_with_port}"
41
+ connected! if should_use_ssl?
42
+ rescue Exception => e
43
+ Marvin::ExceptionTracker.log(e)
44
+ Marvin::IRC::Client.stop
45
+ end
46
+
47
+ def unbind
48
+ @client.process_disconnect
49
+ super
50
+ end
51
+
52
+ def receive_line(line)
53
+ return unless @connected
54
+ line = line.strip
55
+ logger.debug "<< #{line}"
56
+ @client.receive_line(line)
57
+ rescue Exception => e
58
+ logger.warn "Uncaught exception raised; Likely in Marvin"
59
+ Marvin::ExceptionTracker.log(e)
60
+ end
61
+
62
+ def send_line(*lines)
63
+ return unless @connected
64
+ lines.each do |line|
65
+ logger.debug ">> #{line.strip}"
66
+ send_data line
67
+ end
68
+ end
69
+
70
+ def connected!
71
+ @connected = true
72
+ @client.process_connect
73
+ end
74
+
75
+ def should_use_ssl?
76
+ @should_use_ssl ||= @configuration.ssl?
77
+ end
78
+
79
+ def host_with_port
80
+ "#{@configuration.host}:#{@configuration.port}"
81
+ end
82
+
83
+ end
84
+
85
+ ## Client specific details
86
+
87
+ def send_line(*args)
88
+ @em_connection.send_line(*args)
89
+ end
90
+
91
+ class << self
92
+
93
+ # Starts the EventMachine loop and hence starts up the actual
94
+ # networking portion of the IRC Client.
95
+ def run(opts = {}, force = false)
96
+ self.development = opts[:development]
97
+ return if @stopped && !force
98
+ self.setup # So we have options etc
99
+ connections_file = Marvin::Settings.root / "config" / "connections.yml"
100
+ connections = Marvin::Nash.load_file(connections_file) rescue nil
101
+ if connections.present?
102
+ # Use epoll if available
103
+ EventMachine.kqueue
104
+ EventMachine.epoll
105
+ EventMachine.run do
106
+ connections.each_pair do |server, configuration|
107
+ connect(configuration.merge(:server => server.to_s))
108
+ end
109
+ Marvin::Distributed::Server.start if Marvin::Distributed::Handler.registered?
110
+ @@stopped = false
111
+ end
112
+ else
113
+ logger.fatal "config/connections.yml couldn't be loaded."
114
+ end
115
+ end
116
+
117
+ def connect(c, &blk)
118
+ c = normalize_connection_options(c)
119
+ raise ArgumentError, "Your connection options must specify a server" if !c.server?
120
+ raise ArgumentError, "Your connection options must specify a port" if !c.port?
121
+ real_block = blk.present? ? proc { |c| blk.call(connection.client) } : nil
122
+ logger.info "Connecting to #{c.server}:#{c.port} (using ssl: #{c.ssl?}) - Channels: #{c.channels.join(", ")}"
123
+ EventMachine.connect(c.server, c.port, EMConnection, c, &real_block)
124
+ end
125
+
126
+ def stop
127
+ return if @@stopped
128
+ logger.debug "Telling all connections to quit"
129
+ connections.dup.each { |connection| connection.quit }
130
+ logger.debug "Telling Event Machine to Stop"
131
+ EventMachine.stop_event_loop
132
+ logger.debug "Stopped."
133
+ @@stoped = true
134
+ end
135
+
136
+ def add_reconnect(c)
137
+ logger.warn "Adding timer to reconnect to #{c.server}:#{c.port} in 15 seconds"
138
+ EventMachine.add_timer(15) do
139
+ logger.warn "Preparing to reconnect to #{c.server}:#{c.port}"
140
+ connect(c)
141
+ end
142
+ end
143
+
144
+ protected
145
+
146
+ def normalize_connection_options(config)
147
+ config = Marvin::Nash.new(config) if !config.is_a?(Marvin::Nash)
148
+ config = config.normalized
149
+ channels = config.channels
150
+ if channels.present?
151
+ config.channels = [*channels].compact.reject { |c| c.blank? }
152
+ else
153
+ config.channels = []
154
+ end
155
+ config.host ||= config.server
156
+ return config
157
+ end
158
+
159
+ end
160
+
161
+ # Registers a callback handle that will be periodically run.
162
+ def periodically(timing, event_callback)
163
+ EventMachine.add_periodic_timer(timing) { dispatch(event_callback.to_sym) }
164
+ end
165
+
166
+ end
167
+
168
+ end
@@ -0,0 +1,39 @@
1
+ module Marvin::IRC
2
+ class Event
3
+ attr_accessor :keys, :name, :raw_arguments, :prefix
4
+
5
+ def initialize(name, *args)
6
+ @name = name.to_sym
7
+ @keys = args.flatten.map { |k| k.to_sym }
8
+ end
9
+
10
+ def to_hash
11
+ return @hash_value unless @hash_value.blank?
12
+ results = {}
13
+ values = @raw_arguments.to_a
14
+ last_index = @keys.size - 1
15
+ @keys.each_with_index do |key, i|
16
+ results[key] = (i == last_index ? values.join(" ").strip : values.shift)
17
+ end
18
+ results.merge!(@prefix.to_hash) unless @prefix.blank?
19
+ @hash_value = results
20
+ end
21
+
22
+ def inspect
23
+ "#<Marvin::IRC::Event name=#{@name} attributes=#{to_hash.inspect} >"
24
+ end
25
+
26
+ def to_event_name(prefix = nil)
27
+ [prefix, @name].join("_").to_sym
28
+ end
29
+
30
+ def to_incoming_event_name
31
+ to_event_name :incoming
32
+ end
33
+
34
+ def to_outgoing_event_name
35
+ to_event_name :outgoing
36
+ end
37
+
38
+ end
39
+ 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
@@ -0,0 +1,76 @@
1
+ module Marvin
2
+ # A generic class to make it easy to implement loggers
3
+ # in marvin. Users only need to implement 1 method for simple
4
+ # logging but have control to do more. Please note that
5
+ # if you want it correctly log the order of messages, it
6
+ # needs to be the first registered handler, otherwise
7
+ # outgoing messages will be processed before incoming ones.
8
+ class LoggingHandler < CommandHandler
9
+
10
+ on_event :incoming_message do
11
+ if should_log?
12
+ log_incoming(@server, options.nick, options.target, options.message)
13
+ log_message(@server, options.nick, options.target, options.message)
14
+ end
15
+ end
16
+
17
+ on_event :outgoing_message do
18
+ if should_log?
19
+ log_outgoing(@server, @nick, options.target, options.message)
20
+ log_message(@server, @nick, options.target, options.message)
21
+ end
22
+ end
23
+
24
+ on_event :client_connected do
25
+ @server = self.client.host_with_port
26
+ @nick = self.client.nickname
27
+ setup_logging
28
+ end
29
+
30
+ on_event :client_disconnected do
31
+ teardown_logging
32
+ end
33
+
34
+ on_event :outgoing_nick do
35
+ @nick = options.new_nick
36
+ end
37
+
38
+ # Called when the client connects, over ride it to
39
+ # do any of your specific setup.
40
+ def setup_logging
41
+ # NOP
42
+ end
43
+
44
+ # Called when the client disconnects, over ride it to
45
+ # do any of your specific teardown.
46
+ def teardown_logging
47
+ # NOP
48
+ end
49
+
50
+ # Called before logging a message for conditional logging.
51
+ # Override with your implementation specific version.
52
+ def should_log?
53
+ true
54
+ end
55
+
56
+ # Log an incoming message (i.e. from another user)
57
+ # Note that +server+ is in the port host:port,
58
+ # nick is the origin nick and target is either
59
+ # a channel or the bots nick (if PM'ed)
60
+ def log_incoming(server, nick, target, message)
61
+ end
62
+
63
+ # Log an outgoing message (i.e. from this user)
64
+ # Note that +server+ is in the port host:port,
65
+ # nick is the bots nick and target is either
66
+ # a channel or the user the bot is replying to.
67
+ def log_outgoing(server, nick, target, message)
68
+ end
69
+
70
+ # Logs a message - used when you want to log
71
+ # both incoming and outgoing messages
72
+ def log_message(server, nick, target, message)
73
+ end
74
+
75
+ end
76
+ end