tyler-uppercut 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,125 @@
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
+ The way I intend a Notifier to be used though is with Starling. Take the following example...
77
+
78
+ <pre>
79
+ <code>
80
+ notifier = BasicNotifier.new('user@server','password', :starling => 'localhost:22122', :queue => 'basic')
81
+ notifier.listen
82
+ sleep
83
+ </code>
84
+ </pre>
85
+
86
+ <pre>
87
+ <code>
88
+ require 'starling'
89
+ starling = Starling.new('localhost:22122')
90
+ starling.set 'basic', :error
91
+ </code>
92
+ </pre>
93
+
94
+ In that example, we have the same BasicNotifier. Except this time, we provide it with information on which Starling queue on which Starling server to monitor. Meanwhile, in the separate program, we attach to that same Starling server and push a symbol onto that same queue.
95
+
96
+ In this case, what happens is BasicNotifier sees that symbol and begins processing it, just as if it had been passed in via the #notify method.
97
+
98
+
99
+
100
+ h2. Todo
101
+
102
+
103
+ h3. Security
104
+
105
+ 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.
106
+
107
+ <pre>
108
+ <code>
109
+ BasicAgent.new('user@server/res', 'pw', :roster => ['you@server'])
110
+ </code>
111
+ </pre>
112
+
113
+ The agent created with the above statement will not respond to messages or subscription requests from anyone other than 'you@server'.
114
+
115
+ h3. Features
116
+
117
+ Uppercut is currently very thin on features, as it's in its infancy. Here's a brief list of where I'm
118
+ currently intending to take the project:
119
+
120
+ * send files to and receive files from agents
121
+ * improving the way one writes executables (using the Daemons library is best for the moment)
122
+ * auto-updating of agents
123
+ * I swear I'm going to find a use for MUC
124
+ * allow agents to establish communications on their own, rather than being reactionary (think RSS updates)
125
+ * ... other stuff that sounds fun to code up ...
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ patch: 2
3
+ major: 0
4
+ minor: 6
@@ -0,0 +1,171 @@
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
+ # Define a callback for specific presence events.
23
+ #
24
+ # At the moment this is only confirmed to work with :subscribe and :unsubscribe, but it may work with other types as well.
25
+ # Example:
26
+ #
27
+ # on :subscribe do |conversation|
28
+ # conversation.send "Welcome! Send 'help' for instructions."
29
+ # end
30
+ #
31
+ def on(type, &block)
32
+ define_method("__on_#{type.to_s}__") { |conversation| block[conversation] }
33
+ end
34
+
35
+ private
36
+
37
+ def gensym
38
+ '__uc' + (self.instance_methods.grep(/^__uc/).size).to_s.rjust(8,'0')
39
+ end
40
+ end
41
+
42
+ DEFAULT_OPTIONS = { :connect => true }
43
+
44
+ # Create a new instance of an Agent, possibly connecting to the server.
45
+ #
46
+ # user should be a String in the form: "user@server/Resource". pw is
47
+ # simply the password for this account. The final, and optional, argument
48
+ # is a boolean which controls whether or not it will attempt to connect to
49
+ # the server immediately. Defaults to true.
50
+ def initialize(user,pw,options={})
51
+ options = DEFAULT_OPTIONS.merge(options)
52
+
53
+ @user = user
54
+ @pw = pw
55
+ connect if options[:connect]
56
+ listen if options[:listen]
57
+
58
+ @allowed_roster = options[:roster]
59
+ @redirects = {}
60
+ end
61
+
62
+
63
+ def inspect #:nodoc:
64
+ "<Uppercut::Agent #{@user} " +
65
+ "#{listening? ? 'Listening' : 'Not Listening'}:" +
66
+ "#{connected? ? 'Connected' : 'Disconnected'}>"
67
+ end
68
+
69
+ # Makes an Agent instance begin listening for incoming messages and
70
+ # subscription requests.
71
+ #
72
+ # Current listen simply eats any errors that occur, in the interest of
73
+ # keeping the remote agent alive. These should be logged at some point
74
+ # in the future. Pass debug as true to prevent this behaviour.
75
+ #
76
+ # Calling listen fires off a new Thread whose sole purpose is to listen
77
+ # for new incoming messages and then fire off a new Thread which dispatches
78
+ # the message to the proper handler.
79
+ def listen(debug=false)
80
+ connect unless connected?
81
+
82
+ @listen_thread = Thread.new {
83
+ @client.add_message_callback do |message|
84
+ next if message.body.nil?
85
+ next unless allowed_roster_includes?(message.from)
86
+
87
+ Thread.new do
88
+ begin
89
+ dispatch(message)
90
+ rescue => e
91
+ log e
92
+ raise if debug
93
+ end
94
+ end
95
+ end
96
+ @roster ||= Jabber::Roster::Helper.new(@client)
97
+ @roster.add_presence_callback do |item, oldp, newp|
98
+ dispatch_presence(item, newp)
99
+ end
100
+ @roster.add_subscription_request_callback do |item,presence|
101
+ next unless allowed_roster_includes?(presence.from)
102
+ @roster.accept_subscription(presence.from)
103
+ dispatch_presence(item, presence)
104
+ end
105
+ @roster.add_subscription_callback do |item, presence|
106
+ dispatch_presence(item, presence)
107
+ end
108
+ sleep
109
+ }
110
+ end
111
+
112
+ # Stops the Agent from listening to incoming messages.
113
+ #
114
+ # Simply kills the thread if it is running.
115
+ def stop
116
+ @listen_thread.kill if listening?
117
+ end
118
+
119
+ # True if the Agent is currently listening for incoming messages.
120
+ def listening?
121
+ @listen_thread && @listen_thread.alive?
122
+ end
123
+
124
+ def redirect_from(contact,&block)
125
+ @redirects[contact] ||= []
126
+ @redirects[contact].push block
127
+ end
128
+
129
+ attr_accessor :allowed_roster
130
+
131
+ private
132
+
133
+ def dispatch(msg)
134
+ bare_from = msg.from.bare
135
+ block = @redirects[bare_from].respond_to?(:shift) && @redirects[bare_from].shift
136
+ return block[msg.body] if block
137
+
138
+ self.methods.grep(/^__uc/).sort.detect { |m| send(m,msg) != :no_match }
139
+ end
140
+
141
+ def dispatch_presence(item, presence)
142
+ handler_method = "__on_#{presence.type.to_s}__"
143
+ self.send(handler_method, Conversation.new(presence.from, self)) if respond_to?(handler_method)
144
+ end
145
+
146
+ def __ucDefault(msg)
147
+ Message.new(msg.from,self).send("I don't know what \"#{msg.body}\" means.")
148
+ end
149
+
150
+ def matches?(pattern,msg)
151
+ captures = nil
152
+ case pattern
153
+ when String
154
+ captures = [] if pattern == msg
155
+ when Regexp
156
+ match_data = pattern.match(msg)
157
+ captures = match_data.captures if match_data
158
+ end
159
+ captures
160
+ end
161
+
162
+ def allowed_roster_includes?(jid)
163
+ return true unless @allowed_roster
164
+
165
+ jid = jid.to_s
166
+ return true if @allowed_roster.include?(jid)
167
+ return true if @allowed_roster.include?(jid.sub(/\/[^\/]+$/,''))
168
+ end
169
+
170
+ end
171
+ 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.bare,&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,64 @@
1
+ class Uppercut
2
+ class Notifier < Base
3
+ class << self
4
+ @@notifiers = []
5
+
6
+ def notifier(name,&block)
7
+ @@notifiers << name
8
+ define_method(name, &block)
9
+ end
10
+ end
11
+
12
+ def notify(name,data=nil)
13
+ return false unless connected?
14
+ return nil unless @@notifiers.include?(name)
15
+
16
+ send(name,Message.new(self),data)
17
+ end
18
+
19
+ def initialize(user,pw,options={})
20
+ options = DEFAULT_OPTIONS.merge(options)
21
+
22
+ initialize_queue options[:starling], options[:queue]
23
+
24
+ @user = user
25
+ @pw = pw
26
+ connect if options[:connect]
27
+ listen if options[:listen]
28
+ end
29
+
30
+ DEFAULT_OPTIONS = { :connect => true }
31
+
32
+ def listen
33
+ connect unless connected?
34
+
35
+ @listen_thread = Thread.new {
36
+ loop { notify @starling.get(@queue) }
37
+ }
38
+ end
39
+
40
+ def stop
41
+ @listen_thread.kill if listening?
42
+ end
43
+
44
+ def listening?
45
+ @listen_thread && @listen_thread.alive?
46
+ end
47
+
48
+ def inspect #:nodoc:
49
+ "<Uppercut::Notifier #{@user} " +
50
+ "#{listening? ? 'Listening' : 'Not Listening'} " +
51
+ "#{connected? ? 'Connected' : 'Disconnected'}>"
52
+ end
53
+
54
+ private
55
+
56
+ def initialize_queue(server,queue)
57
+ return unless queue && server
58
+ require 'starling'
59
+ @queue = queue
60
+ @starling = Starling.new(server)
61
+ end
62
+
63
+ end
64
+ 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,178 @@
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
+ msg.from = Jabber::JID.fake_jid
162
+
163
+ @agent.send(:dispatch, msg)
164
+ @agent.instance_eval { @called_hi_regex }.should_not == true
165
+ @agent.instance_eval { @called_hi }.should == true
166
+ end
167
+
168
+ it "matches by regular expression" do
169
+ msg = Jabber::Message.new(nil)
170
+ msg.body = 'high'
171
+ msg.from = Jabber::JID.fake_jid
172
+
173
+ @agent.send(:dispatch, msg)
174
+ @agent.instance_eval { @called_hi }.should_not == true
175
+ @agent.instance_eval { @called_hi_regex }.should == true
176
+ end
177
+ end
178
+ 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,103 @@
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
+
36
+
37
+ # TESTING HELPER METHODS
38
+
39
+ def receive_message(from,body,type=:chat)
40
+ msg = Message.new(nil)
41
+ msg.type = type
42
+ msg.body = body
43
+ msg.from = from
44
+ @on_message[msg]
45
+ end
46
+ end
47
+
48
+ class Presence
49
+ attr_accessor :from
50
+
51
+ def initialize(a,b)
52
+ end
53
+
54
+ def from
55
+ end
56
+ end
57
+
58
+ class Message
59
+ attr_accessor :type, :body, :from
60
+ def initialize(to)
61
+ @to = to
62
+ end
63
+ end
64
+
65
+ class Roster
66
+ class Helper
67
+ def initialize(client)
68
+ @client = client
69
+ end
70
+
71
+ def accept_subscription(a)
72
+ end
73
+
74
+ attr_reader :on_subscription_request
75
+ def add_subscription_request_callback(&block)
76
+ @on_subscription_request = block
77
+ end
78
+
79
+ def add_presence_callback(&block)
80
+ @on_presence = block
81
+ end
82
+
83
+ def add_subscription_callback(&block)
84
+ @on_subscription = block
85
+ end
86
+ end
87
+ end
88
+
89
+ class JID
90
+ def self.fake_jid
91
+ new 'foo', 'bar.com', 'baz'
92
+ end
93
+
94
+ def initialize(node,domain,res)
95
+ @node, @domain, @res = node, domain, res
96
+ end
97
+
98
+ def bare
99
+ self.class.new @node, @domain, nil
100
+ end
101
+ end
102
+ end
103
+
@@ -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 CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tyler-uppercut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler McMullen
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-11-07 00:00:00 -08:00
12
+ date: 2008-12-02 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -29,8 +29,21 @@ extensions: []
29
29
 
30
30
  extra_rdoc_files: []
31
31
 
32
- files: []
33
-
32
+ files:
33
+ - README.textile
34
+ - VERSION.yml
35
+ - lib/uppercut
36
+ - lib/uppercut/agent.rb
37
+ - lib/uppercut/base.rb
38
+ - lib/uppercut/conversation.rb
39
+ - lib/uppercut/message.rb
40
+ - lib/uppercut/notifier.rb
41
+ - lib/uppercut.rb
42
+ - spec/agent_spec.rb
43
+ - spec/conversation_spec.rb
44
+ - spec/jabber_stub.rb
45
+ - spec/notifier_spec.rb
46
+ - spec/spec_helper.rb
34
47
  has_rdoc: false
35
48
  homepage: http://github.com/tyler/uppercut
36
49
  post_install_message: