sprsquish-blather 0.3.4 → 0.4.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.
- data/LICENSE +1 -1
- data/README.rdoc +41 -12
- data/examples/echo.rb +1 -1
- data/examples/execute.rb +0 -5
- data/examples/pubsub/cli.rb +64 -0
- data/examples/pubsub/ping_pong.rb +18 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/lib/blather/client/client.rb +19 -13
- data/lib/blather/client/dsl/pubsub.rb +133 -0
- data/lib/blather/client/dsl.rb +16 -0
- data/lib/blather/client.rb +1 -1
- data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
- data/lib/blather/core_ext/active_support.rb +1 -117
- data/lib/blather/core_ext/nokogiri.rb +35 -0
- data/lib/blather/errors/sasl_error.rb +3 -1
- data/lib/blather/errors/stanza_error.rb +10 -17
- data/lib/blather/errors/stream_error.rb +11 -14
- data/lib/blather/errors.rb +3 -20
- data/lib/blather/jid.rb +1 -0
- data/lib/blather/roster.rb +9 -0
- data/lib/blather/roster_item.rb +6 -1
- data/lib/blather/stanza/disco/disco_info.rb +45 -33
- data/lib/blather/stanza/disco/disco_items.rb +32 -21
- data/lib/blather/stanza/disco.rb +7 -1
- data/lib/blather/stanza/iq/query.rb +16 -8
- data/lib/blather/stanza/iq/roster.rb +33 -22
- data/lib/blather/stanza/iq.rb +13 -8
- data/lib/blather/stanza/message.rb +20 -31
- data/lib/blather/stanza/presence/status.rb +13 -21
- data/lib/blather/stanza/presence/subscription.rb +11 -16
- data/lib/blather/stanza/presence.rb +3 -5
- data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
- data/lib/blather/stanza/pubsub/create.rb +43 -0
- data/lib/blather/stanza/pubsub/errors.rb +9 -0
- data/lib/blather/stanza/pubsub/event.rb +77 -0
- data/lib/blather/stanza/pubsub/items.rb +63 -0
- data/lib/blather/stanza/pubsub/publish.rb +58 -0
- data/lib/blather/stanza/pubsub/retract.rb +53 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
- data/lib/blather/stanza/pubsub/subscription.rb +66 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
- data/lib/blather/stanza/pubsub.rb +63 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
- data/lib/blather/stanza/pubsub_owner.rb +41 -0
- data/lib/blather/stanza.rb +35 -18
- data/lib/blather/stream/client.rb +1 -2
- data/lib/blather/stream/component.rb +9 -5
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
- data/lib/blather/stream/features/session.rb +44 -0
- data/lib/blather/stream/features/tls.rb +28 -0
- data/lib/blather/stream/features.rb +53 -0
- data/lib/blather/stream/parser.rb +70 -46
- data/lib/blather/stream.rb +76 -168
- data/lib/blather/xmpp_node.rb +113 -52
- data/lib/blather.rb +35 -12
- data/spec/blather/client/client_spec.rb +44 -58
- data/spec/blather/client/dsl/pubsub_spec.rb +465 -0
- data/spec/blather/client/dsl_spec.rb +19 -6
- data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
- data/spec/blather/errors/sasl_error_spec.rb +8 -8
- data/spec/blather/errors/stanza_error_spec.rb +25 -33
- data/spec/blather/errors/stream_error_spec.rb +21 -16
- data/spec/blather/errors_spec.rb +4 -11
- data/spec/blather/jid_spec.rb +31 -30
- data/spec/blather/roster_item_spec.rb +34 -23
- data/spec/blather/roster_spec.rb +27 -12
- data/spec/blather/stanza/discos/disco_info_spec.rb +61 -42
- data/spec/blather/stanza/discos/disco_items_spec.rb +47 -35
- data/spec/blather/stanza/iq/query_spec.rb +34 -11
- data/spec/blather/stanza/iq/roster_spec.rb +47 -30
- data/spec/blather/stanza/iq_spec.rb +19 -14
- data/spec/blather/stanza/message_spec.rb +30 -17
- data/spec/blather/stanza/presence/status_spec.rb +43 -20
- data/spec/blather/stanza/presence/subscription_spec.rb +41 -21
- data/spec/blather/stanza/presence_spec.rb +34 -21
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +62 -0
- data/spec/blather/stanza_spec.rb +53 -38
- data/spec/blather/stream/client_spec.rb +231 -88
- data/spec/blather/stream/component_spec.rb +14 -5
- data/spec/blather/stream/parser_spec.rb +145 -0
- data/spec/blather/xmpp_node_spec.rb +192 -96
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +5 -4
- metadata +53 -17
- data/Rakefile +0 -139
- data/ext/extconf.rb +0 -65
- data/ext/push_parser.c +0 -209
- data/lib/blather/core_ext/libxml.rb +0 -28
- data/lib/blather/stream/resource.rb +0 -48
- data/lib/blather/stream/session.rb +0 -36
- data/lib/blather/stream/stream_handler.rb +0 -39
- data/lib/blather/stream/tls.rb +0 -33
- data/spec/blather/core_ext/libxml_spec.rb +0 -58
data/lib/blather/stream.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module Blather
|
2
2
|
|
3
3
|
class Stream < EventMachine::Connection
|
4
|
+
class NoConnection < RuntimeError; end
|
5
|
+
|
6
|
+
STREAM_NS = 'http://etherx.jabber.org/streams'
|
7
|
+
attr_accessor :jid, :password
|
8
|
+
|
4
9
|
##
|
5
10
|
# Start the stream between client and server
|
6
11
|
# [client] must be an object that will respond to #call and #jid=
|
@@ -10,9 +15,35 @@ module Blather
|
|
10
15
|
# [port] (optional) must be the port to connect to. defaults to 5222
|
11
16
|
def self.start(client, jid, pass, host = nil, port = 5222)
|
12
17
|
jid = JID.new jid
|
13
|
-
host
|
18
|
+
if host
|
19
|
+
connect host, port, self, client, jid, pass
|
20
|
+
else
|
21
|
+
require 'resolv'
|
22
|
+
srv = []
|
23
|
+
Resolv::DNS.open { |dns| srv = dns.getresources("_xmpp-client._tcp.#{jid.domain}", Resolv::DNS::Resource::IN::SRV) }
|
24
|
+
if srv.empty?
|
25
|
+
connect jid.domain, port, self, client, jid, pass
|
26
|
+
else
|
27
|
+
srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
|
28
|
+
conn = nil
|
29
|
+
srv.each { |r| break unless (conn = connect(r.target.to_s, r.port, self, client, jid, pass)) === false }
|
30
|
+
conn
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
14
34
|
|
15
|
-
|
35
|
+
##
|
36
|
+
# Attempt a connection
|
37
|
+
# Stream will raise +NoConnection+ if it receives #unbind before #post_init
|
38
|
+
# this catches that and returns false prompting for another attempt
|
39
|
+
def self.connect(host, port, conn, client, jid, pass)
|
40
|
+
EM.connect host, port, conn, client, jid, pass
|
41
|
+
rescue NoConnection
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
[:started, :stopped, :ready, :negotiating].each do |state|
|
46
|
+
define_method("#{state}?") { @state == state }
|
16
47
|
end
|
17
48
|
|
18
49
|
##
|
@@ -21,73 +52,53 @@ module Blather
|
|
21
52
|
# responds to #to_s
|
22
53
|
def send(stanza)
|
23
54
|
#TODO Queue if not ready
|
24
|
-
|
55
|
+
Blather.logger.debug "SENDING: (#{caller[1]}) #{stanza}"
|
25
56
|
send_data stanza.respond_to?(:to_xml) ? stanza.to_xml : stanza.to_s
|
26
57
|
end
|
27
58
|
|
28
|
-
##
|
29
|
-
# True if the stream is in the stopped state
|
30
|
-
def stopped?
|
31
|
-
@state == :stopped
|
32
|
-
end
|
33
|
-
|
34
|
-
##
|
35
|
-
# True when the stream is in the negotiation phase.
|
36
|
-
def negotiating?
|
37
|
-
![:stopped, :ready].include? @state
|
38
|
-
end
|
39
|
-
|
40
|
-
##
|
41
|
-
# True when the stream is ready
|
42
|
-
# The stream is ready immediately after receiving <stream:stream>
|
43
|
-
# and before any feature negotion. Once feature negoation starts
|
44
|
-
# the stream will not be ready until all negotations have completed
|
45
|
-
# successfully.
|
46
|
-
def ready?
|
47
|
-
@state == :ready
|
48
|
-
end
|
49
|
-
|
50
59
|
##
|
51
60
|
# Called by EM.connect to initialize stream variables
|
52
61
|
def initialize(client, jid, pass) # :nodoc:
|
53
62
|
super()
|
54
63
|
|
55
64
|
@error = nil
|
56
|
-
@client = client
|
65
|
+
@receiver = @client = client
|
57
66
|
|
58
67
|
self.jid = jid
|
59
|
-
@
|
60
|
-
|
61
|
-
@to = @jid.domain
|
68
|
+
@to = self.jid.domain
|
69
|
+
@password = pass
|
62
70
|
end
|
63
71
|
|
64
72
|
##
|
65
73
|
# Called when EM completes the connection to the server
|
66
74
|
# this kicks off the starttls/authorize/bind process
|
67
75
|
def connection_completed # :nodoc:
|
68
|
-
# @keepalive = EM::
|
69
|
-
|
70
|
-
dispatch
|
76
|
+
# @keepalive = EM::PeriodicTimer.new(60) { send_data ' ' }
|
77
|
+
start
|
71
78
|
end
|
72
79
|
|
73
80
|
##
|
74
81
|
# Called by EM with data from the wire
|
75
82
|
def receive_data(data) # :nodoc:
|
76
|
-
|
77
|
-
|
78
|
-
@parser
|
83
|
+
Blather.logger.debug "\n#{'-'*30}\n"
|
84
|
+
Blather.logger.debug "<< #{data}"
|
85
|
+
@parser << data
|
79
86
|
|
80
|
-
rescue ParseWarning => e
|
81
|
-
@client.receive_data e
|
82
87
|
rescue ParseError => e
|
83
88
|
@error = e
|
84
|
-
send "<stream:error><xml-not-well-formed xmlns='
|
89
|
+
send "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
|
85
90
|
stop
|
86
91
|
end
|
87
92
|
|
93
|
+
def post_init
|
94
|
+
@connected = true
|
95
|
+
end
|
96
|
+
|
88
97
|
##
|
89
98
|
# Called by EM when the connection is closed
|
90
99
|
def unbind # :nodoc:
|
100
|
+
raise NoConnection unless @connected
|
101
|
+
|
91
102
|
# @keepalive.cancel
|
92
103
|
@state = :stopped
|
93
104
|
@client.receive_data @error if @error
|
@@ -97,54 +108,41 @@ module Blather
|
|
97
108
|
##
|
98
109
|
# Called by the parser with parsed nodes
|
99
110
|
def receive(node) # :nodoc:
|
100
|
-
|
111
|
+
Blather.logger.debug "RECEIVING (#{node.element_name}) #{node}"
|
101
112
|
@node = node
|
102
113
|
|
103
|
-
if @node.
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
114
|
+
if @node.namespace && @node.namespace.prefix == 'stream'
|
115
|
+
case @node.element_name
|
116
|
+
when 'stream'
|
117
|
+
@state = :ready if @state == :stopped
|
118
|
+
return
|
119
|
+
when 'error'
|
120
|
+
handle_stream_error
|
121
|
+
return
|
122
|
+
when 'end'
|
123
|
+
stop
|
124
|
+
return
|
125
|
+
when 'features'
|
126
|
+
@state = :negotiating
|
127
|
+
@receiver = Features.new(
|
128
|
+
self,
|
129
|
+
proc { ready! },
|
130
|
+
proc { |err| @error = err; stop }
|
131
|
+
)
|
132
|
+
end
|
123
133
|
end
|
134
|
+
@receiver.receive_data @node.to_stanza
|
124
135
|
end
|
125
136
|
|
126
137
|
##
|
127
138
|
# Ensure the JID gets attached to the client
|
128
139
|
def jid=(new_jid) # :nodoc:
|
129
|
-
|
140
|
+
Blather.logger.debug "NEW JID: #{new_jid}"
|
130
141
|
@jid = JID.new new_jid
|
131
142
|
@client.jid = @jid
|
132
143
|
end
|
133
144
|
|
134
145
|
protected
|
135
|
-
##
|
136
|
-
# Dispatch based on current state
|
137
|
-
def dispatch
|
138
|
-
__send__ @state
|
139
|
-
end
|
140
|
-
|
141
|
-
##
|
142
|
-
# Start the stream
|
143
|
-
# Each time the stream is started or re-started we need to kill off the old
|
144
|
-
# parser so as not to confuse it
|
145
|
-
def start
|
146
|
-
end
|
147
|
-
|
148
146
|
##
|
149
147
|
# Stop the stream
|
150
148
|
def stop
|
@@ -154,105 +152,15 @@ module Blather
|
|
154
152
|
end
|
155
153
|
end
|
156
154
|
|
157
|
-
##
|
158
|
-
# Called when @state == :stopped to start the stream
|
159
|
-
# Counter intuitive, I know
|
160
|
-
def stopped
|
161
|
-
start
|
162
|
-
end
|
163
|
-
|
164
|
-
##
|
165
|
-
# Called when @state == :ready
|
166
|
-
# Simply passes the stanza to the client
|
167
|
-
def ready
|
168
|
-
@client.receive_data @node.to_stanza
|
169
|
-
end
|
170
|
-
|
171
155
|
def handle_stream_error
|
172
|
-
@error = StreamError.import
|
156
|
+
@error = StreamError.import(@node)
|
173
157
|
stop
|
174
|
-
@state = :error
|
175
158
|
end
|
176
159
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
feature = @features.first
|
182
|
-
LOG.debug "FEATURE: #{feature}"
|
183
|
-
@state = case feature ? feature.namespaces.default.href : nil
|
184
|
-
when 'urn:ietf:params:xml:ns:xmpp-tls' then :establish_tls
|
185
|
-
when 'urn:ietf:params:xml:ns:xmpp-sasl' then :authenticate_sasl
|
186
|
-
when 'urn:ietf:params:xml:ns:xmpp-bind' then :bind_resource
|
187
|
-
when 'urn:ietf:params:xml:ns:xmpp-session' then :establish_session
|
188
|
-
else :ready
|
189
|
-
end
|
190
|
-
|
191
|
-
# Dispatch to the individual feature methods unless
|
192
|
-
# feature negotiation is complete
|
193
|
-
dispatch unless ready?
|
194
|
-
end
|
195
|
-
|
196
|
-
##
|
197
|
-
# Start TLS
|
198
|
-
def establish_tls
|
199
|
-
unless @tls
|
200
|
-
@tls = TLS.new self
|
201
|
-
# on success destroy the TLS object and restart the stream
|
202
|
-
@tls.on_success { LOG.debug "TLS: SUCCESS"; @tls = nil; start }
|
203
|
-
# on failure stop the stream
|
204
|
-
@tls.on_failure { |err| LOG.debug "TLS: FAILURE"; @error = err; stop }
|
205
|
-
|
206
|
-
@node = @features.shift
|
207
|
-
end
|
208
|
-
@tls.handle @node
|
209
|
-
end
|
210
|
-
|
211
|
-
##
|
212
|
-
# Authenticate via SASL
|
213
|
-
def authenticate_sasl
|
214
|
-
unless @sasl
|
215
|
-
@sasl = SASL.new(self, @jid, @pass)
|
216
|
-
# on success destroy the SASL object and restart the stream
|
217
|
-
@sasl.on_success { LOG.debug "SASL SUCCESS"; @sasl = nil; start }
|
218
|
-
# on failure set the error and stop the stream
|
219
|
-
@sasl.on_failure { |err| LOG.debug "SASL FAIL"; @error = err; stop }
|
220
|
-
|
221
|
-
@node = @features.shift
|
222
|
-
end
|
223
|
-
@sasl.handle @node
|
224
|
-
end
|
225
|
-
|
226
|
-
##
|
227
|
-
# Bind to the resource provided by either the client or the server
|
228
|
-
def bind_resource
|
229
|
-
unless @resource
|
230
|
-
@resource = Resource.new self, @jid
|
231
|
-
# on success destroy the Resource object, set the jid, continue along the features dispatch process
|
232
|
-
@resource.on_success { |jid| LOG.debug "RESOURCE: SUCCESS"; @resource = nil; self.jid = jid; @state = :features; dispatch }
|
233
|
-
# on failure end the stream
|
234
|
-
@resource.on_failure { |err| LOG.debug "RESOURCE: FAILURE"; @error = err; stop }
|
235
|
-
|
236
|
-
@node = @features.shift
|
237
|
-
end
|
238
|
-
@resource.handle @node
|
239
|
-
end
|
240
|
-
|
241
|
-
##
|
242
|
-
# Establish the session between client and server
|
243
|
-
def establish_session
|
244
|
-
unless @session
|
245
|
-
@session = Session.new self, @to
|
246
|
-
# on success destroy the session object, let the client know the stream has been started
|
247
|
-
# then continue the features dispatch process
|
248
|
-
@session.on_success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.post_init; @state = :features; dispatch }
|
249
|
-
# on failure end the stream
|
250
|
-
@session.on_failure { |err| LOG.debug "SESSION: FAILURE"; @error = err; stop }
|
251
|
-
|
252
|
-
@node = @features.shift
|
253
|
-
end
|
254
|
-
@session.handle @node
|
160
|
+
def ready!
|
161
|
+
@state = :started
|
162
|
+
@receiver = @client
|
163
|
+
@client.post_init
|
255
164
|
end
|
256
165
|
end
|
257
|
-
|
258
166
|
end
|
data/lib/blather/xmpp_node.rb
CHANGED
@@ -4,13 +4,13 @@ module Blather
|
|
4
4
|
# Base XML Node
|
5
5
|
# All XML classes subclass XMPPNode
|
6
6
|
# it allows the addition of helpers
|
7
|
-
class XMPPNode < XML::Node
|
7
|
+
class XMPPNode < Nokogiri::XML::Node
|
8
8
|
BASE_NAMES = %w[presence message iq].freeze
|
9
9
|
|
10
10
|
@@registrations = {}
|
11
11
|
|
12
|
-
class_inheritable_accessor :
|
13
|
-
:
|
12
|
+
class_inheritable_accessor :registered_ns,
|
13
|
+
:registered_name
|
14
14
|
|
15
15
|
##
|
16
16
|
# Lets a subclass register itself
|
@@ -19,9 +19,9 @@ module Blather
|
|
19
19
|
# up the class name of the object to instantiate when a new
|
20
20
|
# stanza is received
|
21
21
|
def self.register(name, ns = nil)
|
22
|
-
self.
|
23
|
-
self.
|
24
|
-
@@registrations[[self.
|
22
|
+
self.registered_name = name.to_s
|
23
|
+
self.registered_ns = ns
|
24
|
+
@@registrations[[self.registered_name, self.registered_ns]] = self
|
25
25
|
end
|
26
26
|
|
27
27
|
##
|
@@ -36,7 +36,7 @@ module Blather
|
|
36
36
|
# of that class and imports all the <tt>node</tt>'s attributes
|
37
37
|
# and children into it.
|
38
38
|
def self.import(node)
|
39
|
-
klass = class_from_registration(node.element_name, node.namespace)
|
39
|
+
klass = class_from_registration(node.element_name, (node.namespace.href if node.namespace))
|
40
40
|
if klass && klass != self
|
41
41
|
klass.import(node)
|
42
42
|
else
|
@@ -55,16 +55,18 @@ module Blather
|
|
55
55
|
# end
|
56
56
|
#
|
57
57
|
# n = Node.new
|
58
|
-
# n
|
58
|
+
# n[:type] = 'foo'
|
59
59
|
# n.type == :foo
|
60
|
-
# n
|
60
|
+
# n[:name] = 'bar'
|
61
61
|
# n.name == 'bar'
|
62
62
|
def self.attribute_reader(*syms)
|
63
63
|
opts = syms.last.is_a?(Hash) ? syms.pop : {}
|
64
|
+
convert_str = "val.#{opts[:call]} if val" if opts[:call]
|
64
65
|
syms.flatten.each do |sym|
|
65
66
|
class_eval(<<-END, __FILE__, __LINE__)
|
66
67
|
def #{sym}
|
67
|
-
|
68
|
+
val = self[:#{sym}]
|
69
|
+
#{convert_str}
|
68
70
|
end
|
69
71
|
END
|
70
72
|
end
|
@@ -79,13 +81,13 @@ module Blather
|
|
79
81
|
#
|
80
82
|
# n = Node.new
|
81
83
|
# n.type = 'foo'
|
82
|
-
# n
|
84
|
+
# n[:type] == 'foo'
|
83
85
|
def self.attribute_writer(*syms)
|
84
86
|
syms.flatten.each do |sym|
|
85
87
|
next if sym.is_a?(Hash)
|
86
88
|
class_eval(<<-END, __FILE__, __LINE__)
|
87
89
|
def #{sym}=(value)
|
88
|
-
|
90
|
+
self[:#{sym}] = value
|
89
91
|
end
|
90
92
|
END
|
91
93
|
end
|
@@ -110,14 +112,74 @@ module Blather
|
|
110
112
|
attribute_writer *syms
|
111
113
|
end
|
112
114
|
|
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)
|
120
|
+
#
|
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
|
141
|
+
end
|
142
|
+
|
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
|
152
|
+
#
|
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
|
172
|
+
end
|
173
|
+
|
113
174
|
##
|
114
175
|
# Automatically sets the namespace registered by the subclass
|
115
|
-
def
|
116
|
-
name ||= self.
|
117
|
-
content = content.to_s if content
|
176
|
+
def self.new(name = nil, doc = nil)
|
177
|
+
name ||= self.registered_name
|
118
178
|
|
119
|
-
super name.to_s,
|
120
|
-
|
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
|
121
183
|
end
|
122
184
|
|
123
185
|
##
|
@@ -126,80 +188,79 @@ module Blather
|
|
126
188
|
self.class.import self
|
127
189
|
end
|
128
190
|
|
191
|
+
alias_method :nokogiri_namespace=, :namespace=
|
129
192
|
def namespace=(namespaces)
|
130
193
|
case namespaces
|
131
|
-
when XML::Namespace
|
132
|
-
self.
|
194
|
+
when Nokogiri::XML::Namespace
|
195
|
+
self.nokogiri_namespace = namespaces
|
133
196
|
when String
|
134
|
-
self.
|
197
|
+
self.add_namespace nil, namespaces
|
135
198
|
when Hash
|
199
|
+
if ns = namespaces.delete(nil)
|
200
|
+
self.add_namespace nil, ns
|
201
|
+
end
|
136
202
|
namespaces.each do |p, n|
|
137
|
-
|
203
|
+
ns = self.add_namespace p, n
|
204
|
+
self.nokogiri_namespace = ns
|
138
205
|
end
|
139
206
|
end
|
140
207
|
end
|
141
208
|
|
142
|
-
def
|
143
|
-
|
209
|
+
def namespace_href
|
210
|
+
namespace.href if namespace
|
144
211
|
end
|
145
212
|
|
146
213
|
##
|
147
214
|
# Remove a child with the name and (optionally) namespace given
|
148
215
|
def remove_child(name, ns = nil)
|
149
|
-
|
150
|
-
|
216
|
+
child = xpath(name, ns).first
|
217
|
+
child.remove if child
|
151
218
|
end
|
152
219
|
|
153
220
|
##
|
154
221
|
# Remove all children with a given name
|
155
222
|
def remove_children(name)
|
156
|
-
name
|
157
|
-
self.find(name).each { |n| n.remove! }
|
223
|
+
xpath("./*[local-name()='#{name}']").remove
|
158
224
|
end
|
159
225
|
|
160
226
|
##
|
161
227
|
# Pull the content from a child
|
162
|
-
def content_from(name)
|
163
|
-
|
164
|
-
|
228
|
+
def content_from(name, ns = nil)
|
229
|
+
child = xpath(name, ns).first
|
230
|
+
child.content if child
|
165
231
|
end
|
166
232
|
|
167
233
|
##
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
234
|
+
# Sets the content for the specified node.
|
235
|
+
# 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
|
237
|
+
def set_content_for(node, content = nil)
|
238
|
+
if content
|
239
|
+
child = xpath(node).first
|
240
|
+
self << (child = XMPPNode.new(node, self.document)) unless child
|
241
|
+
child.content = content
|
242
|
+
else
|
243
|
+
remove_child node
|
244
|
+
end
|
173
245
|
end
|
174
246
|
|
247
|
+
alias_method :copy, :dup
|
248
|
+
|
175
249
|
##
|
176
250
|
# Inherit all of <tt>stanza</tt>'s attributes and children
|
177
251
|
def inherit(stanza)
|
252
|
+
set_namespace stanza.namespace if stanza.namespace
|
178
253
|
inherit_attrs stanza.attributes
|
179
|
-
stanza.children.each { |c| self << c.
|
254
|
+
stanza.children.each { |c| self << c.dup }
|
180
255
|
self
|
181
256
|
end
|
182
257
|
|
183
258
|
##
|
184
259
|
# Inherit only <tt>stanza</tt>'s attributes
|
185
260
|
def inherit_attrs(attrs)
|
186
|
-
attrs.each { |
|
261
|
+
attrs.each { |name, value| self[name] = value }
|
187
262
|
self
|
188
263
|
end
|
189
|
-
|
190
|
-
##
|
191
|
-
# Turn itself into an XML string and remove all whitespace between nodes
|
192
|
-
def to_xml
|
193
|
-
# TODO: Fix this for HTML nodes (and any other that might require whitespace)
|
194
|
-
to_s.gsub(">\n<", '><')
|
195
|
-
end
|
196
|
-
|
197
|
-
##
|
198
|
-
# Override #find to work when a node isn't attached to a document
|
199
|
-
def find(what, nslist = nil)
|
200
|
-
what = what.to_s
|
201
|
-
(self.doc ? super(what, nslist) : select { |i| i.element_name == what })
|
202
|
-
end
|
203
264
|
end #XMPPNode
|
204
265
|
|
205
|
-
end
|
266
|
+
end
|
data/lib/blather.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
# Require the necessary files
|
2
|
-
require File.join(File.dirname(__FILE__), *%w[.. ext push_parser])
|
3
2
|
%w[
|
4
3
|
rubygems
|
5
4
|
eventmachine
|
6
|
-
|
5
|
+
nokogiri
|
7
6
|
digest/md5
|
8
7
|
logger
|
9
8
|
|
10
9
|
blather/core_ext/active_support
|
11
|
-
blather/core_ext/
|
10
|
+
blather/core_ext/nokogiri
|
12
11
|
|
13
12
|
blather/errors
|
14
13
|
blather/errors/sasl_error
|
@@ -31,20 +30,44 @@ require File.join(File.dirname(__FILE__), *%w[.. ext push_parser])
|
|
31
30
|
blather/stanza/presence/status
|
32
31
|
blather/stanza/presence/subscription
|
33
32
|
|
33
|
+
blather/stanza/pubsub
|
34
|
+
blather/stanza/pubsub/affiliations
|
35
|
+
blather/stanza/pubsub/create
|
36
|
+
blather/stanza/pubsub/event
|
37
|
+
blather/stanza/pubsub/items
|
38
|
+
blather/stanza/pubsub/publish
|
39
|
+
blather/stanza/pubsub/retract
|
40
|
+
blather/stanza/pubsub/subscribe
|
41
|
+
blather/stanza/pubsub/subscription
|
42
|
+
blather/stanza/pubsub/subscriptions
|
43
|
+
blather/stanza/pubsub/unsubscribe
|
44
|
+
|
45
|
+
blather/stanza/pubsub_owner
|
46
|
+
blather/stanza/pubsub_owner/delete
|
47
|
+
blather/stanza/pubsub_owner/purge
|
48
|
+
|
34
49
|
blather/stream
|
35
50
|
blather/stream/client
|
36
51
|
blather/stream/component
|
37
|
-
blather/stream/stream_handler
|
38
52
|
blather/stream/parser
|
39
|
-
blather/stream/
|
40
|
-
blather/stream/
|
41
|
-
blather/stream/
|
42
|
-
blather/stream/
|
53
|
+
blather/stream/features
|
54
|
+
blather/stream/features/resource
|
55
|
+
blather/stream/features/sasl
|
56
|
+
blather/stream/features/session
|
57
|
+
blather/stream/features/tls
|
43
58
|
].each { |r| require r }
|
44
59
|
|
45
|
-
XML.indent_tree_output = false
|
46
|
-
|
47
60
|
module Blather
|
48
|
-
|
49
|
-
|
61
|
+
@@logger = nil
|
62
|
+
def self.logger
|
63
|
+
unless @@logger
|
64
|
+
self.logger = Logger.new($stdout)
|
65
|
+
self.logger.level = Logger::INFO
|
66
|
+
end
|
67
|
+
@@logger
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.logger=(logger)
|
71
|
+
@@logger = logger
|
72
|
+
end
|
50
73
|
end
|