cancer 0.1.0.a1
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/.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
|