blather 0.4.1 → 0.4.2

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/README.rdoc CHANGED
@@ -101,6 +101,21 @@ The different types of guards are:
101
101
  # Equivalent to !stanza.find('/iq/ns:pubsub', :ns => 'pubsub:namespace').empty?
102
102
  iq '/iq/ns:pubsub', :ns => 'pubsub:namespace'
103
103
 
104
+ === Filters
105
+
106
+ Blather provides before and after filters that work much the way regular handlers work. Filters come in a before and after
107
+ flavor. They're called in order of definition and can be guarded like handlers.
108
+
109
+ before { |s| "I'm run before any handler" }
110
+ before { |s| "I'm run next" }
111
+
112
+ before(:message) { |s| "I'm only run in front of message stanzas" }
113
+ before(nil, :id => 1) { |s| "I'll only be run when the stanza's ID == 1" }
114
+
115
+ # ... handlers
116
+
117
+ after { |s| "I'm run after everything" }
118
+
104
119
  == On the Command Line:
105
120
 
106
121
  Default usage is:
@@ -126,7 +141,7 @@ Command line options:
126
141
 
127
142
  Jeff Smick <sprsquish@gmail.com>
128
143
 
129
- = Contributors
144
+ === Contributors
130
145
 
131
146
  Nolan Darilek <nolan@thewordnerd.info>
132
147
 
data/examples/echo.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'blather/client'
4
- Blather.logger.level = Logger::DEBUG
4
+
5
5
  when_ready { puts "Connected ! send messages to #{jid.stripped}." }
6
6
 
7
7
  subscription :request? do |s|
data/examples/ping.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'blather/client'
2
+
3
+ setup 'echo@jabber.local/ping', 'echo'
4
+
5
+ status :from => Blather::JID.new('echo@jabber.local/pong') do |s|
6
+ say s.from, 'ping'
7
+ end
8
+
9
+ message :chat?, :body => 'pong' do |m|
10
+ say m.from, 'ping'
11
+ end
data/examples/pong.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'blather/client'
2
+
3
+ setup 'echo@jabber.local/pong', 'echo'
4
+ message :chat?, :body => 'ping' do |m|
5
+ say m.from, 'pong'
6
+ end
@@ -7,8 +7,8 @@ options = {}
7
7
  optparse = OptionParser.new do |opts|
8
8
  opts.banner = "Run with #{$0} [options] user@server/resource password [host] [port]"
9
9
 
10
- opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do |debug|
11
- options[:debug] = debug
10
+ opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do
11
+ options[:debug] = true
12
12
  end
13
13
 
14
14
  opts.on('-d', '--daemonize', 'Daemonize the process') do |daemonize|
@@ -60,10 +60,12 @@ at_exit do
60
60
  if options[:log]
61
61
  log = File.new(options[:log], 'a')
62
62
  log.sync = options[:debug]
63
- Blather.logger.level = Logger::DEBUG if options[:debug]
64
63
  $stdout.reopen log
65
64
  $stderr.reopen $stdout
66
65
  end
66
+
67
+ Blather.logger.level = Logger::DEBUG if options[:debug]
68
+
67
69
  trap(:INT) { EM.stop }
68
70
  trap(:TERM) { EM.stop }
69
71
  EM.run { client.run }
@@ -2,29 +2,67 @@ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
2
2
 
3
3
  module Blather #:nodoc:
4
4
 
5
- class Client #:nodoc:
5
+ # = Blather Client
6
+ #
7
+ # Blather's Client class provides a set of helpers for working with common XMPP tasks such as setting up and starting
8
+ # the connection, settings status, registering and dispatching filters and handlers and roster management.
9
+ #
10
+ # Client can be used separately from the DSL if you'd like to implement your own DSL
11
+ # Here's the echo example using the client without the DSL:
12
+ #
13
+ # require 'blather/client/client'
14
+ # client = Client.setup 'echo@jabber.local', 'echo'
15
+ #
16
+ # client.register_handler(:ready) { puts "Connected ! send messages to #{client.jid.stripped}." }
17
+ #
18
+ # client.register_handler :subscription, :request? do |s|
19
+ # client.write s.approve!
20
+ # end
21
+ #
22
+ # client.register_handler :message, :chat?, :body => 'exit' do |m|
23
+ # client.write Blather::Stanza::Message.new(m.from, 'Exiting...')
24
+ # client.close
25
+ # end
26
+ #
27
+ # client.register_handler :message, :chat?, :body do |m|
28
+ # client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}")
29
+ # end
30
+ #
31
+ class Client
6
32
  attr_reader :jid,
7
33
  :roster
8
34
 
9
- def initialize
35
+ ##
36
+ # Initialize and setup the client
37
+ # * +jid+ - the JID to login with
38
+ # * +password+ - password associated with the JID
39
+ # * +host+ - hostname or IP to connect to. If nil the stream will look one up based on the domain in the JID
40
+ # * +port+ - port to connect to
41
+ def self.setup(jid, password, host = nil, port = nil)
42
+ self.new.setup(jid, password, host, port)
43
+ end
44
+
45
+ def initialize # :nodoc:
10
46
  @state = :initializing
11
47
 
12
48
  @status = Stanza::Presence::Status.new
13
49
  @handlers = {}
14
50
  @tmp_handlers = {}
51
+ @filters = {:before => [], :after => []}
15
52
  @roster = Roster.new self
16
53
 
17
54
  setup_initial_handlers
18
55
  end
19
56
 
20
- def jid=(new_jid)
21
- @jid = JID.new new_jid
22
- end
23
-
57
+ ##
58
+ # Get the current status. Taken from the +state+ attribute of Status
24
59
  def status
25
60
  @status.state
26
61
  end
27
62
 
63
+ ##
64
+ # Set the status. Status can be set with either a single value or an array containing
65
+ # [state, message, to].
28
66
  def status=(state)
29
67
  state, msg, to = state
30
68
 
@@ -35,77 +73,121 @@ module Blather #:nodoc:
35
73
  write status
36
74
  end
37
75
 
38
- def setup?
39
- @setup.is_a? Array
40
- end
41
-
42
- def setup(jid, password, host = nil, port = nil)
43
- @jid = JID.new(jid)
44
- @setup = [@jid, password]
45
- @setup << host if host
46
- @setup << port if port
47
- self
48
- end
49
-
76
+ ##
77
+ # Start the connection.
50
78
  def run
51
79
  raise 'not setup!' unless setup?
52
80
  klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
53
81
  @stream = klass.start self, *@setup
54
82
  end
55
83
 
84
+ ##
85
+ # Register a filter to be run before or after the handler chain is run.
86
+ # * +type+ - the type of filter. Must be +:before+ or +:after+
87
+ # * +guards+ - guards that should be checked before the filter is called
88
+ def register_filter(type, handler = nil, *guards, &filter)
89
+ raise "Invalid filter: #{type}. Must be :before or :after" unless [:before, :after].include?(type)
90
+ @filters[type] << [guards, handler, filter]
91
+ end
92
+
93
+ ##
94
+ # Register a temporary handler. Temporary handlers are based on the ID of the JID and live
95
+ # only until a stanza with said ID is received.
96
+ # * +id+ - the ID of the stanza that should be handled
56
97
  def register_tmp_handler(id, &handler)
57
98
  @tmp_handlers[id] = handler
58
99
  end
59
100
 
101
+ ##
102
+ # Register a handler
103
+ # * +type+ - the handler type. Should be registered in Stanza.handler_list. Blather will log a warning if it's not.
104
+ # * +guards+ - the list of guards that must be verified before the handler will be called
60
105
  def register_handler(type, *guards, &handler)
61
- check_guards guards
106
+ check_handler type, guards
62
107
  @handlers[type] ||= []
63
108
  @handlers[type] << [guards, handler]
64
109
  end
65
110
 
111
+ ##
112
+ # Write data to the stream
66
113
  def write(stanza)
67
114
  @stream.send(stanza) if @stream
68
115
  end
69
116
 
117
+ ##
118
+ # Helper that will create a temporary handler for the stanza being sent before writing it to the stream.
119
+ #
120
+ # client.write_with_handler(stanza) { |s| "handle stanza here" }
121
+ #
122
+ # is equivalent to:
123
+ #
124
+ # client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
125
+ # client.write stanza
70
126
  def write_with_handler(stanza, &handler)
71
127
  register_tmp_handler stanza.id, &handler
72
128
  write stanza
73
129
  end
74
130
 
75
- def post_init
76
- self.jid.node ? client_post_init : ready!
77
- end
78
-
131
+ ##
132
+ # Close the connection
79
133
  def close
80
134
  @stream.close_connection_after_writing
81
135
  end
82
136
 
83
- def unbind
137
+ def post_init # :nodoc:
138
+ self.jid.node ? client_post_init : ready!
139
+ end
140
+
141
+ def unbind # :nodoc:
84
142
  EM.stop if EM.reactor_running?
85
143
  end
86
144
 
87
- def receive_data(stanza)
88
- if handler = @tmp_handlers.delete(stanza.id)
89
- handler.call stanza
90
- else
91
- stanza.handler_heirarchy.each do |type|
92
- break if call_handler_for(type, stanza)# && (stanza.is_a?(BlatherError) || stanza.type == :iq)
93
- end
145
+ def receive_data(stanza) # :nodoc:
146
+ catch(:halt) do
147
+ run_filters :before, stanza
148
+ handle_stanza stanza
149
+ run_filters :after, stanza
94
150
  end
95
151
  end
96
152
 
153
+ def jid=(new_jid) # :nodoc :
154
+ @jid = JID.new new_jid
155
+ end
156
+
157
+ def setup? # :nodoc:
158
+ @setup.is_a? Array
159
+ end
160
+
161
+ def setup(jid, password, host = nil, port = nil) # :nodoc:
162
+ @jid = JID.new(jid)
163
+ @setup = [@jid, password]
164
+ @setup << host if host
165
+ @setup << port if port
166
+ self
167
+ end
168
+
97
169
  protected
98
- def setup_initial_handlers
170
+ def check_handler(type, guards)
171
+ Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
172
+ check_guards guards
173
+ end
174
+
175
+ def current_handlers
176
+ [:ready] + Stanza.handler_list + BlatherError.handler_list
177
+ end
178
+
179
+ def setup_initial_handlers # :nodoc:
99
180
  register_handler :error do |err|
100
181
  raise err
101
182
  end
102
183
 
103
184
  register_handler :iq, :type => [:get, :set] do |iq|
104
- write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node)
185
+ write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
105
186
  end
106
187
 
107
188
  register_handler :status do |status|
108
189
  roster[status.from].status = status if roster[status.from]
190
+ nil
109
191
  end
110
192
 
111
193
  register_handler :roster do |node|
@@ -113,12 +195,12 @@ module Blather #:nodoc:
113
195
  end
114
196
  end
115
197
 
116
- def ready!
198
+ def ready! # :nodoc:
117
199
  @state = :ready
118
200
  call_handler_for :ready, nil
119
201
  end
120
202
 
121
- def client_post_init
203
+ def client_post_init # :nodoc:
122
204
  write_with_handler Stanza::Iq::Roster.new do |node|
123
205
  roster.process node
124
206
  write @status
@@ -126,25 +208,43 @@ module Blather #:nodoc:
126
208
  end
127
209
  end
128
210
 
129
- def call_handler_for(type, stanza)
130
- if @handlers[type]
131
- @handlers[type].find do |guards, handler|
132
- if guards.first.is_a?(String)
133
- unless (result = stanza.find(*guards)).empty?
134
- handler.call(stanza, result)
135
- end
136
- elsif !guarded?(guards, stanza)
137
- handler.call(stanza)
138
- end
211
+ def run_filters(type, stanza) # :nodoc:
212
+ @filters[type].each do |guards, handler, filter|
213
+ next if handler && !stanza.handler_heirarchy.include?(handler)
214
+ catch(:pass) { call_handler filter, guards, stanza }
215
+ end
216
+ end
217
+
218
+ def handle_stanza(stanza) # :nodoc:
219
+ if handler = @tmp_handlers.delete(stanza.id)
220
+ handler.call stanza
221
+ else
222
+ stanza.handler_heirarchy.each do |type|
223
+ break if call_handler_for(type, stanza)
139
224
  end
140
225
  end
141
226
  end
142
227
 
228
+ def call_handler_for(type, stanza) # :nodoc:
229
+ return unless handler = @handlers[type]
230
+ handler.find do |guards, handler|
231
+ catch(:pass) { call_handler handler, guards, stanza }
232
+ end
233
+ end
234
+
235
+ def call_handler(handler, guards, stanza) # :nodoc:
236
+ if guards.first.respond_to?(:to_str) && !(result = stanza.find(*guards)).empty?
237
+ handler.call(stanza, result)
238
+ elsif !guarded?(guards, stanza)
239
+ handler.call(stanza)
240
+ end
241
+ end
242
+
143
243
  ##
144
244
  # If any of the guards returns FALSE this returns true
145
245
  # the logic is reversed to allow short circuiting
146
246
  # (why would anyone want to loop over more values than necessary?)
147
- def guarded?(guards, stanza)
247
+ def guarded?(guards, stanza) # :nodoc:
148
248
  guards.find do |guard|
149
249
  case guard
150
250
  when Symbol
@@ -156,10 +256,9 @@ module Blather #:nodoc:
156
256
  # return FALSE unless any inequality is found
157
257
  guard.find do |method, test|
158
258
  value = stanza.__send__(method)
159
- case test
160
- when Regexp
161
- !value.to_s.match(test)
162
- when Array
259
+ if test.class.respond_to?(:last_match)
260
+ !(test =~ value)
261
+ elsif test.is_a?(Array)
163
262
  !test.include? value
164
263
  else
165
264
  test != value
@@ -171,7 +270,7 @@ module Blather #:nodoc:
171
270
  end
172
271
  end
173
272
 
174
- def check_guards(guards)
273
+ def check_guards(guards) # :nodoc:
175
274
  guards.each do |guard|
176
275
  case guard
177
276
  when Array
@@ -38,6 +38,18 @@ module Blather
38
38
  client.close
39
39
  end
40
40
 
41
+ ##
42
+ # Setup a before filter
43
+ def before(handler = nil, *guards, &block)
44
+ client.register_filter :before, handler, *guards, &block
45
+ end
46
+
47
+ ##
48
+ # Setup an after filter
49
+ def after(handler = nil, *guards, &block)
50
+ client.register_filter :after, handler, *guards, &block
51
+ end
52
+
41
53
  ##
42
54
  # Set handler for a stanza type
43
55
  def handle(stanza_type, *guards, &block)
@@ -82,6 +94,18 @@ module Blather
82
94
  client.jid
83
95
  end
84
96
 
97
+ ##
98
+ # Halt the handler chain
99
+ def halt
100
+ throw :halt
101
+ end
102
+
103
+ ##
104
+ # Pass responsibility to the next handler
105
+ def pass
106
+ throw :pass
107
+ end
108
+
85
109
  ##
86
110
  # Request items or info from an entity
87
111
  # discover (items|info), [jid], [node] do |response|
@@ -27,7 +27,7 @@ module DSL
27
27
  ##
28
28
  # Discover Nodes
29
29
  # Yields a list of DiscoItem::Item objects
30
- # +path+ is the node's path. Default is '/'
30
+ # * +path+ is the node's path. Default is '/'
31
31
  def nodes(path = nil, host = nil, &callback)
32
32
  path ||= '/'
33
33
  stanza = Stanza::DiscoItems.new(:get, path)
@@ -38,7 +38,7 @@ module DSL
38
38
  ##
39
39
  # Discover node information
40
40
  # Yields a DiscoInfo node
41
- # +path+ is the node's path
41
+ # * +path+ is the node's path
42
42
  def node(path, host = nil, &callback)
43
43
  stanza = Stanza::DiscoInfo.new(:get, path)
44
44
  stanza.to = send_to(host)
@@ -47,9 +47,9 @@ module DSL
47
47
 
48
48
  ##
49
49
  # Retrieve items for a node
50
- # +path+ is the node's path
51
- # +list+ can be an array of items to retrieve
52
- # +max+ can be the maximum number of items to return
50
+ # * +path+ is the node's path
51
+ # * +list+ can be an array of items to retrieve
52
+ # * +max+ can be the maximum number of items to return
53
53
  def items(path, list = [], max = nil, host = nil, &callback)
54
54
  request Stanza::PubSub::Items.request(send_to(host), path, list, max), :items, callback
55
55
  end
@@ -57,8 +57,8 @@ module DSL
57
57
  ##
58
58
  # Subscribe to a node
59
59
  # Yields the resulting Subscription object
60
- # +node+ is the node to subscribe to
61
- # +jid+ is the jid that should be used. Defaults to the stripped current JID
60
+ # * +node+ is the node to subscribe to
61
+ # * +jid+ is the jid that should be used. Defaults to the stripped current JID
62
62
  def subscribe(node, jid = nil, host = nil)
63
63
  jid ||= DSL.client.jid.stripped
64
64
  request(Stanza::PubSub::Subscribe.new(:set, send_to(host), node, jid)) { |n| yield n if block_given? }
@@ -67,8 +67,8 @@ module DSL
67
67
  ##
68
68
  # Unsubscribe from a node
69
69
  # Yields the resulting Unsubscribe object
70
- # +node+ is the node to subscribe to
71
- # +jid+ is the jid that should be used. Defaults to the stripped current JID
70
+ # * +node+ is the node to subscribe to
71
+ # * +jid+ is the jid that should be used. Defaults to the stripped current JID
72
72
  def unsubscribe(node, jid = nil, host = nil)
73
73
  jid ||= DSL.client.jid.stripped
74
74
  request(Stanza::PubSub::Unsubscribe.new(:set, send_to(host), node, jid)) { |n| yield n if block_given? }
@@ -77,8 +77,8 @@ module DSL
77
77
  ##
78
78
  # Publish an item to a node
79
79
  # Yields the resulting Publish node
80
- # +node+ is the node to publish to
81
- # +payload+ is the payload to send (see Blather::Stanza::PubSub::Publish for details)
80
+ # * +node+ is the node to publish to
81
+ # * +payload+ is the payload to send (see Blather::Stanza::PubSub::Publish for details)
82
82
  def publish(node, payload, host = nil)
83
83
  request(Stanza::PubSub::Publish.new(send_to(host), node, :set, payload)) { |n| yield n if block_given? }
84
84
  end
@@ -86,8 +86,8 @@ module DSL
86
86
  ##
87
87
  # Delete items from a node
88
88
  # Yields the resulting node
89
- # +node+ is the node to retract items from
90
- # +ids+ is a list of ids to retract. This can also be a single id
89
+ # * +node+ is the node to retract items from
90
+ # * +ids+ is a list of ids to retract. This can also be a single id
91
91
  def retract(node, ids = [], host = nil)
92
92
  request(Stanza::PubSub::Retract.new(send_to(host), node, :set, ids)) { |n| yield n if block_given? }
93
93
  end
@@ -96,7 +96,7 @@ module DSL
96
96
  # Create a node
97
97
  # Yields the resulting node
98
98
  # This does not (yet) handle configuration
99
- # +node+ is the node to create
99
+ # * +node+ is the node to create
100
100
  def create(node, host = nil)
101
101
  request(Stanza::PubSub::Create.new(:set, send_to(host), node)) { |n| yield n if block_given? }
102
102
  end
@@ -104,7 +104,7 @@ module DSL
104
104
  ##
105
105
  # Purge all node items
106
106
  # Yields the resulting node
107
- # +node+ is the node to purge
107
+ # * +node+ is the node to purge
108
108
  def purge(node, host = nil)
109
109
  request(Stanza::PubSubOwner::Purge.new(:set, send_to(host), node)) { |n| yield n if block_given? }
110
110
  end
@@ -112,7 +112,7 @@ module DSL
112
112
  ##
113
113
  # Delete a node
114
114
  # Yields the resulting node
115
- # +node+ is the node to delete
115
+ # * +node+ is the node to delete
116
116
  def delete(node, host = nil)
117
117
  request(Stanza::PubSubOwner::Delete.new(:set, send_to(host), node)) { |n| yield n if block_given? }
118
118
  end