1 |
|
-module(chrme_cli). |
2 |
|
|
3 |
|
-export([main/1]). |
4 |
|
|
5 |
|
-define(EXIT_OK, 0). |
6 |
|
-define(EXIT_USAGE_ERROR, 1). |
7 |
|
-define(EXIT_ERROR, 2). |
8 |
|
|
9 |
|
main(Args) -> |
10 |
:-( |
case Args of |
11 |
|
[] -> |
12 |
:-( |
print_usage(), |
13 |
:-( |
halt(?EXIT_OK); |
14 |
|
["-h"] -> |
15 |
:-( |
print_usage(), |
16 |
:-( |
halt(?EXIT_OK); |
17 |
|
["--help"] -> |
18 |
:-( |
print_usage(), |
19 |
:-( |
halt(?EXIT_OK); |
20 |
|
["-V"] -> |
21 |
:-( |
print_version(), |
22 |
:-( |
halt(?EXIT_OK); |
23 |
|
["--version"] -> |
24 |
:-( |
print_version(), |
25 |
:-( |
halt(?EXIT_OK); |
26 |
|
["help" | Rest] -> |
27 |
:-( |
do_help(Rest), |
28 |
:-( |
halt(?EXIT_OK); |
29 |
|
_ -> |
30 |
:-( |
case parse_args(Args) of |
31 |
|
{ok, Opts, CmdArgs} -> |
32 |
:-( |
dispatch(Opts, CmdArgs); |
33 |
|
{error, Msg} -> |
34 |
:-( |
io:format("Error: ~s~n", [Msg]), |
35 |
:-( |
print_usage(), |
36 |
:-( |
halt(?EXIT_USAGE_ERROR) |
37 |
|
end |
38 |
|
end. |
39 |
|
|
40 |
|
do_navigate(Opts, ["--id", IdStr, UrlStr]) -> |
41 |
:-( |
Host = maps:get(host, Opts), |
42 |
:-( |
Port = maps:get(port, Opts), |
43 |
:-( |
Name = cli_nav, |
44 |
:-( |
TargetId = list_to_binary(IdStr), |
45 |
:-( |
case chrme_session:attach(Name, Host, Port, TargetId) of |
46 |
|
{ok, _Session} -> |
47 |
:-( |
chrme_session:await_start(Name), |
48 |
:-( |
_ = chrme_page:enable(Name), |
49 |
:-( |
case chrme_page:navigate(Name, list_to_binary(UrlStr)) of |
50 |
|
{ok, Nav} -> |
51 |
:-( |
Frame = maps:get(frame_id, Nav), |
52 |
:-( |
Loader = maps:get(loader_id, Nav), |
53 |
:-( |
io:format("Navigated existing target ~ts to ~ts. FrameId=~ts loaderId=~p~n", |
54 |
|
[TargetId, UrlStr, Frame, Loader]), |
55 |
:-( |
halt(?EXIT_OK); |
56 |
|
Err -> |
57 |
:-( |
io:format("Navigation error: ~p~n", [Err]), |
58 |
:-( |
halt(?EXIT_ERROR) |
59 |
|
end; |
60 |
|
{error, Err} -> |
61 |
:-( |
io:format("Error attaching to target ~ts: ~p~n", [TargetId, Err]), |
62 |
:-( |
halt(?EXIT_ERROR) |
63 |
|
end; |
64 |
|
do_navigate(Opts, ["-i", IdStr, UrlStr]) -> |
65 |
:-( |
do_navigate(Opts, ["--id", IdStr, UrlStr]); |
66 |
|
do_navigate(Opts, [UrlStr]) -> |
67 |
:-( |
Host = maps:get(host, Opts), |
68 |
:-( |
Port = maps:get(port, Opts), |
69 |
:-( |
Name = cli_nav, |
70 |
:-( |
Url = list_to_binary(UrlStr), |
71 |
:-( |
case chrme_session:start(Name, Host, Port, Url) of |
72 |
|
{ok, _Session} -> |
73 |
:-( |
chrme_session:await_start(Name), |
74 |
:-( |
_ = chrme_page:enable(Name), |
75 |
:-( |
case chrme_page:navigate(Name, Url) of |
76 |
|
{ok, Nav} -> |
77 |
:-( |
Frame = maps:get(frame_id, Nav), |
78 |
:-( |
Loader = maps:get(loader_id, Nav), |
79 |
:-( |
io:format("Navigated to ~ts. FrameId=~ts loaderId=~p~n", [Url, Frame, Loader]), |
80 |
:-( |
halt(?EXIT_OK); |
81 |
|
Err -> |
82 |
:-( |
io:format("Navigation error: ~p~n", [Err]), |
83 |
:-( |
halt(?EXIT_ERROR) |
84 |
|
end; |
85 |
|
{error, Err} -> |
86 |
:-( |
io:format("Error starting session: ~p~n", [Err]), |
87 |
:-( |
halt(?EXIT_ERROR) |
88 |
|
end; |
89 |
|
do_navigate(_, _) -> |
90 |
:-( |
io:format("Usage: chrme navigate <url> | --id <target-id> <url>~n", []), |
91 |
:-( |
halt(?EXIT_USAGE_ERROR). |
92 |
|
|
93 |
|
%% Evaluate JavaScript expression on a new or existing target |
94 |
|
do_evaluate(Opts, ["--id", IdStr | ExprParts]) -> |
95 |
:-( |
Host = maps:get(host, Opts), |
96 |
:-( |
Port = maps:get(port, Opts), |
97 |
:-( |
Name = cli_eval, |
98 |
:-( |
TargetId = list_to_binary(IdStr), |
99 |
:-( |
case chrme_session:attach(Name, Host, Port, TargetId) of |
100 |
|
{ok, _Session} -> |
101 |
:-( |
chrme_session:await_start(Name), |
102 |
:-( |
_ = chrme_cdp:call(Name, <<"Runtime.enable">>, #{}), |
103 |
:-( |
Expr = list_to_binary(string:join(ExprParts, " ")), |
104 |
:-( |
case chrme_runtime:evaluate(Name, Expr) of |
105 |
:-( |
{ok, Result} -> io:format("~p~n", [Result]), halt(?EXIT_OK); |
106 |
:-( |
{error, Err} -> io:format("Evaluation error: ~p~n", [Err]), halt(?EXIT_ERROR) |
107 |
|
end; |
108 |
|
{error, Err} -> |
109 |
:-( |
io:format("Error attaching to target ~ts: ~p~n", [TargetId, Err]), |
110 |
:-( |
halt(?EXIT_ERROR) |
111 |
|
end; |
112 |
|
do_evaluate(Opts, ["-i", IdStr | ExprParts]) -> |
113 |
:-( |
do_evaluate(Opts, ["--id", IdStr | ExprParts]); |
114 |
|
do_evaluate(Opts, ExprParts) when is_list(ExprParts) -> |
115 |
:-( |
Host = maps:get(host, Opts), |
116 |
:-( |
Port = maps:get(port, Opts), |
117 |
:-( |
Name = cli_eval, |
118 |
|
%% Open a blank page by default |
119 |
:-( |
case chrme_session:start(Name, Host, Port, list_to_binary("about:blank")) of |
120 |
|
{ok, _Session} -> |
121 |
:-( |
chrme_session:await_start(Name), |
122 |
:-( |
_ = chrme_cdp:call(Name, <<"Runtime.enable">>, #{}), |
123 |
:-( |
Expr = list_to_binary(string:join(ExprParts, " ")), |
124 |
:-( |
case chrme_runtime:evaluate(Name, Expr) of |
125 |
:-( |
{ok, Result} -> io:format("~p~n", [Result]), halt(?EXIT_OK); |
126 |
:-( |
{error, Err} -> io:format("Evaluation error: ~p~n", [Err]), halt(?EXIT_ERROR) |
127 |
|
end; |
128 |
|
{error, Err} -> |
129 |
:-( |
io:format("Error creating session: ~p~n", [Err]), |
130 |
:-( |
halt(?EXIT_ERROR) |
131 |
|
end; |
132 |
|
do_evaluate(_, _) -> |
133 |
:-( |
io:format("Usage: chrme evaluate [--id <target-id>] <expression>~n", []), |
134 |
:-( |
halt(?EXIT_USAGE_ERROR). |
135 |
|
|
136 |
|
%% Dump the DOM of a target (new or existing) |
137 |
|
do_dom(Opts, ["--id", IdStr]) -> |
138 |
:-( |
Host = maps:get(host, Opts), |
139 |
:-( |
Port = maps:get(port, Opts), |
140 |
:-( |
Name = cli_dom, |
141 |
:-( |
TargetId = list_to_binary(IdStr), |
142 |
:-( |
case chrme_session:attach(Name, Host, Port, TargetId) of |
143 |
|
{ok, _Session} -> |
144 |
:-( |
chrme_session:await_start(Name), |
145 |
:-( |
_ = chrme_cdp:call(Name, <<"DOM.enable">>, #{}), |
146 |
:-( |
case chrme_dom:get_document(Name) of |
147 |
|
{ok, Doc} -> |
148 |
:-( |
NodeId = maps:get(<<"nodeId">>, Doc), |
149 |
:-( |
case chrme_dom:get_outer_html(Name, NodeId) of |
150 |
|
{ok, Html} -> |
151 |
:-( |
io:format("~ts~n", [Html]), |
152 |
:-( |
halt(?EXIT_OK); |
153 |
|
{error, Err} -> |
154 |
:-( |
io:format("DOM dump error: ~p~n", [Err]), |
155 |
:-( |
halt(?EXIT_ERROR) |
156 |
|
end; |
157 |
|
{error, Err} -> |
158 |
:-( |
io:format("Error getting document: ~p~n", [Err]), |
159 |
:-( |
halt(?EXIT_ERROR) |
160 |
|
end; |
161 |
|
{error, Err} -> |
162 |
:-( |
io:format("Error attaching to target ~s: ~p~n", [IdStr, Err]), |
163 |
:-( |
halt(?EXIT_ERROR) |
164 |
|
end; |
165 |
|
do_dom(Opts, ["-i", IdStr]) -> |
166 |
:-( |
do_dom(Opts, ["--id", IdStr]); |
167 |
|
do_dom(Opts, [UrlStr]) -> |
168 |
:-( |
Host = maps:get(host, Opts), |
169 |
:-( |
Port = maps:get(port, Opts), |
170 |
:-( |
Name = cli_dom, |
171 |
:-( |
Url = list_to_binary(UrlStr), |
172 |
:-( |
case chrme_session:start(Name, Host, Port, Url) of |
173 |
|
{ok, _Session} -> |
174 |
:-( |
chrme_session:await_start(Name), |
175 |
:-( |
_ = chrme_cdp:call(Name, <<"Page.enable">>, #{}), |
176 |
:-( |
_ = chrme_cdp:call(Name, <<"DOM.enable">>, #{}), |
177 |
:-( |
case chrme_page:navigate(Name, Url) of |
178 |
|
{ok, _Nav} -> |
179 |
:-( |
case chrme_dom:get_document(Name) of |
180 |
|
{ok, Doc} -> |
181 |
:-( |
NodeId = maps:get(<<"nodeId">>, Doc), |
182 |
:-( |
case chrme_dom:get_outer_html(Name, NodeId) of |
183 |
|
{ok, Html} -> |
184 |
:-( |
io:format("~ts~n", [Html]), |
185 |
:-( |
halt(?EXIT_OK); |
186 |
|
{error, Err} -> |
187 |
:-( |
io:format("DOM dump error: ~p~n", [Err]), |
188 |
:-( |
halt(?EXIT_ERROR) |
189 |
|
end; |
190 |
|
{error, Err} -> |
191 |
:-( |
io:format("Error getting document: ~p~n", [Err]), |
192 |
:-( |
halt(?EXIT_ERROR) |
193 |
|
end; |
194 |
|
{error, Err} -> |
195 |
:-( |
io:format("Navigation error: ~p~n", [Err]), |
196 |
:-( |
halt(?EXIT_ERROR) |
197 |
|
end; |
198 |
|
{error, Err} -> |
199 |
:-( |
io:format("Error starting session: ~p~n", [Err]), |
200 |
:-( |
halt(?EXIT_ERROR) |
201 |
|
end; |
202 |
|
do_dom(_, _) -> |
203 |
:-( |
io:format("Usage: chrme dom [--id <target-id>] <url>~n", []), |
204 |
:-( |
halt(?EXIT_USAGE_ERROR). |
205 |
|
|
206 |
|
%% Stream all protocol events for a target (new or existing) |
207 |
|
do_events(Opts, ["--id", IdStr]) -> |
208 |
:-( |
Host = maps:get(host, Opts), |
209 |
:-( |
Port = maps:get(port, Opts), |
210 |
:-( |
Name = cli_events, |
211 |
:-( |
TargetId = list_to_binary(IdStr), |
212 |
:-( |
case chrme_session:attach(Name, Host, Port, TargetId) of |
213 |
|
{ok, _Session} -> |
214 |
:-( |
chrme_session:await_start(Name), |
215 |
:-( |
_ = chrme_cdp:call(Name, <<"Page.enable">>, #{}), |
216 |
:-( |
_ = chrme_cdp:call(Name, <<"Network.enable">>, #{}), |
217 |
:-( |
_ = chrme_cdp:call(Name, <<"Runtime.enable">>, #{}), |
218 |
:-( |
_ = chrme_cdp:call(Name, <<"DOM.enable">>, #{}), |
219 |
:-( |
Ref = make_ref(), |
220 |
:-( |
CallbackName = {cli_events, Name, Ref}, |
221 |
:-( |
CallbackFun = fun |
222 |
:-( |
(stop) -> true; |
223 |
|
(Msg) -> |
224 |
:-( |
io:format("~s~n", [jsone:encode(Msg)]), |
225 |
:-( |
true |
226 |
|
end, |
227 |
:-( |
chrme_ws_apic:add_callback(Name, {CallbackName, CallbackFun}), |
228 |
:-( |
io:format("Streaming events for target ~ts. Press Ctrl+C to quit.~n", [TargetId]), |
229 |
:-( |
events_loop(); |
230 |
|
{error, Err} -> |
231 |
:-( |
io:format("Error attaching to target ~s: ~p~n", [IdStr, Err]), |
232 |
:-( |
halt(?EXIT_ERROR) |
233 |
|
end; |
234 |
|
do_events(Opts, ["-i", IdStr]) -> |
235 |
:-( |
do_events(Opts, ["--id", IdStr]); |
236 |
|
do_events(Opts, []) -> |
237 |
:-( |
Host = maps:get(host, Opts), |
238 |
:-( |
Port = maps:get(port, Opts), |
239 |
:-( |
Name = cli_events, |
240 |
:-( |
case chrme_session:start(Name, Host, Port, list_to_binary("about:blank")) of |
241 |
|
{ok, _Session} -> |
242 |
:-( |
chrme_session:await_start(Name), |
243 |
:-( |
_ = chrme_cdp:call(Name, <<"Page.enable">>, #{}), |
244 |
:-( |
_ = chrme_cdp:call(Name, <<"Network.enable">>, #{}), |
245 |
:-( |
_ = chrme_cdp:call(Name, <<"Runtime.enable">>, #{}), |
246 |
:-( |
_ = chrme_cdp:call(Name, <<"DOM.enable">>, #{}), |
247 |
:-( |
Ref = make_ref(), |
248 |
:-( |
CallbackName = {cli_events, Name, Ref}, |
249 |
:-( |
CallbackFun = fun |
250 |
:-( |
(stop) -> true; |
251 |
|
(Msg) -> |
252 |
:-( |
io:format("~s~n", [jsone:encode(Msg)]), |
253 |
:-( |
true |
254 |
|
end, |
255 |
:-( |
chrme_ws_apic:add_callback(Name, {CallbackName, CallbackFun}), |
256 |
:-( |
io:format("Streaming events for new target. Press Ctrl+C to quit.~n", []), |
257 |
:-( |
events_loop(); |
258 |
|
_ -> |
259 |
:-( |
io:format("Usage: chrme events [--id <target-id>]~n", []), |
260 |
:-( |
halt(?EXIT_USAGE_ERROR) |
261 |
|
end. |
262 |
|
|
263 |
|
events_loop() -> |
264 |
:-( |
receive |
265 |
:-( |
_ -> events_loop() |
266 |
|
end. |
267 |
|
|
268 |
|
print_usage() -> |
269 |
:-( |
io:format("Usage: chrme [OPTIONS] <command> [args]~n~n", []), |
270 |
:-( |
io:format("Options:~n", []), |
271 |
:-( |
io:format(" -h, --help Show this help message~n", []), |
272 |
:-( |
io:format(" -V, --version Show version~n", []), |
273 |
:-( |
io:format(" -H, --host <host> Debugging HTTP host (default \"localhost\")~n", []), |
274 |
:-( |
io:format(" -P, --port <port> Debugging HTTP port (default 9222)~n", []), |
275 |
:-( |
io:format(" -e, --endpoint <ws_url> WebSocket debugger URL (attach only)~n", []), |
276 |
:-( |
io:format(" -i, --id <target-id> Attach to an existing target ID~n", []), |
277 |
:-( |
io:format(" -b, --chrome-binary <path> Path to Chrome executable (launch only)~n", []), |
278 |
:-( |
io:format(" --headless Launch Chrome in headless mode (default true)~n", []), |
279 |
:-( |
io:format(" --no-headless Launch Chrome with UI~n", []), |
280 |
:-( |
io:format(" --user-data-dir <dir> Chrome user data directory~n", []), |
281 |
:-( |
io:format(" -v, --verbose Enable verbose output~n~n", []), |
282 |
:-( |
io:format("Commands:~n", []), |
283 |
:-( |
io:format(" launch Launch a new Chrome instance~n", []), |
284 |
:-( |
io:format(" list List open debugging targets~n", []), |
285 |
:-( |
io:format(" new Create a new debugging target~n", []), |
286 |
:-( |
io:format(" navigate Navigate a new page to a URL~n", []), |
287 |
:-( |
io:format(" screenshot Capture a screenshot (not implemented)~n", []), |
288 |
:-( |
io:format(" evaluate Evaluate JavaScript~n", []), |
289 |
:-( |
io:format(" dom Dump DOM~n", []), |
290 |
:-( |
io:format(" pdf Print to PDF (not implemented)~n", []), |
291 |
:-( |
io:format(" events Stream events~n", []), |
292 |
:-( |
io:format(" help Show this message or help for a specific command~n", []), |
293 |
:-( |
io:format(" version Show version~n", []). |
294 |
|
|
295 |
|
print_version() -> |
296 |
:-( |
_ = case application:load(chrme) of |
297 |
:-( |
ok -> ok; |
298 |
:-( |
_ -> ok |
299 |
|
end, |
300 |
:-( |
Vsn = case application:get_key(chrme, vsn) of |
301 |
:-( |
V when is_list(V); is_binary(V) -> V; |
302 |
:-( |
_ -> "unknown" |
303 |
|
end, |
304 |
:-( |
io:format("~s~n", [Vsn]). |
305 |
|
|
306 |
|
parse_args(Args) -> |
307 |
:-( |
Defaults = #{ |
308 |
|
command => undefined, |
309 |
|
host => list_to_binary("localhost"), |
310 |
|
port => 9222, |
311 |
|
endpoint => undefined, |
312 |
|
chrome_binary => list_to_binary("/usr/bin/google-chrome"), |
313 |
|
headless => true, |
314 |
|
user_data_dir => list_to_binary("/tmp/chrme_default_user_data_dir"), |
315 |
|
verbose => false |
316 |
|
}, |
317 |
:-( |
parse_args_loop(Args, Defaults). |
318 |
|
|
319 |
|
parse_args_loop([], Opts) -> |
320 |
:-( |
case maps:get(command, Opts) of |
321 |
:-( |
undefined -> {error, "No command specified"}; |
322 |
:-( |
_ -> {ok, Opts, []} |
323 |
|
end; |
324 |
|
parse_args_loop([Arg|Rest], Opts) -> |
325 |
:-( |
case Arg of |
326 |
|
"-H" -> |
327 |
:-( |
case Rest of |
328 |
:-( |
[Host|T] -> parse_args_loop(T, Opts#{host => list_to_binary(Host)}); |
329 |
:-( |
_ -> {error, "Missing argument for -H/--host"} |
330 |
|
end; |
331 |
|
"--host" -> |
332 |
:-( |
case Rest of |
333 |
:-( |
[Host|T] -> parse_args_loop(T, Opts#{host => list_to_binary(Host)}); |
334 |
:-( |
_ -> {error, "Missing argument for -H/--host"} |
335 |
|
end; |
336 |
|
"-P" -> |
337 |
:-( |
case Rest of |
338 |
|
[P|T] -> |
339 |
:-( |
Opts1 = case catch list_to_integer(P) of |
340 |
:-( |
I when is_integer(I) -> Opts#{port => I}; |
341 |
:-( |
_ -> Opts |
342 |
|
end, |
343 |
:-( |
parse_args_loop(T, Opts1); |
344 |
:-( |
_ -> {error, "Missing argument for -P/--port"} |
345 |
|
end; |
346 |
|
"--port" -> |
347 |
:-( |
case Rest of |
348 |
|
[P|T] -> |
349 |
:-( |
Opts1 = case catch list_to_integer(P) of |
350 |
:-( |
I when is_integer(I) -> Opts#{port => I}; |
351 |
:-( |
_ -> Opts |
352 |
|
end, |
353 |
:-( |
parse_args_loop(T, Opts1); |
354 |
:-( |
_ -> {error, "Missing argument for -P/--port"} |
355 |
|
end; |
356 |
|
"-e" -> |
357 |
:-( |
case Rest of |
358 |
:-( |
[EP|T] -> parse_args_loop(T, Opts#{endpoint => list_to_binary(EP)}); |
359 |
:-( |
_ -> {error, "Missing argument for -e/--endpoint"} |
360 |
|
end; |
361 |
|
"--endpoint" -> |
362 |
:-( |
case Rest of |
363 |
:-( |
[EP|T] -> parse_args_loop(T, Opts#{endpoint => list_to_binary(EP)}); |
364 |
:-( |
_ -> {error, "Missing argument for -e/--endpoint"} |
365 |
|
end; |
366 |
|
"-b" -> |
367 |
:-( |
case Rest of |
368 |
:-( |
[Bin|T] -> parse_args_loop(T, Opts#{chrome_binary => list_to_binary(Bin)}); |
369 |
:-( |
_ -> {error, "Missing argument for -b/--chrome-binary"} |
370 |
|
end; |
371 |
|
"--chrome-binary" -> |
372 |
:-( |
case Rest of |
373 |
:-( |
[Bin|T] -> parse_args_loop(T, Opts#{chrome_binary => list_to_binary(Bin)}); |
374 |
:-( |
_ -> {error, "Missing argument for -b/--chrome-binary"} |
375 |
|
end; |
376 |
:-( |
"--headless" -> parse_args_loop(Rest, Opts#{headless => true}); |
377 |
:-( |
"--no-headless" -> parse_args_loop(Rest, Opts#{headless => false}); |
378 |
|
"--user-data-dir" -> |
379 |
:-( |
case Rest of |
380 |
:-( |
[Dir|T] -> parse_args_loop(T, Opts#{user_data_dir => list_to_binary(Dir)}); |
381 |
:-( |
_ -> {error, "Missing argument for --user-data-dir"} |
382 |
|
end; |
383 |
:-( |
"-v" -> parse_args_loop(Rest, Opts#{verbose => true}); |
384 |
:-( |
"--verbose" -> parse_args_loop(Rest, Opts#{verbose => true}); |
385 |
|
Cmd when is_list(Cmd), Cmd =/= [], hd(Cmd) =:= $- -> |
386 |
:-( |
{error, ["Unknown flag: ", Cmd]}; |
387 |
:-( |
Cmd -> {ok, Opts#{command => Cmd}, Rest} |
388 |
|
end. |
389 |
|
|
390 |
|
dispatch(Opts, CmdArgs) -> |
391 |
:-( |
Cmd = maps:get(command, Opts), |
392 |
:-( |
CmdAtom = list_to_atom(Cmd), |
393 |
:-( |
case CmdAtom of |
394 |
:-( |
launch -> do_launch(Opts, CmdArgs); |
395 |
:-( |
list -> do_list(Opts, CmdArgs); |
396 |
:-( |
new -> do_new(Opts, CmdArgs); |
397 |
:-( |
navigate -> do_navigate(Opts, CmdArgs); |
398 |
:-( |
evaluate -> do_evaluate(Opts, CmdArgs); |
399 |
:-( |
dom -> do_dom(Opts, CmdArgs); |
400 |
:-( |
help -> do_help(CmdArgs), halt(?EXIT_OK); |
401 |
:-( |
version -> print_version(), halt(?EXIT_OK); |
402 |
:-( |
events -> do_events(Opts, CmdArgs); |
403 |
|
_ -> |
404 |
:-( |
io:format("Unknown command: ~s~n", [Cmd]), |
405 |
:-( |
print_usage(), |
406 |
:-( |
halt(?EXIT_USAGE_ERROR) |
407 |
|
end. |
408 |
|
|
409 |
|
do_launch(Opts, CmdArgs) -> |
410 |
:-( |
Host = maps:get(host, Opts), |
411 |
:-( |
Port = maps:get(port, Opts), |
412 |
:-( |
Exe = maps:get(chrome_binary, Opts), |
413 |
:-( |
Headless = maps:get(headless, Opts), |
414 |
:-( |
DataDir = maps:get(user_data_dir, Opts), |
415 |
:-( |
Extra = case CmdArgs of |
416 |
:-( |
["--" | Rest] -> [list_to_binary(X) || X <- Rest]; |
417 |
:-( |
_ -> [list_to_binary(X) || X <- CmdArgs] |
418 |
|
end, |
419 |
:-( |
Name = cli_chrome, |
420 |
:-( |
LOpts = #{name => Name, |
421 |
|
executable => Exe, |
422 |
|
remote_port => Port, |
423 |
|
user_data_dir => DataDir, |
424 |
|
headless => Headless, |
425 |
|
extra_args => Extra}, |
426 |
:-( |
case chrme_launcher:start(LOpts) of |
427 |
:-( |
{ok, _} -> ok; |
428 |
|
{error, LaunchErr} -> |
429 |
:-( |
io:format("Error launching Chrome: ~p~n", [LaunchErr]), |
430 |
:-( |
halt(?EXIT_ERROR) |
431 |
|
end, |
432 |
:-( |
chrme_launcher:await_start(Name), |
433 |
:-( |
case chrme_http_apic:version(Host, Port) of |
434 |
|
{ok, V} -> |
435 |
:-( |
io:format("HTTP debug endpoint: http://~ts:~p~n", [Host, Port]), |
436 |
:-( |
case maps:find(<<"webSocketDebuggerUrl">>, V) of |
437 |
:-( |
{ok, Ws} -> io:format("WebSocket debugger URL: ~ts~n", [Ws]); |
438 |
:-( |
error -> ok |
439 |
|
end; |
440 |
|
{error, VersionErr} -> |
441 |
:-( |
io:format("Error fetching version: ~p~n", [VersionErr]) |
442 |
|
end, |
443 |
:-( |
halt(?EXIT_OK). |
444 |
|
|
445 |
|
do_list(Opts, _Args) -> |
446 |
:-( |
Host = maps:get(host, Opts), |
447 |
:-( |
Port = maps:get(port, Opts), |
448 |
:-( |
case chrme_http_apic:list_targets(Host, Port) of |
449 |
|
{ok, Targets} -> |
450 |
:-( |
io:format("id\ttype\ttitle\turl\n", []), |
451 |
:-( |
lists:foreach(fun(T) -> |
452 |
:-( |
Id = maps:get(<<"id">>, T), |
453 |
:-( |
Type = maps:get(<<"type">>, T), |
454 |
:-( |
Title = maps:get(<<"title">>, T), |
455 |
:-( |
Url = maps:get(<<"url">>, T), |
456 |
:-( |
io:format("~ts\t~ts\t~ts\t~ts\n", [Id, Type, Title, Url]) |
457 |
|
end, Targets), |
458 |
:-( |
halt(?EXIT_OK); |
459 |
|
{error, Reason} -> |
460 |
:-( |
io:format("Error listing targets: ~p~n", [Reason]), |
461 |
:-( |
halt(?EXIT_ERROR) |
462 |
|
end. |
463 |
|
|
464 |
|
do_new(Opts, CmdArgs) -> |
465 |
:-( |
case CmdArgs of |
466 |
|
[UrlStr] -> |
467 |
:-( |
Host = maps:get(host, Opts), |
468 |
:-( |
Port = maps:get(port, Opts), |
469 |
:-( |
Url = list_to_binary(UrlStr), |
470 |
:-( |
case chrme_http_apic:new_target(Host, Port, Url) of |
471 |
|
{ok, M} -> |
472 |
:-( |
Id = maps:get(<<"id">>, M), |
473 |
:-( |
Ws = maps:get(<<"webSocketDebuggerUrl">>, M), |
474 |
:-( |
io:format("Target id: ~ts~n", [Id]), |
475 |
:-( |
io:format("WebSocket URL: ~ts~n", [Ws]), |
476 |
:-( |
halt(?EXIT_OK); |
477 |
|
{error, Reason} -> |
478 |
:-( |
io:format("Error creating target: ~p~n", [Reason]), |
479 |
:-( |
halt(?EXIT_ERROR) |
480 |
|
end; |
481 |
|
_ -> |
482 |
:-( |
io:format("Usage: chrme new <url>~n", []), |
483 |
:-( |
halt(?EXIT_USAGE_ERROR) |
484 |
|
end. |
485 |
|
|
486 |
|
do_help([]) -> |
487 |
:-( |
print_usage(); |
488 |
|
do_help([Cmd|_]) -> |
489 |
:-( |
CA = list_to_atom(Cmd), |
490 |
:-( |
case CA of |
491 |
:-( |
launch -> io:format("Usage: chrme launch [options] [-- <extra args>]\n", []); |
492 |
:-( |
list -> io:format("Usage: chrme list [options]\n", []); |
493 |
:-( |
new -> io:format("Usage: chrme new <url> [options]\n", []); |
494 |
:-( |
navigate -> io:format("Usage: chrme navigate <url> [options]\n", []); |
495 |
:-( |
evaluate -> io:format("Usage: chrme evaluate [--id <target-id>] <expression>\n", []); |
496 |
:-( |
dom -> io:format("Usage: chrme dom [--id <target-id>] <url>\n", []); |
497 |
:-( |
events -> io:format("Usage: chrme events [--id <target-id>]\n", []); |
498 |
:-( |
help -> io:format("Usage: chrme help [command]\n", []); |
499 |
:-( |
version -> io:format("Usage: chrme version\n", []); |
500 |
:-( |
_ -> io:format("Unknown command: ~s~n", [Cmd]) |
501 |
|
end. |