2 %% Copyright (C) 2006 Damien Katz
4 %% This program is free software; you can redistribute it and/or
5 %% modify it under the terms of the GNU General Public License
6 %% as published by the Free Software Foundation; either version 2
7 %% of the License, or (at your option) any later version.
9 %% This program is distributed in the hope that it will be useful,
10 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
11 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 %% GNU General Public License for more details.
14 %% You should have received a copy of the GNU General Public License
15 %% along with this program; if not, write to the Free Software
16 %% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 -behaviour(gen_server
).
21 -export([start_link
/1]).
23 -export([init
/1, terminate
/2, handle_call
/3, handle_cast
/2, handle_info
/2,code_change
/3,stop
/0]).
24 -export([start_doc_map
/1, map_docs
/2, stop_doc_map
/1]).
26 -export([test
/0, test
/1]).
28 -include("couch_db.hrl").
31 gen_server:start_link({local
, couch_js
}, couch_js
, JsExec
, []).
34 exit(whereis(couch_js
), close
).
39 readline(Port
, Acc
) ->
41 {Port
, {data
, {noeol
, Data
}}} ->
42 readline(Port
, [Data
|Acc
]);
43 {Port
, {data
, {eol
, Data
}}} ->
44 lists:flatten(lists:reverse(Acc
, Data
));
46 throw({map_process_error
, Err
})
49 writeline(Port
, String
) ->
50 true
= port_command(Port
, String
++ "\n").
52 % send command and get a response.
53 prompt(Port
, String
) ->
54 writeline(Port
, String
),
57 start_doc_map(Functions
) ->
58 case gen_server:call(couch_js
, get_port
) of
62 couch_log:info("Spawing new js instance."),
63 Port
= open_port({spawn, Cmd
}, [stream
, {line
, 1000}, exit_status
, hide
, {cd
, filename:dirname(Cmd
)}])
65 "true" = prompt(Port
, "[\"reset\"]"),
66 % send the functions as json strings
67 lists:foreach(fun(FunctionSource
) ->
68 case prompt(Port
, "[\"add_map_fun\"," ++ cjson:encode(FunctionSource
) ++ "]") of
71 ErrorString
= cjson:decode(ErrorJsonString
),
72 throw({error_compiling_map_function
, ErrorString
})
78 map_docs(Port
, Docs
) ->
83 Json
= cjson:encode(couch_doc:to_json_obj(Doc
, false
)),
84 ResultsStr
= prompt(Port
, "[\"map_doc\"," ++ Json
++ "]"),
85 Results
= cjson:decode(ResultsStr
),
88 case obj_to_key_value(Result
) of
93 {obj
, [{"multi", List
}]} ->
95 lists:reverse(lists:map(
97 case obj_to_key_value(Result2
) of
104 tuple_to_list(List
)));
109 tuple_to_list(Results
))
114 % returns {ok, {Key,Value}} if the input is an object with just a key and/or value
115 % member, otherwise returns the input unchanged
116 obj_to_key_value({obj
, PropList
}) ->
118 [{"key", Key
}, {"value", Value
}] ->
120 [{"value", Value
}, {"key", Key
}] ->
124 [{"value", Value
}] ->
129 obj_to_key_value(Other
) ->
135 stop_doc_map(Port
) ->
136 ok
= gen_server:call(couch_js
, {return_port
, Port
}),
141 Port
= open_port({spawn, Cmd
}, [stream
, {line
, 1000}, exit_status
, hide
, {cd
, filename:dirname(Cmd
)}]),
144 terminate(_Reason
, _Server
) ->
148 handle_call(get_port
, {FromPid
, _
}, {Cmd
, [Port
|Rest
]}) ->
149 true
= port_connect(Port
, FromPid
),
151 {reply
, {ok
, Port
}, {Cmd
, Rest
}};
152 handle_call(get_port
, _From
, {Cmd
, []})->
153 {reply
, {empty
, Cmd
}, {Cmd
, []}};
154 handle_call({return_port
, Port
}, _From
, {Cmd
, Ports
}) ->
155 true
= port_connect(Port
, self()),
156 {reply
, ok
, {Cmd
, Ports
++ [Port
]}}.
158 handle_cast(_Whatever
, {Cmd
, Ports
}) ->
159 {noreply
, {Cmd
, Ports
}}.
161 handle_info({Port
, {exit_status
, 0}}, {Cmd
, Ports
}) ->
162 {noreply
, {Cmd
, Ports
-- [Port
]}};
163 handle_info({Port
, {exit_status
, Status
}}, {Cmd
, Ports
}) ->
164 couch_log:error("Abnormal shutdown of JS process (exit_status: ~w).", [Status
]),
165 {noreply
, {Cmd
, Ports
-- [Port
]}};
166 handle_info(_Whatever
, {Cmd
, Ports
}) ->
167 {noreply
, {Cmd
, Ports
}}.
169 code_change(_OldVsn
, State
, _Extra
) ->
173 test("../js/js -f main.js").
177 {ok
, DocMap
} = start_doc_map(["function(doc) {if (doc[0] == 'a') return doc[1];}"]),
178 {ok
, Results
} = map_docs(DocMap
, [#doc
{body
={"a", "b"}}, #doc
{body
={"c", "d"}},#doc
{body
={"a", "c"}}]),
179 io:format("Results: ~w~n", [Results
]),
180 stop_doc_map(DocMap
),