blather 0.4.7 → 0.4.8

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 (59) hide show
  1. data/README.md +162 -0
  2. data/examples/{print_heirarchy.rb → print_hierarchy.rb} +5 -5
  3. data/examples/stream_only.rb +27 -0
  4. data/lib/blather.rb +4 -0
  5. data/lib/blather/client/client.rb +91 -73
  6. data/lib/blather/client/dsl.rb +156 -32
  7. data/lib/blather/client/dsl/pubsub.rb +86 -54
  8. data/lib/blather/core_ext/active_support.rb +9 -9
  9. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +2 -2
  10. data/lib/blather/core_ext/nokogiri.rb +12 -7
  11. data/lib/blather/errors.rb +25 -14
  12. data/lib/blather/errors/sasl_error.rb +21 -3
  13. data/lib/blather/errors/stanza_error.rb +37 -21
  14. data/lib/blather/errors/stream_error.rb +27 -17
  15. data/lib/blather/jid.rb +79 -24
  16. data/lib/blather/roster.rb +39 -21
  17. data/lib/blather/roster_item.rb +43 -21
  18. data/lib/blather/stanza.rb +88 -40
  19. data/lib/blather/stanza/disco.rb +12 -2
  20. data/lib/blather/stanza/disco/disco_info.rb +112 -20
  21. data/lib/blather/stanza/disco/disco_items.rb +81 -12
  22. data/lib/blather/stanza/iq.rb +94 -38
  23. data/lib/blather/stanza/iq/query.rb +16 -22
  24. data/lib/blather/stanza/iq/roster.rb +98 -20
  25. data/lib/blather/stanza/message.rb +266 -111
  26. data/lib/blather/stanza/presence.rb +118 -42
  27. data/lib/blather/stanza/presence/status.rb +140 -60
  28. data/lib/blather/stanza/presence/subscription.rb +44 -10
  29. data/lib/blather/stanza/pubsub.rb +70 -15
  30. data/lib/blather/stanza/pubsub/affiliations.rb +36 -7
  31. data/lib/blather/stanza/pubsub/create.rb +26 -4
  32. data/lib/blather/stanza/pubsub/errors.rb +13 -4
  33. data/lib/blather/stanza/pubsub/event.rb +56 -10
  34. data/lib/blather/stanza/pubsub/items.rb +46 -6
  35. data/lib/blather/stanza/pubsub/publish.rb +52 -7
  36. data/lib/blather/stanza/pubsub/retract.rb +45 -6
  37. data/lib/blather/stanza/pubsub/subscribe.rb +30 -4
  38. data/lib/blather/stanza/pubsub/subscription.rb +74 -6
  39. data/lib/blather/stanza/pubsub/subscriptions.rb +35 -9
  40. data/lib/blather/stanza/pubsub/unsubscribe.rb +30 -4
  41. data/lib/blather/stanza/pubsub_owner.rb +17 -7
  42. data/lib/blather/stanza/pubsub_owner/delete.rb +23 -5
  43. data/lib/blather/stanza/pubsub_owner/purge.rb +23 -5
  44. data/lib/blather/stream.rb +96 -29
  45. data/lib/blather/stream/parser.rb +6 -9
  46. data/lib/blather/xmpp_node.rb +101 -153
  47. data/spec/blather/client/client_spec.rb +1 -1
  48. data/spec/blather/errors_spec.rb +5 -5
  49. data/spec/blather/stanza/message_spec.rb +56 -0
  50. data/spec/blather/stanza/presence/status_spec.rb +1 -1
  51. data/spec/blather/stanza_spec.rb +3 -3
  52. data/spec/blather/xmpp_node_spec.rb +19 -74
  53. metadata +6 -10
  54. data/README.rdoc +0 -185
  55. data/examples/drb_client.rb +0 -5
  56. data/examples/ping.rb +0 -11
  57. data/examples/pong.rb +0 -6
  58. data/examples/pubsub/cli.rb +0 -64
  59. data/examples/pubsub/ping_pong.rb +0 -18
@@ -42,7 +42,6 @@ class Stream # :nodoc:
42
42
  @current = node
43
43
  end
44
44
 
45
-
46
45
  ns_keys = namespaces.map { |pre, href| pre }
47
46
  namespaces.delete_if { |pre, href| NS_TO_IGNORE.include? href }
48
47
  @namespace_definitions.push []
@@ -56,14 +55,12 @@ class Stream # :nodoc:
56
55
 
57
56
  deliver(node) if elem == 'stream'
58
57
 
59
- =begin
60
- $stderr.puts "\n\n"
61
- $stderr.puts [elem, attrs, prefix, uri, namespaces].inspect
62
- $stderr.puts @namespaces.inspect
63
- $stderr.puts [@namespaces[[prefix, uri]].prefix, @namespaces[[prefix, uri]].href].inspect if @namespaces[[prefix, uri]]
64
- $stderr.puts node.inspect
65
- $stderr.puts node.document.to_s.gsub(/\n\s*/,'')
66
- =end
58
+ # $stderr.puts "\n\n"
59
+ # $stderr.puts [elem, attrs, prefix, uri, namespaces].inspect
60
+ # $stderr.puts @namespaces.inspect
61
+ # $stderr.puts [@namespaces[[prefix, uri]].prefix, @namespaces[[prefix, uri]].href].inspect if @namespaces[[prefix, uri]]
62
+ # $stderr.puts node.inspect
63
+ # $stderr.puts node.document.to_s.gsub(/\n\s*/,'')
67
64
  end
68
65
 
69
66
  def end_element_namespace(elem, prefix, uri)
@@ -1,42 +1,50 @@
1
1
  module Blather
2
2
 
3
- ##
4
3
  # Base XML Node
5
- # All XML classes subclass XMPPNode
6
- # it allows the addition of helpers
4
+ # All XML classes subclass XMPPNode it allows the addition of helpers
7
5
  class XMPPNode < Nokogiri::XML::Node
6
+ # @private
8
7
  BASE_NAMES = %w[presence message iq].freeze
9
8
 
9
+ # @private
10
10
  @@registrations = {}
11
11
 
12
12
  class_inheritable_accessor :registered_ns,
13
13
  :registered_name
14
14
 
15
- ##
16
- # Lets a subclass register itself
15
+ # Register a new stanza class to a name and/or namespace
17
16
  #
18
17
  # This registers a namespace that is used when looking
19
18
  # up the class name of the object to instantiate when a new
20
19
  # stanza is received
20
+ #
21
+ # @param [#to_s] name the name of the node
22
+ # @param [String, nil] ns the namespace the node belongs to
21
23
  def self.register(name, ns = nil)
22
24
  self.registered_name = name.to_s
23
25
  self.registered_ns = ns
24
26
  @@registrations[[self.registered_name, self.registered_ns]] = self
25
27
  end
26
28
 
27
- ##
28
29
  # Find the class to use given the name and namespace of a stanza
29
- def self.class_from_registration(name, xmlns)
30
+ #
31
+ # @param [#to_s] name the name to lookup
32
+ # @param [String, nil] xmlns the namespace the node belongs to
33
+ # @return [Class, nil] the class appropriate for the name/ns combination
34
+ def self.class_from_registration(name, ns = nil)
30
35
  name = name.to_s
31
- @@registrations[[name, xmlns]] || @@registrations[[name, nil]]
36
+ @@registrations[[name, ns]] || @@registrations[[name, nil]]
32
37
  end
33
38
 
34
- ##
35
- # Looks up the class to use then instantiates an object
36
- # of that class and imports all the <tt>node</tt>'s attributes
37
- # and children into it.
39
+ # Import an XML::Node to the appropriate class
40
+ #
41
+ # Looks up the class the node should be then creates it based on the
42
+ # elements of the XML::Node
43
+ # @param [XML::Node] node the node to import
44
+ # @return the appropriate object based on the node name and namespace
38
45
  def self.import(node)
39
- klass = class_from_registration(node.element_name, (node.namespace.href if node.namespace))
46
+ ns = (node.namespace.href if node.namespace)
47
+ klass = class_from_registration(node.element_name, ns)
40
48
  if klass && klass != self
41
49
  klass.import(node)
42
50
  else
@@ -44,151 +52,71 @@ module Blather
44
52
  end
45
53
  end
46
54
 
47
- ##
48
- # Provides an attribute reader helper. Default behavior is to
49
- # conver the values of the attribute into a symbol. This can
50
- # be turned off by passing <tt>:to_sym => false</tt>
55
+ # Create a new Node object
51
56
  #
52
- # class Node
53
- # attribute_reader :type
54
- # attribute_reader :name, :to_sym => false
55
- # end
56
- #
57
- # n = Node.new
58
- # n[:type] = 'foo'
59
- # n.type == :foo
60
- # n[:name] = 'bar'
61
- # n.name == 'bar'
62
- def self.attribute_reader(*syms)
63
- opts = syms.last.is_a?(Hash) ? syms.pop : {}
64
- convert_str = "val.#{opts[:call]} if val" if opts[:call]
65
- syms.flatten.each do |sym|
66
- class_eval(<<-END, __FILE__, __LINE__)
67
- def #{sym}
68
- val = self[:#{sym}]
69
- #{convert_str}
70
- end
71
- END
72
- end
73
- end
57
+ # @param [String, nil] name the element name
58
+ # @param [XML::Document, nil] doc the document to attach the node to. If
59
+ # not provided one will be created
60
+ # @return a new object with the registered name and namespace
61
+ def self.new(name = nil, doc = nil)
62
+ name ||= self.registered_name
74
63
 
75
- ##
76
- # Provides an attribute writer helper.
77
- #
78
- # class Node
79
- # attribute_writer :type
80
- # end
81
- #
82
- # n = Node.new
83
- # n.type = 'foo'
84
- # n[:type] == 'foo'
85
- def self.attribute_writer(*syms)
86
- syms.flatten.each do |sym|
87
- next if sym.is_a?(Hash)
88
- class_eval(<<-END, __FILE__, __LINE__)
89
- def #{sym}=(value)
90
- self[:#{sym}] = value
91
- end
92
- END
93
- end
64
+ node = super name.to_s, (doc || Nokogiri::XML::Document.new)
65
+ node.document.root = node unless doc
66
+ node.namespace = self.registered_ns unless BASE_NAMES.include?(name.to_s)
67
+ node
94
68
  end
95
69
 
96
- ##
97
- # Provides an attribute accessor helper combining
98
- # <tt>attribute_reader</tt> and <tt>attribute_writer</tt>
99
- #
100
- # class Node
101
- # attribute_accessor :type
102
- # attribute_accessor :name, :to_sym => false
103
- # end
70
+ # Helper method to read an attribute
104
71
  #
105
- # n = Node.new
106
- # n.type = 'foo'
107
- # n.type == :foo
108
- # n.name = 'bar'
109
- # n.name == 'bar'
110
- def self.attribute_accessor(*syms)
111
- attribute_reader *syms
112
- attribute_writer *syms
72
+ # @param [#to_sym] attr_name the name of the attribute
73
+ # @param [String, Symbol, nil] to_call the name of the method to call on
74
+ # the returned value
75
+ # @return nil or the value
76
+ def read_attr(attr_name, to_call = nil)
77
+ val = self[attr_name.to_sym]
78
+ val && to_call ? val.__send__(to_call) : val
113
79
  end
114
80
 
115
- ##
116
- # Provides a content reader helper that returns the content of a node
117
- # +method+ is the method to create
118
- # +conversion+ is a method to call on the content before sending it back
119
- # +node+ is the name of the content node (this defaults to the method name)
81
+ # Helper method to write a value to an attribute
120
82
  #
121
- # class Node
122
- # content_attr_reader :body
123
- # content_attr_reader :type, :to_sym
124
- # content_attr_reader :id, :to_i, :identity
125
- # end
126
- #
127
- # n = Node.new 'foo'
128
- # n.to_s == "<foo><body>foobarbaz</body><type>error</type><identity>1000</identity></foo>"
129
- # n.body == 'foobarbaz'
130
- # n.type == :error
131
- # n.id == 1000
132
- def self.content_attr_reader(method, conversion = nil, node = nil)
133
- node ||= method
134
- conversion = "val.#{conversion} if val.respond_to?(:#{conversion})" if conversion
135
- class_eval(<<-END, __FILE__, __LINE__)
136
- def #{method}
137
- val = content_from :#{node}
138
- #{conversion}
139
- end
140
- END
83
+ # @param [#to_sym] attr_name the name of the attribute
84
+ # @param [#to_s] value the value to set the attribute to
85
+ def write_attr(attr_name, value)
86
+ self[attr_name.to_sym] = value
141
87
  end
142
88
 
143
- ##
144
- # Provides a content writer helper that creates or updates the content of a node
145
- # +method+ is the method to create
146
- # +node+ is the name of the node to create (defaults to the method name)
147
- #
148
- # class Node
149
- # content_attr_writer :body
150
- # content_attr_writer :id, :identity
151
- # end
89
+ # Helper method to read the content of a node
152
90
  #
153
- # n = Node.new 'foo'
154
- # n.body = 'thebodytext'
155
- # n.id = 'id-text'
156
- # n.to_s == '<foo><body>thebodytext</body><identity>id-text</identity></foo>'
157
- def self.content_attr_writer(method, node = nil)
158
- node ||= method
159
- class_eval(<<-END, __FILE__, __LINE__)
160
- def #{method}=(val)
161
- set_content_for :#{node}, val
162
- end
163
- END
164
- end
165
-
166
- ##
167
- # Provides a quick way of building +content_attr_reader+ and +content_attr_writer+
168
- # for the same method and node
169
- def self.content_attr_accessor(method, conversion = nil, node = nil)
170
- content_attr_reader method, conversion, node
171
- content_attr_writer method, node
91
+ # @param [#to_sym] node the name of the node
92
+ # @param [String, Symbol, nil] to_call the name of the method to call on
93
+ # the returned value
94
+ # @return nil or the value
95
+ def read_content(node, to_call = nil)
96
+ val = content_from node.to_sym
97
+ val && to_call ? val.__send__(to_call) : val
172
98
  end
173
99
 
174
- ##
175
- # Automatically sets the namespace registered by the subclass
176
- def self.new(name = nil, doc = nil)
177
- name ||= self.registered_name
178
-
179
- node = super name.to_s, (doc || Nokogiri::XML::Document.new)
180
- node.document.root = node unless doc
181
- node.namespace = self.registered_ns unless BASE_NAMES.include?(name.to_s)
182
- node
183
- end
184
-
185
- ##
186
- # Quickway of turning itself into a proper object
100
+ # Turn the object into a proper stanza
101
+ #
102
+ # @return a stanza object
187
103
  def to_stanza
188
104
  self.class.import self
189
105
  end
190
106
 
107
+ # @private
191
108
  alias_method :nokogiri_namespace=, :namespace=
109
+ # Attach a namespace to the node
110
+ #
111
+ # @overload namespace=(ns)
112
+ # Attach an already created XML::Namespace
113
+ # @param [XML::Namespace] ns the namespace object
114
+ # @overload namespace=(ns)
115
+ # Create a new namespace and attach it
116
+ # @param [String] ns the namespace uri
117
+ # @overload namespace=(namespaces)
118
+ # Createa and add new namespaces from a hash
119
+ # @param [Hash] namespaces a hash of prefix => uri pairs
192
120
  def namespace=(namespaces)
193
121
  case namespaces
194
122
  when Nokogiri::XML::Namespace
@@ -206,34 +134,46 @@ module Blather
206
134
  end
207
135
  end
208
136
 
137
+ # Helper method to get the node's namespace
138
+ #
139
+ # @return [XML::Namespace, nil] The node's namespace object if it exists
209
140
  def namespace_href
210
141
  namespace.href if namespace
211
142
  end
212
143
 
213
- ##
214
144
  # Remove a child with the name and (optionally) namespace given
145
+ #
146
+ # @param [String] name the name or xpath of the node to remove
147
+ # @param [String, nil] ns the namespace the node is in
215
148
  def remove_child(name, ns = nil)
216
149
  child = xpath(name, ns).first
217
150
  child.remove if child
218
151
  end
219
152
 
220
- ##
221
- # Remove all children with a given name
153
+ # Remove all children with a given name regardless of namespace
154
+ #
155
+ # @param [String] name the name of the nodes to remove
222
156
  def remove_children(name)
223
157
  xpath("./*[local-name()='#{name}']").remove
224
158
  end
225
159
 
226
- ##
227
- # Pull the content from a child
160
+ # The content of the named node
161
+ #
162
+ # @param [String] name the name or xpath of the node
163
+ # @param [String, nil] ns the namespace the node is in
164
+ # @return [String, nil] the content of the node
228
165
  def content_from(name, ns = nil)
229
166
  child = xpath(name, ns).first
230
167
  child.content if child
231
168
  end
232
169
 
233
- ##
234
170
  # Sets the content for the specified node.
235
171
  # If the node exists it is updated. If not a new node is created
236
- # If the node exists and the content is nil, the node will be removed entirely
172
+ # If the node exists and the content is nil, the node will be removed
173
+ # entirely
174
+ #
175
+ # @param [String] node the name of the node to update/create
176
+ # @param [String, nil] content the content to set within the node
237
177
  def set_content_for(node, content = nil)
238
178
  if content
239
179
  child = xpath(node).first
@@ -246,8 +186,10 @@ module Blather
246
186
 
247
187
  alias_method :copy, :dup
248
188
 
249
- ##
250
- # Inherit all of <tt>stanza</tt>'s attributes and children
189
+ # Inherit the attributes and children of an XML::Node
190
+ #
191
+ # @param [XML::Node] stanza the node to inherit
192
+ # @return [self]
251
193
  def inherit(stanza)
252
194
  set_namespace stanza.namespace if stanza.namespace
253
195
  inherit_attrs stanza.attributes
@@ -259,12 +201,18 @@ module Blather
259
201
  self
260
202
  end
261
203
 
262
- ##
263
- # Inherit only <tt>stanza</tt>'s attributes
204
+ # Inherit a set of attributes
205
+ #
206
+ # @param [Hash] attrs a hash of attributes to set on the node
207
+ # @return [self]
264
208
  def inherit_attrs(attrs)
265
209
  attrs.each { |name, value| self[name] = value }
266
210
  self
267
211
  end
268
- end #XMPPNode
269
212
 
270
- end
213
+ def inspect
214
+ self.to_xml
215
+ end
216
+ end # XMPPNode
217
+
218
+ end # Blather
@@ -164,7 +164,7 @@ describe Blather::Client do
164
164
  @client.receive_data stanza
165
165
  end
166
166
 
167
- it 'allows for passing to the next handler in the heirarchy' do
167
+ it 'allows for passing to the next handler in the hierarchy' do
168
168
  stanza = Blather::Stanza::Iq::Query.new
169
169
  response = mock(:query => nil, :iq => nil)
170
170
  @client.register_handler(:query) do |_|
@@ -2,15 +2,15 @@ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
3
  describe Blather::BlatherError do
4
4
  it 'is handled by :error' do
5
- Blather::BlatherError.new.handler_heirarchy.must_equal [:error]
5
+ Blather::BlatherError.new.handler_hierarchy.must_equal [:error]
6
6
  end
7
7
  end
8
8
 
9
9
  describe 'Blather::ParseError' do
10
10
  before { @error = Blather::ParseError.new('</generate-parse-error>"') }
11
11
 
12
- it 'is registers with the handler heirarchy' do
13
- @error.handler_heirarchy.must_equal [:parse_error, :error]
12
+ it 'is registers with the handler hierarchy' do
13
+ @error.handler_hierarchy.must_equal [:parse_error, :error]
14
14
  end
15
15
 
16
16
  it 'contains the error message' do
@@ -22,8 +22,8 @@ end
22
22
  describe 'Blather::UnknownResponse' do
23
23
  before { @error = Blather::UnknownResponse.new(Blather::XMPPNode.new('foo-bar')) }
24
24
 
25
- it 'is registers with the handler heirarchy' do
26
- @error.handler_heirarchy.must_equal [:unknown_response_error, :error]
25
+ it 'is registers with the handler hierarchy' do
26
+ @error.handler_hierarchy.must_equal [:unknown_response_error, :error]
27
27
  end
28
28
 
29
29
  it 'holds on to a copy of the failure node' do
@@ -73,4 +73,60 @@ describe Blather::Stanza::Message do
73
73
  Blather::Stanza::Message.new.must_respond_to :"#{valid_type}?"
74
74
  end
75
75
  end
76
+
77
+ it 'ensures an html node exists when asked for xhtml_node' do
78
+ search_args = [
79
+ '/message/html_ns:html',
80
+ {:html_ns => Blather::Stanza::Message::HTML_NS}
81
+ ]
82
+ msg = Blather::Stanza::Message.new
83
+ msg.find_first(*search_args).must_be_nil
84
+
85
+ msg.xhtml_node
86
+ msg.find_first(*search_args).wont_be_nil
87
+ end
88
+
89
+ it 'ensures a body node exists when asked for xhtml_node' do
90
+ search_args = [
91
+ '/message/html_ns:html/body_ns:body',
92
+ {:html_ns => Blather::Stanza::Message::HTML_NS,
93
+ :body_ns => Blather::Stanza::Message::HTML_BODY_NS}
94
+ ]
95
+ msg = Blather::Stanza::Message.new
96
+ msg.find_first(*search_args).must_be_nil
97
+
98
+ msg.xhtml_node
99
+ msg.find_first(*search_args).wont_be_nil
100
+ end
101
+
102
+ it 'returns an existing node when asked for xhtml_node' do
103
+ msg = Blather::Stanza::Message.new
104
+ msg << (h = Blather::XMPPNode.new('html', msg.document))
105
+ h.namespace = Blather::Stanza::Message::HTML_NS
106
+ h << (b = Blather::XMPPNode.new('body', msg.document))
107
+ b.namespace = Blather::Stanza::Message::HTML_BODY_NS
108
+
109
+ msg.xhtml_node.must_equal(b)
110
+ end
111
+
112
+ it 'has an xhtml setter' do
113
+ msg = Blather::Stanza::Message.new
114
+ xhtml = "<some>xhtml</some>"
115
+ msg.xhtml = xhtml
116
+ msg.xhtml_node.content.strip.must_equal(xhtml)
117
+ end
118
+
119
+ it 'sets valid xhtml even if the input is not valid' do
120
+ msg = Blather::Stanza::Message.new
121
+ xhtml = "<some>xhtml"
122
+ msg.xhtml = xhtml
123
+ msg.xhtml_node.content.strip.must_equal("<some>xhtml</some>")
124
+ end
125
+
126
+ it 'has an xhtml getter' do
127
+ msg = Blather::Stanza::Message.new
128
+ xhtml = "<some>xhtml</some>"
129
+ msg.xhtml = xhtml
130
+ msg.xhtml.must_equal(xhtml)
131
+ end
76
132
  end