spade-runtime 0.1.0.1

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.
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
+