-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathexecjs.ex
More file actions
81 lines (69 loc) · 2.08 KB
/
execjs.ex
File metadata and controls
81 lines (69 loc) · 2.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
defmodule Execjs do
import Execjs.Escape, only: [escape: 1]
defmodule Error, do: defexception [:message]
defmodule RuntimeError, do: defexception [:message]
defmodule RuntimeUnavailable do
defexception message: "Could not find JavaScript runtime"
end
@spec eval(String.t) :: any
def eval(source) when is_binary(source) do
exec ~s[eval("#{escape(source)}")]
end
@spec compile(String.t) :: (String.t -> String.t)
def compile(source) when is_binary(source) do
{ pre, post } = { "(function(){\n#{source};\n", ";\n})()" }
fn (source) ->
pre <> source <> post
end
end
@spec call((String.t -> String.t), String.t, list(any)) :: any
def call(context, identifier, args) when is_binary(identifier) and is_list(args) do
source = "return #{identifier}.apply(this, #{Poison.encode!(args, escape: :javascript)})"
exec context.(source)
end
defp exec(source) do
runtime = Execjs.Runtimes.best_available
program = runtime.template(source)
command = runtime.command |> System.find_executable
tmpfile = compile_to_tempfile(program)
try do
port = Port.open({ :spawn_executable, command },
[:stream, :in, :binary,:nouse_stdio, :eof, :hide, { :args, [tmpfile] }])
extract_result(loop(port))
after
File.rm! tmpfile
end
end
defp loop(port) do
loop(port, "")
end
defp loop(port, acc) do
receive do
{ ^port, { :data, data } } ->
loop(port, acc <> data)
{ ^port, :eof } ->
send port, { self(), :close }
receive do: ({ ^port, :closed } -> :ok)
acc
end
end
defp compile_to_tempfile(program) do
hash = :erlang.phash2(:crypto.strong_rand_bytes(8))
filename = "execjs-#{hash}.js"
path = Path.join(System.tmp_dir!, filename)
File.write! path, program
path
end
defp extract_result(output) do
case Poison.decode!(output) do
[ "ok", value ] ->
value
[ "ok" ] ->
:undefined
[ "err", message ] ->
raise %RuntimeError{message: message}
[ "err" ] ->
raise %Error{}
end
end
end