cancer 0.1.0.a1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +3 -0
- data/.gitignore +5 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/cancer.gemspec +156 -0
- data/documentation/STREAM_INITIATION.markdown +18 -0
- data/examples/example.rb +80 -0
- data/examples/example_2.rb +20 -0
- data/examples/example_em.rb +11 -0
- data/examples/example_em_helper.rb +23 -0
- data/examples/example_im_roster.rb +26 -0
- data/examples/example_xep_0004.rb +124 -0
- data/examples/example_xep_0047.rb +35 -0
- data/examples/example_xep_0050.rb +78 -0
- data/examples/example_xep_0065.rb +66 -0
- data/examples/example_xep_0096.dup.rb +40 -0
- data/examples/example_xep_0096_xep_0047.rb +42 -0
- data/examples/example_xep_0096_xep_0065.rb +40 -0
- data/lib/cancer.rb +122 -0
- data/lib/cancer/adapter.rb +33 -0
- data/lib/cancer/adapters/em.rb +88 -0
- data/lib/cancer/adapters/socket.rb +122 -0
- data/lib/cancer/builder.rb +28 -0
- data/lib/cancer/controller.rb +32 -0
- data/lib/cancer/dependencies.rb +187 -0
- data/lib/cancer/events/abstract_event.rb +10 -0
- data/lib/cancer/events/base_matchers.rb +63 -0
- data/lib/cancer/events/binary_matchers.rb +30 -0
- data/lib/cancer/events/eventable.rb +92 -0
- data/lib/cancer/events/exception_events.rb +28 -0
- data/lib/cancer/events/handler.rb +33 -0
- data/lib/cancer/events/manager.rb +130 -0
- data/lib/cancer/events/named_events.rb +25 -0
- data/lib/cancer/events/proc_matcher.rb +16 -0
- data/lib/cancer/events/xml_events.rb +44 -0
- data/lib/cancer/exceptions.rb +53 -0
- data/lib/cancer/jid.rb +215 -0
- data/lib/cancer/lock.rb +32 -0
- data/lib/cancer/spec.rb +35 -0
- data/lib/cancer/spec/error.rb +18 -0
- data/lib/cancer/spec/extentions.rb +79 -0
- data/lib/cancer/spec/matcher.rb +30 -0
- data/lib/cancer/spec/mock_adapter.rb +139 -0
- data/lib/cancer/spec/mock_stream.rb +15 -0
- data/lib/cancer/spec/transcript.rb +107 -0
- data/lib/cancer/stream.rb +182 -0
- data/lib/cancer/stream/builder.rb +20 -0
- data/lib/cancer/stream/controller.rb +36 -0
- data/lib/cancer/stream/event_helper.rb +12 -0
- data/lib/cancer/stream/xep.rb +52 -0
- data/lib/cancer/stream_parser.rb +144 -0
- data/lib/cancer/support.rb +27 -0
- data/lib/cancer/support/hash.rb +32 -0
- data/lib/cancer/support/string.rb +22 -0
- data/lib/cancer/synchronized_stanza.rb +79 -0
- data/lib/cancer/thread_pool.rb +118 -0
- data/lib/cancer/xep.rb +43 -0
- data/lib/cancer/xeps/core.rb +109 -0
- data/lib/cancer/xeps/core/bind.rb +33 -0
- data/lib/cancer/xeps/core/sasl.rb +113 -0
- data/lib/cancer/xeps/core/session.rb +22 -0
- data/lib/cancer/xeps/core/stream.rb +18 -0
- data/lib/cancer/xeps/core/terminator.rb +21 -0
- data/lib/cancer/xeps/core/tls.rb +34 -0
- data/lib/cancer/xeps/im.rb +323 -0
- data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
- data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
- data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
- data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
- data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
- data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
- data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
- data/lib/cancer/xeps/xep_0095_si.rb +211 -0
- data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
- data/lib/cancer/xeps/xep_0114_component.rb +73 -0
- data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
- data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
- data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
- data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
- data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/stream/stanza_errors_spec.rb +47 -0
- data/spec/stream/stream_errors_spec.rb +38 -0
- data/spec/stream/stream_initialization_spec.rb +160 -0
- data/spec/xep_0050/local_spec.rb +165 -0
- data/spec/xep_0050/remote_spec.rb +44 -0
- metadata +200 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
# Out of Band Data
|
4
|
+
# http://xmpp.org/extensions/xep-0066.html
|
5
|
+
module XEP_0066
|
6
|
+
include Cancer::XEP
|
7
|
+
|
8
|
+
dependency 'core'
|
9
|
+
dependency 'xep-0095', :weak => true
|
10
|
+
|
11
|
+
IQ_NS = 'jabber:iq:oob'
|
12
|
+
X_NS = 'jabber:x:oob'
|
13
|
+
|
14
|
+
def self.enhance_stream(stream)
|
15
|
+
stream.extend_stream do
|
16
|
+
include Cancer::XEP_0066::StreamHelpers
|
17
|
+
end
|
18
|
+
stream.extend_builder do
|
19
|
+
include Cancer::XEP_0066::BuilderHelper
|
20
|
+
end
|
21
|
+
stream.install_controller Cancer::XEP_0066::Controller
|
22
|
+
stream.install_event_helper Cancer::XEP_0066::EventDSL
|
23
|
+
if stream.respond_to?(:register_stream_method)
|
24
|
+
stream.register_stream_method(Cancer::XEP_0066::StreamMethod, Cancer::XEP_0066::IQ_NS)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module StreamHelpers
|
29
|
+
|
30
|
+
def oob_offer(to, uri, desc=nil)
|
31
|
+
send_iq(to, :set) do |x|
|
32
|
+
x.query(:xmlns => Cancer::XEP_0066::IQ_NS) do
|
33
|
+
x.url { x.text(uri.to_s) }
|
34
|
+
x.desc { x.text(desc) } if desc
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
module BuilderHelper
|
42
|
+
|
43
|
+
def oob_offer(uri, desc=nil)
|
44
|
+
self.x(:xmlns => Cancer::XEP_0066::X_NS) do
|
45
|
+
self.url { x.text(uri.to_s) }
|
46
|
+
self.desc { x.text(desc) } if desc
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
module EventDSL
|
53
|
+
|
54
|
+
def oob_offer
|
55
|
+
( xpath('/c:iq[@type="set"]/oob:query', 'c' => CLIENT_NS, 'oob' => IQ_NS)
|
56
|
+
| xpath('/c:message/oob:x', 'c' => CLIENT_NS, 'oob' => X_NS)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class StreamMethod < Cancer::XEP_0095::StreamMethod
|
63
|
+
def handle(to, stream_id, &proc)
|
64
|
+
@stream.oob_offer(to, uri, :stream_id => stream_id)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
# Stream Initiation
|
4
|
+
# http://xmpp.org/extensions/xep-0095.html
|
5
|
+
module XEP_0095
|
6
|
+
include Cancer::XEP
|
7
|
+
|
8
|
+
dependency 'core'
|
9
|
+
dependency 'xep-0020'
|
10
|
+
dependency 'xep-0030'
|
11
|
+
|
12
|
+
NS = 'http://jabber.org/protocol/si'
|
13
|
+
|
14
|
+
def self.enhance_stream(stream)
|
15
|
+
stream.extend_stream do
|
16
|
+
include Cancer::XEP_0095::StreamHelper
|
17
|
+
end
|
18
|
+
stream.install_controller Cancer::XEP_0095::Controller
|
19
|
+
stream.install_event_helper Cancer::XEP_0095::EventDSL
|
20
|
+
stream.disco.feature NS
|
21
|
+
end
|
22
|
+
|
23
|
+
class Profile
|
24
|
+
def initialize(stream)
|
25
|
+
@stream = stream
|
26
|
+
end
|
27
|
+
def enhance_si(si, options={})
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class StreamMethod
|
33
|
+
def initialize(stream)
|
34
|
+
@stream = stream
|
35
|
+
end
|
36
|
+
def handle(to, stream_id, &proc)
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module StreamHelper
|
42
|
+
|
43
|
+
def profiles
|
44
|
+
@profiles ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def register_profile(handler, uri)
|
48
|
+
self.profiles[uri.to_s] = handler.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def stream_methods
|
52
|
+
@stream_methods ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def register_stream_method(handler, uri)
|
56
|
+
self.stream_methods[uri.to_s] = handler.new(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def si_offer(to, options={}, &proc)
|
60
|
+
si_options = {}
|
61
|
+
si_options[:'id'] = options.delete(:id) || rand(1<<20)
|
62
|
+
si_options[:'mime-type'] = options.delete(:mime_type) || options.delete(:'mime-type') || options.delete(:type)
|
63
|
+
si_options[:profile] = options.delete(:profile)
|
64
|
+
si_options[:xmlns] = NS
|
65
|
+
|
66
|
+
profile = self.profiles[si_options[:profile]]
|
67
|
+
|
68
|
+
result = send_iq(to, :set) do |x|
|
69
|
+
x.si(si_options) do
|
70
|
+
profile.enhance_si(x, options)
|
71
|
+
x.negotiate_features do |form|
|
72
|
+
form.define('stream-method') do |field|
|
73
|
+
field.type = :'list-single'
|
74
|
+
|
75
|
+
self.stream_methods.keys.each do |uri|
|
76
|
+
field.add_option uri
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
form = Cancer::XEP_0004::Form.load(result.first(
|
86
|
+
"/c:iq[@type='result']/si:si/neg:feature/x:x",
|
87
|
+
'c' => CLIENT_NS,
|
88
|
+
'si' => Cancer::XEP_0095::NS,
|
89
|
+
'neg' => Cancer::XEP_0020::NS,
|
90
|
+
'x' => Cancer::XEP_0004::NS
|
91
|
+
))
|
92
|
+
stream_method = form['stream-method']
|
93
|
+
|
94
|
+
self.stream_methods[stream_method].handle(to, si_options[:id], &proc)
|
95
|
+
rescue
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class Controller < Cancer::Controller
|
103
|
+
|
104
|
+
NAMESPACES = { 'c' => CLIENT_NS, 'si' => NS }
|
105
|
+
|
106
|
+
on { |e| e.xpath('/c:iq[@type="set"]/si:si', NAMESPACES) }
|
107
|
+
def received_si(e)
|
108
|
+
si = e.xml.first('/c:iq/si:si', NAMESPACES)
|
109
|
+
fire! Cancer::XEP_0095::Event, self, e.xml, si
|
110
|
+
end
|
111
|
+
|
112
|
+
def pending_streams
|
113
|
+
@pending_streams ||= {}
|
114
|
+
end
|
115
|
+
|
116
|
+
def catch_stream(id, event)
|
117
|
+
self.pending_streams[id.to_s] = event
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class Event < Cancer::Events::AbstractEvent
|
123
|
+
attr_reader :si, :iq, :accept_proc
|
124
|
+
def initialize(controller, iq, si)
|
125
|
+
@controller, @iq, @si = controller, iq, si
|
126
|
+
end
|
127
|
+
|
128
|
+
def accept!(&proc)
|
129
|
+
form = Cancer::XEP_0004::Form.load(si.all("neg:feature/x:x",
|
130
|
+
'neg' => Cancer::XEP_0020::NS,
|
131
|
+
'x' => Cancer::XEP_0004::NS))
|
132
|
+
|
133
|
+
stream_method = (form.field('stream-method').option_values & @controller.stream_methods.keys).first
|
134
|
+
form['stream-method'] = stream_method
|
135
|
+
|
136
|
+
unless stream_method
|
137
|
+
# error
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
|
141
|
+
@controller.send_iq(@iq[:from], :result, @iq[:id]) do |x|
|
142
|
+
x.si(:xmlns => Cancer::XEP_0095::NS, :id => @si[:id]) do
|
143
|
+
x.negotiate_features(:set, form)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# catch stream
|
148
|
+
|
149
|
+
@accept_proc = proc
|
150
|
+
controller = @controller.controllers_by_name['Cancer::XEP_0095::Controller']
|
151
|
+
controller.catch_stream(@si[:id], self)
|
152
|
+
end
|
153
|
+
|
154
|
+
def reject!
|
155
|
+
@controller.send_iq(@iq[:from], :error, @iq[:id]) do |x|
|
156
|
+
x.error(:code => 403, :type => :cancel) do
|
157
|
+
x.forbidden(:xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas')
|
158
|
+
x.text_(:xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas') do
|
159
|
+
x.text('Offer Declined')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def caught_stream!(stream)
|
166
|
+
@stream = stream
|
167
|
+
@stream.accept!(&@accept_proc)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class EventMatcher < Cancer::Events::Matcher
|
172
|
+
|
173
|
+
def initialize(options={})
|
174
|
+
@mime_type = options[:mime_type] || options[:type]
|
175
|
+
@profile = options[:profile]
|
176
|
+
end
|
177
|
+
|
178
|
+
def match?(event)
|
179
|
+
Cancer::XEP_0095::Event === event and match_mime_type?(event) and match_profile?(event)
|
180
|
+
end
|
181
|
+
|
182
|
+
def match_mime_type?(event, pattern=@mime_type)
|
183
|
+
case pattern
|
184
|
+
when String then event.si[:'mime-type'] == pattern
|
185
|
+
when Regexp then event.si[:'mime-type'] =~ pattern
|
186
|
+
when Array then pattern.any? { |p| match_mime_type?(event, p) }
|
187
|
+
when NilClass then true
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def match_profile?(event, pattern=@profile)
|
192
|
+
case pattern
|
193
|
+
when String then event.si[:profile] == pattern
|
194
|
+
when Regexp then event.si[:profile] =~ pattern
|
195
|
+
when Array then pattern.any? { |p| match_profile?(event, p) }
|
196
|
+
when NilClass then true
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
module EventDSL
|
203
|
+
|
204
|
+
def si(options={})
|
205
|
+
Cancer::XEP_0095::EventMatcher.new(options)
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
# SI File Transfer
|
4
|
+
# http://xmpp.org/extensions/xep-0096.html
|
5
|
+
module XEP_0096
|
6
|
+
include Cancer::XEP
|
7
|
+
|
8
|
+
dependency 'core'
|
9
|
+
dependency 'xep-0095'
|
10
|
+
|
11
|
+
NS = 'http://jabber.org/protocol/si/profile/file-transfer'
|
12
|
+
|
13
|
+
def self.enhance_stream(stream)
|
14
|
+
stream.extend_stream do
|
15
|
+
include Cancer::XEP_0096::StreamHelper
|
16
|
+
end
|
17
|
+
stream.install_controller Cancer::XEP_0096::Controller
|
18
|
+
stream.install_event_helper Cancer::XEP_0096::EventDSL
|
19
|
+
if stream.respond_to?(:register_profile)
|
20
|
+
stream.register_profile(Cancer::XEP_0096::Profile, Cancer::XEP_0096::NS)
|
21
|
+
stream.disco.feature NS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module StreamHelper
|
26
|
+
def si_file_offer(to, path)
|
27
|
+
si_offer(to, :path => path, :profile => Cancer::XEP_0096::NS) do |f|
|
28
|
+
|
29
|
+
File.open(path, 'r') do |file|
|
30
|
+
f.write file.read(4096) until file.eof?
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Profile < Cancer::XEP_0095::Profile
|
38
|
+
def enhance_si(si, options={})
|
39
|
+
si.file(:xmlns => Cancer::XEP_0096::NS,
|
40
|
+
:name => File.basename(options[:path]),
|
41
|
+
:size => File.size(options[:path]),
|
42
|
+
:date => fomat_time(File.mtime(options[:path])),
|
43
|
+
:hash => Digest::MD5.file(options[:path]).hexdigest
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fomat_time(time)
|
48
|
+
def time.xmlschema(fraction_digits=0)
|
49
|
+
sprintf('%d-%02d-%02dT%02d:%02d:%02d',
|
50
|
+
year, mon, day, hour, min, sec) +
|
51
|
+
if fraction_digits == 0
|
52
|
+
''
|
53
|
+
elsif fraction_digits <= 6
|
54
|
+
'.' + sprintf('%06d', usec)[0, fraction_digits]
|
55
|
+
else
|
56
|
+
'.' + sprintf('%06d', usec) + '0' * (fraction_digits - 6)
|
57
|
+
end +
|
58
|
+
if utc?
|
59
|
+
'Z'
|
60
|
+
else
|
61
|
+
off = utc_offset
|
62
|
+
sign = off < 0 ? '-' : '+'
|
63
|
+
sprintf('%s%02d:%02d', sign, *(off.abs / 60).divmod(60))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
time.xmlschema
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Controller < Cancer::Controller
|
71
|
+
|
72
|
+
NAMESPACES = { 'c' => CLIENT_NS, 'si' => Cancer::XEP_0095::NS, 'ft' => NS }
|
73
|
+
|
74
|
+
on { |e| e.xpath('/c:iq[@type="set"]/si:si/ft:file', NAMESPACES) }
|
75
|
+
def received_si(e)
|
76
|
+
ft = e.xml.first('/c:iq/si:si/ft:file', NAMESPACES)
|
77
|
+
si = ft.parent
|
78
|
+
fire! Cancer::XEP_0096::Event, self, e.xml, si, ft
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class Event < Cancer::XEP_0095::Event
|
84
|
+
attr_reader :si, :iq, :ft
|
85
|
+
attr_reader :status
|
86
|
+
|
87
|
+
def initialize(controller, iq, si, ft)
|
88
|
+
@controller, @iq, @si, @ft = controller, iq, si, ft
|
89
|
+
@status = :pending
|
90
|
+
end
|
91
|
+
|
92
|
+
def accept!(options={}, &proc)
|
93
|
+
if options[:path]
|
94
|
+
super() do |f|
|
95
|
+
@status = :active
|
96
|
+
@controller.fire! self
|
97
|
+
|
98
|
+
File.open(options[:path], 'w+', options[:mode] || 0644) do |file|
|
99
|
+
file.write f.readpartial(4096) until f.eof?
|
100
|
+
end
|
101
|
+
|
102
|
+
correct_size = lambda { (@ft[:size].nil? or (File.size(options[:path]) == @ft[:size].to_i)) }
|
103
|
+
correct_hash = lambda { (@ft[:hash].nil? or (Digest::MD5.file(options[:path]).hexdigest == @ft[:hash].to_s)) }
|
104
|
+
|
105
|
+
if correct_size.call and correct_hash.call
|
106
|
+
@status = :completed
|
107
|
+
@controller.fire! self
|
108
|
+
else
|
109
|
+
File.unlink(options[:path])
|
110
|
+
@status = :failed
|
111
|
+
@controller.fire! self
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
super() do |f|
|
116
|
+
@status = :active
|
117
|
+
@controller.fire! self
|
118
|
+
|
119
|
+
proc.call(f)
|
120
|
+
|
121
|
+
@status = :completed
|
122
|
+
@controller.fire! self
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def filename
|
128
|
+
@ft[:name].to_s
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class EventMatcher < Cancer::XEP_0095::EventMatcher
|
133
|
+
|
134
|
+
def initialize(options={})
|
135
|
+
options[:profile] = NS
|
136
|
+
super(options)
|
137
|
+
@filename = options[:filename]
|
138
|
+
@status = options[:status] || :pending
|
139
|
+
end
|
140
|
+
|
141
|
+
def match?(event)
|
142
|
+
Cancer::XEP_0096::Event === event and super(event) and match_filename?(event) and match_status?(event)
|
143
|
+
end
|
144
|
+
|
145
|
+
def match_filename?(event, pattern=@filename)
|
146
|
+
case pattern
|
147
|
+
when String then event.filename == pattern
|
148
|
+
when Regexp then event.filename =~ pattern
|
149
|
+
when Array then pattern.any? { |p| match_filename?(event, p) }
|
150
|
+
when NilClass then true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def match_status?(event, pattern=@status)
|
155
|
+
case pattern
|
156
|
+
when Symbol then event.status == pattern
|
157
|
+
when Array then pattern.any? { |p| match_status?(event, p) }
|
158
|
+
when NilClass then true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
module EventDSL
|
165
|
+
|
166
|
+
def si_file(options={})
|
167
|
+
Cancer::XEP_0096::EventMatcher.new(options)
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|