blather 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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