Sutto-marvin 0.2.2 → 0.2.3

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