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