blather 0.2.3 → 0.3.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.
Files changed (47) hide show
  1. data/README.rdoc +7 -4
  2. data/Rakefile +3 -1
  3. data/examples/print_heirarchy.rb +76 -0
  4. data/lib/blather.rb +3 -3
  5. data/lib/blather/client.rb +4 -247
  6. data/lib/blather/client/client.rb +168 -0
  7. data/lib/blather/client/dsl.rb +99 -0
  8. data/lib/blather/errors.rb +5 -0
  9. data/lib/blather/errors/sasl_error.rb +6 -70
  10. data/lib/blather/errors/stanza_error.rb +12 -176
  11. data/lib/blather/errors/stream_error.rb +8 -186
  12. data/lib/blather/stanza.rb +2 -3
  13. data/lib/blather/stanza/{iq/disco.rb → disco.rb} +1 -3
  14. data/lib/blather/stanza/{iq/discos → disco}/disco_info.rb +3 -5
  15. data/lib/blather/stanza/{iq/discos → disco}/disco_items.rb +0 -2
  16. data/lib/blather/stanza/iq/query.rb +1 -1
  17. data/lib/blather/stanza/iq/roster.rb +2 -2
  18. data/lib/blather/stanza/pubsub/subscriber.rb +64 -0
  19. data/lib/blather/stream.rb +13 -7
  20. data/lib/blather/stream/component.rb +1 -1
  21. data/lib/blather/stream/parser.rb +11 -4
  22. data/lib/blather/stream/resource.rb +1 -1
  23. data/lib/blather/stream/sasl.rb +15 -9
  24. data/lib/blather/xmpp_node.rb +10 -4
  25. data/spec/blather/client/client_spec.rb +4 -0
  26. data/spec/blather/client/dsl_spec.rb +4 -0
  27. data/spec/blather/client_spec.rb +0 -0
  28. data/spec/blather/errors/sasl_error_spec.rb +2 -25
  29. data/spec/blather/errors/stanza_error_spec.rb +7 -18
  30. data/spec/blather/errors/stream_error_spec.rb +4 -15
  31. data/spec/blather/stanza/{iq/discos → discos}/disco_info_spec.rb +12 -12
  32. data/spec/blather/stanza/{iq/discos → discos}/disco_items_spec.rb +1 -1
  33. data/spec/blather/stanza/iq/query_spec.rb +7 -0
  34. data/spec/blather/stanza/iq/roster_spec.rb +21 -21
  35. data/spec/blather/stanza/pubsub/subscriber_spec.rb +70 -0
  36. data/spec/blather/stanza_spec.rb +1 -7
  37. data/spec/blather/stream/client_spec.rb +36 -7
  38. data/spec/spec_helper.rb +1 -1
  39. metadata +16 -18
  40. data/ext/Makefile +0 -149
  41. data/ext/mkmf.log +0 -30
  42. data/ext/push_parser.bundle +0 -0
  43. data/ext/push_parser.o +0 -0
  44. data/lib/autotest/discover.rb +0 -1
  45. data/lib/autotest/spec.rb +0 -60
  46. data/spec/blather/stanza/pubsub/event_spec.rb +0 -13
  47. data/spec/build_safe.rb +0 -20
data/README.rdoc CHANGED
@@ -77,6 +77,11 @@ There are 5 different types of guards:
77
77
  # Equivalent to stanza.body.match /exit/
78
78
  message :body => /exit/
79
79
 
80
+ # Hash with array (:name => [:gone, :forbidden])
81
+ # Calls the key on the stanza and check for inclusion in the array
82
+ # Equivalent to [:gone, :forbidden].include?(stanza.name)
83
+ stanza_error :name => [:gone, :fobidden]
84
+
80
85
  # Proc
81
86
  # Calls the proc passing in the stanza
82
87
  # Checks that the ID is modulo 3
@@ -90,14 +95,12 @@ There are 5 different types of guards:
90
95
 
91
96
  = TODO
92
97
 
93
- * Cleanup API
94
- * Add lambda callback ability to Iq stanzas
95
98
  * Better Documentation
96
- * Service Discovery (XEP-0030: http://xmpp.org/extensions/xep-0030.html)
99
+ * Add XPath guard that passes the result to the handler
100
+ * Add Disco the the DSL
97
101
  * PubSub (XEP-0060: http://xmpp.org/extensions/xep-0060.html)
98
102
  * More examples (Re-write XMPP4R examples into Blather)
99
103
 
100
104
  = License
101
105
 
102
106
  Please see LICENSE
103
- The LibXML-Ruby license can be found in its directory
data/Rakefile CHANGED
@@ -18,7 +18,9 @@ begin
18
18
  gem.add_dependency 'eventmachine', '>= 0.12.6'
19
19
  gem.add_dependency 'libxml-ruby', '>= 1.1.3'
20
20
 
21
- gem.files = FileList['examples/**/*', 'lib/**/*', 'ext/*'].to_a
21
+ gem.files = FileList['examples/**/*', 'lib/**/*', 'ext/*.{rb,c}'].to_a
22
+
23
+ gem.test_files = FileList['spec/**/*.rb']
22
24
 
23
25
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
26
  end
@@ -0,0 +1,76 @@
1
+ require 'blather'
2
+
3
+ class Object
4
+ begin
5
+ ObjectSpace.each_object(Class.new) {}
6
+
7
+ # Exclude this class unless it's a subclass of our supers and is defined.
8
+ # We check defined? in case we find a removed class that has yet to be
9
+ # garbage collected. This also fails for anonymous classes -- please
10
+ # submit a patch if you have a workaround.
11
+ def subclasses_of(*superclasses) #:nodoc:
12
+ subclasses = []
13
+
14
+ superclasses.each do |sup|
15
+ ObjectSpace.each_object(class << sup; self; end) do |k|
16
+ if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
17
+ subclasses << k
18
+ end
19
+ end
20
+ end
21
+
22
+ subclasses
23
+ end
24
+ rescue RuntimeError
25
+ # JRuby and any implementations which cannot handle the objectspace traversal
26
+ # above fall back to this implementation
27
+ def subclasses_of(*superclasses) #:nodoc:
28
+ subclasses = []
29
+
30
+ superclasses.each do |sup|
31
+ ObjectSpace.each_object(Class) do |k|
32
+ if superclasses.any? { |superclass| k < superclass } &&
33
+ (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
34
+ subclasses << k
35
+ end
36
+ end
37
+ subclasses.uniq!
38
+ end
39
+ subclasses
40
+ end
41
+ end
42
+ end
43
+
44
+ class Hash
45
+ def deep_merge(second)
46
+ # From: http://www.ruby-forum.com/topic/142809
47
+ # Author: Stefan Rusterholz
48
+ merger = proc { |key,v1,v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
49
+ self.merge(second, &merger)
50
+ end
51
+ end
52
+
53
+ handlers = {}
54
+ (Object.subclasses_of(Blather::Stanza) + Object.subclasses_of(Blather::BlatherError)).each do |klass|
55
+ handlers = handlers.deep_merge klass.handler_heirarchy.inject('klass' => klass.to_s.gsub('Blather::', '')) { |h,k| {k.to_s => h} }
56
+ end
57
+
58
+ level = 0
59
+ runner = proc do |k,v|
60
+ next if k == 'klass'
61
+
62
+ str = ''
63
+ if level > 0
64
+ (level - 1).times { str << '| ' }
65
+ str << '|-- '
66
+ end
67
+
68
+ puts str+k
69
+ if Hash === v
70
+ level += 1
71
+ v.sort.each &runner
72
+ level -= 1
73
+ end
74
+ end
75
+
76
+ handlers.sort.each &runner
data/lib/blather.rb CHANGED
@@ -26,9 +26,9 @@ $:.unshift File.join(File.dirname(__FILE__), '..')
26
26
  blather/stanza/iq
27
27
  blather/stanza/iq/query
28
28
  blather/stanza/iq/roster
29
- blather/stanza/iq/disco
30
- blather/stanza/iq/discos/disco_info
31
- blather/stanza/iq/discos/disco_items
29
+ blather/stanza/disco
30
+ blather/stanza/disco/disco_info
31
+ blather/stanza/disco/disco_items
32
32
  blather/stanza/message
33
33
  blather/stanza/presence
34
34
  blather/stanza/presence/status
@@ -1,256 +1,13 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. blather])
2
-
3
- module Blather #:nodoc:
4
-
5
- class Client #:nodoc:
6
- attr_accessor :jid,
7
- :roster
8
-
9
- def initialize
10
- @state = :initializing
11
-
12
- @status = Stanza::Presence::Status.new
13
- @handlers = {}
14
- @tmp_handlers = {}
15
- @roster = Roster.new self
16
-
17
- setup_initial_handlers
18
- end
19
-
20
- def setup(jid, password, host = nil, port = 5222)
21
- @setup = [JID.new(jid), password, host, port]
22
- self
23
- end
24
-
25
- def setup?
26
- @setup.is_a?(Array) && !@setup.empty?
27
- end
28
-
29
- def run
30
- raise 'Not setup!' unless @setup.is_a?(Array)
31
- trap(:INT) { EM.stop }
32
- EM.run {
33
- klass = @setup[2].node ? Blather::Stream::Client : Blather::Stream::Component
34
- klass.start Blather.client, *@setup
35
- }
36
-
37
- def temporary_handler(id, &handler)
38
- @tmp_handlers[id] = handler
39
- end
40
-
41
- def register_handler(type, *guards, &handler)
42
- @handlers[type] ||= []
43
- @handlers[type] << [guards, handler]
44
- end
45
-
46
- def status
47
- @status.state
48
- end
49
-
50
- def status=(state)
51
- state, msg, to = state
52
-
53
- status = Stanza::Presence::Status.new state, msg
54
- status.to = to
55
- @statustatus unless to
56
-
57
- write status
58
- end
59
-
60
- def write(stanza)
61
- stanza.from ||= jid if stanza.respond_to?(:from)
62
- @stream.send(stanza) if @stream
63
- end
64
-
65
- def stream_started(stream)
66
- @stream = stream
67
-
68
- #retreive roster
69
- if @stream.is_a?(Stream::Component)
70
- @state = :ready
71
- call_handler_for :ready, nil
72
- else
73
- write Stanza::Iq::Roster.new
74
- end
75
- end
76
-
77
- def stop
78
- @stream.close_connection_after_writing
79
- end
80
-
81
- def stopped
82
- EM.stop
83
- end
84
-
85
- def call(stanza)
86
- if handler = @tmp_handlers.delete(stanza.id)
87
- handler.call stanza
88
- else
89
- stanza.handler_heirarchy.each do |type|
90
- break if call_handler_for(type, stanza) && (stanza.is_a?(BlatherError) || stanza.type == :iq)
91
- end
92
- end
93
- end
94
-
95
- def call_handler_for(type, stanza)
96
- if @handlers[type]
97
- @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
98
- true
99
- end
100
- end
101
-
102
- protected
103
- def setup_initial_handlers
104
- register_handler :error do |err|
105
- raise err
106
- end
107
-
108
- register_handler :iq do |iq|
109
- write(StanzaError::ServiceUnavailable.new(iq, :cancel).to_node) if [:set, :get].include?(iq.type)
110
- end
111
-
112
- register_handler :status do |status|
113
- roster[status.from].status = status if roster[status.from]
114
- end
115
-
116
- register_handler :roster do |node|
117
- roster.process node
118
- if @state == :initializing
119
- @state = :ready
120
- write @status
121
- call_handler_for :ready, nil
122
- end
123
- end
124
- end
125
-
126
- ##
127
- # If any of the guards returns FALSE this returns true
128
- def guarded?(guards, stanza)
129
- guards.find do |guard|
130
- case guard
131
- when Symbol
132
- !stanza.__send__(guard)
133
- when Array
134
- # return FALSE if any item is TRUE
135
- !guard.detect { |condition| !guarded?([condition], stanza) }
136
- when Hash
137
- # return FALSE unless any inequality is found
138
- guard.find do |method, value|
139
- if value.is_a?(Regexp)
140
- !stanza.__send__(method).to_s.match(value)
141
- else
142
- stanza.__send__(method) != value
143
- end
144
- end
145
- when Proc
146
- !guard.call(stanza)
147
- else
148
- raise "Bad guard: #{guard.inspect}"
149
- end
150
- end
151
- end
152
-
153
- end #Client
154
-
155
- def client
156
- @client ||= Client.new
157
- end
158
- module_function :client
159
- end #Blather
160
-
161
- ##
162
- # Prepare server settings
163
- # setup_client [node@domain/resource], [password], [host], [port]
164
- # host and port are optional defaulting to the domain in the JID and 5222 respectively
165
- def setup_client(jid, password, host = nil, port = 5222)
166
- at_exit { Blather.client.setup_client(jid, password, host, port).run }
167
- end
168
-
169
- def setup_component(jid, secret, host, port)
170
- at_exit { Blather.client.setup_component(jid, secret, host, port).run }
171
- end
172
-
173
- ##
174
- # Shutdown the connection.
175
- # Flushes the write buffer then stops EventMachine
176
- def shutdown
177
- Blather.client.stop
178
- end
179
-
180
- ##
181
- # Set handler for a stanza type
182
- def handle(stanza_type, *guards, &block)
183
- Blather.client.register_handler stanza_type, *guards, &block
184
- end
185
-
186
- ##
187
- # Wrapper for "handle :ready" (just a bit of syntactic sugar)
188
- def when_ready(&block)
189
- handle :ready, &block
190
- end
191
-
192
- ##
193
- # Set current status
194
- def status(state = nil, msg = nil)
195
- Blather.client.status = state, msg
196
- end
197
-
198
- ##
199
- # Direct access to the roster
200
- def roster
201
- Blather.client.roster
202
- end
203
-
204
- ##
205
- # Write data to the stream
206
- # Anything that resonds to #to_s can be paseed to the stream
207
- def write(stanza)
208
- Blather.client.write(stanza)
209
- end
210
-
211
- ##
212
- # Helper method to make sending basic messages easier
213
- # say [jid], [msg]
214
- def say(to, msg)
215
- Blather.client.write Blather::Stanza::Message.new(to, msg)
216
- end
217
-
218
- ##
219
- # Wrapper to grab the current JID
220
- def jid
221
- Blather.client.jid
222
- end
223
-
224
- ##
225
- #
226
- def discover(what, who, where, &callback)
227
- stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
228
- stanza.to = who
229
- stanza.node = where
230
-
231
- Blather.client.temporary_handler stanza.id, &callback
232
- write stanza
233
- end
234
-
235
- ##
236
- # Checks to see if the method is part of the handlers list.
237
- # If so it creates a handler, otherwise it'll pass it back
238
- # to Ruby's method_missing handler
239
- def method_missing(method, *args, &block)
240
- if Blather::Stanza.handler_list.include?(method)
241
- handle method, *args, &block
242
- else
243
- super
244
- end
245
- end
1
+ require File.join(File.dirname(__FILE__), *%w[client dsl])
246
2
 
3
+ include Blather::DSL
247
4
 
248
5
  at_exit do
249
- unless Blather.client.setup?
6
+ unless client.setup?
250
7
  if ARGV.length < 2
251
8
  puts "Run with #{$0} user@server/resource password [host] [port]"
252
9
  else
253
- Blather.client.setup(*ARGV).run
10
+ client.setup(*ARGV).run
254
11
  end
255
12
  end
256
13
  end
@@ -0,0 +1,168 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
2
+
3
+ module Blather #:nodoc:
4
+
5
+ class Client #:nodoc:
6
+ attr_accessor :jid,
7
+ :roster
8
+
9
+ def initialize
10
+ @state = :initializing
11
+
12
+ @status = Stanza::Presence::Status.new
13
+ @handlers = {}
14
+ @tmp_handlers = {}
15
+ @roster = Roster.new self
16
+
17
+ setup_initial_handlers
18
+ end
19
+
20
+ def setup?
21
+ @setup.is_a? Array
22
+ end
23
+
24
+ def setup(jid, password, host = nil, port = nil)
25
+ @setup = [JID.new(jid), password]
26
+ @setup << host if host
27
+ @setup << port if port
28
+ self
29
+ end
30
+
31
+ def run
32
+ raise 'not setup!' unless setup?
33
+ trap(:INT) { EM.stop }
34
+ EM.run {
35
+ klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
36
+ klass.start self, *@setup
37
+ }
38
+ end
39
+
40
+ def register_tmp_handler(id, &handler)
41
+ @tmp_handlers[id] = handler
42
+ end
43
+
44
+ def register_handler(type, *guards, &handler)
45
+ @handlers[type] ||= []
46
+ @handlers[type] << [guards, handler]
47
+ end
48
+
49
+ def status
50
+ @status.state
51
+ end
52
+
53
+ def status=(state)
54
+ state, msg, to = state
55
+
56
+ status = Stanza::Presence::Status.new state, msg
57
+ status.to = to
58
+ @status = status unless to
59
+
60
+ write status
61
+ end
62
+
63
+ def write(stanza)
64
+ stanza.from ||= jid if stanza.respond_to?(:from)
65
+ @stream.send(stanza) if @stream
66
+ end
67
+
68
+ def write_with_handler(stanza, &hanlder)
69
+ register_tmp_handler stanza.id, &handler
70
+ write stanza
71
+ end
72
+
73
+ def stream_started(stream)
74
+ @stream = stream
75
+
76
+ #retreive roster
77
+ if @stream.is_a?(Stream::Component)
78
+ @state = :ready
79
+ call_handler_for :ready, nil
80
+ else
81
+ r = Stanza::Iq::Roster.new
82
+ register_tmp_handler r.id do |node|
83
+ roster.process node
84
+ @state = :ready
85
+ write @status
86
+ call_handler_for :ready, nil
87
+ end
88
+ write r
89
+ end
90
+ end
91
+
92
+ def stop
93
+ @stream.close_connection_after_writing
94
+ end
95
+
96
+ def stopped
97
+ EM.stop
98
+ end
99
+
100
+ def call(stanza)
101
+ if handler = @tmp_handlers.delete(stanza.id)
102
+ handler.call stanza
103
+ else
104
+ stanza.handler_heirarchy.each do |type|
105
+ break if call_handler_for(type, stanza) && (stanza.is_a?(BlatherError) || stanza.type == :iq)
106
+ end
107
+ end
108
+ end
109
+
110
+ def call_handler_for(type, stanza)
111
+ if @handlers[type]
112
+ @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
113
+ true
114
+ end
115
+ end
116
+
117
+ protected
118
+ def setup_initial_handlers
119
+ register_handler :error do |err|
120
+ raise err
121
+ end
122
+
123
+ register_handler :iq do |iq|
124
+ write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node) if [:set, :get].include?(iq.type)
125
+ end
126
+
127
+ register_handler :status do |status|
128
+ roster[status.from].status = status if roster[status.from]
129
+ end
130
+
131
+ register_handler :roster do |node|
132
+ roster.process node
133
+ end
134
+ end
135
+
136
+ ##
137
+ # If any of the guards returns FALSE this returns true
138
+ def guarded?(guards, stanza)
139
+ guards.find do |guard|
140
+ case guard
141
+ when Symbol
142
+ !stanza.__send__(guard)
143
+ when Array
144
+ # return FALSE if any item is TRUE
145
+ !guard.detect { |condition| !guarded?([condition], stanza) }
146
+ when Hash
147
+ # return FALSE unless any inequality is found
148
+ guard.find do |method, test|
149
+ value = stanza.__send__(method)
150
+ case test
151
+ when Regexp
152
+ !value.to_s.match(test)
153
+ when Array
154
+ !test.include? value
155
+ else
156
+ test != value
157
+ end
158
+ end
159
+ when Proc
160
+ !guard.call(stanza)
161
+ else
162
+ raise "Bad guard: #{guard.inspect}"
163
+ end
164
+ end
165
+ end
166
+
167
+ end #Client
168
+ end #Blather