From 0949cc8ed1de57e3388cf22a83bc147bce2b3e0f Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Fri, 5 Feb 2010 08:41:18 +0000 Subject: 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. --- lib/stdlib/doc/src/shell.xml | 40 +++++++++++++-- lib/stdlib/doc/src/stdlib_app.xml | 34 ++++++++----- lib/stdlib/src/shell.erl | 104 ++++++++++++++++++++++++++++---------- lib/stdlib/src/shell_default.erl | 11 ++-- lib/stdlib/test/shell_SUITE.erl | 101 ++++++++++++++++++++++++++++++++---- 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 @@
- 19962009 + 19962010 Ericsson AB. All Rights Reserved. @@ -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. - + shell @@ -739,6 +739,27 @@ loop(N) -> returns {error,Reason}. + +
+ Prompting +

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 + shell:prompt_func/1 or by setting the application + configuration parameter shell_prompt_func for the + application STDLIB.

+

A customized prompt function is stated as a tuple + {Mod, Func}. The function is called as + Mod:Func(L), where L is a list of key-value pairs + created by the shell. Currently there is only one pair: + {history, N}, 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 Mod:Func(L) must be allowed or + the default shell prompt function will be called.

+
+ history(N) -> integer() @@ -781,6 +802,19 @@ loop(N) -> linked to the evaluator process survive the exception.

+ + prompt_func(PromptFunc) -> prompt_func() + Sets the shell prompt + + PromptFunc = prompt_func() + prompt_func() = default | {Mod, Func} + Mod = Func = atom() + + +

Sets the shell prompt function to PromptFunc. The + previous prompt function is returned.

+
+
start_restricted(Module) -> ok | {error, Reason} Exits a normal shell and starts a restricted shell. 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 @@
- 2005 - 2007 - Ericsson AB, All Rights Reserved + 20052010 + Ericsson AB. 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/. + 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. STDLIB @@ -63,6 +61,16 @@

This parameter can be used to determine how many commands are saved by the Erlang shell.

+ shell_prompt_func = {Mod, Func} | default + +

where

+ + Mod = atom() + Func = atom() + +

This parameter can be used to set a customized + Erlang shell prompt function.

+
shell_saved_results = integer() >= 0

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"), 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, $.). -- cgit v1.2.3