marvin 0.8.1 → 0.8.2

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.
@@ -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