tyler-uppercut 0.5.0

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/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
+