Sutto-marvin 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/TUTORIAL.textile ADDED
@@ -0,0 +1,54 @@
1
+ h1. A Quick Introduction to Marvin
2
+
3
+ Welcome young one - in today's lesson we're going to learn
4
+ how to build a simple IRC bot using "Marvin":http://github.com/Sutto/marvin.
5
+ If you haven't built an IRC bot before, there is a great
6
+ deal of things you can do - ranging from consuming content (e.g.
7
+ analysing logs of IRC rooms, seeing who swears the most)
8
+ as well as other things such as bridges between other
9
+ protocols (twitter => irc bridges) and all sorts of fancy stuff.
10
+
11
+ h3. What is this marvin thingy-majiggy?
12
+
13
+ Marvin is an IRC Library / Framework (it can be used either way)
14
+ built with an evented design on top of DRb, EventMachine and a bunch
15
+ of other stuff for Ruby. it's currently got a fairly complete client
16
+ and a very incomplete server implementation.
17
+
18
+ h2. Step #1 - Getting Marvin
19
+
20
+ There are currently two different ways to get marvin - via the
21
+ GitHub gem sources or directly from the source.
22
+
23
+ To install from the GitHub gem source, simply use:
24
+
25
+ sudo gem install Sutto-marvin -s http://gems.github.com
26
+
27
+ Alternatively, you can directly clone the repository. To do this, you'll need to run the
28
+ following:
29
+
30
+ git clone git://github.com/Sutto/marvin.git
31
+ cd marvin
32
+ sudo ./script/install
33
+
34
+ Once that's done, not only will you now have a handy "marvin" gem which you
35
+ can require but you will also have a handy executable of the same name which
36
+ you can use.
37
+
38
+ h2. Step #2 - Creating a Skeleton App
39
+
40
+ By default, marvin currently uses a simple skeleton app (not unlike
41
+ Ruby on Rails) as a starting point. To get started, you simply use
42
+ the "marvin create" command with a path to / folder name (which must not
43
+ yet exist) into which you wish to create the folder. For example, if I wanted
44
+ to create a new irc bot called "BlorkBot", I could do the following:
45
+
46
+ marvin create BlorkBot
47
+
48
+ And a new directory with the basic structure called "BlorkBot" will be created
49
+ under the current directory. To get started, you can then open your editor and
50
+ browse the given folder. Of this generated structure, there are only two folders
51
+ you need to worry about for the moment - config and handlers.
52
+
53
+ h2. Step #3 - Basic configuration
54
+
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- patch: 2
2
+ patch: 3
3
3
  major: 0
4
4
  minor: 2
data/bin/marvin CHANGED
@@ -32,6 +32,7 @@ class Marvin < Thor
32
32
  mkdir source(@dest, "lib")
33
33
  say " => Copying files..."
34
34
  copy "config/setup.rb"
35
+ copy "config/boot.rb"
35
36
  copy "config/connections.yml.sample", "config/connections.yml"
36
37
  copy "config/settings.yml.sample", "config/settings.yml"
37
38
  copy "handlers/hello_world.rb"
@@ -67,6 +68,13 @@ class Marvin < Thor
67
68
  start_script(:client)
68
69
  end
69
70
 
71
+ desc "server [PATH]", "starts a server instance from the given location"
72
+ method_options :verbose => :boolean, :daemon => :boolean, :level => :optional, :kill => :boolean
73
+ def server(path = ".")
74
+ @dest = File.expand_path(path)
75
+ start_script(:server)
76
+ end
77
+
70
78
  desc "ring_server [PATH]", "starts a ring server from the given location"
71
79
  method_options :verbose => :boolean, :daemon => :boolean, :level => :optional, :kill => :boolean
72
80
  def ring_server(path = ".")
data/config/boot.rb ADDED
@@ -0,0 +1,14 @@
1
+ # Load Marvin, do any initialization etc.
2
+ # Note: this is called from inside scripts
3
+ # or anywhere you want to start a base marvin
4
+ # instance.
5
+
6
+ require 'rubygems'
7
+
8
+ MARVIN_ROOT = File.expand_path(File.join(File.dirname(__FILE__), ".."))
9
+
10
+ # Check if a local copy of marvin exists, and set the load path if it does.
11
+ $:.unshift(File.dirname(__FILE__) + "/../lib/") if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
12
+
13
+ # And Require Marvin.
14
+ require 'marvin'
@@ -41,7 +41,7 @@ module Marvin
41
41
  unless command_name.nil?
42
42
  logger.debug "Command Exists - processing"
43
43
  # Dispatch the command.
44
- self.send(command_name, data.split(" ")) if self.respond_to?(command_name)
44
+ self.send(command_name, data.to_s.split(" ")) if self.respond_to?(command_name)
45
45
  end
46
46
  end
47
47
 
@@ -55,6 +55,7 @@ module Marvin
55
55
  logger.warn "Dispatch handler queue is currently holding #{size} items"
56
56
  end
57
57
  else
58
+ logger.debug "Writing #{@queued_messages.size} message to the ring server"
58
59
  @queued_messages.dup.each do |t|
59
60
  ring_server.write(t)
60
61
  @queued_messages.delete(t)
@@ -7,33 +7,46 @@ module Marvin
7
7
 
8
8
  # Store each user
9
9
  UserStore = NamedStore.new(:nicks, :user) do
10
+
11
+ def virtual?(nick)
12
+ self[nick].is_a?(Marvin::IRC::Server::VirtualUserConnection)
13
+ end
14
+
15
+ def reclaim(nick)
16
+ if has_key?(nick) && virtual?(nick)
17
+ self[nick].reclaim!
18
+ end
19
+ end
20
+
21
+ # Nick is not taken when
10
22
  def nick_taken?(nick)
11
- has_key?(nick)
23
+ has_key?(nick) && !virtual(nick)
12
24
  end
13
25
 
14
26
  def each_user_except(user)
15
- self.each_user do |u|
16
- yield u unless user == u
17
- end
27
+ self.each_user { |u| yield u unless user == u }
18
28
  end
19
29
  end
20
30
 
21
31
  # Store each channel
22
32
  ChannelStore = NamedStore.new(:names, :channel)
23
33
 
24
- autoload :Channel, 'marvin/irc/server/channel'
34
+ autoload :RemoteInterface, 'marvin/irc/server/remote_interface'
35
+ autoload :Channel, 'marvin/irc/server/channel'
25
36
  # The actual network connection
26
- autoload :BaseConnection, 'marvin/irc/server/base_connection'
37
+ autoload :BaseConnection, 'marvin/irc/server/base_connection'
27
38
  # An our implementations of protocol-specific stuff.
28
- autoload :AbstractConnection, 'marvin/irc/server/abstract_connection'
29
- autoload :UserConnection, 'marvin/irc/server/user_connection'
30
- autoload :ServerConnection, 'marvin/irc/server/server_connection'
39
+ autoload :VirtualUserConnection, 'marvin/irc/server/virtual_user_connection'
40
+ autoload :AbstractConnection, 'marvin/irc/server/abstract_connection'
41
+ autoload :UserConnection, 'marvin/irc/server/user_connection'
42
+ autoload :ServerConnection, 'marvin/irc/server/server_connection'
31
43
  # Extensions for each part
32
- autoload :User, 'marvin/irc/server/user'
44
+ autoload :User, 'marvin/irc/server/user'
33
45
 
34
46
  # call start_server w/ the default options
35
47
  # and inside an EM::run block.
36
48
  def self.run
49
+ Marvin::IRC::Server::RemoteInterface.start
37
50
  EventMachine::run do
38
51
  Marvin::Logger.info "Starting server..."
39
52
  start_server :bind_addr => "0.0.0.0"
@@ -46,6 +59,7 @@ module Marvin
46
59
  opts[:host] ||= self.host_name
47
60
  opts[:port] ||= 6667
48
61
  EventMachine::start_server(opts[:bind_addr] || opts[:host], opts[:port], BaseConnection, opts)
62
+ Marvin::Logger.info "Server started"
49
63
  end
50
64
 
51
65
  def self.host_name
@@ -55,6 +55,11 @@ module Marvin::IRC::Server
55
55
 
56
56
  end
57
57
 
58
+ def dispatch(name, opts = {})
59
+ opts[:connection] ||= self
60
+ super
61
+ end
62
+
58
63
  private
59
64
 
60
65
  def command(name, *args)
@@ -69,9 +69,13 @@ module Marvin::IRC::Server
69
69
  check_emptyness!
70
70
  end
71
71
 
72
- def message(user, message)
73
- @members.each { |m| m.notify :privmsg, @name, ":#{message}", :prefix => user.prefix unless user == m }
74
- dispatch :outgoing_message, :target => self, :user => user, :message => message
72
+ def message(user, message, virtual = false)
73
+ @members.each do |m|
74
+ if virtual || user != m
75
+ m.notify(:privmsg, @name, ":#{message}", :prefix => user.prefix, :virtual => virtual)
76
+ end
77
+ end
78
+ dispatch :outgoing_message, :target => self, :user => user, :message => message, :virtual => virtual
75
79
  end
76
80
 
77
81
  def notice(user, message)
@@ -0,0 +1,59 @@
1
+ require 'drb'
2
+ require 'rinda/ring'
3
+ module Marvin
4
+ module IRC
5
+ module Server
6
+
7
+ # Make sure that a proxy object is used vs. the real thing.
8
+ [Channel, AbstractConnection, User, NamedStore].each do |c|
9
+ c.class_eval { include DRbUndumped }
10
+ end
11
+
12
+ # A DRb interface to post / receive messages from the
13
+ # a running server instance. Used for things such as
14
+ # posting status updated directly on the server.
15
+ class RemoteInterface
16
+ include DRbUndumped
17
+
18
+ def self.start
19
+ DRb.start_service
20
+ instance = self.new # Create the new instance
21
+ begin
22
+ rs = Rinda::RingFinger.primary
23
+ renewer = Rinda::SimpleRenewer.new
24
+ tuple = [:marvin_server, Marvin::Settings.distributed_namespace, instance]
25
+ Marvin::Logger.info "Publishing information about service to the tuplespace"
26
+ Marvin::Logger.debug "Pushing #{tuple.inspect} to #{rs.__drburi}"
27
+ rs.write(tuple, renewer)
28
+ rescue
29
+ Marvin::Logger.warn "No ring server found - remote interface not running"
30
+ end
31
+ end
32
+
33
+ # Returns an array of all channels
34
+ def channels
35
+ Marvin::IRC::Server::ChannelStore.values
36
+ end
37
+
38
+ # Returns the names of all channels
39
+ def channel_names
40
+ Marvin::IRC::Server::ChannelStore.keys
41
+ end
42
+
43
+ # Returns the channel with the given name.
44
+ def channel(name)
45
+ Marvin::IRC::Server::ChannelStore[name]
46
+ end
47
+
48
+ # Send an action from a user to a specific
49
+ # channel, using from nick as a facade.
50
+ def message(from_nick, target, contents)
51
+ u = (Marvin::IRC::Server::UserStore[from_nick.to_s.downcase] ||= VirtualUserConnection.new(from_nick))
52
+ Marvin::Logger.info "#{from_nick} (#{u.inspect}) messaging #{target}: #{contents}"
53
+ u.send_message(target, contents) unless u.blank?
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -15,11 +15,13 @@ module Marvin::IRC::Server::User::HandleMixin
15
15
  nick = opts[:new_nick]
16
16
  if !nick.blank? && !Marvin::IRC::Server::UserStore.nick_taken?(nick.downcase)
17
17
  if !(new_nick = @nick.nil?)
18
+ logger.info "Reclaiming nick (if taken)"
19
+ Marvin::IRC::Server::UserStore.reclaim(nick)
18
20
  logger.debug "Notifying all users of nick change: #{@nick} => #{nick}"
19
21
  # Get all users and let them now we've changed nick from @nick to nick
20
22
  users = [self]
21
23
  @channels.each do |c|
22
- users += c.members.values
24
+ users += c.members
23
25
  end
24
26
  users.uniq.each { |u| u.notify :NICK, nick, :prefix => prefix }
25
27
  dispatch :outgoing_nick, :nick => @nick, :new_nick => nick
@@ -62,7 +64,7 @@ module Marvin::IRC::Server::User::HandleMixin
62
64
  end
63
65
  chan = (Marvin::IRC::Server::ChannelStore[channel.downcase] ||= Marvin::IRC::Server::Channel.new(channel))
64
66
  if chan.join(self)
65
- rpl :TOPIC, channel, ":#{chan.topic.blank? ? "There is no topic" : nchan.topic}"
67
+ rpl :TOPIC, channel, ":#{chan.topic.blank? ? "There is no topic" : chan.topic}"
66
68
  rpl :NAMREPLY, "=", channel, ":#{chan.members.map { |m| m.nick }.join(" ")}"
67
69
  rpl :ENDOFNAMES, channel, ":End of /NAMES list."
68
70
  @channels << chan
@@ -123,7 +125,7 @@ module Marvin::IRC::Server::User::HandleMixin
123
125
  return if c.blank?
124
126
  if !@channels.include?(c)
125
127
  err :NOTONCHANNEL, opts[:target], ":Not a member of that channel"
126
- elsif opts[:message].nil?
128
+ elsif opts[:topic].blank?
127
129
  t = c.topic
128
130
  if t.blank?
129
131
  rpl :NOTOPIC, c.name, ":No topic is set"
@@ -131,7 +133,7 @@ module Marvin::IRC::Server::User::HandleMixin
131
133
  rpl :TOPIC, c.name, ":#{t}"
132
134
  end
133
135
  else
134
- c.topic self, opts[:message].strip
136
+ c.topic self, opts[:topic].strip
135
137
  end
136
138
  end
137
139
 
@@ -3,6 +3,7 @@ module Marvin::IRC::Server
3
3
  USER_MODES = "aAbBcCdDeEfFGhHiIjkKlLmMnNopPQrRsStUvVwWxXyYzZ0123459*@"
4
4
  CHANNEL_MODES = "bcdefFhiIklmnoPqstv"
5
5
  CHANNEL = /^[\&\#]+/
6
+
6
7
  include User::HandleMixin
7
8
 
8
9
  attr_accessor :nick, :host, :user, :prefix, :password, :mode,
@@ -29,9 +30,15 @@ module Marvin::IRC::Server
29
30
 
30
31
  # Notification messages
31
32
 
32
- def message(user, message)
33
- notify :PRIVMSG, @nick, ":#{message}", :prefix => user.prefix
34
- dispatch :outgoing_message, :user => user, :message => message, :target => self
33
+ def send_message(target, message)
34
+ t = target_from(target)
35
+ logger.debug "Sending #{t.inspect} #{message.inspect} from #{self.inspect}"
36
+ t.message(self, message) unless t.blank?
37
+ end
38
+
39
+ def message(user, message, virtual = false)
40
+ notify :PRIVMSG, @nick, ":#{message}", :prefix => user.prefix, :virtual => virtual
41
+ dispatch :outgoing_message, :user => user, :message => message, :target => self, :virtual => virtual
35
42
  end
36
43
 
37
44
  def notice(user, message)
@@ -93,7 +100,7 @@ module Marvin::IRC::Server
93
100
  end
94
101
 
95
102
  def update_prefix!
96
- @prefix = "#{@nick}!~#{@user}@#{peer_name}" if details_complete?
103
+ @prefix = "#{@nick}!n=#{@user}@#{peer_name}" if details_complete?
97
104
  end
98
105
 
99
106
  def details_complete?
@@ -0,0 +1,80 @@
1
+ module Marvin::IRC::Server
2
+ class VirtualUserConnection
3
+ include Marvin::Dispatchable
4
+
5
+ CHANNEL = /^[\&\#]+/
6
+
7
+ attr_accessor :nick, :channels
8
+
9
+ def initialize(nick)
10
+ self.nick = nick
11
+ self.channels = []
12
+ end
13
+
14
+ def prefix
15
+ "#{@nick}!n=rruser@relayrelay.com"
16
+ end
17
+
18
+ def notify(*args)
19
+ Marvin::Logger.debug "Virtual User #{self.nick} got notify w/ #{args.inspect}"
20
+ end
21
+
22
+ # Notify the remote handler we've received a message
23
+ def message(user, message, virtual = false)
24
+ dispatch :received_message, :user => user, :message => message, :target => self unless virtual
25
+ end
26
+
27
+ def send_message(target, message)
28
+ t = target_from(target)
29
+ Marvin::Logger.debug "DRb Message from #{self.nick} to #{t.inspect}: #{message.inspect}"
30
+ if t.blank?
31
+ Marvin::Logger.debug "Target not found"
32
+ return nil
33
+ else
34
+ Marvin::Logger.debug "Sending message to #{t.inspect}"
35
+ return t.message(self, message, true)
36
+ end
37
+ end
38
+
39
+ def reclaim!
40
+ self.channels.each { |c| c.part(self, "Reclaiming nick for virtual user...") }
41
+ Marvin::IRC::Server::UserStore.delete(self.nick.downcase)
42
+ end
43
+
44
+ def join(channel)
45
+ return nil if channel !~ CHANNEL
46
+ chan = (Marvin::IRC::Server::ChannelStore[channel.downcase] ||= Marvin::IRC::Server::Channel.new(channel))
47
+ if chan.join(self)
48
+ @channels << channel
49
+ return chan
50
+ end
51
+ end
52
+
53
+ class << self
54
+
55
+ def claimed?(nick)
56
+ Marvin::IRC::Server::UserStore.virtual?(nick)
57
+ end
58
+
59
+ def claim(nick)
60
+ unless Marvin::IRC::Server::UserStore[nick.downcase].blank?
61
+ return(Marvin::IRC::Server::UserStore[nick.downcase] ||= Marvin::IRC::Server::VirtualUserConnection.new(nick))
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ private
68
+
69
+ def target_from(target)
70
+ if target =~ CHANNEL
71
+ c = Marvin::IRC::Server::ChannelStore[target.downcase]
72
+ c = join(target) if c.nil? || !c.member?(self)
73
+ return c
74
+ else
75
+ return Marvin::IRC::Server::UserStore[target.downcase] ||= Marvin::IRC::Server::VirtualUserConnection.new(target)
76
+ end
77
+ end
78
+
79
+ end
80
+ end
data/script/client CHANGED
@@ -1,9 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
-
4
- $:.unshift(File.dirname(__FILE__) + "/../lib/") if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
5
- MARVIN_ROOT = File.join(File.dirname(__FILE__), "..")
6
-
7
- # And Require Marvin.
8
- require 'marvin'
2
+ require File.join(File.dirname(__FILE__), "..", "config", "boot")
9
3
  Marvin::Loader.run! :client
@@ -1,9 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
-
4
- $:.unshift(File.dirname(__FILE__) + "/../lib/") if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
5
- MARVIN_ROOT = File.join(File.dirname(__FILE__), "..")
6
-
7
- # And Require Marvin.
8
- require 'marvin'
2
+ require File.join(File.dirname(__FILE__), "..", "config", "boot")
9
3
  Marvin::Loader.run! :distributed_client
data/script/install CHANGED
@@ -1,3 +1 @@
1
- gem uninstall marvin --quiet
2
- gem build marvin.gemspec
3
- gem install marvin-0.1.*.gem
1
+ gem uninstall marvin --quiet && rake gemspec && gem build marvin.gemspec && gem install marvin-*.gem
data/script/ring_server CHANGED
@@ -1,12 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- #!/usr/bin/env ruby
4
- require 'rubygems'
5
-
6
- $:.unshift(File.dirname(__FILE__) + "/../lib/") if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
7
- MARVIN_ROOT = File.join(File.dirname(__FILE__), "..")
8
-
9
- # And Require Marvin.
10
- require 'marvin'
2
+ require File.join(File.dirname(__FILE__), "..", "config", "boot")
11
3
  Marvin::Loader.run! :ring_server
12
4
 
data/script/server CHANGED
@@ -1,12 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- #!/usr/bin/env ruby
4
- require 'rubygems'
5
-
6
- $:.unshift(File.dirname(__FILE__) + "/../lib/") if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
7
- MARVIN_ROOT = File.join(File.dirname(__FILE__), "..")
8
-
9
- # And Require Marvin.
10
- require 'marvin'
2
+ require File.join(File.dirname(__FILE__), "..", "config", "boot")
11
3
  Marvin::Loader.run! :server
12
4
 
data/script/status CHANGED
@@ -1,11 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- #!/usr/bin/env ruby
3
- require 'rubygems'
4
-
5
- $:.unshift(File.dirname(__FILE__) + "/../lib/") if File.exist?(File.dirname(__FILE__) + "/../lib/marvin.rb")
6
- MARVIN_ROOT = File.join(File.dirname(__FILE__), "..")
7
-
8
- # And Require Marvin.
9
- require 'marvin'
10
-
2
+ require File.join(File.dirname(__FILE__), "..", "config", "boot")
11
3
  Marvin::Status.show!
data/test/parser_test.rb CHANGED
@@ -6,6 +6,8 @@ require File.join(File.dirname(__FILE__), 'test_helper')
6
6
  # 3) The two different types of prefixes
7
7
  # 4) The Event class
8
8
  class ParserTest < Test::Unit::TestCase
9
+
10
+ # The default parser
9
11
  @@parser = Marvin::Parsers::SimpleParser
10
12
 
11
13
  context "When parsing a LIST" do
data/test/util_test.rb ADDED
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class UtilTest < Test::Unit::TestCase
4
+
5
+ context "Converting a string to a channel name" do
6
+
7
+ should "not convert it if it starts with #" do
8
+ assert_equal "#offrails", Marvin::Util.channel_name("#offrails")
9
+ end
10
+
11
+ should "append a # if not present" do
12
+ assert_equal "#offrails", Marvin::Util.channel_name("offrails")
13
+ end
14
+
15
+ should "also be available as chan" do
16
+ assert_equal "#offrails", Marvin::Util.chan("#offrails")
17
+ assert_equal "#offrails", Marvin::Util.chan("offrails")
18
+ end
19
+
20
+ end
21
+
22
+ context "Parsing arguments" do
23
+
24
+ should "parse 'a b c' as ['a', 'b', 'c']" do
25
+ assert_equal ['a', 'b', 'c'], Marvin::Util.arguments("a b c")
26
+ end
27
+
28
+ should "parse 'a b :c d' as ['a', 'b', 'c d']" do
29
+ assert_equal ['a', 'b', 'c d'], Marvin::Util.arguments("a b :c d")
30
+ end
31
+
32
+ should "parse 'a :b c :d e' as ['a', 'b c :d e']" do
33
+ assert_equal ['a', 'b c :d e'], Marvin::Util.arguments('a :b c :d e')
34
+ end
35
+
36
+ end
37
+
38
+ context "Preparing last parameters" do
39
+
40
+ should "prepend a :" do
41
+ assert_equal ':zomg ninjas', Marvin::Util.last_param('zomg ninjas')
42
+ assert_equal '::zomg ninjas', Marvin::Util.last_param(':zomg ninjas')
43
+ end
44
+
45
+ should "strip the input" do
46
+ assert_equal ':zomg ninjas', Marvin::Util.last_param(' zomg ninjas ')
47
+ end
48
+
49
+ should "be available as lp" do
50
+ assert_equal ':zomg ninjas', Marvin::Util.lp('zomg ninjas')
51
+ assert_equal '::zomg ninjas', Marvin::Util.lp(':zomg ninjas')
52
+ assert_equal ':zomg ninjas', Marvin::Util.lp(' zomg ninjas ')
53
+ end
54
+
55
+ end
56
+
57
+ end
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.2.2
4
+ version: 0.2.3
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: 2008-12-07 00:00:00 -08:00
12
+ date: 2009-01-03 00:00:00 -08:00
13
13
  default_executable: marvin
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -58,6 +58,7 @@ extra_rdoc_files: []
58
58
 
59
59
  files:
60
60
  - README.textile
61
+ - TUTORIAL.textile
61
62
  - VERSION.yml
62
63
  - bin/marvin
63
64
  - lib/marvin
@@ -87,10 +88,12 @@ files:
87
88
  - lib/marvin/irc/server/base_connection.rb
88
89
  - lib/marvin/irc/server/channel.rb
89
90
  - lib/marvin/irc/server/named_store.rb
91
+ - lib/marvin/irc/server/remote_interface.rb
90
92
  - lib/marvin/irc/server/user
91
93
  - lib/marvin/irc/server/user/handle_mixin.rb
92
94
  - lib/marvin/irc/server/user.rb
93
95
  - lib/marvin/irc/server/user_connection.rb
96
+ - lib/marvin/irc/server/virtual_user_connection.rb
94
97
  - lib/marvin/irc/server.rb
95
98
  - lib/marvin/irc.rb
96
99
  - lib/marvin/loader.rb
@@ -116,6 +119,7 @@ files:
116
119
  - test/parser_comparison.rb
117
120
  - test/parser_test.rb
118
121
  - test/test_helper.rb
122
+ - test/util_test.rb
119
123
  - spec/marvin
120
124
  - spec/marvin/abstract_client_test.rb
121
125
  - spec/spec_helper.rb
@@ -131,6 +135,7 @@ files:
131
135
  - handlers/logging_handler.rb
132
136
  - handlers/tweet_tweet.rb
133
137
  - config/setup.rb
138
+ - config/boot.rb
134
139
  - config/settings.yml.sample
135
140
  - config/connections.yml.sample
136
141
  has_rdoc: false