blather 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +41 -12
  3. data/examples/echo.rb +1 -1
  4. data/examples/execute.rb +0 -5
  5. data/examples/pubsub/cli.rb +64 -0
  6. data/examples/pubsub/ping_pong.rb +18 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/xmpp4r/echo.rb +35 -0
  9. data/lib/blather.rb +35 -12
  10. data/lib/blather/client.rb +1 -1
  11. data/lib/blather/client/client.rb +19 -13
  12. data/lib/blather/client/dsl.rb +16 -0
  13. data/lib/blather/client/dsl/pubsub.rb +133 -0
  14. data/lib/blather/core_ext/active_support.rb +1 -117
  15. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
  16. data/lib/blather/core_ext/nokogiri.rb +35 -0
  17. data/lib/blather/errors.rb +3 -20
  18. data/lib/blather/errors/sasl_error.rb +3 -1
  19. data/lib/blather/errors/stanza_error.rb +10 -17
  20. data/lib/blather/errors/stream_error.rb +11 -14
  21. data/lib/blather/jid.rb +1 -0
  22. data/lib/blather/roster.rb +9 -0
  23. data/lib/blather/roster_item.rb +6 -1
  24. data/lib/blather/stanza.rb +35 -18
  25. data/lib/blather/stanza/disco.rb +7 -1
  26. data/lib/blather/stanza/disco/disco_info.rb +45 -33
  27. data/lib/blather/stanza/disco/disco_items.rb +32 -21
  28. data/lib/blather/stanza/iq.rb +13 -8
  29. data/lib/blather/stanza/iq/query.rb +16 -8
  30. data/lib/blather/stanza/iq/roster.rb +33 -22
  31. data/lib/blather/stanza/message.rb +20 -31
  32. data/lib/blather/stanza/presence.rb +3 -5
  33. data/lib/blather/stanza/presence/status.rb +13 -21
  34. data/lib/blather/stanza/presence/subscription.rb +11 -16
  35. data/lib/blather/stanza/pubsub.rb +63 -0
  36. data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
  37. data/lib/blather/stanza/pubsub/create.rb +43 -0
  38. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  39. data/lib/blather/stanza/pubsub/event.rb +77 -0
  40. data/lib/blather/stanza/pubsub/items.rb +63 -0
  41. data/lib/blather/stanza/pubsub/publish.rb +58 -0
  42. data/lib/blather/stanza/pubsub/retract.rb +53 -0
  43. data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
  44. data/lib/blather/stanza/pubsub/subscription.rb +66 -0
  45. data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
  46. data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
  47. data/lib/blather/stanza/pubsub_owner.rb +41 -0
  48. data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
  49. data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
  50. data/lib/blather/stream.rb +76 -168
  51. data/lib/blather/stream/client.rb +1 -2
  52. data/lib/blather/stream/component.rb +9 -5
  53. data/lib/blather/stream/features.rb +53 -0
  54. data/lib/blather/stream/features/resource.rb +63 -0
  55. data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
  56. data/lib/blather/stream/features/session.rb +44 -0
  57. data/lib/blather/stream/features/tls.rb +28 -0
  58. data/lib/blather/stream/parser.rb +70 -46
  59. data/lib/blather/xmpp_node.rb +113 -52
  60. data/spec/blather/client/client_spec.rb +44 -58
  61. data/spec/blather/client/dsl/pubsub_spec.rb +465 -0
  62. data/spec/blather/client/dsl_spec.rb +19 -6
  63. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  64. data/spec/blather/errors/sasl_error_spec.rb +8 -8
  65. data/spec/blather/errors/stanza_error_spec.rb +25 -33
  66. data/spec/blather/errors/stream_error_spec.rb +21 -16
  67. data/spec/blather/errors_spec.rb +4 -11
  68. data/spec/blather/jid_spec.rb +31 -30
  69. data/spec/blather/roster_item_spec.rb +34 -23
  70. data/spec/blather/roster_spec.rb +27 -12
  71. data/spec/blather/stanza/discos/disco_info_spec.rb +61 -42
  72. data/spec/blather/stanza/discos/disco_items_spec.rb +47 -35
  73. data/spec/blather/stanza/iq/query_spec.rb +34 -11
  74. data/spec/blather/stanza/iq/roster_spec.rb +47 -30
  75. data/spec/blather/stanza/iq_spec.rb +19 -14
  76. data/spec/blather/stanza/message_spec.rb +30 -17
  77. data/spec/blather/stanza/presence/status_spec.rb +43 -20
  78. data/spec/blather/stanza/presence/subscription_spec.rb +41 -21
  79. data/spec/blather/stanza/presence_spec.rb +34 -21
  80. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  81. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  82. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  83. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  84. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  85. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  86. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  87. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  88. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  89. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  90. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  92. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  93. data/spec/blather/stanza/pubsub_spec.rb +62 -0
  94. data/spec/blather/stanza_spec.rb +53 -38
  95. data/spec/blather/stream/client_spec.rb +231 -88
  96. data/spec/blather/stream/component_spec.rb +14 -5
  97. data/spec/blather/stream/parser_spec.rb +145 -0
  98. data/spec/blather/xmpp_node_spec.rb +192 -96
  99. data/spec/fixtures/pubsub.rb +311 -0
  100. data/spec/spec_helper.rb +5 -4
  101. metadata +54 -18
  102. data/Rakefile +0 -139
  103. data/ext/extconf.rb +0 -65
  104. data/ext/push_parser.c +0 -209
  105. data/lib/blather/core_ext/libxml.rb +0 -28
  106. data/lib/blather/stream/resource.rb +0 -48
  107. data/lib/blather/stream/session.rb +0 -36
  108. data/lib/blather/stream/stream_handler.rb +0 -39
  109. data/lib/blather/stream/tls.rb +0 -33
  110. data/spec/blather/core_ext/libxml_spec.rb +0 -58
@@ -1,120 +1,4 @@
1
- # Thanks to Rails ActiveSupport for everything in this file
2
-
3
- # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
4
- # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
5
- # to, for example, an array without those additions being shared with either their parent, siblings, or
6
- # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
7
- class Class # :nodoc:
8
- def class_inheritable_reader(*syms)
9
- syms.each do |sym|
10
- next if sym.is_a?(Hash)
11
- class_eval <<-EOS
12
- def self.#{sym}
13
- read_inheritable_attribute(:#{sym})
14
- end
15
-
16
- def #{sym}
17
- self.class.#{sym}
18
- end
19
- EOS
20
- end
21
- end
22
-
23
- def class_inheritable_writer(*syms)
24
- syms.each do |sym|
25
- class_eval <<-EOS
26
- def self.#{sym}=(obj)
27
- write_inheritable_attribute(:#{sym}, obj)
28
- end
29
- EOS
30
- end
31
- end
32
-
33
- def class_inheritable_array_writer(*syms)
34
- syms.each do |sym|
35
- class_eval <<-EOS
36
- def self.#{sym}=(obj)
37
- write_inheritable_array(:#{sym}, obj)
38
- end
39
- EOS
40
- end
41
- end
42
-
43
- def class_inheritable_hash_writer(*syms)
44
- syms.each do |sym|
45
- class_eval <<-EOS
46
- def self.#{sym}=(obj)
47
- write_inheritable_hash(:#{sym}, obj)
48
- end
49
- EOS
50
- end
51
- end
52
-
53
- def class_inheritable_accessor(*syms)
54
- class_inheritable_reader(*syms)
55
- class_inheritable_writer(*syms)
56
- end
57
-
58
- def class_inheritable_array(*syms)
59
- class_inheritable_reader(*syms)
60
- class_inheritable_array_writer(*syms)
61
- end
62
-
63
- def class_inheritable_hash(*syms)
64
- class_inheritable_reader(*syms)
65
- class_inheritable_hash_writer(*syms)
66
- end
67
-
68
- def inheritable_attributes
69
- @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
70
- end
71
-
72
- def write_inheritable_attribute(key, value)
73
- if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
74
- @inheritable_attributes = {}
75
- end
76
- inheritable_attributes[key] = value
77
- end
78
-
79
- def write_inheritable_array(key, elements)
80
- write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
81
- write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
82
- end
83
-
84
- def write_inheritable_hash(key, hash)
85
- write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
86
- write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
87
- end
88
-
89
- def read_inheritable_attribute(key)
90
- inheritable_attributes[key]
91
- end
92
-
93
- def reset_inheritable_attributes
94
- @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
95
- end
96
-
97
- private
98
- # Prevent this constant from being created multiple times
99
- EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
100
-
101
- def inherited_with_inheritable_attributes(child)
102
- inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
103
-
104
- if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
105
- new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
106
- else
107
- new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
108
- memo.update(key => value.duplicable? ? value.dup : value)
109
- end
110
- end
111
-
112
- child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
113
- end
114
-
115
- alias inherited_without_inheritable_attributes inherited
116
- alias inherited inherited_with_inheritable_attributes
117
- end #Class
1
+ require File.join(File.dirname(__FILE__), 'active_support', 'inheritable_attributes')
118
2
 
119
3
  class Object # :nodoc:
120
4
  def duplicable?; true; end
@@ -0,0 +1,117 @@
1
+ # Thanks to Rails ActiveSupport for everything in this file
2
+
3
+ # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
4
+ # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
5
+ # to, for example, an array without those additions being shared with either their parent, siblings, or
6
+ # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
7
+ class Class # :nodoc:
8
+ def class_inheritable_reader(*syms)
9
+ syms.each do |sym|
10
+ next if sym.is_a?(Hash)
11
+ class_eval <<-EOS
12
+ def self.#{sym}
13
+ read_inheritable_attribute(:#{sym})
14
+ end
15
+
16
+ def #{sym}
17
+ self.class.#{sym}
18
+ end
19
+ EOS
20
+ end
21
+ end
22
+
23
+ def class_inheritable_writer(*syms)
24
+ syms.each do |sym|
25
+ class_eval <<-EOS
26
+ def self.#{sym}=(obj)
27
+ write_inheritable_attribute(:#{sym}, obj)
28
+ end
29
+ EOS
30
+ end
31
+ end
32
+
33
+ def class_inheritable_array_writer(*syms)
34
+ syms.each do |sym|
35
+ class_eval <<-EOS
36
+ def self.#{sym}=(obj)
37
+ write_inheritable_array(:#{sym}, obj)
38
+ end
39
+ EOS
40
+ end
41
+ end
42
+
43
+ def class_inheritable_hash_writer(*syms)
44
+ syms.each do |sym|
45
+ class_eval <<-EOS
46
+ def self.#{sym}=(obj)
47
+ write_inheritable_hash(:#{sym}, obj)
48
+ end
49
+ EOS
50
+ end
51
+ end
52
+
53
+ def class_inheritable_accessor(*syms)
54
+ class_inheritable_reader(*syms)
55
+ class_inheritable_writer(*syms)
56
+ end
57
+
58
+ def class_inheritable_array(*syms)
59
+ class_inheritable_reader(*syms)
60
+ class_inheritable_array_writer(*syms)
61
+ end
62
+
63
+ def class_inheritable_hash(*syms)
64
+ class_inheritable_reader(*syms)
65
+ class_inheritable_hash_writer(*syms)
66
+ end
67
+
68
+ def inheritable_attributes
69
+ @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
70
+ end
71
+
72
+ def write_inheritable_attribute(key, value)
73
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
74
+ @inheritable_attributes = {}
75
+ end
76
+ inheritable_attributes[key] = value
77
+ end
78
+
79
+ def write_inheritable_array(key, elements)
80
+ write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
81
+ write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
82
+ end
83
+
84
+ def write_inheritable_hash(key, hash)
85
+ write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
86
+ write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
87
+ end
88
+
89
+ def read_inheritable_attribute(key)
90
+ inheritable_attributes[key]
91
+ end
92
+
93
+ def reset_inheritable_attributes
94
+ @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
95
+ end
96
+
97
+ private
98
+ # Prevent this constant from being created multiple times
99
+ EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
100
+
101
+ def inherited_with_inheritable_attributes(child)
102
+ inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
103
+
104
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
105
+ new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
106
+ else
107
+ new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
108
+ memo.update(key => value.duplicable? ? value.dup : value)
109
+ end
110
+ end
111
+
112
+ child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
113
+ end
114
+
115
+ alias inherited_without_inheritable_attributes inherited
116
+ alias inherited inherited_with_inheritable_attributes
117
+ end #Class
@@ -0,0 +1,35 @@
1
+ module Nokogiri # :nodoc:
2
+ module XML
3
+
4
+ class Node
5
+ alias_method :element_name, :name
6
+ alias_method :element_name=, :name=
7
+
8
+ alias_method :attr_set, :[]=
9
+ def []=(name, value)
10
+ name = name.to_s
11
+ value.nil? ? remove_attribute(name) : attr_set(name, value.to_s)
12
+ end
13
+
14
+ # alias_method :attr_get, :[]
15
+ # def [](name)
16
+ # attr_get name.to_s
17
+ # end
18
+
19
+ alias_method :nokogiri_xpath, :xpath
20
+ def xpath(*paths)
21
+ paths[0] = paths[0].to_s
22
+ if paths.size > 1 && (namespaces = paths.pop).is_a?(Hash)
23
+ paths << namespaces.inject({}) { |h,v| h[v[0].to_s] = v[1]; h }
24
+ end
25
+ nokogiri_xpath *paths
26
+ end
27
+ alias_method :find, :xpath
28
+
29
+ def find_first(*paths)
30
+ xpath(*paths).first
31
+ end
32
+ end
33
+
34
+ end #XML
35
+ end #Blather
@@ -34,31 +34,14 @@ module Blather
34
34
  end
35
35
  end
36
36
 
37
- ##
38
- # TLS negotiations broke down
39
- class TLSFailure < BlatherError
40
- register :tls_failure
41
- end
42
-
43
- class ParseWarning < BlatherError
44
- register :parse_warning
45
- attr_reader :libxml_error, :message
46
-
47
- def initialize(err)
48
- @libxml_error = err
49
- @message = err.to_s
50
- end
51
- end
52
-
53
37
  ##
54
38
  # Something bad happened while parsing the incoming stream
55
39
  class ParseError < BlatherError
56
40
  register :parse_error
57
- attr_reader :libxml_error, :message
41
+ attr_reader :message
58
42
 
59
- def initialize(err)
60
- @libxml_error = err
61
- @message = err.to_s
43
+ def initialize(msg)
44
+ @message = msg.to_s
62
45
  end
63
46
  end
64
47
  end
@@ -1,6 +1,8 @@
1
1
  module Blather
2
2
 
3
3
  class SASLError < BlatherError
4
+ SASL_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
5
+
4
6
  class_inheritable_accessor :err_name
5
7
  @@registrations = {}
6
8
 
@@ -16,7 +18,7 @@ class SASLError < BlatherError
16
18
  end
17
19
 
18
20
  def name
19
- @node.children.first.element_name.gsub('-', '_').to_sym if @node
21
+ @node.find_first('err_ns:*', :err_ns => SASL_ERR_NS).element_name.gsub('-', '_').to_sym if @node
20
22
  end
21
23
  end #SASLError
22
24
 
@@ -4,6 +4,7 @@ module Blather
4
4
  # Stanza errors
5
5
  # RFC3920 Section 9.3 (http://xmpp.org/rfcs/rfc3920.html#stanzas-error)
6
6
  class StanzaError < BlatherError
7
+ STANZA_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-stanzas'
7
8
  VALID_TYPES = [:cancel, :continue, :modify, :auth, :wait]
8
9
 
9
10
  register :stanza_error
@@ -19,9 +20,9 @@ class StanzaError < BlatherError
19
20
 
20
21
  error_node = node.find_first '//*[local-name()="error"]'
21
22
 
22
- name = error_node.find_first('child::*[name()!="text"]', 'urn:ietf:params:xml:ns:xmpp-stanzas').element_name
23
+ name = error_node.find_first('child::*[name()!="text"]', STANZA_ERR_NS).element_name
23
24
  type = error_node['type']
24
- text = node.find_first '//err_ns:text', :err_ns => 'urn:ietf:params:xml:ns:xmpp-stanzas'
25
+ text = node.find_first 'descendant::*[name()="text"]', STANZA_ERR_NS
25
26
  text = text.content if text
26
27
 
27
28
  extras = error_node.find("descendant::*[name()!='text' and name()!='#{name}']").map { |n| n }
@@ -59,27 +60,19 @@ class StanzaError < BlatherError
59
60
  # Creates an XML node from the error
60
61
  def to_node
61
62
  node = self.original.reply
63
+ node.type = 'error'
64
+ node << (error_node = XMPPNode.new('error'))
62
65
 
63
- error_node = XMPPNode.new 'error'
64
- err = XMPPNode.new(@name)
66
+ error_node << (err = XMPPNode.new(@name, error_node.document))
65
67
  err.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
66
- error_node << err
67
68
 
68
69
  if self.text
69
- text = XMPPNode.new('text')
70
+ error_node << (text = XMPPNode.new('text', error_node.document))
70
71
  text.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
71
- text << self.text
72
- error_node << text
73
- end
74
-
75
- self.extras.each do |extra|
76
- extra_copy = extra.copy
77
- extra_copy.namespace = extra.namespace
78
- error_node << extra_copy
72
+ text.content = self.text
79
73
  end
80
74
 
81
- node << error_node
82
- node.type = 'error'
75
+ self.extras.each { |extra| error_node << extra.dup }
83
76
  node
84
77
  end
85
78
 
@@ -90,7 +83,7 @@ class StanzaError < BlatherError
90
83
  end
91
84
 
92
85
  def inspect # :nodoc:
93
- "Stanza Error (#{@name}): #{self.text}"
86
+ "Stanza Error (#{@name}): #{self.text} [#{self.extras}]"
94
87
  end
95
88
  alias_method :to_s, :inspect # :nodoc:
96
89
  end #StanzaError
@@ -4,6 +4,8 @@ module Blather
4
4
  # Stream Errors
5
5
  # RFC3920 Section 9.3 (http://xmpp.org/rfcs/rfc3920.html#streams-error-rules)
6
6
  class StreamError < BlatherError
7
+ STREAM_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-streams'
8
+
7
9
  register :stream_error
8
10
 
9
11
  attr_reader :text, :extras
@@ -12,11 +14,12 @@ class StreamError < BlatherError
12
14
  # Factory method for instantiating the proper class
13
15
  # for the error
14
16
  def self.import(node)
15
- name = node.find_first('descendant::*[name()!="text"]', 'urn:ietf:params:xml:ns:xmpp-streams').element_name
16
- text = node.find_first '//err_ns:text', :err_ns => 'urn:ietf:params:xml:ns:xmpp-streams'
17
+ name = node.find_first('descendant::*[name()!="text"]', STREAM_ERR_NS).element_name
18
+
19
+ text = node.find_first 'descendant::*[name()="text"]', STREAM_ERR_NS
17
20
  text = text.content if text
18
21
 
19
- extras = node.find("descendant::*[name()!='text' and name()!='#{name}']").map { |n| n }
22
+ extras = node.find("descendant::*[namespace-uri()!='#{STREAM_ERR_NS}']").map { |n| n }
20
23
 
21
24
  self.new name, text, extras
22
25
  end
@@ -41,22 +44,16 @@ class StreamError < BlatherError
41
44
  def to_node
42
45
  node = XMPPNode.new('stream:error')
43
46
 
44
- err = XMPPNode.new(@name)
47
+ node << (err = XMPPNode.new(@name, node.document))
45
48
  err.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
46
- node << err
47
49
 
48
50
  if self.text
49
- text = XMPPNode.new('text')
51
+ node << (text = XMPPNode.new('text', node.document))
50
52
  text.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
51
- text << self.text
52
- node << text
53
+ text.content = self.text
53
54
  end
54
55
 
55
- self.extras.each do |extra|
56
- extra_copy = extra.copy
57
- extra_copy.namespace = extra.namespace
58
- node << extra_copy
59
- end
56
+ self.extras.each { |extra| node << extra.dup }
60
57
  node
61
58
  end
62
59
 
@@ -67,7 +64,7 @@ class StreamError < BlatherError
67
64
  end
68
65
 
69
66
  def inspect # :nodoc:
70
- "Stream Error (#{@name}): #{self.text}"
67
+ "Stream Error (#{@name}): #{self.text}" + (self.extras.empty? ? '' : " [#{self.extras}]")
71
68
  end
72
69
  alias_method :to_s, :inspect # :nodoc:
73
70
  end #StreamError