Skip to content
Newer
Older
100644 166 lines (142 sloc) 4.06 KB
0be476d @sstephenson Extract ExecJS::Runtime
authored
1 require "tempfile"
2
3 module ExecJS
c5e22c4 @sstephenson Runtime -> ExternalRuntime
authored
4 class ExternalRuntime
82773bf @josh Context
josh authored
5 class Context
c595c07 @josh Remove pure evaluation
josh authored
6 def initialize(runtime, source = "")
043c06b @josh Encode strings to utf-8 that are passed to compile
josh authored
7 source = source.encode('UTF-8') if source.respond_to?(:encode)
8
82773bf @josh Context
josh authored
9 @runtime = runtime
043c06b @josh Encode strings to utf-8 that are passed to compile
josh authored
10 @source = source
50791dd @josh Add Context#call
josh authored
11 end
12
4bc602f @josh Pure function hint
josh authored
13 def eval(source, options = {})
ae253df @lautis Fix typo in variable name
lautis authored
14 source = source.encode('UTF-8') if source.respond_to?(:encode)
0b4977e @josh Try to convert to UTF8 early
josh authored
15
82773bf @josh Context
josh authored
16 if /\S/ =~ source
3097959 @josh Switch to multi_json lib
josh authored
17 exec("return eval(#{MultiJson.encode("(#{source})")})")
82773bf @josh Context
josh authored
18 end
19 end
20
4bc602f @josh Pure function hint
josh authored
21 def exec(source, options = {})
ae253df @lautis Fix typo in variable name
lautis authored
22 source = source.encode('UTF-8') if source.respond_to?(:encode)
0b4977e @josh Try to convert to UTF8 early
josh authored
23
c595c07 @josh Remove pure evaluation
josh authored
24 compile_to_tempfile([@source, source].join("\n")) do |file|
0dc15c3 @josh Make runner_source and exec_runtime protected
josh authored
25 extract_result(@runtime.send(:exec_runtime, file.path))
82773bf @josh Context
josh authored
26 end
27 end
28
f815272 @sstephenson properties -> identifier
authored
29 def call(identifier, *args)
3097959 @josh Switch to multi_json lib
josh authored
30 eval "#{identifier}.apply(this, #{MultiJson.encode(args)})"
50791dd @josh Add Context#call
josh authored
31 end
32
82773bf @josh Context
josh authored
33 protected
34 def compile_to_tempfile(source)
a1277a6 @josh Style
josh authored
35 tempfile = Tempfile.open(['execjs', '.js'])
82773bf @josh Context
josh authored
36 tempfile.write compile(source)
37 tempfile.close
38 yield tempfile
39 ensure
40 tempfile.close!
41 end
42
43 def compile(source)
0dc15c3 @josh Make runner_source and exec_runtime protected
josh authored
44 @runtime.send(:runner_source).dup.tap do |output|
bd4c71c @josh Fix sub \ escaping
josh authored
45 output.sub!('#{source}') do
46 source
47 end
67c8927 @josh Encode JS unicode literals for JSC runner
josh authored
48 output.sub!('#{encoded_source}') do
49 encoded_source = encode_unicode_codepoints(source)
50 MultiJson.encode("(function(){ #{encoded_source} })()")
51 end
82773bf @josh Context
josh authored
52 output.sub!('#{json2_source}') do
53 IO.read(ExecJS.root + "/support/json2.js")
54 end
55 end
56 end
57
58 def extract_result(output)
3097959 @josh Switch to multi_json lib
josh authored
59 status, value = output.empty? ? [] : MultiJson.decode(output)
82773bf @josh Context
josh authored
60 if status == "ok"
61 value
67c8927 @josh Encode JS unicode literals for JSC runner
josh authored
62 elsif value == "SyntaxError: Parse error"
63 raise RuntimeError, value
82773bf @josh Context
josh authored
64 else
65 raise ProgramError, value
66 end
67 end
67c8927 @josh Encode JS unicode literals for JSC runner
josh authored
68
69 if "".respond_to?(:codepoints)
70 def encode_unicode_codepoints(str)
71 str.gsub(/[\u0080-\uffff]/) do |ch|
72 "\\u%04x" % ch.codepoints.to_a
73 end
74 end
75 else
76 def encode_unicode_codepoints(str)
77 str.unpack("U*").map { |b|
78 if b >= 128
79 "\\u%04x" % b
80 else
81 [b].pack("C")
82 end
83 }.join("")
84 end
85 end
82773bf @josh Context
josh authored
86 end
87
d19c760 @sstephenson Less magic
authored
88 attr_reader :name
89
954db08 @sstephenson Instantiate with options instead of subclassing
authored
90 def initialize(options)
d19c760 @sstephenson Less magic
authored
91 @name = options[:name]
954db08 @sstephenson Instantiate with options instead of subclassing
authored
92 @command = options[:command]
93 @runner_path = options[:runner_path]
5d0d51b @sstephenson therubyracer gem installs an incompatible `v8` binary - make sure we …
authored
94 @test_args = options[:test_args]
95 @test_match = options[:test_match]
c00ee39 @josh Support "nodejs" binary name
josh authored
96 @binary = locate_binary
954db08 @sstephenson Instantiate with options instead of subclassing
authored
97 end
98
82773bf @josh Context
josh authored
99 def exec(source)
100 context = Context.new(self)
c595c07 @josh Remove pure evaluation
josh authored
101 context.exec(source)
82773bf @josh Context
josh authored
102 end
103
0be476d @sstephenson Extract ExecJS::Runtime
authored
104 def eval(source)
82773bf @josh Context
josh authored
105 context = Context.new(self)
c595c07 @josh Remove pure evaluation
josh authored
106 context.eval(source)
0be476d @sstephenson Extract ExecJS::Runtime
authored
107 end
108
82773bf @josh Context
josh authored
109 def compile(source)
c595c07 @josh Remove pure evaluation
josh authored
110 Context.new(self, source)
c5e22c4 @sstephenson Runtime -> ExternalRuntime
authored
111 end
112
b22082f @sstephenson Add Runtime#available?
authored
113 def available?
3097959 @josh Switch to multi_json lib
josh authored
114 require "multi_json"
c00ee39 @josh Support "nodejs" binary name
josh authored
115 @binary ? true : false
b22082f @sstephenson Add Runtime#available?
authored
116 end
117
0dc15c3 @josh Make runner_source and exec_runtime protected
josh authored
118 protected
119 def runner_source
120 @runner_source ||= IO.read(@runner_path)
121 end
82773bf @josh Context
josh authored
122
0dc15c3 @josh Make runner_source and exec_runtime protected
josh authored
123 def exec_runtime(filename)
124 output = sh("#{@binary} #{filename} 2>&1")
125 if $?.success?
126 output
127 else
128 raise RuntimeError, output
129 end
82773bf @josh Context
josh authored
130 end
131
c00ee39 @josh Support "nodejs" binary name
josh authored
132 def locate_binary
3cf772e @sstephenson Add `which` support for Windows
authored
133 if binary = which(@command)
c00ee39 @josh Support "nodejs" binary name
josh authored
134 if @test_args
135 output = `#{binary} #{@test_args} 2>&1`
136 binary if output.match(@test_match)
137 else
138 binary
139 end
140 end
141 end
142
3cf772e @sstephenson Add `which` support for Windows
authored
143 def which(command)
612c95e @josh Fix external runtime which to return the expanded path
josh authored
144 Array(command).each do |name|
88fa6b7 @swaits Preserve command flags passed to which
swaits authored
145 name, args = name.split(/\s+/, 2)
3cf772e @sstephenson Add `which` support for Windows
authored
146 result = if ExecJS.windows?
147 `#{ExecJS.root}/support/which.bat #{name}`
148 else
da30b02 Fix external runtime detection under zsh and other shells.
Marcus Brito authored
149 `command -v #{name} 2>/dev/null`
3cf772e @sstephenson Add `which` support for Windows
authored
150 end
612c95e @josh Fix external runtime which to return the expanded path
josh authored
151
152 if path = result.strip.split("\n").first
88fa6b7 @swaits Preserve command flags passed to which
swaits authored
153 return args ? "#{path} #{args}" : path
612c95e @josh Fix external runtime which to return the expanded path
josh authored
154 end
3cf772e @sstephenson Add `which` support for Windows
authored
155 end
612c95e @josh Fix external runtime which to return the expanded path
josh authored
156 nil
3cf772e @sstephenson Add `which` support for Windows
authored
157 end
158
67c8927 @josh Encode JS unicode literals for JSC runner
josh authored
159 def sh(command)
160 output = nil
161 IO.popen(command) { |f| output = f.read }
162 output
24a41df @josh Fix JSC encoding
josh authored
163 end
0be476d @sstephenson Extract ExecJS::Runtime
authored
164 end
165 end
Something went wrong with that request. Please try again.