spade-runtime 0.1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/bin/spaderun +9 -0
- data/lib/spade-runtime.rb +1 -0
- data/lib/spade/runtime.rb +20 -0
- data/lib/spade/runtime/bundle.rb +173 -0
- data/lib/spade/runtime/cli.rb +7 -0
- data/lib/spade/runtime/cli/base.rb +181 -0
- data/lib/spade/runtime/compiler.rb +34 -0
- data/lib/spade/runtime/console.rb +39 -0
- data/lib/spade/runtime/context.rb +114 -0
- data/lib/spade/runtime/exports.rb +86 -0
- data/lib/spade/runtime/loader.rb +209 -0
- data/lib/spade/runtime/reactor.rb +159 -0
- data/lib/spade/runtime/server.rb +66 -0
- data/lib/spade/runtime/shell.rb +36 -0
- data/lib/spade/runtime/version.rb +5 -0
- data/spade-runtime.gemspec +36 -0
- data/spec/cli/update_spec.rb +64 -0
- data/spec/javascript/async-test.js +123 -0
- data/spec/javascript/compiler/javascript.js +13 -0
- data/spec/javascript/compiler/ruby.js +14 -0
- data/spec/javascript/loader-test.js +64 -0
- data/spec/javascript/normalize-test.js +73 -0
- data/spec/javascript/packages-test.js +44 -0
- data/spec/javascript/relative-require-test.js +72 -0
- data/spec/javascript/require-test.js +117 -0
- data/spec/javascript/sandbox/compile.js +37 -0
- data/spec/javascript/sandbox/creation.js +44 -0
- data/spec/javascript/sandbox/format.js +79 -0
- data/spec/javascript/sandbox/misc.js +57 -0
- data/spec/javascript/sandbox/preprocessor.js +81 -0
- data/spec/javascript/sandbox/require.js +48 -0
- data/spec/javascript/sandbox/run-command.js +21 -0
- data/spec/javascript/spade/externs.js +14 -0
- data/spec/javascript/spade/load-factory.js +15 -0
- data/spec/javascript/spade/misc.js +23 -0
- data/spec/javascript/spade/ready.js +12 -0
- data/spec/javascript/spade/register.js +13 -0
- data/spec/javascript_spec.rb +7 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/cli.rb +109 -0
- data/spec/support/core_test.rb +61 -0
- data/spec/support/matchers.rb +12 -0
- data/spec/support/path.rb +62 -0
- metadata +218 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
# ==========================================================================
|
2
|
+
# Project: Spade - CommonJS Runtime
|
3
|
+
# Copyright: ©2010 Strobe Inc. All rights reserved.
|
4
|
+
# License: Licened under MIT license (see LICENSE)
|
5
|
+
# ==========================================================================
|
6
|
+
|
7
|
+
require 'v8'
|
8
|
+
require 'spade/runtime/compiler'
|
9
|
+
require 'spade/runtime/console'
|
10
|
+
require 'spade/runtime/loader'
|
11
|
+
require 'spade/runtime/reactor'
|
12
|
+
|
13
|
+
module Spade::Runtime
|
14
|
+
|
15
|
+
# Creates a basic context suitable for running modules. The environments
|
16
|
+
# setup in this context will mimic a browser worker thread context,
|
17
|
+
# including timeouts and a console. A navigator object is also defined
|
18
|
+
# that provides some general information about the context.
|
19
|
+
class Context < V8::Context
|
20
|
+
|
21
|
+
attr_reader :reactor
|
22
|
+
attr_reader :verbose
|
23
|
+
|
24
|
+
def require(mod_name)
|
25
|
+
self.eval("require('#{mod_name}');");
|
26
|
+
end
|
27
|
+
|
28
|
+
# Load the spade and racer-loader.
|
29
|
+
def initialize(opts={})
|
30
|
+
@reactor = opts[:reactor]
|
31
|
+
@verbose = opts[:verbose]
|
32
|
+
super(opts) do |ctx|
|
33
|
+
ctx['reactor'] = @reactor
|
34
|
+
ctx['console'] = Console.new
|
35
|
+
ctx['window'] = ctx.scope
|
36
|
+
ctx.eval %[
|
37
|
+
(function() {
|
38
|
+
var r = reactor;
|
39
|
+
setTimeout = function(c,i) { return r.set_timeout(c,i); };
|
40
|
+
setInterval = function(c,i) { return r.set_interval(c,i); };
|
41
|
+
clearTimeout = function(t) { return r.clear_timeout(t); };
|
42
|
+
clearInterval = function(t) { return r.clear_interval(t); };
|
43
|
+
navigator = {
|
44
|
+
appName: 'spade',
|
45
|
+
appVersion: "#{Spade::Runtime::VERSION}",
|
46
|
+
platform: "#{RUBY_PLATFORM}",
|
47
|
+
userAgent: 'spade #{Spade::Runtime::VERSION}; #{RUBY_PLATFORM}'
|
48
|
+
}
|
49
|
+
|
50
|
+
exit = function(status) { return r.exit(status || 0); };
|
51
|
+
})();
|
52
|
+
]
|
53
|
+
|
54
|
+
ctx['reactor'] = nil
|
55
|
+
|
56
|
+
yield(ctx) if block_given?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# The primary context created when running spade exec or spade console.
|
63
|
+
# This context will also automatically start a reactor loop.
|
64
|
+
class MainContext < Context
|
65
|
+
|
66
|
+
attr_accessor :rootdir
|
67
|
+
attr_accessor :caller_id
|
68
|
+
|
69
|
+
# Load the spade and racer-loader.
|
70
|
+
def initialize(opts={})
|
71
|
+
env = opts[:env] || ENV
|
72
|
+
@rootdir = opts[:rootdir] || opts['rootdir']
|
73
|
+
@caller_id = opts[:caller_id] || opts['caller_id']
|
74
|
+
@reactor = opts[:reactor] ||= Reactor.new(self)
|
75
|
+
lang = opts[:language] ||= (env['LANG']||'en_US').gsub(/\..*/, '')
|
76
|
+
lang = lang.gsub '_', '-'
|
77
|
+
|
78
|
+
|
79
|
+
super(opts) do |ctx|
|
80
|
+
ctx['ENV'] = env.to_hash
|
81
|
+
ctx['ENV']['SPADE_PLATFORM'] = { 'ENGINE' => 'spade' }
|
82
|
+
ctx['ENV']['LANG'] = lang
|
83
|
+
|
84
|
+
ctx['ARGV'] = opts[:argv] || ARGV
|
85
|
+
|
86
|
+
# Load spade and patch in compiler and loader plugins
|
87
|
+
ctx.load(Spade::JSPATH)
|
88
|
+
ctx['rubyLoader'] = Loader.new(self)
|
89
|
+
ctx['rubyCompiler'] = Compiler.new(self)
|
90
|
+
|
91
|
+
ctx.eval %[
|
92
|
+
spade.loader = rubyLoader;
|
93
|
+
spade.compiler = rubyCompiler;
|
94
|
+
spade.defaultSandbox.rootdir = #{@rootdir.to_json};
|
95
|
+
spade.globalize();
|
96
|
+
]
|
97
|
+
|
98
|
+
ctx.eval("spade.defaultSandbox.callerId = #{@caller_id.to_json};") if @caller_id
|
99
|
+
|
100
|
+
ctx['rubyLoader'] = ctx['rubyCompiler'] = nil
|
101
|
+
|
102
|
+
@reactor.start do
|
103
|
+
yield(self) if block_given?
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# ==========================================================================
|
2
|
+
# Project: Spade - CommonJS Runtime
|
3
|
+
# Copyright: ©2010 Strobe Inc. All rights reserved.
|
4
|
+
# License: Licened under MIT license (see LICENSE)
|
5
|
+
# ==========================================================================
|
6
|
+
|
7
|
+
module Spade
|
8
|
+
module Runtime
|
9
|
+
module Namespace
|
10
|
+
|
11
|
+
def [](name)
|
12
|
+
begin
|
13
|
+
self.class.const_defined?(name) ? self.class.const_get(name) : yield
|
14
|
+
rescue NameError => e
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class Exports
|
22
|
+
|
23
|
+
attr_reader :context
|
24
|
+
|
25
|
+
def initialize(ctx)
|
26
|
+
@context = ctx
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](name)
|
30
|
+
|
31
|
+
begin
|
32
|
+
if self.class.const_defined?(name)
|
33
|
+
ret = self.class.const_get(name)
|
34
|
+
|
35
|
+
# If we are returning a class, create a custom subclass the first
|
36
|
+
# time that also exposes the current context.
|
37
|
+
if ret.instance_of? Class
|
38
|
+
@klass_cache ||= {}
|
39
|
+
unless @klass_cache[name]
|
40
|
+
|
41
|
+
proc1 = proc { @context }
|
42
|
+
@klass_cache[name] = Class.new(ret) do
|
43
|
+
|
44
|
+
@context = proc1.call
|
45
|
+
|
46
|
+
def self.context
|
47
|
+
@context
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
@klass_cache[name]
|
55
|
+
else
|
56
|
+
ret
|
57
|
+
end
|
58
|
+
|
59
|
+
else
|
60
|
+
yield
|
61
|
+
end
|
62
|
+
|
63
|
+
rescue NameError => e
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Namespace
|
72
|
+
include Runtime::Namespace
|
73
|
+
|
74
|
+
def self.included(base)
|
75
|
+
warn("Spade::Namespace is deprecated. Use Spade::Runtime::Namespace instead. From #{base.name}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Exports < Runtime::Exports
|
80
|
+
def self.inherited(subclass)
|
81
|
+
warn("Spade::Exports is deprecated. Used Spade::Runtime::Exports instead. From #{subclass.name}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# ==========================================================================
|
2
|
+
# Project: Spade - CommonJS Runtime
|
3
|
+
# Copyright: ©2010 Strobe Inc. All rights reserved.
|
4
|
+
# License: Licened under MIT license (see LICENSE)
|
5
|
+
# ==========================================================================
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
|
10
|
+
module Spade::Runtime
|
11
|
+
|
12
|
+
class Loader
|
13
|
+
|
14
|
+
def initialize(ctx)
|
15
|
+
@ctx = ctx
|
16
|
+
end
|
17
|
+
|
18
|
+
def discoverRoot(path)
|
19
|
+
Spade.discover_root path
|
20
|
+
end
|
21
|
+
|
22
|
+
def root(path=nil)
|
23
|
+
return @ctx.rootdir if path.nil?
|
24
|
+
@ctx.rootdir = path
|
25
|
+
@packages = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# exposed to JS. Find the JS file on disk and register the module
|
29
|
+
def loadFactory(spade, id, formats, done=nil)
|
30
|
+
formats = formats.to_a # We may get a V8::Array, we want a normal one
|
31
|
+
|
32
|
+
# load individual files
|
33
|
+
if id =~ /^file:\//
|
34
|
+
js_path = id[5..-1]
|
35
|
+
if File.exists? js_path
|
36
|
+
load_module id, js_path, ['js'], js_path
|
37
|
+
end
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
parts = id.split '/'
|
42
|
+
package_name = parts.shift
|
43
|
+
package_info = packages[package_name]
|
44
|
+
skip_module = false
|
45
|
+
|
46
|
+
return nil if package_info.nil?
|
47
|
+
|
48
|
+
if parts.size==1 && parts[0] == '~package'
|
49
|
+
skip_module = true
|
50
|
+
elsif parts.size==1 && parts[0] == 'main'
|
51
|
+
parts = (package_info[:json]['main'] || 'lib/main').split('/')
|
52
|
+
dirname = parts[0...-1]
|
53
|
+
parts = [parts[-1]]
|
54
|
+
else
|
55
|
+
dirname = extract_dirname(parts, package_info)
|
56
|
+
end
|
57
|
+
|
58
|
+
# register the package first - also make sure dependencies are
|
59
|
+
# registered since they are needed for loading plugins
|
60
|
+
unless package_info[:registered]
|
61
|
+
package_info[:registered] = true
|
62
|
+
@ctx.eval "spade.register('#{package_name}', #{package_info[:json].to_json});"
|
63
|
+
|
64
|
+
deps = package_info[:json]['dependencies'] || [];
|
65
|
+
deps.each do |dep_name, ignored|
|
66
|
+
dep_package_info = packages[dep_name]
|
67
|
+
next unless dep_package_info && !dep_package_info[:registered]
|
68
|
+
dep_package_info[:registered] = true
|
69
|
+
@ctx.eval "spade.register('#{dep_name}', #{dep_package_info[:json].to_json});"
|
70
|
+
|
71
|
+
# Add new formats if they are specified in our dependencies
|
72
|
+
dep_formats = dep_package_info[:json]['plugin:formats']
|
73
|
+
formats.unshift(*dep_formats.keys).uniq! if dep_formats
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
unless skip_module
|
79
|
+
filename = parts.pop
|
80
|
+
base_path = package_info[:path]
|
81
|
+
formats << ['js'] if formats.empty?
|
82
|
+
formats.each do |fmt|
|
83
|
+
cur_path = File.join(base_path, dirname, parts, filename+'.'+fmt)
|
84
|
+
if File.exist? cur_path
|
85
|
+
load_module id, cur_path, fmt, cur_path
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
rb_path = File.join(package_info[:path],dirname,parts, filename+'.rb')
|
91
|
+
if File.exists? rb_path
|
92
|
+
load_ruby id, rb_path
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
|
101
|
+
# exposed to JS. Determines if the named id exists in the system
|
102
|
+
def exists(spade, id, formats)
|
103
|
+
|
104
|
+
# individual files
|
105
|
+
return File.exists?(id[5..-1]) if id =~ /^file:\//
|
106
|
+
|
107
|
+
parts = id.split '/'
|
108
|
+
package_name = parts.shift
|
109
|
+
package_info = packages[package_name]
|
110
|
+
|
111
|
+
return false if package_info.nil?
|
112
|
+
return true if parts.size==1 && parts[0] == '~package'
|
113
|
+
|
114
|
+
dirname = extract_dirname(parts, package_info)
|
115
|
+
|
116
|
+
|
117
|
+
filename = parts.pop
|
118
|
+
base_path = package_info[:path]
|
119
|
+
formats = ['js'] if formats.nil?
|
120
|
+
formats.each do |fmt|
|
121
|
+
cur_path = File.join(base_path, dirname, parts, filename+'.'+fmt)
|
122
|
+
return true if File.exist? cur_path
|
123
|
+
end
|
124
|
+
|
125
|
+
rb_path = File.join(package_info[:path],dirname,parts, filename+'.rb')
|
126
|
+
return File.exists? rb_path
|
127
|
+
end
|
128
|
+
|
129
|
+
def load_module(id, module_path, format, path)
|
130
|
+
module_contents = File.read(module_path).to_json # encode as string
|
131
|
+
@ctx.eval("spade.register('#{id}',#{module_contents}, { format: #{format.to_s.to_json}, filename: #{path.to_s.to_json} });")
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
def load_ruby(id, rb_path)
|
136
|
+
|
137
|
+
klass = Spade.exports_for rb_path
|
138
|
+
exports = klass.nil? ? {} : klass.new(@ctx)
|
139
|
+
@ctx['$__rb_exports__'] = exports
|
140
|
+
|
141
|
+
@ctx.eval(%[(function() {
|
142
|
+
var exp = $__rb_exports__;
|
143
|
+
spade.register('#{id}', function(r,e,m) { m.exports = exp; });
|
144
|
+
})();])
|
145
|
+
|
146
|
+
@ctx['$__rb_exports__'] = nil
|
147
|
+
end
|
148
|
+
|
149
|
+
def packages
|
150
|
+
@packages unless @packages.nil?
|
151
|
+
@packages = {}
|
152
|
+
|
153
|
+
if defined?(Spade::Packager)
|
154
|
+
package_paths = Dir.glob(File.join(LibGems.dir, 'gems', '*'))
|
155
|
+
package_paths.each{|path| add_package(path) }
|
156
|
+
end
|
157
|
+
|
158
|
+
# in reverse order of precedence
|
159
|
+
%w[.spade/packages vendor/cache vendor/packages packages].each do |p|
|
160
|
+
package_paths = Dir.glob File.join(@ctx.rootdir, p.split('/'), '*')
|
161
|
+
package_paths.each { |path| add_package(path) }
|
162
|
+
end
|
163
|
+
|
164
|
+
# add self
|
165
|
+
add_package @ctx.rootdir
|
166
|
+
|
167
|
+
@packages
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def extract_dirname(parts, package_info)
|
173
|
+
if parts.size>0 && parts[0].chars.first == '~'
|
174
|
+
dirname = parts.shift[1..-1]
|
175
|
+
if package_info[:directories][dirname]
|
176
|
+
dirname = package_info[:directories][dirname]
|
177
|
+
else
|
178
|
+
dirname = dirname
|
179
|
+
end
|
180
|
+
else
|
181
|
+
dirname = package_info[:directories]['lib']
|
182
|
+
dirname = 'lib' if dirname.nil?
|
183
|
+
end
|
184
|
+
|
185
|
+
dirname
|
186
|
+
end
|
187
|
+
|
188
|
+
def add_package(path)
|
189
|
+
json_package = File.join(path, 'package.json')
|
190
|
+
return unless File.exists?(json_package)
|
191
|
+
|
192
|
+
json = JSON.load(File.read(json_package)) rescue nil
|
193
|
+
return if json.nil?
|
194
|
+
|
195
|
+
directories = json["directories"] || { "lib" => "lib" }
|
196
|
+
json["root"] = "file:/"+File.split(path).join('/')
|
197
|
+
@packages[json["name"]] = {
|
198
|
+
:registered => false,
|
199
|
+
:path => path,
|
200
|
+
:directories => directories,
|
201
|
+
:json => json
|
202
|
+
}
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# ==========================================================================
|
2
|
+
# Project: Spade - CommonJS Runtime
|
3
|
+
# Copyright: ©2010 Strobe Inc. All rights reserved.
|
4
|
+
# License: Licened under MIT license (see LICENSE)
|
5
|
+
# ==========================================================================
|
6
|
+
|
7
|
+
require 'eventmachine'
|
8
|
+
|
9
|
+
module Spade::Runtime
|
10
|
+
|
11
|
+
# The reactor exposes some API
|
12
|
+
class Reactor
|
13
|
+
|
14
|
+
attr_reader :timers, :running, :holds, :stopped
|
15
|
+
|
16
|
+
def initialize(ctx)
|
17
|
+
@timers = []
|
18
|
+
@running = false
|
19
|
+
@stopped = false
|
20
|
+
@holds = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
# Starts the event loop
|
24
|
+
def start
|
25
|
+
return if @running
|
26
|
+
|
27
|
+
@running = true
|
28
|
+
add_hold # release when finish is called
|
29
|
+
|
30
|
+
EventMachine.run do
|
31
|
+
@running = true
|
32
|
+
@timers.each { |t| t.start unless t.running }
|
33
|
+
|
34
|
+
yield if block_given?
|
35
|
+
|
36
|
+
@stopped = true
|
37
|
+
release_hold
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def exit(status=0)
|
43
|
+
stop_loop
|
44
|
+
Kernel.exit(status)
|
45
|
+
end
|
46
|
+
|
47
|
+
def next_tick(&block)
|
48
|
+
if @running
|
49
|
+
add_hold
|
50
|
+
EventMachine.next_tick do
|
51
|
+
yield
|
52
|
+
release_hold
|
53
|
+
end
|
54
|
+
else
|
55
|
+
add_timer(0) { yield }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
######################################################
|
60
|
+
## Holds
|
61
|
+
##
|
62
|
+
## Prevents the loop from exiting
|
63
|
+
|
64
|
+
def add_hold
|
65
|
+
@holds = @holds + 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def release_hold
|
69
|
+
@holds = @holds - 1
|
70
|
+
stop_loop if @stopped && @holds <= 0
|
71
|
+
end
|
72
|
+
|
73
|
+
def stop_loop
|
74
|
+
@running = false
|
75
|
+
EventMachine.stop_event_loop
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
######################################################
|
80
|
+
## Timers
|
81
|
+
##
|
82
|
+
|
83
|
+
def add_timer(interval, periodic = false, &block)
|
84
|
+
add_hold
|
85
|
+
Timer.new(self).tap do |timer|
|
86
|
+
timer.periodic = periodic
|
87
|
+
timer.interval = interval
|
88
|
+
timer.callback = block
|
89
|
+
@timers << timer
|
90
|
+
timer.start if @running
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def remove_timer(timer)
|
95
|
+
'remove_timer'
|
96
|
+
release_hold if @timers.delete(timer)
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_timeout(callback, interval)
|
100
|
+
add_timer interval, false do
|
101
|
+
callback.methodcall(self)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_interval(callback, interval)
|
106
|
+
add_timer interval, true do
|
107
|
+
callback.methodcall(self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def clear_timeout(timer)
|
112
|
+
timer.cancel
|
113
|
+
end
|
114
|
+
|
115
|
+
def clear_interval(timer)
|
116
|
+
timer.cancel
|
117
|
+
end
|
118
|
+
|
119
|
+
class Timer
|
120
|
+
|
121
|
+
attr_accessor :periodic, :interval, :callback
|
122
|
+
attr_reader :running
|
123
|
+
|
124
|
+
def initialize(reactor)
|
125
|
+
@interval = 0
|
126
|
+
@periodic = false
|
127
|
+
@callback = nil
|
128
|
+
@reactor = reactor
|
129
|
+
@running = false
|
130
|
+
end
|
131
|
+
|
132
|
+
def start
|
133
|
+
@running = true
|
134
|
+
if @periodic
|
135
|
+
@timer = EventMachine.add_periodic_timer(@interval.to_f / 1000) do
|
136
|
+
@callback.call
|
137
|
+
end
|
138
|
+
else
|
139
|
+
@timer = EventMachine.add_timer(@interval.to_f / 1000) do
|
140
|
+
@callback.call
|
141
|
+
@timer = nil
|
142
|
+
@reactor.remove_timer(self)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def cancel
|
148
|
+
EventMachine.cancel_timer(@timer) if @timer
|
149
|
+
@timer = nil
|
150
|
+
@reactor.remove_timer(self)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|