tyler-uppercut 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Tyler McMullen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,103 @@
1
+ h1. Uppercut
2
+
3
+
4
+ h2. Overview
5
+
6
+ Uppercut is a little DSL for writing agents and notifiers which you interact with via your Jabber client.
7
+
8
+
9
+ h2. Making an Agent
10
+
11
+ You could put together a very simple agent as follows:
12
+
13
+ <pre>
14
+ <code>
15
+ class BasicAgent < Uppercut::Agent
16
+ command 'date' do |c|
17
+ m.send `date`
18
+ end
19
+
20
+ command /^cat (.*)/ do |c,rest|
21
+ m.send File.read(rest)
22
+ end
23
+
24
+ command 'report' do |c|
25
+ m.send 'Hostname: ' + `hostname`
26
+ m.send 'Running as: ' + ENV['USER']
27
+ end
28
+ end
29
+ </code>
30
+ </pre>
31
+
32
+ With the above code, we've created an Agent template of sorts. It responds to three commands: {date cat report}.
33
+ The block which is passed to "command" should always have at least one paramater. The first parameter will
34
+ always be an Uppercut::Conversation object, which can be used to respond to the user who sent the input. When passing
35
+ a regular expression as the first parameter to "command", all of the _captures_ which the pattern match generates
36
+ will also be passed to the block. This can be seen in the "cat" example above. There is one capture and it is
37
+ passed in as _rest_.
38
+
39
+ Then to actually put the Agent to work...
40
+
41
+ <pre>
42
+ <code>
43
+ BasicAgent.new('user@server/resource','password').listen
44
+ </code>
45
+ </pre>
46
+
47
+ This creates a new BasicAgent instance and sends it _listen_ to make it start accepting messages.
48
+ Note that by default when an agent is listening, it ignores all errors. (In the future, I'll have it log them.)
49
+
50
+
51
+ h2. Making a Notifier
52
+
53
+ <pre>
54
+ <code>
55
+ class BasicNotifier < Uppercut::Notifier
56
+ notifier :error do |m,data|
57
+ m.to 'tbmcmullen@gmail.com'
58
+ m.send "Something in your app blew up: #{data}"
59
+ end
60
+ end
61
+ </code>
62
+ </pre>
63
+
64
+ So, we make a new Notifier class and call a _notifier_ block within it. This makes a notifier with the name :error, which sends a message to tbmcmullen@gmail.com when it fires.
65
+
66
+ <pre>
67
+ <code>
68
+ notifier = BasicNotifier.new('user@server/resource','password').connect
69
+
70
+ notifier.notify :error, 'Sprocket Error!'
71
+ </code>
72
+ </pre>
73
+
74
+ The purpose of a Notifier is just to make a multi-purpose event-driven notification system. You could use it notify yourself of errors in your Rails app, or ping you when a new comment arrives on your blog, or ... I dunno, something else.
75
+
76
+ I am very aware of how limited this system is at the moment. I have some big plans for Notifier, so just hang tight while I hack them out...
77
+
78
+ h2. Todo
79
+
80
+
81
+ h3. Security
82
+
83
+ If you intend to use this on an open Jabber network (read: Google Talk) take some precautions. Agent#new takes an optional list of JIDs that the agent will respond to.
84
+
85
+ <pre>
86
+ <code>
87
+ BasicAgent.new('user@server/res', 'pw', :roster => ['you@server'])
88
+ </code>
89
+ </pre>
90
+
91
+ The agent created with the above statement will not respond to messages or subscription requests from anyone other than 'you@server'.
92
+
93
+ h3. Features
94
+
95
+ Uppercut is currently very thin on features, as it's in its infancy. Here's a brief list of where I'm
96
+ currently intending to take the project:
97
+
98
+ * send files to and receive files from agents
99
+ * improving the way one writes executables (using the Daemons library is best for the moment)
100
+ * auto-updating of agents
101
+ * I swear I'm going to find a use for MUC
102
+ * allow agents to establish communications on their own, rather than being reactionary (think RSS updates)
103
+ * ... other stuff that sounds fun to code up ...
data/Rakefile ADDED
@@ -0,0 +1,71 @@
1
+ # Based on the merb-core Rakefile. Thanks.
2
+
3
+ require "rake"
4
+ require "rake/clean"
5
+ require "rake/gempackagetask"
6
+ require "rake/rdoctask"
7
+ require "spec/rake/spectask"
8
+ require "fileutils"
9
+
10
+ require File.dirname(__FILE__) + "/lib/uppercut"
11
+
12
+ include FileUtils
13
+
14
+ NAME = "uppercut"
15
+
16
+ ##############################################################################
17
+ # Packaging & Installation
18
+ ##############################################################################
19
+ CLEAN.include ["**/.*.sw?", "pkg", "lib/*.bundle", "*.gem", "doc/rdoc", ".config", "coverage", "cache"]
20
+
21
+ windows = (PLATFORM =~ /win32|cygwin/) rescue nil
22
+ install_home = ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : ""
23
+
24
+ SUDO = windows ? "" : "sudo"
25
+
26
+ desc "Packages Uppercut."
27
+ task :default => :package
28
+
29
+ task :uppercut => [:clean, :rdoc, :package]
30
+
31
+ spec = eval(File.read(File.join(File.dirname(__FILE__), 'uppercut.gemspec')))
32
+
33
+ Rake::GemPackageTask.new(spec) do |package|
34
+ package.gem_spec = spec
35
+ end
36
+
37
+ desc "Run :package and install the resulting .gem"
38
+ task :install => :package do
39
+ sh %{#{SUDO} gem install #{install_home} --local pkg/#{NAME}-#{Uppercut::VERSION}.gem --no-rdoc --no-ri}
40
+ end
41
+
42
+ desc "Run :clean and uninstall the .gem"
43
+ task :uninstall => :clean do
44
+ sh %{#{SUDO} gem uninstall #{NAME}}
45
+ end
46
+
47
+ ##############################################################################
48
+ # Documentation
49
+ ##############################################################################
50
+ task :doc => [:rdoc]
51
+ namespace :doc do
52
+
53
+ Rake::RDocTask.new do |rdoc|
54
+ files = ["README", "LICENSE", "CHANGELOG", "lib/**/*.rb"]
55
+ rdoc.rdoc_files.add(files)
56
+ rdoc.main = "README"
57
+ rdoc.title = "Uppercut Docs"
58
+ rdoc.rdoc_dir = "doc/rdoc"
59
+ rdoc.options << "--line-numbers" << "--inline-source"
60
+ end
61
+
62
+ end
63
+
64
+
65
+ ##############################################################################
66
+ # Specs
67
+ ##############################################################################
68
+ desc "Run all specs"
69
+ Spec::Rake::SpecTask.new('specs') do |t|
70
+ t.spec_files = FileList['specs/*_spec.rb']
71
+ end
@@ -0,0 +1,21 @@
1
+ class BasicAgent < Uppercut::Agent
2
+ command 'date' do |m|
3
+ m.send `date`
4
+ end
5
+
6
+ command /^cat (.*)/ do |m,rest|
7
+ m.send File.read(rest)
8
+ end
9
+
10
+ command 'report' do |m|
11
+ m.send 'Hostname: ' + `hostname`
12
+ m.send 'Running as: ' + ENV['USER']
13
+ end
14
+
15
+ command 'dangerous' do |c|
16
+ c.send "Are you sure?!"
17
+ c.wait_for do |reply|
18
+ c.send %w(yes y).include?(reply.downcase) ? "Okay! Done boss!" : "Cancelled!"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,145 @@
1
+ class Uppercut
2
+ class Agent < Base
3
+ class << self
4
+ # Define a new command for the agent.
5
+ #
6
+ # The pattern can be a String or a Regexp. If a String is passed, it
7
+ # will dispatch this command only on an exact match. A Regexp simply
8
+ # must match.
9
+ #
10
+ # There is always at least one argument sent to the block. The first
11
+ # is a always an Uppercut::Message object, which can be used to reply
12
+ # to the sender. The rest of the arguments to the block correspond to
13
+ # any captures in the pattern Regexp. (Does not apply to String
14
+ # patterns).
15
+ def command(pattern,&block)
16
+ define_method(gensym) do |msg|
17
+ return :no_match unless captures = matches?(pattern,msg.body)
18
+ block[Conversation.new(msg.from,self),*captures]
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def gensym
25
+ '__uc' + (self.instance_methods.grep(/^__uc/).size).to_s.rjust(8,'0')
26
+ end
27
+ end
28
+
29
+ DEFAULT_OPTIONS = { :connect => true }
30
+
31
+ # Create a new instance of an Agent, possibly connecting to the server.
32
+ #
33
+ # user should be a String in the form: "user@server/Resource". pw is
34
+ # simply the password for this account. The final, and optional, argument
35
+ # is a boolean which controls whether or not it will attempt to connect to
36
+ # the server immediately. Defaults to true.
37
+ def initialize(user,pw,options={})
38
+ options = DEFAULT_OPTIONS.merge(options)
39
+
40
+ @user = user
41
+ @pw = pw
42
+ connect if options[:connect]
43
+ listen if options[:listen]
44
+
45
+ @allowed_roster = options[:roster]
46
+ @redirects = {}
47
+ end
48
+
49
+
50
+ def inspect #:nodoc:
51
+ "<Uppercut::Agent #{@user} " +
52
+ "#{listening? ? 'Listening' : 'Not Listening'}:" +
53
+ "#{connected? ? 'Connected' : 'Disconnected'}>"
54
+ end
55
+
56
+ # Makes an Agent instance begin listening for incoming messages and
57
+ # subscription requests.
58
+ #
59
+ # Current listen simply eats any errors that occur, in the interest of
60
+ # keeping the remote agent alive. These should be logged at some point
61
+ # in the future. Pass debug as true to prevent this behaviour.
62
+ #
63
+ # Calling listen fires off a new Thread whose sole purpose is to listen
64
+ # for new incoming messages and then fire off a new Thread which dispatches
65
+ # the message to the proper handler.
66
+ def listen(debug=false)
67
+ connect unless connected?
68
+
69
+ @listen_thread = Thread.new {
70
+ @client.add_message_callback do |message|
71
+ next if message.body.nil?
72
+ next unless allowed_roster_includes?(message.from)
73
+
74
+ Thread.new do
75
+ begin
76
+ dispatch(message)
77
+ rescue => e
78
+ log e
79
+ raise if debug
80
+ end
81
+ end
82
+ end
83
+ @roster ||= Jabber::Roster::Helper.new(@client)
84
+ @roster.add_subscription_request_callback do |item,presence|
85
+ next unless allowed_roster_includes?(presence.from)
86
+ @roster.accept_subscription(presence.from)
87
+ end
88
+ sleep
89
+ }
90
+ end
91
+
92
+ # Stops the Agent from listening to incoming messages.
93
+ #
94
+ # Simply kills the thread if it is running.
95
+ def stop
96
+ @listen_thread.kill if listening?
97
+ end
98
+
99
+ # True if the Agent is currently listening for incoming messages.
100
+ def listening?
101
+ @listen_thread && @listen_thread.alive?
102
+ end
103
+
104
+ def redirect_from(contact,&block)
105
+ @redirects[contact] ||= []
106
+ @redirects[contact].push block
107
+ end
108
+
109
+ attr_accessor :allowed_roster
110
+
111
+ private
112
+
113
+ def dispatch(msg)
114
+ block = @redirects[msg.from].respond_to?(:shift) && @redirects[msg.from].shift
115
+ return block[msg.body] if block
116
+
117
+ self.methods.grep(/^__uc/).sort.detect { |m| send(m,msg) != :no_match }
118
+ end
119
+
120
+ def __ucDefault(msg)
121
+ Message.new(msg.from,self).send("I don't know what \"#{msg.body}\" means.")
122
+ end
123
+
124
+ def matches?(pattern,msg)
125
+ captures = nil
126
+ case pattern
127
+ when String
128
+ captures = [] if pattern == msg
129
+ when Regexp
130
+ match_data = pattern.match(msg)
131
+ captures = match_data.captures if match_data
132
+ end
133
+ captures
134
+ end
135
+
136
+ def allowed_roster_includes?(jid)
137
+ return true unless @allowed_roster
138
+
139
+ jid = jid.to_s
140
+ return true if @allowed_roster.include?(jid)
141
+ return true if @allowed_roster.include?(jid.sub(/\/[^\/]+$/,''))
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,90 @@
1
+ class Uppercut
2
+ class Base
3
+ def stanza(msg) #:nodoc:
4
+ return false unless connected?
5
+ send! msg
6
+ end
7
+
8
+ # Attempt to connect to the server, if not already connected.
9
+ #
10
+ # Raises a simple RuntimeError if it fails to connect. This should be
11
+ # changed eventually to be more useful.
12
+ def connect
13
+ return if connected?
14
+ connect!
15
+ raise 'Failed to connected' unless connected?
16
+ present!
17
+ end
18
+
19
+ # Disconnects from the server if it is connected.
20
+ def disconnect
21
+ disconnect! if connected?
22
+ end
23
+
24
+ # Disconnects and connects to the server.
25
+ def reconnect
26
+ disconnect
27
+ connect
28
+ end
29
+
30
+ attr_reader :client, :roster
31
+
32
+ # True if the Agent is currently connected to the Jabber server.
33
+ def connected?
34
+ @client.respond_to?(:is_connected?) && @client.is_connected?
35
+ end
36
+
37
+ private
38
+
39
+ def connect!
40
+ @connect_lock ||= Mutex.new
41
+ return if @connect_lock.locked?
42
+
43
+ client = Jabber::Client.new(@user)
44
+
45
+ @connect_lock.lock
46
+
47
+ client.connect
48
+ client.auth(@pw)
49
+ @client = client
50
+
51
+ @connect_lock.unlock
52
+ end
53
+
54
+ def disconnect!
55
+ @client.close if connected?
56
+ @client = nil
57
+ end
58
+
59
+ def present!
60
+ send! Jabber::Presence.new(nil,"Available")
61
+ end
62
+
63
+ # Taken directly from xmpp4r-simple (thanks Blaine!)
64
+ def send!(msg)
65
+ attempts = 0
66
+ begin
67
+ attempts += 1
68
+ @client.send(msg)
69
+ rescue Errno::EPIPE, IOError => e
70
+ sleep 1
71
+ disconnect!
72
+ connect!
73
+ retry unless attempts > 3
74
+ raise e
75
+ rescue Errno::ECONNRESET => e
76
+ sleep (attempts^2) * 60 + 60
77
+ disconnect!
78
+ connect!
79
+ retry unless attempts > 3
80
+ raise e
81
+ end
82
+ end
83
+
84
+ def log(error)
85
+ # todo
86
+ p error
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ class Uppercut
2
+ class Conversation < Message
3
+ attr_reader :to
4
+
5
+ def initialize(to,base) #:nodoc:
6
+ @to = to
7
+ super base
8
+ end
9
+
10
+ # Wait for another message from this contact.
11
+ #
12
+ # Expects a block which should receive one parameter, which will be a
13
+ # String.
14
+ #
15
+ # One common use of _wait_for_ is for confirmation of a sensitive action.
16
+ #
17
+ # command('foo') do |c|
18
+ # c.send 'Are you sure?'
19
+ # c.wait_for do |reply|
20
+ # do_it if reply.downcase == 'yes'
21
+ # end
22
+ # end
23
+ def wait_for(&block)
24
+ @base.redirect_from(@to,&block)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ class Uppercut
2
+ class Message
3
+ attr_accessor :to, :message
4
+
5
+ def initialize(base) #:nodoc:
6
+ @base = base
7
+ end
8
+
9
+ # Send a blob of text.
10
+ def send(body=nil)
11
+ msg = Jabber::Message.new(@to)
12
+ msg.type = :chat
13
+ msg.body = body || @message
14
+ @base.stanza(msg)
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,32 @@
1
+ class Uppercut
2
+ class Notifier < Base
3
+ class << self
4
+ @@notifiers = {}
5
+
6
+ def notifier(name,&block)
7
+ @@notifiers[name] = block
8
+ end
9
+ end
10
+
11
+ def notify(name,data=nil)
12
+ return false unless connected?
13
+ @@notifiers[name].call(Message.new(self),data)
14
+ end
15
+
16
+ def initialize(user,pw,options={})
17
+ options = DEFAULT_OPTIONS.merge(options)
18
+
19
+ @user = user
20
+ @pw = pw
21
+ connect if options[:connect]
22
+ end
23
+
24
+ DEFAULT_OPTIONS = { :connect => true }
25
+
26
+ def inspect #:nodoc:
27
+ "<Uppercut::Notifier #{@user} " +
28
+ "#{connected? ? 'Connected' : 'Disconnected'}>"
29
+ end
30
+
31
+ end
32
+ end
data/lib/uppercut.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'xmpp4r'
3
+ require 'xmpp4r/roster'
4
+
5
+ %w(base agent notifier message conversation).each do |mod|
6
+ require File.join(File.dirname(__FILE__), "uppercut/#{mod}")
7
+ end
8
+
@@ -0,0 +1,176 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Uppercut::Agent do
4
+ before :each do
5
+ @agent = TestAgent.new('test@foo.com', 'pw', :connect => false)
6
+ end
7
+
8
+ describe :new do
9
+ it "connects by default" do
10
+ agent = Uppercut::Agent.new('test@foo','pw')
11
+ agent.should be_connected
12
+ end
13
+
14
+ it "does not connect by default with :connect = false" do
15
+ agent = Uppercut::Agent.new('test@foo','pw', :connect => false)
16
+ agent.should_not be_connected
17
+ end
18
+
19
+ it "starts to listen with :listen = true" do
20
+ agent = Uppercut::Agent.new('test@foo','pw', :listen => true)
21
+ agent.should be_listening
22
+ end
23
+
24
+ it "initializes @redirects with a blank hash" do
25
+ agent = Uppercut::Agent.new('test@foo','pw', :connect => false)
26
+ agent.instance_eval { @redirects }.should == {}
27
+ end
28
+
29
+ it "populates @pw and @user" do
30
+ agent = Uppercut::Agent.new('test@foo','pw')
31
+ agent.instance_eval { @pw }.should == 'pw'
32
+ agent.instance_eval { @user }.should == 'test@foo'
33
+ end
34
+
35
+ it "populates @allowed_roster with :roster option" do
36
+ jids = %w(bob@foo fred@foo)
37
+ agent = Uppercut::Agent.new('test@foo','pw', :roster => jids)
38
+ agent.instance_eval { @allowed_roster }.should == jids
39
+ end
40
+ end
41
+
42
+ describe :connect do
43
+ it "does not try to connect if already connected" do
44
+ @agent.connect
45
+ old_client = @agent.client
46
+
47
+ @agent.connect
48
+ (@agent.client == old_client).should == true
49
+ end
50
+
51
+ it "connects if disconnected" do
52
+ @agent.should_not be_connected
53
+
54
+ old_client = @agent.client
55
+
56
+ @agent.connect
57
+ (@agent.client == old_client).should_not == true
58
+ end
59
+
60
+ it "sends a Presence notification" do
61
+ @agent.connect
62
+ @agent.client.sent.first.class.should == Jabber::Presence
63
+ end
64
+ end
65
+
66
+ describe :disconnect do
67
+ it "does not try to disconnect if not connected" do
68
+ @agent.client.should be_nil
69
+ @agent.instance_eval { @client = :foo }
70
+
71
+ @agent.disconnect
72
+ @agent.client.should == :foo
73
+ end
74
+
75
+ it "sets @client to nil" do
76
+ @agent.connect
77
+ @agent.client.should_not be_nil
78
+
79
+ @agent.disconnect
80
+ @agent.client.should be_nil
81
+ end
82
+ end
83
+
84
+ describe :reconnect do
85
+ it "calls disconnect then connect" do
86
+ @agent.should_receive(:disconnect).once.ordered
87
+ @agent.should_receive(:connect).once.ordered
88
+
89
+ @agent.reconnect
90
+ end
91
+ end
92
+
93
+ describe :connected? do
94
+ it "returns true if client#is_connected? is true" do
95
+ @agent.connect
96
+ @agent.client.instance_eval { @connected = true }
97
+ @agent.should be_connected
98
+ end
99
+ end
100
+
101
+ describe :listen do
102
+ it "connects if not connected" do
103
+ @agent.listen
104
+ @agent.should be_connected
105
+ end
106
+
107
+ it "spins off a new thread in @listen_thread" do
108
+ @agent.listen
109
+ @agent.instance_eval { @listen_thread.class }.should == Thread
110
+ end
111
+
112
+ it "creates a receive message callback" do
113
+ @agent.listen
114
+ @agent.client.on_message.class.should == Proc
115
+ end
116
+
117
+ it "creates a subscription request callback" do
118
+ @agent.listen
119
+ @agent.roster.on_subscription_request.class.should == Proc
120
+ end
121
+
122
+ it "calls dispatch when receving a message" do
123
+ @agent.listen
124
+ @agent.should_receive(:dispatch)
125
+ @agent.client.receive_message("foo@bar.com","test")
126
+ end
127
+ end
128
+
129
+ describe :stop do
130
+ it "kills the @listen_thread" do
131
+ @agent.listen
132
+ @agent.instance_eval { @listen_thread.alive? }.should == true
133
+
134
+ @agent.stop
135
+ @agent.instance_eval { @listen_thread.alive? }.should_not == true
136
+ end
137
+ end
138
+
139
+ describe :listening? do
140
+ it "returns true if @listen_thread is alive" do
141
+ @agent.listen
142
+ @agent.instance_eval { @listen_thread.alive? }.should == true
143
+ @agent.should be_listening
144
+ end
145
+
146
+ it "returns false if @listen_thread is not alive" do
147
+ @agent.listen
148
+ @agent.stop
149
+ @agent.should_not be_listening
150
+ end
151
+
152
+ it "returns false if @listen_thread has not been set" do
153
+ @agent.should_not be_listening
154
+ end
155
+ end
156
+
157
+ describe :dispatch do
158
+ it "calls the first matching command" do
159
+ msg = Jabber::Message.new(nil)
160
+ msg.body = 'hi'
161
+
162
+ @agent.send(:dispatch, msg)
163
+ @agent.instance_eval { @called_hi_regex }.should_not == true
164
+ @agent.instance_eval { @called_hi }.should == true
165
+ end
166
+
167
+ it "matches by regular expression" do
168
+ msg = Jabber::Message.new(nil)
169
+ msg.body = 'high'
170
+
171
+ @agent.send(:dispatch, msg)
172
+ @agent.instance_eval { @called_hi }.should_not == true
173
+ @agent.instance_eval { @called_hi_regex }.should == true
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,14 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+ describe Uppercut::Conversation do
5
+ before(:each) do
6
+ @conv = Uppercut::Conversation.new('test@foo.com', nil)
7
+ end
8
+
9
+ describe :contact do
10
+ it "should have a contact method" do
11
+ @conv.should.respond_to?(:contact)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,76 @@
1
+ Object.send(:remove_const, :Jabber)
2
+ class Jabber
3
+ class Client
4
+ def initialize(user)
5
+ @user = user
6
+ end
7
+
8
+ def connect
9
+ @connected = true
10
+ end
11
+
12
+ def auth(pw)
13
+ @pw = pw
14
+ end
15
+
16
+ def is_connected?
17
+ @connected
18
+ end
19
+
20
+ def close
21
+ @connected = nil
22
+ end
23
+
24
+ attr_reader :sent
25
+ def send(msg)
26
+ @sent ||= []
27
+ @sent << msg
28
+ end
29
+
30
+ attr_reader :on_message
31
+ def add_message_callback(&block)
32
+ @on_message = block
33
+ end
34
+
35
+ # TESTING HELPER METHODS
36
+
37
+ def receive_message(from,body,type=:chat)
38
+ msg = Message.new(nil)
39
+ msg.type = type
40
+ msg.body = body
41
+ msg.from = from
42
+ @on_message[msg]
43
+ end
44
+ end
45
+
46
+ class Presence
47
+ def initialize(a,b)
48
+ end
49
+
50
+ def from
51
+ end
52
+ end
53
+
54
+ class Message
55
+ attr_accessor :type, :body, :from
56
+ def initialize(to)
57
+ @to = to
58
+ end
59
+ end
60
+
61
+ class Roster
62
+ class Helper
63
+ def initialize(client)
64
+ @client = client
65
+ end
66
+
67
+ def accept_subscription(a)
68
+ end
69
+
70
+ attr_reader :on_subscription_request
71
+ def add_subscription_request_callback(&block)
72
+ @on_subscription_request = block
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,85 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Uppercut::Notifier do
4
+ before :each do
5
+ @notifier = TestNotifier.new('test@foo.com', 'pw', :connect => false)
6
+ end
7
+
8
+ describe :new do
9
+ it "connects by default" do
10
+ notifier = Uppercut::Notifier.new('test@foo','pw')
11
+ notifier.should be_connected
12
+ end
13
+
14
+ it "does not connect by default with :connect = false" do
15
+ notifier = Uppercut::Notifier.new('test@foo','pw', :connect => false)
16
+ notifier.should_not be_connected
17
+ end
18
+
19
+ it "populates @pw and @user" do
20
+ notifier = Uppercut::Notifier.new('test@foo','pw')
21
+ notifier.instance_eval { @pw }.should == 'pw'
22
+ notifier.instance_eval { @user }.should == 'test@foo'
23
+ end
24
+ end
25
+
26
+ describe :connect do
27
+ it "does not try to connect if already connected" do
28
+ @notifier.connect
29
+ old_client = @notifier.client
30
+
31
+ @notifier.connect
32
+ (@notifier.client == old_client).should == true
33
+ end
34
+
35
+ it "connects if disconnected" do
36
+ @notifier.should_not be_connected
37
+
38
+ old_client = @notifier.client
39
+
40
+ @notifier.connect
41
+ (@notifier.client == old_client).should_not == true
42
+ end
43
+
44
+ it "sends a Presence notification" do
45
+ @notifier.connect
46
+ @notifier.client.sent.first.class.should == Jabber::Presence
47
+ end
48
+ end
49
+
50
+ describe :disconnect do
51
+ it "does not try to disconnect if not connected" do
52
+ @notifier.client.should be_nil
53
+ @notifier.instance_eval { @client = :foo }
54
+
55
+ @notifier.disconnect
56
+ @notifier.client.should == :foo
57
+ end
58
+
59
+ it "sets @client to nil" do
60
+ @notifier.connect
61
+ @notifier.client.should_not be_nil
62
+
63
+ @notifier.disconnect
64
+ @notifier.client.should be_nil
65
+ end
66
+ end
67
+
68
+ describe :reconnect do
69
+ it "calls disconnect then connect" do
70
+ @notifier.should_receive(:disconnect).once.ordered
71
+ @notifier.should_receive(:connect).once.ordered
72
+
73
+ @notifier.reconnect
74
+ end
75
+ end
76
+
77
+ describe :connected? do
78
+ it "returns true if client#is_connected? is true" do
79
+ @notifier.connect
80
+ @notifier.client.instance_eval { @connected = true }
81
+ @notifier.should be_connected
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'set'
4
+
5
+ $: << File.dirname(__FILE__)
6
+ $: << File.join(File.dirname(__FILE__),'../lib')
7
+
8
+ # Loads uppercut and jabber
9
+ require 'uppercut'
10
+
11
+ # Unloads jabber, replacing it with a stub
12
+ require 'jabber_stub'
13
+
14
+ class TestAgent < Uppercut::Agent
15
+ command 'hi' do |c|
16
+ c.instance_eval { @base.instance_eval { @called_hi = true } }
17
+ c.send 'called hi'
18
+ end
19
+
20
+ command /^hi/ do |c|
21
+ c.instance_eval { @base.instance_eval { @called_hi_regex = true } }
22
+ c.send 'called high regex'
23
+ end
24
+
25
+ command /(good)?bye/ do |c,good|
26
+ @called_goodbye = true
27
+ c.send good ? "Good bye to you as well!" : "Rot!"
28
+ end
29
+
30
+ command 'wait' do |c|
31
+ @called_wait = true
32
+ c.send 'Waiting...'
33
+ c.wait_for do |reply|
34
+ @called_wait_block = true
35
+ c.send 'Hooray!'
36
+ end
37
+ end
38
+ end
39
+
40
+ class TestNotifier < Uppercut::Notifier
41
+ notifier :foo do |m,data|
42
+ m.to = 'foo@bar.com'
43
+ m.send 'Foo happened!'
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tyler-uppercut
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Tyler McMullen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: xmpp4r
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: Uppercut. DSL for putting Jabber to work for you.
25
+ email: tbmcmullen@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - LICENSE
34
+ - README.textile
35
+ - Rakefile
36
+ - lib/uppercut.rb
37
+ - lib/uppercut/agent.rb
38
+ - lib/uppercut/base.rb
39
+ - lib/uppercut/conversation.rb
40
+ - lib/uppercut/message.rb
41
+ - lib/uppercut/notifier.rb
42
+ - specs/agent_spec.rb
43
+ - specs/conversation_spec.rb
44
+ - specs/jabber_stub.rb
45
+ - specs/notifier_spec.rb
46
+ - specs/spec_helper.rb
47
+ - examples/basic_agent.rb
48
+ has_rdoc: false
49
+ homepage: http://codehallow.com
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Uppercut. DSL for putting Jabber to work for you.
74
+ test_files: []
75
+