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
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ tmp
2
+ devbin
data/bin/spaderun ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ $:.unshift(lib) if File.exists?(lib)
4
+
5
+ require 'spade/runtime'
6
+
7
+ Spade::Runtime::CLI::Base.start
8
+
9
+
@@ -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,7 @@
1
+ module Spade
2
+ module Runtime
3
+ module CLI
4
+ autoload :Base, 'spade/runtime/cli/base'
5
+ end
6
+ end
7
+ 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