marvin 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,53 @@
1
+ class RawConnectionLogger < Marvin::Base
2
+
3
+ on_event :client_connected, :setup_logging
4
+ on_event :reloaded, :setup_logging
5
+
6
+ on_event :client_disconnected, :teardown_logging
7
+ on_event :reloaded, :teardown_logging
8
+
9
+ @@files = {}
10
+
11
+ def setup_logging
12
+ logger.info "Setting up logging"
13
+ @@files[client.host_with_port] = nil
14
+ self.file # Access the ivar to load it
15
+ end
16
+
17
+ def teardown_logging
18
+ logger.info "Stopping logging"
19
+ if @@files[client.host_with_port].present?
20
+ @@files[client.host_with_port].puts ""
21
+ @@files[client.host_with_port].close
22
+ @@files.delete client.host_with_port
23
+ end
24
+ end
25
+
26
+ on_event :incoming_line do
27
+ log_line true
28
+ end
29
+
30
+ on_event :outgoing_line do
31
+ log_line false
32
+ end
33
+
34
+ def log_line(incoming = true)
35
+ color = incoming ? :green : :blue
36
+ prefix = incoming ? "<< " : ">> "
37
+ file.puts Marvin::ANSIFormatter.format(color, "#{prefix}#{options.line}".strip)
38
+ end
39
+
40
+ def file
41
+ @@files[client.host_with_port] ||= begin
42
+ logger.info "Loading Logger..."
43
+ uri = URI.parse("irc://#{client.host_with_port}")
44
+ log_path = File.join(Marvin::Settings.root, "log", "connections", "#{uri.host}-#{uri.port}-#{Time.now.to_i}.log")
45
+ logger.info "Logging to #{log_path}"
46
+ FileUtils.mkdir_p(File.dirname(log_path))
47
+ file = File.open(log_path, "a+")
48
+ file.sync = true if file.respond_to?(:sync=)
49
+ file
50
+ end
51
+ end
52
+
53
+ end
@@ -5,7 +5,7 @@ require 'perennial'
5
5
  module Marvin
6
6
  include Perennial
7
7
 
8
- VERSION = [0, 8, 1, 0]
8
+ VERSION = [0, 8, 2, 0]
9
9
 
10
10
  # Client
11
11
  autoload :TestClient, 'marvin/test_client'
@@ -39,8 +39,12 @@ module Marvin
39
39
 
40
40
  end
41
41
 
42
- def self.version(include_minor = false)
43
- VERSION[0, (include_minor ? 4 : 3)].join(".")
42
+ # Returns a string of the current version,
43
+ # optionally including a build number.
44
+ # @param [Boolean] include_build include a build version in the string
45
+ def self.version(include_build = nil)
46
+ include_build = VERSION[3].to_i > 0 if include_build.nil?
47
+ VERSION[0, (include_build ? 4 : 3)].join(".")
44
48
  end
45
49
 
46
50
  has_library :util, :abstract_client, :abstract_parser, :irc, :exception_tracker
@@ -1,6 +1,14 @@
1
1
  require "marvin/irc/event"
2
2
 
3
3
  module Marvin
4
+ # An abstract class implementing (and mixing in) a lot of
5
+ # the default functionality involved on handling an irc
6
+ # connection.
7
+ #
8
+ # To provide an implementation, you must subclass and implement
9
+ # #send_line, .add_reconnect, and any other loader etc methods.
10
+ #
11
+ # @see Marvin::IRC::Client
4
12
  class AbstractClient
5
13
 
6
14
  is :dispatchable, :loggable
@@ -29,6 +37,9 @@ module Marvin
29
37
  # current connection, dispatching a :client_connected event
30
38
  # once it has finished. During this process, it will
31
39
  # call #client= on each handler if they respond to it.
40
+ #
41
+ # @see Marvin::AbstractClient.setup
42
+ # @see Marvin::Base#client=
32
43
  def process_connect
33
44
  self.class.setup
34
45
  logger.info "Initializing the current instance"
@@ -38,48 +49,71 @@ module Marvin
38
49
  dispatch :client_connected
39
50
  end
40
51
 
52
+ # Handles a lost connection / disconnect, stopping the loader if it's the
53
+ # last connection (purposely terminated) otherwise scheduling a reconnection
54
+ #
55
+ # @see Marvin::Loader.stop!
41
56
  def process_disconnect
42
57
  logger.info "Handling disconnect for #{host_with_port}"
43
58
  connections.delete(self)
44
59
  dispatch :client_disconnected
45
- unless @disconnect_expected
60
+ if @disconnect_expected
61
+ Marvin::Loader.stop! if connections.blank?
62
+ else
46
63
  logger.warn "Unexpectly lost connection to server; adding reconnect"
47
64
  self.class.add_reconnect @connection_config
48
- else
49
- Marvin::Loader.stop! if connections.blank?
50
65
  end
51
66
  end
52
67
 
68
+ # Iterates over handles and calls client= when defined. Used before dispatching
69
+ # in order to ensure each hander has the correct client. Note that this needs
70
+ # to be improved soon.
53
71
  def setup_handlers
54
72
  handlers.each { |h| h.client = self if h.respond_to?(:client=) }
55
73
  end
56
74
 
75
+ # If @@development is true, We'll attempt to reload any changed files (namely,
76
+ # handlers).s
57
77
  def process_development
58
78
  Marvin::Reloading.reload! if @@development
59
79
  end
60
80
 
81
+ # Before dispatching, check if we need to reload and setup handlers correctly.
61
82
  def pre_dispatching
62
83
  process_development
63
84
  setup_handlers
64
85
  end
65
86
 
66
- # Sets the current class-wide settings of this IRC Client
67
- # to either an OpenStruct or the results of #to_hash on
68
- # any other value that is passed in.
87
+ # Sets the current class-wide settings of this IRC Client to
88
+ # an instance of Marvin::Nash with the properties of a hash
89
+ # / nash passed in.
90
+ #
91
+ # The new configuration will be normalized before use (namely,
92
+ # it will convert nested items to nashes)
93
+ #
94
+ # @param [Marvin::Nash, Hash] config the new configuration
69
95
  def self.configuration=(config)
70
96
  config = Marvin::Nash.new(config.to_hash) unless config.is_a?(Marvin::Nash)
71
97
  @@configuration = config.normalized
72
98
  end
73
99
 
100
+ # Check if if the cient class has been setup yet
101
+ # @return [Boolean] is the client class setup
74
102
  def self.setup?
75
103
  @setup ||= false
76
104
  end
77
105
 
106
+ # Conditional configure
78
107
  def self.setup
79
108
  return if setup?
80
109
  configure
81
110
  end
82
111
 
112
+ # Configure the class - namely, merge the app-wide configuration
113
+ # and if given a block, merge the results in.
114
+ #
115
+ # @yieldparam [Marvin::Nash] an empty nash
116
+ # @yieldreturn [Marvin::Nash] any changed settings.
83
117
  def self.configure
84
118
  config = Marvin::Nash.new
85
119
  config.merge! Marvin::Settings.configuration
@@ -88,32 +122,45 @@ module Marvin
88
122
  config.merge! nash
89
123
  end
90
124
  @@configuration = config
91
- # Help is only currently available on an instance running
92
- # distributed handler.
125
+ # Help is only currently available on an instance NOT running the distributed handler.
93
126
  Marvin::CoreCommands.register! unless Marvin::Distributed::Handler.registered?
94
127
  @setup = true
95
128
  end
96
129
 
97
130
  ## Handling all of the the actual client stuff.
98
131
 
132
+ # Receives a raw line (without EOL characters) and dispatch the
133
+ # incoming_line event. Once that's done, parse the line and
134
+ # dispatch an event if present.
135
+ #
136
+ # @param [String] line the incoming line
99
137
  def receive_line(line)
100
138
  dispatch :incoming_line, :line => line
101
139
  event = Marvin::Settings.parser.parse(line)
102
140
  dispatch(event.to_incoming_event_name, event.to_hash) unless event.nil?
103
141
  end
104
142
 
143
+ # Returns a list of default channels to join on connect
144
+ # @return [Array<String>] an array of channel names that are joined on connect
105
145
  def default_channels
106
146
  @default_channels ||= []
107
147
  end
108
148
 
149
+ # Sets the list of default channels to join
150
+ # @param [Array<String>] channels the array of channel names
109
151
  def default_channels=(channels)
110
152
  @default_channels = channels.to_a.map { |c| c.to_s }
111
153
  end
112
154
 
155
+ # Returns the irc server and port
156
+ # @return [String] server port in "server:port" format
113
157
  def host_with_port
114
158
  @host_with_port ||= "#{server}:#{port}"
115
159
  end
116
160
 
161
+ # Returns a list available nicks / nicks to try
162
+ # on connect.
163
+ # @return [Array<String>] The array of nicks
117
164
  def nicks
118
165
  if @nicks.blank? && !@nicks_loaded
119
166
  logger.info "Setting default nick list"
@@ -1,4 +1,5 @@
1
1
  module Marvin
2
+ # Abstract Class for implementing abstract parsers.
2
3
  class AbstractParser
3
4
 
4
5
  attr_accessor :line, :command, :event
@@ -15,6 +16,8 @@ module Marvin
15
16
  @event
16
17
  end
17
18
 
19
+ # Parses a line and return the associated event.
20
+ # @return [Marvin::IRC:Event] the parsed event
18
21
  def self.parse(line)
19
22
  new(line.strip).to_event
20
23
  end
@@ -42,8 +42,6 @@ module Marvin
42
42
  handle_welcome
43
43
  when Marvin::IRC::Replies[:ERR_NICKNAMEINUSE]
44
44
  handle_nick_taken
45
- when Marvin::IRC::Replies[:RPL_TOPIC]
46
- handle_channel_topic
47
45
  end
48
46
  code = opts[:code].to_i
49
47
  args = Marvin::Util.arguments(opts[:data])
@@ -73,7 +71,7 @@ module Marvin
73
71
  logger.info "Attemping to set nickname to '#{next_nick}'"
74
72
  nick next_nick
75
73
  else
76
- logger.fatal "No Nicknames available - QUITTING"
74
+ logger.error "No Nicknames available - QUITTING"
77
75
  quit
78
76
  end
79
77
  end
@@ -89,7 +87,7 @@ module Marvin
89
87
  # Make sure we show user server errors
90
88
  def handle_incoming_error(opts = {})
91
89
  if opts[:message].present?
92
- logger.info "Got ERROR Message: #{opts[:message]}"
90
+ logger.error "Server ERROR Message: #{opts[:message]}"
93
91
  end
94
92
  end
95
93
 
@@ -64,7 +64,7 @@ module Marvin
64
64
  name, command, data = message.split(/\s+/, 2)
65
65
  return if name !~ /^#{client.nickname}:/i
66
66
  else
67
- command, data = message.splt(/\s+/, 2)
67
+ command, data = message.split(/\s+/, 2)
68
68
  end
69
69
  data ||= ""
70
70
  if (command_name = extract_command_name(command)).present?
@@ -19,7 +19,7 @@ module Marvin
19
19
  attr_accessor :processing, :configuration
20
20
 
21
21
  def initialize(*args)
22
- @configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::nash.new
22
+ @configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::Nash.new
23
23
  super(*args)
24
24
  end
25
25
 
@@ -85,7 +85,10 @@ module Marvin::IRC
85
85
  ## Client specific details
86
86
 
87
87
  def send_line(*args)
88
- @em_connection.send_line(*args)
88
+ args.each do |line|
89
+ @em_connection.send_line(line)
90
+ dispatch :outgoing_line, :line => line
91
+ end
89
92
  end
90
93
 
91
94
  class << self
@@ -163,7 +166,9 @@ module Marvin::IRC
163
166
 
164
167
  # Registers a callback handle that will be periodically run.
165
168
  def periodically(timing, event_callback)
166
- EventMachine.add_periodic_timer(timing) { dispatch(event_callback.to_sym) }
169
+ EventMachine.add_periodic_timer(timing) do
170
+ dispatch(event_callback.to_sym)
171
+ end
167
172
  end
168
173
 
169
174
  end
@@ -24,7 +24,7 @@ module Marvin::IRC
24
24
  end
25
25
 
26
26
  def to_event_name(prefix = nil)
27
- [prefix, @name].join("_").to_sym
27
+ [prefix, @name].compact.join("_").to_sym
28
28
  end
29
29
 
30
30
  def to_incoming_event_name
@@ -1,29 +1,27 @@
1
1
  module Marvin
2
- # The middle man is a class you can use to register
3
- # other handlers on. e.g. it acts as a way to 'filter'
4
- # incoming and outgoing messages. Akin to Rack / WSGI
5
- # middleware.
6
2
  class MiddleMan
3
+ is :loggable, :dispatchable
7
4
 
8
- # Set the logger cattr to the default marvin logger.
9
- cattr_accessor :logger
10
- self.logger ||= Marvin::Logger
11
-
12
- # By default, we are *not* setup.
13
- @@setup = false
14
-
15
- # Our list of subhandlers. We make sure
16
- # the list is unique to our subclass / class.
17
- class_inheritable_accessor :subhandlers
18
- self.subhandlers = []
19
-
20
- # Finally, the client.
21
5
  attr_reader :client
22
6
 
23
- # When we're told to set the client,
24
- # not only do we set out own instance
25
- # but we also echo the command down
26
- # to all of our sub-clients.
7
+ class << self
8
+ def setup?
9
+ @@setup ||= false
10
+ end
11
+
12
+ def setup!
13
+ Marvin::Settings.client.register_handler self.new
14
+ @@setup = true
15
+ end
16
+
17
+ # Setup iff setup hasn't been done.
18
+ def setup
19
+ setup! unless setup?
20
+ end
21
+ end
22
+
23
+ # When we're told to set the client, not only do we set out own instance
24
+ # but we also echo the command down to all of our sub-clients.
27
25
  def client=(new_client)
28
26
  @client = new_client
29
27
  setup_subhandler_clients
@@ -35,68 +33,15 @@ module Marvin
35
33
 
36
34
  # Filter incoming events.
37
35
  def handle(message, options)
38
- # Process the current event.
39
36
  message, options = process_event(message, options)
40
- full_handler_name = "handle_#{message}"
41
- self.send(full_handler_name, opts) if respond_to?(full_handler_name)
42
- self.subhandlers.each do |sh|
43
- forward_message_to_handler(sh, message, options, full_handler_name)
44
- end
45
- rescue HaltHandlerProcessing
46
- logger.info "Asked to halt the filter processing chain inside a middleman."
47
- rescue Exception => e
48
- logger.fatal "Exception processing handle #{message}"
49
- Marvin::ExceptionTracker.log(e)
50
- end
51
-
52
- class << self
53
-
54
- def setup?
55
- @@setup
56
- end
57
-
58
- # Forcefully do the setup routine.
59
- def setup!
60
- # Register ourselves as a new handler.
61
- Marvin::Settings.client.register_handler self.new
62
- @@setup = true
63
- end
64
-
65
- # Setup iff setup hasn't been done.
66
- def setup
67
- return if self.setup?
68
- self.setup!
69
- end
70
-
71
- # Register a single subhandler.
72
- def register_handler(handler, run_setup = true)
73
- self.setup if run_setup
74
- self.subhandlers << handler unless handler.blank?
75
- end
76
-
77
- # Registers a group of subhandlers.
78
- def register_handlers(*args)
79
- self.setup
80
- args.each { |h| self.register_handler(h, false) }
81
- end
82
-
37
+ dispatch(message, options)
83
38
  end
84
39
 
85
40
  private
86
41
 
87
42
  def setup_subhandler_clients
88
- self.subhandlers.each { |sh| sh.client = self.client if sh.respond_to?(:client=) }
89
- end
90
-
91
- # This should probably be extracted into some sort of Util's library as
92
- # it's shared across a couple of classes but I really can't be bothered
93
- # at the moment - I just want to test the concept.
94
- def forward_message_to_handler(handler, message, options, full_handler_name)
95
- if handler.respond_to?(full_handler_name)
96
- handler.send(full_handler_name, options)
97
- elsif handler.respond_to?(:handle)
98
- handler.handle message, options
99
- end
43
+ current_client = self.client
44
+ handlers.each { |sh| sh.client = current_client if sh.respond_to?(:client=) }
100
45
  end
101
46
 
102
47
  end
@@ -23,7 +23,7 @@ class AbstractClientTest < Test::Unit::TestCase
23
23
  assert_dispatched :client_connected, 0, {}
24
24
  end
25
25
 
26
- should "dispatch :client_connected as the first event on process_connect" do
26
+ should "dispatch the correct events" do
27
27
  assert_resets_client
28
28
  client.default_channels = ["#awesome", "#rock"]
29
29
  client.process_connect
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marvin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darcy Laycock
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-05 00:00:00 +08:00
12
+ date: 2009-12-08 00:00:00 +08:00
13
13
  default_executable: marvin
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: "0"
44
44
  version:
45
45
  - !ruby/object:Gem::Dependency
46
- name: thoughtbot-shoulda
46
+ name: shoulda
47
47
  type: :development
48
48
  version_requirement:
49
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -81,6 +81,7 @@ files:
81
81
  - handlers/debug_handler.rb
82
82
  - handlers/hello_world.rb
83
83
  - handlers/keiki_thwopper.rb
84
+ - handlers/raw_connection_logger.rb
84
85
  - handlers/simple_logger.rb
85
86
  - handlers/tweet_tweet.rb
86
87
  - lib/marvin.rb