Sutto-marvin 0.1.0.20081016 → 0.1.20081115

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,155 @@
1
+ h1. Marvin
2
+
3
+ Marvin is a simple IRC Framework for Rails suitable for building things
4
+ such as simple IRC bots. Extracted from real use - we'd originally used
5
+ a heavily modified version of MatzBot - it's been built to service a
6
+ particular need.
7
+
8
+ h2. Background
9
+
10
+ Marvin is an event driven framework in two ways - for one, it uses
11
+ EventMachine for all networking purposes - as a result, it's both
12
+ relatively stable / reliable and also powerful.
13
+
14
+ Following on from this, the irc library is event driven. At the base
15
+ level, you choose a client (By Default, Marvin::IRC::Client.) and then you register
16
+ any number of handlers. Whenever an event happens e.g. an incoming message,
17
+ a connection unbinding or event just post_init, each handler is notified
18
+ and given a small set of details about the event.
19
+
20
+ Handlers are very simple - in fact, you could get away with registering
21
+ Object.new as a handler.
22
+
23
+ To function, handlers only require one method: handle - which takes
24
+ two options. an event name (e.g. :incoming_message) and a hash
25
+ of the aforementioned attributes / details. This data can then be processed.
26
+ Alternatively, if a handler has a "handle_[event_name]" method (e.g.
27
+ handle_incoming_message), it will instead be called. Also, if client=
28
+ is implemented this will be called when the client is setup containing
29
+ a reference to said client. This is used to that the handler can
30
+ respond to actions.
31
+
32
+ Like Rack for HTTP, Marvin provides a fair amount of example
33
+ handlers for simple stuff inside IRC.
34
+
35
+ h2. Getting Started
36
+
37
+ The easiest way to get started with Marvin is by installing the Marvin gem. To
38
+ do this, make sure Github is added to your gem sources (and you are using
39
+ rubygems >= 1.2.0):
40
+
41
+ $ gem sources -a http://gems.github.com
42
+ $ sudo gem install username-marvin
43
+
44
+ Once you have installed the gem, you should have access to the "marvin" command:
45
+
46
+ $ marvin --help
47
+
48
+ You can create a new marvin folder:
49
+
50
+ $ marvin create my_marvin_project
51
+
52
+ Then simply edit your settings in the +config/settings.yml+
53
+
54
+ default:
55
+ name: "My Marvin Bot"
56
+ server: irc.freenode.net
57
+ port: 6667
58
+ channel: "#marvin-testing"
59
+ use_logging: false
60
+ datastore_location: tmp/datastore.json
61
+ development:
62
+ user: MarvinBot
63
+ name: MarvinBot
64
+ nick: MarvinBot3000
65
+ production:
66
+ deployed: false
67
+
68
+ You can use the defaults or configure it. By default your marvin bot will not
69
+ do any logging. You will need to set that up. From this point you can begin
70
+ running the bot as a daemon or as a console application:
71
+
72
+ $ ./script/run
73
+
74
+ The bot should join the specified channel and will resond to some simple
75
+ commands by default:
76
+
77
+ |<YourName> MarvinBot3000: hello
78
+ |<MarvinBot3000> YourName: Hola!
79
+
80
+ h2. Marvin::Base - A handler starting point
81
+
82
+ The first, Marvin::Base provides a base set of methods (e.g. say,
83
+ reply etc etc.) which make writing a client easier. You can simply
84
+ inherit from Marvin::Base, write some logic and then use the class
85
+ method on_event to define responses to events. The passed in meta
86
+ data for each event is then usable via options.attribute_name - an
87
+ openstruct version of the details. e.g.
88
+
89
+ class NinjaStuff < Marvin::Base
90
+ on_event :incoming_message do
91
+ do_something
92
+ end
93
+ def do_something
94
+ reply options.message # Will echo back the message
95
+ end
96
+ end
97
+
98
+ Or the like. Also, the halt! method can be called in any subclass to
99
+ halt the handler callback chain.
100
+
101
+ h2. Marvin::CommandHandler - Ridiculously easy Bots
102
+
103
+ With Marvin::CommandHandler, you get to define seriously
104
+ simple classes which can act as a simple bot. It takes
105
+ great inspiration from "MatzBot":http://github.com/defunkt/matzbot/tree/master
106
+ which was actually one of the main inspirations for
107
+ creating marvin.
108
+
109
+ To write a CommandHandler, you simply create a subclass
110
+ (ala ActiveRecord::Base), define a few methods and then
111
+ just use the "exposes" class method. e.g.
112
+
113
+ class MySecondExample < Marvin::CommandHandler
114
+ exposes :hello
115
+ def hello(data)
116
+ reply "Hello!"
117
+ end
118
+ end
119
+
120
+ Where data is an array of parameters. exposed methods will be called
121
+ when they match the following pattern:
122
+
123
+ Botname: <exposed-method> <space-seperated-list-meaning-data>
124
+
125
+ h2. Marvin::MiddleMan - Introducing middleware
126
+
127
+ Marvin::MiddleMan lets you insert middleware between handlers
128
+ and you're client - letting you do things such as translating
129
+ all messages on the fly. It's build to be extensible and is
130
+ relatively simple to use. On any Marvin::Base subclass (baring
131
+ the MiddleMan itself), you can simple use the normal methods
132
+ of registering a handler with one exception - you now pass
133
+ one argument, the class reference to your middleman class.
134
+
135
+ h2. Marvin::DataStore - A dead simple persistent hash store
136
+
137
+ Want to save data between when you stop and start your IRC
138
+ Client? With Marvin, it's really, really simple - Marvin::DataStore
139
+ offers a simple syntax for persistent data stores.
140
+
141
+ New datastores can be created with Marvin::DataStore.new("global-store-key").
142
+ From there, you have a hash to do whatever the hell you want. Just
143
+ make sure the data you store is JSON-serializable.
144
+
145
+ When you start - stop a server (via Marvin::Loader.run! and Marvin::Loader.stop!)
146
+ you're data will be loaded from and written to disk accordingly.
147
+
148
+ If you're inside a Marvin::Base subclass it's even easier. You can get a cattr_access
149
+ style accessor for free - just use the "uses_datastore" method. e.g:
150
+
151
+ class X < Marvin::Base
152
+ uses_datastore "datastore-global-key", :cattr_name
153
+ end
154
+
155
+ Then, self.cattr_name will point to the data store instance.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ patch: 20081115
3
+ major: 0
4
+ minor: 1
@@ -2,13 +2,8 @@ class HelloWorld < Marvin::CommandHandler
2
2
 
3
3
  exposes :hello
4
4
 
5
- uses_datastore "hello-count", :counts
6
-
7
5
  def hello(data)
8
- self.counts ||= {}
9
- self.counts[options.nick] ||= 0
10
- self.counts[options.nick] += 1
11
- reply "Oh hai there - This is hello ##{self.counts[options.nick]} from you!"
6
+ reply "Hola!" unless target == "#all"
12
7
  end
13
8
 
14
9
  end
@@ -16,4 +16,6 @@ class TweetTweet < Marvin::Base
16
16
  def show_tweet(tweet)
17
17
  end
18
18
 
19
+
20
+
19
21
  end
@@ -5,11 +5,12 @@ require "marvin/irc/event"
5
5
  module Marvin
6
6
  class AbstractClient
7
7
 
8
- cattr_accessor :events, :handlers, :configuration, :logger, :is_setup, :connections
8
+ include Marvin::Dispatchable
9
+
10
+ cattr_accessor :events, :configuration, :logger, :is_setup, :connections
9
11
  attr_accessor :channels, :nickname
10
12
 
11
13
  # Set the default values for the variables
12
- self.handlers = []
13
14
  self.events = []
14
15
  self.configuration = OpenStruct.new
15
16
  self.configuration.channels = []
@@ -27,12 +28,13 @@ module Marvin
27
28
  logger.debug "Setting the client for each handler"
28
29
  self.handlers.each { |h| h.client = self if h.respond_to?(:client=) }
29
30
  logger.debug "Dispatching the default :client_connected event"
30
- dispatch_event :client_connected
31
+ dispatch :client_connected
31
32
  end
32
33
 
33
34
  def process_disconnect
34
35
  self.connections.delete(self) if self.connections.include?(self)
35
- dispatch_event :client_disconnected
36
+ dispatch :client_disconnected
37
+ Marvin::Loader.stop! if self.connections.blank?
36
38
  end
37
39
 
38
40
  # Sets the current class-wide settings of this IRC Client
@@ -64,53 +66,10 @@ module Marvin
64
66
 
65
67
  ## Handling all of the the actual client stuff.
66
68
 
67
- # Appends a handler to the end of the handler callback
68
- # chain. Note that they will be called in the order they
69
- # are appended.
70
- def self.register_handler(handler)
71
- return if handler.blank?
72
- self.handlers << handler
73
- end
74
-
75
69
  def receive_line(line)
76
- dispatch_event :incoming_line, :line => line
70
+ dispatch :incoming_line, :line => line
77
71
  event = Marvin::Settings.default_parser.parse(line)
78
- dispatch_event(event.to_incoming_event_name, event.to_hash) unless event.nil?
79
- end
80
-
81
- # Handles the dispatch of an event and it's associated options
82
- # / properties (defaulting to an empty hash) to both the client
83
- # (used for things such as responding to PING) and each of the
84
- # registered handlers.
85
- def dispatch_event(name, opts = {})
86
- # The full handler name is simply what is used to handle
87
- # a single event (e.g. handle_incoming_message)
88
- full_handler_name = "handle_#{name}"
89
-
90
- # If the current handle_name method is defined on this
91
- # class, we dispatch to that first. We use this to provide
92
- # functionality such as responding to PING's and handling
93
- # required stuff on connections.
94
- self.send(full_handler_name, opts) if respond_to?(full_handler_name)
95
-
96
- begin
97
- # For each of the handlers, check first if they respond to
98
- # the full handler name (e.g. handle_incoming_message) - calling
99
- # that if it exists - otherwise falling back to the handle method.
100
- # if that doesn't exist, nothing is done.
101
- self.handlers.each do |handler|
102
- if handler.respond_to?(full_handler_name)
103
- handler.send(full_handler_name, opts)
104
- elsif handler.respond_to?(:handle)
105
- handler.handle name, opts
106
- end
107
- end
108
- # Raise an exception in order to stop the flow
109
- # of the control. Ths enables handlers to prevent
110
- # responses from happening multiple times.
111
- rescue HaltHandlerProcessing
112
- logger.debug "Handler Progress halted; Continuing on."
113
- end
72
+ dispatch(event.to_incoming_event_name, event.to_hash) unless event.nil?
114
73
  end
115
74
 
116
75
  # Default handlers
@@ -123,16 +82,18 @@ module Marvin
123
82
  def handle_client_connected(opts = {})
124
83
  logger.debug "About to handle post init"
125
84
  # IRC Connection is establish so we send all the required commands to the server.
126
- logger.debug "sending user command"
127
- command :user, self.configuration.user, "0", "*", Marvin::Util.last_param(self.configuration.name)
128
- default_nickname = self.configuration.nick || self.configuration.nicknames.shift
129
85
  logger.debug "Setting default nickname"
86
+ default_nickname = self.configuration.nick || self.configuration.nicknames.shift
130
87
  nick default_nickname
88
+ logger.debug "sending user command"
89
+ command :user, self.configuration.user, "0", "*", Marvin::Util.last_param(self.configuration.name)
131
90
  # If a password is specified, we will attempt to message
132
91
  # NickServ to identify ourselves.
133
92
  say ":IDENTIFY #{self.configuration.password}", "NickServ" unless self.configuration.password.blank?
134
93
  # Join the default channels
135
94
  self.configuration.channels.each { |c| self.join c }
95
+ rescue Exception => e
96
+ Marvin::ExceptionTracker.log(e)
136
97
  end
137
98
 
138
99
  # The default handler for when a users nickname is taken on
@@ -167,7 +128,7 @@ module Marvin
167
128
  def handle_incoming_numeric(opts = {})
168
129
  code = opts[:code].to_i
169
130
  args = Marvin::Util.arguments(opts[:data])
170
- dispatch_event :incoming_numeric_processed, {:code => code, :data => args}
131
+ dispatch :incoming_numeric_processed, {:code => code, :data => args}
171
132
  end
172
133
 
173
134
  ## General IRC Functions
@@ -190,14 +151,14 @@ module Marvin
190
151
  self.channels << channel
191
152
  command :JOIN, channel
192
153
  logger.info "Joined channel #{channel}"
193
- dispatch_event :outgoing_join, :target => channel
154
+ dispatch :outgoing_join, :target => channel
194
155
  end
195
156
 
196
157
  def part(channel, reason = nil)
197
158
  channel = Marvin::Util.channel_name(channel)
198
159
  if self.channels.include?(channel)
199
160
  command :part, channel, Marvin::Util.last_param(reason)
200
- dispatch_event :outgoing_part, :target => channel, :reason => reason
161
+ dispatch :outgoing_part, :target => channel, :reason => reason
201
162
  logger.info "Parted from room #{channel}#{reason ? " - #{reason}" : ""}"
202
163
  else
203
164
  logger.warn "Tried to disconnect from #{channel} - which you aren't a part of"
@@ -212,7 +173,7 @@ module Marvin
212
173
  end
213
174
  logger.debug "Parted from all channels, quitting"
214
175
  command :quit
215
- dispatch_event :quit
176
+ dispatch :quit
216
177
  # Remove the connections from the pool
217
178
  self.connections.delete(self)
218
179
  logger.info "Quit from server"
@@ -221,19 +182,19 @@ module Marvin
221
182
  def msg(target, message)
222
183
  command :privmsg, target, Marvin::Util.last_param(message)
223
184
  logger.info "Message sent to #{target} - #{message}"
224
- dispatch_event :outgoing_message, :target => target, :message => message
185
+ dispatch :outgoing_message, :target => target, :message => message
225
186
  end
226
187
 
227
188
  def action(target, message)
228
189
  action_text = Marvin::Util.last_param "\01ACTION #{message.strip}\01"
229
190
  command :privmsg, target, action_text
230
- dispatch_event :outgoing_action, :target => target, :message => message
191
+ dispatch :outgoing_action, :target => target, :message => message
231
192
  logger.info "Action sent to #{target} - #{message}"
232
193
  end
233
194
 
234
195
  def pong(data)
235
196
  command :pong, data
236
- dispatch_event :outgoing_pong
197
+ dispatch :outgoing_pong
237
198
  logger.info "PONG sent to #{data}"
238
199
  end
239
200
 
@@ -241,7 +202,7 @@ module Marvin
241
202
  logger.info "Changing nickname to #{new_nick}"
242
203
  command :nick, new_nick
243
204
  self.nickname = new_nick
244
- dispatch_event :outgoing_nick, :new_nick => new_nick
205
+ dispatch :outgoing_nick, :new_nick => new_nick
245
206
  logger.info "Nickname changed to #{new_nick}"
246
207
  end
247
208
 
data/lib/marvin/base.rb CHANGED
@@ -97,11 +97,11 @@ module Marvin
97
97
 
98
98
  # Determines whether the previous message was inside a channel.
99
99
  def from_channel?
100
- self.target && self.target[0..0] == "#"
100
+ self.target && self.target[0] == ?#
101
101
  end
102
102
 
103
103
  def addressed?
104
- self.from_user? || options.message.split(" ").first == "#{self.client.nickname}:"
104
+ self.from_user? || options.message.split(" ").first.downcase == "#{self.client.nickname.downcase}:"
105
105
  end
106
106
 
107
107
  def setup_defaults(options)
@@ -2,4 +2,10 @@ class File
2
2
  def self.present_dir
3
3
  File.dirname(__FILE__)
4
4
  end
5
+ end
6
+
7
+ class String
8
+ def /(*args)
9
+ File.join(self, *args)
10
+ end
5
11
  end
@@ -0,0 +1,94 @@
1
+ module Marvin
2
+ # = Marvin::Dispatchable
3
+ # A Generic mixin which lets you define an object
4
+ # Which accepts handlers which can have arbitrary
5
+ # events dispatched.
6
+ # == Usage
7
+ #
8
+ # class X
9
+ # include Marvin::Dispatchable
10
+ # self.handlers << SomeHandler.new
11
+ # end
12
+ # X.new.dispatch(:name, {:args => "Values"})
13
+ #
14
+ # Will first check if SomeHandler#handle_name exists,
15
+ # calling handle_name({:args => "Values"}) if it does,
16
+ # otherwise calling SomeHandler#handle(:name, {:args => "Values"})
17
+ module Dispatchable
18
+
19
+ def self.included(parent)
20
+ parent.class_eval do
21
+ include InstanceMethods
22
+ extend ClassMethods
23
+ end
24
+ end
25
+
26
+ module InstanceMethods
27
+
28
+ # Returns the handlers registered on this class,
29
+ # used inside +dispatch+.
30
+ def handlers
31
+ self.class.handlers
32
+ end
33
+
34
+ # Dispatch an 'event' with a given name to the handlers
35
+ # registered on the current class. Used as a nicer way of defining
36
+ # behaviours that should occur under a given set of circumstances.
37
+ # == Params
38
+ # +name+: The name of the current event
39
+ # +opts+: an optional hash of options to pass
40
+ def dispatch(name, opts = {})
41
+ # The full handler name is the method we call given it exists.
42
+ full_handler_name = :"handle_#{name.to_s.underscore}"
43
+ # First, dispatch locally if the method is defined.
44
+ if self.respond_to?(full_handler_name)
45
+ self.send(full_handler_name, opts)
46
+ end
47
+ # Iterate through all of the registered handlers,
48
+ # If there is a method named handle_<event_name>
49
+ # defined we sent that otherwise we call the handle
50
+ # method on the handler. Note that the handle method
51
+ # is the only required aspect of a handler. An improved
52
+ # version of this would likely cache the respond_to?
53
+ # call.
54
+ self.handlers.each do |handler|
55
+ if handler.respond_to?(full_handler_name)
56
+ handler.sent(full_handler_name, opts)
57
+ else
58
+ handler.handle name, opts
59
+ end
60
+ end
61
+ # If we get the HaltHandlerProcessing exception, we
62
+ # catch it and continue on our way. In essence, we
63
+ # stop the dispatch of events to the next set of the
64
+ # handlers.
65
+ rescue HaltHandlerProcessing
66
+ end
67
+
68
+ end
69
+
70
+ module ClassMethods
71
+
72
+ # Return an array of all registered handlers, stored in the
73
+ # class variable @@handlers. Used inside the #handlers instance
74
+ # method as well as inside things such as register_handler.
75
+ def handlers
76
+ @@handlers ||= []
77
+ end
78
+
79
+ # Assigns a new array of handlers and assigns each.
80
+ def handlers=(new_value)
81
+ @@handlers = []
82
+ new_value.to_a.each { |h| register_handler h }
83
+ end
84
+
85
+ # Appends a handler to the list of handlers for this object.
86
+ # Handlers are called in the order they are registered.
87
+ def register_handler(handler)
88
+ self.handlers << handler unless handler.nil? || !handler.respond_to?(:handle)
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,12 @@
1
+ module Marvin
2
+ module Handler
3
+
4
+ # Received a given +message+ with a set of default
5
+ # +opts+ (defaulting back to an empty hash), which
6
+ # will be used to perform some sort of action.
7
+ def handle(message, opts = {})
8
+ Marvin::Logger.debug "NOP handle - got message #{message.inspect}"
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require 'webrick'
2
+
3
+ module Marvin::IRC
4
+ class BaseServer < WEBrick::GenericServer
5
+
6
+ def run(sock)
7
+ File.open("x", "w+") { |f| f.puts sock.inspect }
8
+ end
9
+
10
+ end
11
+ end
@@ -57,12 +57,10 @@ module Marvin::IRC
57
57
  end
58
58
 
59
59
  def post_init
60
- super
61
60
  client.process_connect
62
61
  end
63
62
 
64
63
  def unbind
65
- super
66
64
  client.process_disconnect
67
65
  end
68
66
 
@@ -98,7 +96,7 @@ module Marvin::IRC
98
96
 
99
97
  # Registers a callback handle that will be periodically run.
100
98
  def periodically(timing, event_callback)
101
- callback = proc { self.dispatch_event event_callback.to_sym }
99
+ callback = proc { self.dispatch event_callback.to_sym }
102
100
  EventMachine::add_periodic_timer(timing, &callback)
103
101
  end
104
102
 
@@ -56,7 +56,13 @@ module Marvin::IRC
56
56
 
57
57
  # Registers a callback handle that will be periodically run.
58
58
  def periodically(timing, event_callback)
59
- callback = proc { self.dispatch_event event_callback.to_sym }
59
+ callback = proc { self.dispatch event_callback.to_sym }
60
+ Thread.new do
61
+ while true
62
+ callback.call
63
+ sleep timing
64
+ end
65
+ end
60
66
  end
61
67
 
62
68
  end
data/lib/marvin/irc.rb CHANGED
@@ -1,8 +1,9 @@
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'
3
+ autoload :Client, 'marvin/irc/client'
4
+ autoload :Event, 'marvin/irc/event'
5
+ autoload :SocketClient, 'marvin/irc/socket_client'
6
6
  autoload :AbstractServer, 'marvin/irc/abstract_server'
7
+ autoload :BaseServer, 'marvin/irc/base_server'
7
8
  end
8
9
  end
@@ -85,9 +85,7 @@ module Marvin
85
85
  private
86
86
 
87
87
  def setup_subhandler_clients
88
- self.subhandlers.each do |sh|
89
- sh.client = self.client if sh.respond_to?(:client=)
90
- end
88
+ self.subhandlers.each { |sh| sh.client = self.client if sh.respond_to?(:client=) }
91
89
  end
92
90
 
93
91
  # This should probably be extracted into some sort of Util's library as
@@ -86,6 +86,8 @@ module Marvin
86
86
 
87
87
  register_event :ping, /^\:(.+)\!\~?(.+)\@(.+) PING (.*)$/,
88
88
  :nick, :ident, :host, :data
89
+
90
+ register_event :ping, /PING (.*)$/, :data
89
91
 
90
92
  register_event :numeric, /^\:(\S+) ([0-9]+) (.*)$/,
91
93
  :server, :code, :data
@@ -39,9 +39,9 @@ module Marvin
39
39
  self.receive_line irc_command
40
40
  end
41
41
 
42
- def dispatch_event(name, opts = {})
42
+ def dispatch(name, opts = {})
43
43
  self.dispatched_events << [name, opts]
44
- super(name, opts = {})
44
+ super(name, opts)
45
45
  end
46
46
 
47
47
  def self.run
data/lib/marvin/util.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module Marvin
2
- class Util
3
- class << self
2
+ module Util
4
3
 
5
4
  # Return the channel-name version of a string by
6
5
  # appending "#" to the front if it doesn't already
@@ -25,6 +24,7 @@ module Marvin
25
24
  end
26
25
  alias lp last_param
27
26
 
28
- end
27
+ extend self
28
+
29
29
  end
30
30
  end
data/lib/marvin.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  $:.unshift File.dirname(__FILE__) # Append the current working dir to the front of the line.
2
2
 
3
3
  require 'rubygems'
4
- require 'extlib'
4
+ require 'active_support'
5
5
  require 'marvin/core_ext'
6
6
 
7
7
  # Make all exceptions available
@@ -9,6 +9,7 @@ require 'marvin/exceptions'
9
9
 
10
10
  module Marvin
11
11
  autoload :Util, 'marvin/util'
12
+ autoload :Dispatchable, 'marvin/dispatchable'
12
13
  autoload :AbstractClient, 'marvin/abstract_client'
13
14
  autoload :Base, 'marvin/base'
14
15
  autoload :ClientMixin, 'marvin/client_mixin'
data/script/run CHANGED
@@ -11,9 +11,15 @@ IS_DAEMON = ARGV.include?("--is-daemon")
11
11
  # And Require Marvin.
12
12
  require 'marvin'
13
13
 
14
- trap "SIGINT" do
15
- Marvin::Loader.stop!
16
- exit
14
+ ["INT", "TERM"].each do |sig|
15
+
16
+ # Trap a given signal and run all
17
+ # of our callbacks etc,
18
+ trap sig do
19
+ Marvin::Loader.stop!
20
+ exit
21
+ end
22
+
17
23
  end
18
24
 
19
25
  Marvin::Loader.run!
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ # Tests the behaviour of the AbstractClient functionality
4
+ # via a thin wrapper of the class via Marvin::TestClient.
5
+
6
+ describe "the base Marvin::TestClient functionality" do
7
+
8
+ it "should dispatch :client_connected as the first event on process_connect" do
9
+ client(true).dispatched_events.should == []
10
+ client.process_connect
11
+ client.dispatched_events.first.should == [:client_connected, {}]
12
+ end
13
+
14
+ it "should dispatch :client_connected as the first event on process_connect" do
15
+ client(true).dispatched_events.should == []
16
+ client.process_connect
17
+ Marvin::Logger.info client.outgoing_commands.inspect
18
+ client.dispatched_events[-2].first.should == :outgoing_nick
19
+ client.dispatched_events[-1].first.should == :outgoing_join
20
+ client.outgoing_commands.length.should == 3
21
+ client.outgoing_commands[0].should =~ /^USER \w+ 0 \* :\w+ \r\n$/
22
+ client.outgoing_commands[1].should =~ /^NICK \w+ \r\n$/
23
+ client.outgoing_commands[2].should =~ /^JOIN \#[A-Za-z0-9\-\_]+ \r\n$/
24
+ end
25
+
26
+ it "should dispatch :client_disconnect on process_disconnect" do
27
+ client(true).dispatched_events.should == []
28
+ client.process_disconnect
29
+ client.dispatched_events.last.should == [:client_disconnected, {}]
30
+ end
31
+
32
+ it "should add an :incoming_line event for each incoming line" do
33
+ client(true).dispatched_events.should == []
34
+ client.receive_line "SOME RANDOM LINE THAT HAS ZERO ACTUAL USE"
35
+ client.dispatched_events.first.should == [:incoming_line, {:line => "SOME RANDOM LINE THAT HAS ZERO ACTUAL USE"}]
36
+ end
37
+
38
+ end
@@ -0,0 +1,14 @@
1
+ require "rubygems"
2
+ require "bacon"
3
+
4
+ require File.dirname(__FILE__) + "/../lib/marvin"
5
+ # Now, Set everything up.
6
+ Marvin::Logger.logger = Logger.new(File.dirname(__FILE__) + "/../log/test.log")
7
+ Marvin::Settings.default_client = Marvin::TestClient
8
+ Marvin::Loader.run!
9
+
10
+ def client(force_new = false)
11
+ $test_client = Marvin::TestClient.new if force_new || $test_client.nil?
12
+ return $test_client
13
+ end
14
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Sutto-marvin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.20081016
4
+ version: 0.1.20081115
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darcy Laycock
@@ -9,8 +9,8 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-05 00:00:00 -07:00
13
- default_executable: marvin
12
+ date: 2008-11-15 00:00:00 -08:00
13
+ default_executable:
14
14
  dependencies: []
15
15
 
16
16
  description: Marvin is a Ruby IRC library / framework for ultimate awesomeness and with an evented design.
@@ -22,6 +22,9 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
+ - README.textile
26
+ - VERSION.yml
27
+ - bin/marvin
25
28
  - lib/marvin
26
29
  - lib/marvin/abstract_client.rb
27
30
  - lib/marvin/abstract_parser.rb
@@ -29,11 +32,14 @@ files:
29
32
  - lib/marvin/command_handler.rb
30
33
  - lib/marvin/core_ext.rb
31
34
  - lib/marvin/data_store.rb
35
+ - lib/marvin/dispatchable.rb
32
36
  - lib/marvin/drb_handler.rb
33
37
  - lib/marvin/exception_tracker.rb
34
38
  - lib/marvin/exceptions.rb
39
+ - lib/marvin/handler.rb
35
40
  - lib/marvin/irc
36
41
  - lib/marvin/irc/abstract_server.rb
42
+ - lib/marvin/irc/base_server.rb
37
43
  - lib/marvin/irc/client.rb
38
44
  - lib/marvin/irc/event.rb
39
45
  - lib/marvin/irc/socket_client.rb
@@ -53,16 +59,18 @@ files:
53
59
  - lib/marvin/test_client.rb
54
60
  - lib/marvin/util.rb
55
61
  - lib/marvin.rb
56
- - bin/marvin
57
- - config/settings.yml.sample
58
- - config/setup.rb
62
+ - spec/marvin
63
+ - spec/marvin/abstract_client_test.rb
64
+ - spec/spec_helper.rb
65
+ - script/daemon-runner
66
+ - script/run
59
67
  - handlers/hello_world.rb
60
68
  - handlers/logging_handler.rb
61
69
  - handlers/tweet_tweet.rb
62
- - script/daemon-runner
63
- - script/run
64
- has_rdoc: true
65
- homepage: http://github.com/sutto/marvin
70
+ - config/setup.rb
71
+ - config/settings.yml.sample
72
+ has_rdoc: false
73
+ homepage: http://blog.ninjahideout.com/
66
74
  post_install_message:
67
75
  rdoc_options: []
68
76
 
@@ -80,8 +88,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
88
  - !ruby/object:Gem::Version
81
89
  version: "0"
82
90
  version:
83
- requirements:
84
- - install the eventmachine gem to get better client support
91
+ requirements: []
92
+
85
93
  rubyforge_project:
86
94
  rubygems_version: 1.2.0
87
95
  signing_key: