spade-runtime 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +2 -0
  2. data/bin/spaderun +9 -0
  3. data/lib/spade-runtime.rb +1 -0
  4. data/lib/spade/runtime.rb +20 -0
  5. data/lib/spade/runtime/bundle.rb +173 -0
  6. data/lib/spade/runtime/cli.rb +7 -0
  7. data/lib/spade/runtime/cli/base.rb +181 -0
  8. data/lib/spade/runtime/compiler.rb +34 -0
  9. data/lib/spade/runtime/console.rb +39 -0
  10. data/lib/spade/runtime/context.rb +114 -0
  11. data/lib/spade/runtime/exports.rb +86 -0
  12. data/lib/spade/runtime/loader.rb +209 -0
  13. data/lib/spade/runtime/reactor.rb +159 -0
  14. data/lib/spade/runtime/server.rb +66 -0
  15. data/lib/spade/runtime/shell.rb +36 -0
  16. data/lib/spade/runtime/version.rb +5 -0
  17. data/spade-runtime.gemspec +36 -0
  18. data/spec/cli/update_spec.rb +64 -0
  19. data/spec/javascript/async-test.js +123 -0
  20. data/spec/javascript/compiler/javascript.js +13 -0
  21. data/spec/javascript/compiler/ruby.js +14 -0
  22. data/spec/javascript/loader-test.js +64 -0
  23. data/spec/javascript/normalize-test.js +73 -0
  24. data/spec/javascript/packages-test.js +44 -0
  25. data/spec/javascript/relative-require-test.js +72 -0
  26. data/spec/javascript/require-test.js +117 -0
  27. data/spec/javascript/sandbox/compile.js +37 -0
  28. data/spec/javascript/sandbox/creation.js +44 -0
  29. data/spec/javascript/sandbox/format.js +79 -0
  30. data/spec/javascript/sandbox/misc.js +57 -0
  31. data/spec/javascript/sandbox/preprocessor.js +81 -0
  32. data/spec/javascript/sandbox/require.js +48 -0
  33. data/spec/javascript/sandbox/run-command.js +21 -0
  34. data/spec/javascript/spade/externs.js +14 -0
  35. data/spec/javascript/spade/load-factory.js +15 -0
  36. data/spec/javascript/spade/misc.js +23 -0
  37. data/spec/javascript/spade/ready.js +12 -0
  38. data/spec/javascript/spade/register.js +13 -0
  39. data/spec/javascript_spec.rb +7 -0
  40. data/spec/spec_helper.rb +23 -0
  41. data/spec/support/cli.rb +109 -0
  42. data/spec/support/core_test.rb +61 -0
  43. data/spec/support/matchers.rb +12 -0
  44. data/spec/support/path.rb +62 -0
  45. 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
+