aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Bolinder <[email protected]>2010-02-05 08:41:18 +0000
committerErlang/OTP <[email protected]>2010-02-05 08:41:18 +0000
commit0949cc8ed1de57e3388cf22a83bc147bce2b3e0f (patch)
treeeb37346659456746523cda458ad1acbc9c69862b
parente5d4b0bddc75eda1ad3445d7164171c0a6e8bbf6 (diff)
downloadotp-0949cc8ed1de57e3388cf22a83bc147bce2b3e0f.tar.gz
otp-0949cc8ed1de57e3388cf22a83bc147bce2b3e0f.tar.bz2
otp-0949cc8ed1de57e3388cf22a83bc147bce2b3e0f.zip
OTP-8393 The new function shell:prompt_func/1 and the new application
configuration parameter shell_prompt_func can be used for customizing the Erlang shell prompt.
-rw-r--r--lib/stdlib/doc/src/shell.xml40
-rw-r--r--lib/stdlib/doc/src/stdlib_app.xml34
-rw-r--r--lib/stdlib/src/shell.erl104
-rw-r--r--lib/stdlib/src/shell_default.erl11
-rw-r--r--lib/stdlib/test/shell_SUITE.erl101
5 files changed, 233 insertions, 57 deletions
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index b8fc64f45e..2a7c5b8c69 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>shell</title>
@@ -739,6 +739,27 @@ loop(N) ->
returns <c>{error,Reason}</c>.</item>
</list>
</section>
+
+ <section>
+ <title>Prompting</title>
+ <p>The default shell prompt function displays the name of the node
+ (if the node can be part of a distributed system) and the
+ current command number. The user can customize the prompt
+ function by calling
+ <c>shell:prompt_func/1</c> or by setting the application
+ configuration parameter <c>shell_prompt_func</c> for the
+ application STDLIB.</p>
+ <p>A customized prompt function is stated as a tuple
+ <c>{Mod,&nbsp;Func}</c>. The function is called as
+ <c>Mod:Func(L)</c>, where <c>L</c> is a list of key-value pairs
+ created by the shell. Currently there is only one pair:
+ <c>{history, N}</c>, where N is the current command number. The
+ function should return a list of characters or an atom. This
+ constraint is due to the Erlang I/O-protocol. Note that in
+ restricted mode the call <c>Mod:Func(L)</c> must be allowed or
+ the default shell prompt function will be called.</p>
+ </section>
+
<funcs>
<func>
<name>history(N) -> integer()</name>
@@ -782,6 +803,19 @@ loop(N) ->
</desc>
</func>
<func>
+ <name>prompt_func(PromptFunc) -> prompt_func()</name>
+ <fsummary>Sets the shell prompt</fsummary>
+ <type>
+ <v>PromptFunc = prompt_func()</v>
+ <v>prompt_func() = default | {Mod, Func}</v>
+ <v>Mod = Func = atom()</v>
+ </type>
+ <desc>
+ <p>Sets the shell prompt function to <c>PromptFunc</c>. The
+ previous prompt function is returned.</p>
+ </desc>
+ </func>
+ <func>
<name>start_restricted(Module) -> ok | {error, Reason}</name>
<fsummary>Exits a normal shell and starts a restricted shell.</fsummary>
<type>
diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml
index da046b8a8d..a615c1bf88 100644
--- a/lib/stdlib/doc/src/stdlib_app.xml
+++ b/lib/stdlib/doc/src/stdlib_app.xml
@@ -4,23 +4,21 @@
<appref>
<header>
<copyright>
- <year>2005</year>
- <year>2007</year>
- <holder>Ericsson AB, All Rights Reserved</holder>
+ <year>2005</year><year>2010</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
- The contents of this file are subject to the Erlang Public License,
- Version 1.1, (the "License"); you may not use this file except in
- compliance with the License. You should have received a copy of the
- Erlang Public License along with this software. If not, it can be
- retrieved online at http://www.erlang.org/.
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- the License for the specific language governing rights and limitations
- under the License.
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
- The Initial Developer of the Original Code is Ericsson AB.
</legalnotice>
<title>STDLIB</title>
@@ -63,6 +61,16 @@
<p>This parameter can be used to determine how many
commands are saved by the Erlang shell.</p>
</item>
+ <tag><c>shell_prompt_func = {Mod, Func} | default</c></tag>
+ <item>
+ <p>where</p>
+ <list type="bulleted">
+ <item><c>Mod = atom()</c></item>
+ <item><c>Func = atom()</c></item>
+ </list>
+ <p>This parameter can be used to set a customized
+ Erlang shell prompt function.</p>
+ </item>
<tag><c>shell_saved_results = integer() >= 0</c></tag>
<item>
<p>This parameter can be used to determine how many
diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl
index a8d31b4e6b..ebb221c151 100644
--- a/lib/stdlib/src/shell.erl
+++ b/lib/stdlib/src/shell.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
-module(shell).
@@ -22,12 +22,14 @@
-export([whereis_evaluator/0, whereis_evaluator/1]).
-export([start_restricted/1, stop_restricted/0]).
-export([local_allowed/3, non_local_allowed/3]).
+-export([prompt_func/1]).
-define(LINEMAX, 30).
-define(CHAR_MAX, 60).
-define(DEF_HISTORY, 20).
-define(DEF_RESULTS, 20).
-define(DEF_CATCH_EXCEPTION, false).
+-define(DEF_PROMPT_FUNC, default).
-define(RECORDS, shell_records).
@@ -235,14 +237,15 @@ server(StartSync) ->
{History,Results} = check_and_get_history_and_results(),
server_loop(0, start_eval(Bs, RT, []), Bs, RT, [], History, Results).
-server_loop(N0, Eval_0, Bs0, RT, Ds0, History0, Results0) ->
+server_loop(N0, Eval_0, Bs00, RT, Ds00, History0, Results0) ->
N = N0 + 1,
- {Res, Eval0} = get_command(prompt(N), Eval_0, Bs0, RT, Ds0),
+ {Eval_1,Bs0,Ds0,Prompt} = prompt(N, Eval_0, Bs00, RT, Ds00),
+ {Res,Eval0} = get_command(Prompt, Eval_1, Bs0, RT, Ds0),
case Res of
{ok,Es0,_EndLine} ->
case expand_hist(Es0, N) of
{ok,Es} ->
- {V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, Ds0),
+ {V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, Ds0, cmd),
{History,Results} = check_and_get_history_and_results(),
add_cmd(N, Es, V),
HB1 = del_cmd(command, N - History, N - History0, false),
@@ -301,7 +304,42 @@ get_command1(Pid, Eval, Bs, RT, Ds) ->
get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds)
end.
-prompt(N) ->
+prompt(N, Eval0, Bs0, RT, Ds0) ->
+ case get_prompt_func() of
+ {M,F} ->
+ L = [{history,N}],
+ C = {call,1,{remote,1,{atom,1,M},{atom,1,F}},[{value,1,L}]},
+ {V,Eval,Bs,Ds} = shell_cmd([C], Eval0, Bs0, RT, Ds0, pmt),
+ {Eval,Bs,Ds,case V of
+ {pmt,Val} ->
+ Val;
+ _ ->
+ bad_prompt_func({M,F}),
+ default_prompt(N)
+ end};
+ default ->
+ {Eval0,Bs0,Ds0,default_prompt(N)}
+ end.
+
+get_prompt_func() ->
+ case application:get_env(stdlib, shell_prompt_func) of
+ {ok,{M,F}=PromptFunc} when is_atom(M), is_atom(F) ->
+ PromptFunc;
+ {ok,default=Default} ->
+ Default;
+ {ok,Term} ->
+ bad_prompt_func(Term),
+ default;
+ undefined ->
+ default
+ end.
+
+bad_prompt_func(M) ->
+ fwrite_severity(benign, <<"Bad prompt function: ~p">>, [M]).
+
+default_prompt(N) ->
+ %% Don't bother flattening the list irrespective of what the
+ %% I/O-protocol states.
case is_alive() of
true -> io_lib:format(<<"(~s)~w> ">>, [node(), N]);
false -> io_lib:format(<<"~w> ">>, [N])
@@ -461,14 +499,16 @@ has_bin(T, I) ->
has_bin(element(I, T)),
has_bin(T, I - 1).
-%% shell_cmd(Sequence, Evaluator, Bindings, RecordTable, Dictionary)
+%% shell_cmd(Sequence, Evaluator, Bindings, RecordTable, Dictionary, What)
%% shell_rep(Evaluator, Bindings, RecordTable, Dictionary) ->
%% {Value,Evaluator,Bindings,Dictionary}
%% Send a command to the evaluator and wait for the reply. Start a new
%% evaluator if necessary.
+%% What = pmt | cmd. When evaluating a prompt ('pmt') the evaluated value
+%% must not be displayed, and it has to be returned.
-shell_cmd(Es, Eval, Bs, RT, Ds) ->
- Eval ! {shell_cmd,self(),{eval,Es}},
+shell_cmd(Es, Eval, Bs, RT, Ds, W) ->
+ Eval ! {shell_cmd,self(),{eval,Es}, W},
shell_rep(Eval, Bs, RT, Ds).
shell_rep(Ev, Bs0, RT, Ds0) ->
@@ -559,26 +599,26 @@ evaluator(Shell, Bs, RT, Ds) ->
eval_loop(Shell, Bs0, RT) ->
receive
- {shell_cmd,Shell,{eval,Es}} ->
+ {shell_cmd,Shell,{eval,Es},W} ->
Ef = {value,
fun(MForFun, As) -> apply_fun(MForFun, As, Shell) end},
Lf = local_func_handler(Shell, RT, Ef),
- Bs = eval_exprs(Es, Shell, Bs0, RT, Lf, Ef),
+ Bs = eval_exprs(Es, Shell, Bs0, RT, Lf, Ef, W),
eval_loop(Shell, Bs, RT)
end.
restricted_eval_loop(Shell, Bs0, RT, RShMod) ->
receive
- {shell_cmd,Shell,{eval,Es}} ->
+ {shell_cmd,Shell,{eval,Es}, W} ->
{LFH,NLFH} = restrict_handlers(RShMod, Shell, RT),
put(restricted_expr_state, []),
- Bs = eval_exprs(Es, Shell, Bs0, RT, {eval,LFH}, {value,NLFH}),
+ Bs = eval_exprs(Es, Shell, Bs0, RT, {eval,LFH}, {value,NLFH}, W),
restricted_eval_loop(Shell, Bs, RT, RShMod)
end.
-eval_exprs(Es, Shell, Bs0, RT, Lf, Ef) ->
+eval_exprs(Es, Shell, Bs0, RT, Lf, Ef, W) ->
try
- {R,Bs2} = exprs(Es, Bs0, RT, Lf, Ef),
+ {R,Bs2} = exprs(Es, Bs0, RT, Lf, Ef, W),
Shell ! {shell_rep,self(),R},
Bs2
catch
@@ -614,10 +654,10 @@ do_catch(_Class, _Reason) ->
false
end.
-exprs(Es, Bs0, RT, Lf, Ef) ->
- exprs(Es, Bs0, RT, Lf, Ef, Bs0).
+exprs(Es, Bs0, RT, Lf, Ef, W) ->
+ exprs(Es, Bs0, RT, Lf, Ef, Bs0, W).
-exprs([E0|Es], Bs1, RT, Lf, Ef, Bs0) ->
+exprs([E0|Es], Bs1, RT, Lf, Ef, Bs0, W) ->
UsedRecords = used_record_defs(E0, RT),
RBs = record_bindings(UsedRecords, Bs1),
case check_command(prep_check([E0]), RBs) of
@@ -629,16 +669,20 @@ exprs([E0|Es], Bs1, RT, Lf, Ef, Bs0) ->
if
Es =:= [] ->
VS = pp(V0, 1, RT),
- io:requests([{put_chars, VS}, nl]),
+ [io:requests([{put_chars, VS}, nl]) || W =:= cmd],
%% Don't send the result back if it will be
%% discarded anyway.
- V = case result_will_be_saved() of
- true -> V0;
- false -> ignored
+ V = if
+ W =:= pmt ->
+ {W,V0};
+ true -> case result_will_be_saved() of
+ true -> V0;
+ false -> ignored
+ end
end,
{{value,V,Bs,get()},Bs};
true ->
- exprs(Es, Bs, RT, Lf, Ef, Bs0)
+ exprs(Es, Bs, RT, Lf, Ef, Bs0, W)
end;
{error,Error} ->
{{command_error,Error},Bs0}
@@ -1383,7 +1427,7 @@ pp(V, I, RT) ->
columns() ->
case io:columns() of
- {ok,N} -> N;
+ {ok,N} -> N;
_ -> 80
end.
@@ -1438,3 +1482,9 @@ results(L) when is_integer(L), L >= 0 ->
catch_exception(Bool) ->
set_env(stdlib, shell_catch_exception, Bool, ?DEF_CATCH_EXCEPTION).
+
+-type prompt_func() :: 'default' | {module(),atom()}.
+-spec prompt_func(prompt_func()) -> prompt_func().
+
+prompt_func(String) ->
+ set_env(stdlib, shell_prompt_func, String, ?DEF_PROMPT_FUNC).
diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl
index 670f8cdb44..3fe359af0e 100644
--- a/lib/stdlib/src/shell_default.erl
+++ b/lib/stdlib/src/shell_default.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -45,6 +45,7 @@ help() ->
format("h() -- history\n"),
format("history(N) -- set how many previous commands to keep\n"),
format("results(N) -- set how many previous command results to keep\n"),
+ format("catch_exception(B) -- how exceptions are handled\n"),
format("v(N) -- use the value of query <N>\n"),
format("rd(R,D) -- define a record\n"),
format("rf() -- remove all record information\n"),
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
index 5827d5f332..da73046c2a 100644
--- a/lib/stdlib/test/shell_SUITE.erl
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
-module(shell_SUITE).
@@ -28,13 +28,13 @@
progex/1, progex_bit_syntax/1, progex_records/1,
progex_lc/1, progex_funs/1,
tickets/1, otp_5990/1, otp_6166/1, otp_6554/1, otp_6785/1,
- otp_7184/1, otp_7232/1]).
+ otp_7184/1, otp_7232/1, otp_8393/1]).
-export([restricted/1, start_restricted_from_shell/1,
start_restricted_on_command_line/1,restricted_local/1]).
%% Internal export.
--export([otp_5435_2/0]).
+-export([otp_5435_2/0, prompt1/1, prompt2/1, prompt3/1, prompt4/1]).
%%
%% Define to run outside of test server
@@ -2256,7 +2256,7 @@ progex_funs(Config) when is_list(Config) ->
ok.
tickets(suite) ->
- [otp_5990, otp_6166, otp_6554, otp_6785, otp_7184, otp_7232].
+ [otp_5990, otp_6166, otp_6554, otp_6785, otp_7184, otp_7232, otp_8393].
otp_5990(doc) ->
"OTP-5990. {erlang,is_record}.";
@@ -2598,6 +2598,80 @@ otp_7232(Config) when is_list(Config) ->
" end}])" = evaluate(Info, []),
ok.
+otp_8393(doc) ->
+ "OTP-8393. Prompt string.";
+otp_8393(suite) -> [];
+otp_8393(Config) when is_list(Config) ->
+ ?line _ = shell:prompt_func(default),
+ ?line "Bad prompt function: '> '" =
+ prompt_err(<<"shell:prompt_func('> ').">>),
+
+ ?line _ = shell:prompt_func(default),
+ ?line "exception error: bad argument in an arithmetic expression"++_ =
+ prompt_err(<<"shell:prompt_func({shell_SUITE,prompt4}).">>),
+
+ ?line _ = shell:prompt_func(default),
+ ?line "default.\n" =
+ t(<<"shell:prompt_func({shell_SUITE,prompt2}).">>),
+
+ ?line _ = shell:prompt_func(default),
+ ?line "default\nl.\n" =
+ t(<<"shell:prompt_func({shell_SUITE,prompt3}). l.">>),
+
+ %% Restricted shell.
+ Contents = <<"-module(test_restricted_shell).
+ -export([local_allowed/3, non_local_allowed/3]).
+ local_allowed(_,_,State) ->
+ {false,State}.
+
+ non_local_allowed({shell,stop_restricted},[],State) ->
+ {true,State};
+ non_local_allowed({shell,prompt_func},[_L],State) ->
+ {true,State};
+ non_local_allowed({shell_SUITE,prompt1},[_L],State) ->
+ {true,State};
+ non_local_allowed(_,_,State) ->
+ {false,State}.
+ ">>,
+ ?line Test = filename:join(?config(priv_dir, Config),
+ "test_restricted_shell.erl"),
+ ?line ok = compile_file(Config, Test, Contents, []),
+ ?line _ = shell:prompt_func(default),
+ ?line "exception exit: restricted shell starts now" =
+ comm_err(<<"begin shell:start_restricted("
+ "test_restricted_shell) end.">>),
+ ?line "default.\n"++_ =
+ t(<<"shell:prompt_func({shell_SUITE,prompt1}).">>),
+ ?line "exception exit: restricted shell does not allow apple(" ++ _ =
+ comm_err(<<"apple(1).">>),
+ ?line "{shell_SUITE,prompt1}.\n" =
+ t(<<"shell:prompt_func(default).">>),
+ ?line "exception exit: restricted shell stopped"=
+ comm_err(<<"begin shell:stop_restricted() end.">>),
+ ?line undefined =
+ application:get_env(stdlib, restricted_shell),
+
+ ?line NR = shell:results(20),
+ ?line "default\n20.\n" =
+ t(<<"shell:prompt_func({shell_SUITE,prompt3}). results(0).">>),
+
+ ?line _ = shell:prompt_func(default),
+ ?line 0 = shell:results(NR),
+ ok.
+
+prompt1(_L) ->
+ "prompt> ".
+
+prompt2(_L) ->
+ {'EXIT', []}.
+
+prompt3(L) ->
+ N = proplists:get_value(history, L),
+ integer_to_list(N).
+
+prompt4(_L) ->
+ erlang:apply({erlang,'/'}, [1,0]).
+
-ifdef(not_used).
exit_term(B) ->
"** exception exit:" ++ Reply = t(B),
@@ -2627,7 +2701,16 @@ comm_err(B) ->
Reply = t(B),
S0 = string:left(Reply, string:chr(Reply, $\n)-1),
S1 = string:strip(S0, left, $*),
- S2 = string:strip(S1, both, $ ),
+ S2 = string:strip(S1, both, $ ),
+ S = string:strip(S2, both, $"),
+ string:strip(S, right, $.).
+
+prompt_err(B) ->
+ Reply = t(B),
+ S00 = string:sub_string(Reply, string:chr(Reply, $\n)+1),
+ S0 = string:left(S00, string:chr(S00, $\n)-1),
+ S1 = string:strip(S0, left, $*),
+ S2 = string:strip(S1, both, $ ),
S = string:strip(S2, both, $"),
string:strip(S, right, $.).