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
data/.gitignore
ADDED
data/bin/spaderun
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'spade/runtime'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spade/core'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'spade/packager'
|
5
|
+
rescue LoadError
|
6
|
+
# Packager isn't necessary, but we should use it if available
|
7
|
+
end
|
8
|
+
|
9
|
+
module Spade
|
10
|
+
module Runtime
|
11
|
+
autoload :Bundle, 'spade/runtime/bundle'
|
12
|
+
autoload :Context, 'spade/runtime/context'
|
13
|
+
autoload :MainContext,'spade/runtime/context'
|
14
|
+
autoload :Server, 'spade/runtime/server'
|
15
|
+
autoload :Shell, 'spade/runtime/shell'
|
16
|
+
autoload :CLI, 'spade/runtime/cli'
|
17
|
+
autoload :Namespace, 'spade/runtime/exports'
|
18
|
+
autoload :Exports, 'spade/runtime/exports'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,173 @@
|
|
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
|
+
module Spade::Runtime
|
10
|
+
BOOT_PATH = File.dirname(Spade::JSPATH)
|
11
|
+
|
12
|
+
module Bundle
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def update(rootdir, opts ={})
|
16
|
+
|
17
|
+
verbose = opts[:verbose]
|
18
|
+
spade_path = File.join(rootdir, Spade::SPADE_DIR)
|
19
|
+
spade_package_path = File.join(spade_path, 'packages')
|
20
|
+
FileUtils.rm_r(spade_path) if File.exists? spade_path
|
21
|
+
|
22
|
+
FileUtils.mkdir_p File.join(spade_package_path)
|
23
|
+
|
24
|
+
FileUtils.ln_s BOOT_PATH, File.join(spade_path, 'boot')
|
25
|
+
|
26
|
+
installed = []
|
27
|
+
package_dirs = []
|
28
|
+
|
29
|
+
# In order of precedence
|
30
|
+
package_dirs += %w[packages vendor/packages vendor/cache].map{|p| File.join(rootdir, p.split('/')) }
|
31
|
+
package_dirs << File.join(LibGems.dir, 'gems') if defined?(Spade::Packager)
|
32
|
+
|
33
|
+
for package_dir in package_dirs
|
34
|
+
Dir.glob(File.join(package_dir, '*')).each do |path|
|
35
|
+
json_file = File.join(path, 'package.json')
|
36
|
+
next unless File.exists?(json_file)
|
37
|
+
|
38
|
+
# How would this happen?
|
39
|
+
next if installed.include? path
|
40
|
+
installed << path
|
41
|
+
|
42
|
+
json = JSON.load File.read(json_file)
|
43
|
+
package_name = json['name']
|
44
|
+
package_version = json['version']
|
45
|
+
|
46
|
+
local = path.index(rootdir) == 0
|
47
|
+
|
48
|
+
# Use relative paths if embedded
|
49
|
+
old_path = if local
|
50
|
+
# Figure out how many levels deep the spade_path is in the project
|
51
|
+
levels = spade_package_path.sub(rootdir, '').split(File::SEPARATOR).reject{|p| p.empty? }.count
|
52
|
+
# Build relative path
|
53
|
+
File.join(['..'] * levels, path.sub(rootdir, ''))
|
54
|
+
else
|
55
|
+
path
|
56
|
+
end
|
57
|
+
|
58
|
+
new_path = File.join(spade_path, 'packages', package_name)
|
59
|
+
|
60
|
+
unless File.exist?(new_path)
|
61
|
+
FileUtils.ln_s old_path, new_path, :force => true
|
62
|
+
|
63
|
+
puts "Installing #{local ? "local" : "remote"} package #{package_name}" if verbose
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
File.open(File.join(rootdir, 'spade-boot.js'), 'w+') do |fp|
|
69
|
+
fp.write gen_spade_boot(rootdir, opts)
|
70
|
+
end
|
71
|
+
puts "Wrote spade-boot.js" if verbose
|
72
|
+
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
def gen_spade_boot(rootdir, opts={})
|
77
|
+
verbose = opts[:verbose]
|
78
|
+
spade_path = File.join(rootdir, Spade::SPADE_DIR, 'packages', '*')
|
79
|
+
known_packages = Dir.glob(spade_path).map do |path|
|
80
|
+
package_name = File.basename path
|
81
|
+
info = JSON.load File.read(File.join(path, 'package.json'))
|
82
|
+
info["sync"] = true
|
83
|
+
info["root"] = "#{Spade::SPADE_DIR}/packages/#{package_name}"
|
84
|
+
info["files"] = package_file_list(path)
|
85
|
+
info
|
86
|
+
end
|
87
|
+
|
88
|
+
if File.exists? File.join(rootdir, 'package.json')
|
89
|
+
info = JSON.load File.read(File.join(rootdir, 'package.json'))
|
90
|
+
else
|
91
|
+
info = {
|
92
|
+
"name" => File.basename(rootdir),
|
93
|
+
"directories" => { "lib" => "lib" }
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
info["root"] = '.'
|
98
|
+
info["sync"] = true
|
99
|
+
info["files"] = package_file_list('.')
|
100
|
+
|
101
|
+
packages = resolve_dependencies(info, known_packages)
|
102
|
+
|
103
|
+
%[// GENERATED: #{Time.now.to_s}
|
104
|
+
// This file is automatically generated by spade. To update run
|
105
|
+
// 'spade update'. To use this file, reference it in your HTML file. Base
|
106
|
+
// sure that your base URL is to the top level directory containing all of
|
107
|
+
// your files.
|
108
|
+
|
109
|
+
/*globals spade */
|
110
|
+
//@ begin boot
|
111
|
+
(function() {
|
112
|
+
// load spade itself
|
113
|
+
var script = document.createElement('script');
|
114
|
+
script.src = "#{Spade::SPADE_DIR}/boot/spade.js";
|
115
|
+
script.onload = function() {
|
116
|
+
|
117
|
+
// Register remaining packages with spade
|
118
|
+
#{packages.map{|p| %[spade.register("#{p["name"]}", #{JSON.pretty_generate(p)});\n] } * "\n"}
|
119
|
+
|
120
|
+
// find the main module to run
|
121
|
+
var main = null;
|
122
|
+
var scripts = document.scripts || document.getElementsByTagName("script"),
|
123
|
+
len = scripts.length;
|
124
|
+
for(var idx=0;!main && idx<len;idx++) {
|
125
|
+
main = scripts[idx].getAttribute('data-require');
|
126
|
+
}
|
127
|
+
scripts = null; // avoid memory leaks in IE
|
128
|
+
|
129
|
+
if (main) spade.ready(function() { spade.require(main); });
|
130
|
+
};
|
131
|
+
|
132
|
+
var head = document.head || document.body || document;
|
133
|
+
head.appendChild(script);
|
134
|
+
|
135
|
+
script = head = null; // avoid memory leaks in IE
|
136
|
+
})();
|
137
|
+
//@ end boot
|
138
|
+
]
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def package_file_list(path)
|
145
|
+
get_all_files(path).map{|p| p.sub(path+File::SEPARATOR, '') } - %w(package.json spade-boot.js)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Supports recursive, be careful
|
149
|
+
def get_all_files(path)
|
150
|
+
Dir.glob(File.join(path, '**', '*')).
|
151
|
+
map{|p| File.symlink?(p) ? get_all_files(p) : p }.
|
152
|
+
flatten.
|
153
|
+
reject{|p| File.extname(p).empty? }
|
154
|
+
end
|
155
|
+
|
156
|
+
def resolve_dependencies(package, available)
|
157
|
+
# TODO: Should available be a hash to speed lookup?
|
158
|
+
if package["dependencies"]
|
159
|
+
# TODO: Check version numbers
|
160
|
+
dependencies = package["dependencies"].keys
|
161
|
+
packages = available.select{|p| dependencies.include?(p["name"]) }.
|
162
|
+
map{|p| resolve_dependencies(p, available) }.
|
163
|
+
flatten
|
164
|
+
(packages << package).uniq
|
165
|
+
else
|
166
|
+
[package]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Spade
|
4
|
+
module Runtime
|
5
|
+
module CLI
|
6
|
+
class Base < Thor
|
7
|
+
|
8
|
+
class_option :working, :required => false,
|
9
|
+
:default => Spade.discover_root(Dir.pwd),
|
10
|
+
:aliases => ['-w'],
|
11
|
+
:desc => 'Root working directory.'
|
12
|
+
|
13
|
+
class_option :verbose, :type => :boolean, :default => false,
|
14
|
+
:aliases => ['-V'],
|
15
|
+
:desc => 'Show additional debug information while running'
|
16
|
+
|
17
|
+
class_option :require, :type => :array, :required => false,
|
18
|
+
:aliases => ['-r'],
|
19
|
+
:desc => "optional JS files to require before invoking main command"
|
20
|
+
|
21
|
+
map "-i" => "console", "--interactive" => "console"
|
22
|
+
desc "console", "Opens an interactive JavaScript console"
|
23
|
+
def console
|
24
|
+
require 'readline'
|
25
|
+
|
26
|
+
shell = Spade::Runtime::Shell.new
|
27
|
+
context(:with => shell) do |ctx|
|
28
|
+
shell.ctx = ctx
|
29
|
+
puts "help() for help. quit() to quit."
|
30
|
+
puts "Spade #{Spade::Runtime::VERSION} (V8 #{V8::VERSION})"
|
31
|
+
puts "WORKING=#{options[:working]}" if options[:verbose]
|
32
|
+
|
33
|
+
trap("SIGINT") { puts "^C" }
|
34
|
+
repl ctx
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
map "-e" => "exec"
|
39
|
+
desc "exec [FILENAME]", "Executes filename or stdin"
|
40
|
+
def exec(*)
|
41
|
+
exec_args = ARGV.dup
|
42
|
+
arg = exec_args.shift while arg != "exec" && !exec_args.empty?
|
43
|
+
|
44
|
+
filename = exec_args.shift
|
45
|
+
puts "Filename: #{filename}" if options[:verbose]
|
46
|
+
|
47
|
+
if filename
|
48
|
+
puts "Working: #{options[:working]}" if options[:verbose]
|
49
|
+
filename = File.expand_path filename, Dir.pwd
|
50
|
+
throw "#{filename} not found" unless File.exists?(filename)
|
51
|
+
fp = File.open filename
|
52
|
+
source = File.basename filename
|
53
|
+
rootdir = Spade.discover_root filename
|
54
|
+
|
55
|
+
caller_id = nil
|
56
|
+
if rootdir
|
57
|
+
json_path = File.join(rootdir, 'package.json')
|
58
|
+
if File.exist?(json_path)
|
59
|
+
package_json = JSON.parse(File.read(json_path))
|
60
|
+
caller_id = "#{package_json['name']}/main"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# peek at first line. If it is poundhash, skip. else rewind file
|
65
|
+
unless fp.readline =~ /^\#\!/
|
66
|
+
fp.rewind
|
67
|
+
end
|
68
|
+
|
69
|
+
# Can't set pos on STDIN so we can only do this for files
|
70
|
+
if options[:verbose]
|
71
|
+
pos = fp.pos
|
72
|
+
puts fp.read
|
73
|
+
fp.pos = pos
|
74
|
+
end
|
75
|
+
else
|
76
|
+
fp = $stdin
|
77
|
+
source = '<stdin>'
|
78
|
+
rootdir = options[:working]
|
79
|
+
caller_id = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
if options[:verbose]
|
83
|
+
puts "source: #{source}"
|
84
|
+
puts "rootdir: #{rootdir}"
|
85
|
+
puts "caller_id: #{caller_id}"
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
# allow for poundhash
|
90
|
+
context(:argv => exec_args, :rootdir => rootdir, :caller_id => caller_id) do |ctx|
|
91
|
+
ctx.eval(fp, source) # eval the rest
|
92
|
+
end
|
93
|
+
rescue Interrupt => e
|
94
|
+
puts; exit
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
map "server" => "preview"
|
99
|
+
desc "preview", "Starts a preview server for testing"
|
100
|
+
long_desc %[
|
101
|
+
The preview command starts a simple file server that can be used to
|
102
|
+
load JavaScript-based apps in the browser. This is a convenient way to
|
103
|
+
run apps in the browser instead of having to setup Apache on your
|
104
|
+
local machine. If you are already loading apps through your own web
|
105
|
+
server (for ex using Rails) the preview server is not required.
|
106
|
+
]
|
107
|
+
method_option :port, :type => :string, :default => '4020',
|
108
|
+
:aliases => ['-p'],
|
109
|
+
:desc => 'Port number'
|
110
|
+
def preview
|
111
|
+
require 'spade/runtime/server'
|
112
|
+
trap("SIGINT") { Spade::Runtime::Server.shutdown }
|
113
|
+
Spade::Runtime::Server.run(options[:working], options[:port]);
|
114
|
+
end
|
115
|
+
|
116
|
+
desc "update", "Update package info in the current project"
|
117
|
+
def update
|
118
|
+
Spade::Runtime::Bundle.update(options[:working], :verbose => options[:verbose])
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def repl(ctx)
|
124
|
+
ctx.reactor.next_tick do
|
125
|
+
line = Readline.readline("spade> ", true)
|
126
|
+
begin
|
127
|
+
result = ctx.eval(line, '<console>')
|
128
|
+
puts result unless result.nil?
|
129
|
+
rescue V8::JSError => e
|
130
|
+
puts e.message
|
131
|
+
puts e.backtrace(:javascript)
|
132
|
+
rescue StandardError => e
|
133
|
+
puts e
|
134
|
+
puts e.backtrace.join("\n")
|
135
|
+
end
|
136
|
+
repl(ctx)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Loads a JS file into the context. This is not a require; just load
|
141
|
+
def load(cxt, libfile)
|
142
|
+
begin
|
143
|
+
content = File.readlines(libfile)
|
144
|
+
content.shift if content.first && (content.first =~ /^\#\!/)
|
145
|
+
cxt.eval(content*'')
|
146
|
+
#cxt.load(libfile)
|
147
|
+
rescue V8::JSError => e
|
148
|
+
puts e.message
|
149
|
+
puts e.backtrace(:javascript)
|
150
|
+
rescue StandardError => e
|
151
|
+
puts e
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Initialize a context to work against. This will load also handle
|
156
|
+
# autorequires
|
157
|
+
def context(opts={})
|
158
|
+
opts[:rootdir] ||= options[:working]
|
159
|
+
opts[:verbose] = options[:verbose]
|
160
|
+
Spade::Runtime::MainContext.new(opts) do |ctx|
|
161
|
+
|
162
|
+
requires = opts[:require]
|
163
|
+
requires.each { |r| load(ctx, r) } if requires
|
164
|
+
|
165
|
+
yield(ctx) if block_given?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def method_missing(meth, *)
|
170
|
+
if File.exist?(meth.to_s)
|
171
|
+
ARGV.unshift("exec")
|
172
|
+
invoke :exec
|
173
|
+
else
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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::Runtime
|
8
|
+
|
9
|
+
# Compiler plugin for the default context. Know how to create a new
|
10
|
+
# isolated context for the object
|
11
|
+
class Compiler
|
12
|
+
|
13
|
+
def initialize(ctx)
|
14
|
+
@ctx = ctx
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup(sandbox)
|
18
|
+
if sandbox['isIsolated']
|
19
|
+
sandbox['ctx'] = Context.new :reactor => @ctx.reactor
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def compile(data, sandbox, filename)
|
24
|
+
ctx = sandbox['ctx'] || @ctx
|
25
|
+
ctx.eval("(#{data})", filename)
|
26
|
+
end
|
27
|
+
|
28
|
+
def teardown(sandbox)
|
29
|
+
sandbox['ctx'] = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
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
|
+
|
8
|
+
module Spade::Runtime
|
9
|
+
|
10
|
+
class Console
|
11
|
+
|
12
|
+
def debug(*args)
|
13
|
+
puts "\033[35mDEBUG: #{args * ','}\033[m"
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def info(*args)
|
18
|
+
puts args * ','
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def error(*args)
|
23
|
+
puts "\033[31mERROR: #{args * ','}\033[m"
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def warn(*args)
|
28
|
+
puts "\033[33mWARN: #{args * ','}\033[m"
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def log(*args)
|
33
|
+
puts args * ','
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|