Current section
Files
Jump to
Current section
Files
src/rebar3_grisp_io_io.erl
-module(rebar3_grisp_io_io).
% API
-export([abort/1, abort/2]).
-export([ask/2]).
-export([console/1, console/2]).
-export([error_message/1, error_message/2]).
-export([success/1, success/2]).
%--- MACROS --------------------------------------------------------------------
-define(WHITESPACE, unicode_util:whitespace()).
%--- Types ---------------------------------------------------------------------
-type input_type() :: string | password.
%--- API -----------------------------------------------------------------------
abort(Msg) ->
abort(Msg, []).
abort(Msg, Args) ->
rebar_api:abort(color(196, Msg), Args).
ask(Prompt, Type) ->
UserInput = do_ask(Prompt, Type),
case get(Type, UserInput) of
{error, Reason} ->
error_message("Error wrong input. Reason: ~s", [Reason]);
{ok, Value} ->
Value
end.
console(Msg) ->
console(Msg, []).
console(Msg, Args) ->
rebar_api:console(Msg, Args).
error_message(Msg) ->
error_message(Msg, []).
error_message(Msg, Args) ->
rebar_api:error(Msg, Args).
success(Msg) ->
success(Msg, []).
success(Msg, Args) ->
Text = io_lib:format(Msg, Args),
rebar_api:console(color(40, Text), []).
%--- Internals -----------------------------------------------------------------
-spec do_ask(Prompt, Type) -> UserInput | no_return() when
Type :: input_type(),
Prompt :: string(),
UserInput :: unicode:chardata().
do_ask(Prompt, password) ->
NewPrompt = erlang:iolist_to_binary([Prompt, " > "]),
UserInput = do_get_password(NewPrompt),
trim(trim(UserInput), both, [$\n]);
do_ask(Prompt, _) ->
NewPrompt = erlang:iolist_to_binary(["\n", Prompt, " > "]),
RawUserInput = io:get_line(erlang:binary_to_list(NewPrompt)),
trim(trim(RawUserInput), both, [$\n]).
do_get_password(Prompt) ->
ok = io:setopts([binary]),
Overwriter = fun() ->
prompt_password(Prompt),
receive
{done, _Pid, _Ref} ->
ok
end
end,
Pid = spawn_link(Overwriter),
PwLine = try
io:get_line(binary_to_list(Prompt))
after
Ref = make_ref(),
Pid ! {done, self(), Ref},
receive
{done, Pid, Ref} ->
ok
after
timer:seconds(5) ->
error(win32_prompt_timeout)
end
end,
PwLine.
prompt_password(Prompt) ->
% This is spawned to continually overwrite the prompt the user is
% entering data on, in order to hide characters typed.
ClearLine = "\e[2K",
receive
{done, Parent, Ref} ->
Parent ! {done, self(), Ref},
Spaces = lists:duplicate(byte_size(Prompt) + 24, $ ),
io:fwrite(standard_error, "~ts\r~ts\r", [ClearLine, Spaces])
after
1 ->
Spaces = lists:duplicate(24, $ ),
io:fwrite(standard_error, "~ts\r~ts~ts\r~ts",
[ClearLine, Prompt, Spaces, Prompt]),
prompt_password(Prompt)
end.
get(Type, String) when Type =:= string orelse Type =:= password ->
case String of
[] ->
{error, no_data};
_ when is_list(String) ->
Trimmed = string:trim(String, both, ?WHITESPACE),
{ok, unicode:characters_to_binary(Trimmed)};
_ ->
{error, wrong_data_type}
end;
get(Type, UserInput) ->
error({unknown_type, Type, UserInput}).
-ifdef(unicode_str).
trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
trim(Str, both, Chars) -> string:trim(Str, both, Chars).
-else.
trim(Str) -> string:strip(rebar_utils:to_list(Str)).
trim(Str, Dir, [Chars|_]) -> string:strip(rebar_utils:to_list(Str), Dir, Chars).
-endif.
% @doc Add foreground coloring to the given text using ANSI ecape code
% See: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
% @end
-spec color(Code, Text) -> string() when
Code :: 0..255,
Text :: string().
color(Code, Text) ->
io_lib:format("\033[38;5;~pm~s\033[m", [Code, Text]).