blather 0.4.7 → 0.4.8

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