Skip to content

Commit 79f29d0

Browse files
SEA-232 rebar3 grisp configure (#21)
* io module and groundwork * Choices in grisp_tools * CI options flow * destination flag key change * add description in parameters * File Style update * Adds author name and email as well as copiright year * Skipping questions when user provides values when -i true * Setting definition includes short flag * spec documentation * renamed and changed descr of settings * modified otp_release to otp_version * ask is now an event instead of a io:format call * prompt rephrasing and allow grisp.io integration w/o linking * say/2 now uses the event api --------- Co-authored-by: Luca Succi <luca.succi@stritzinger.com>
1 parent 41211c8 commit 79f29d0

File tree

3 files changed

+267
-0
lines changed

3 files changed

+267
-0
lines changed

src/grisp_tools.erl

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
-export([deploy/1]).
88
-export([list_packages/1]).
99
-export([report/1]).
10+
-export([configure/1]).
1011

1112
%--- API -----------------------------------------------------------------------
1213

@@ -27,3 +28,5 @@ deploy(State) -> grisp_tools_deploy:run(State).
2728
list_packages(Opts) -> grisp_tools_package:list(Opts).
2829

2930
report(Opts) -> grisp_tools_report:run(Opts).
31+
32+
configure(Opts) -> grisp_tools_configure:run(Opts).

src/grisp_tools_configure.erl

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
-module(grisp_tools_configure).
2+
3+
% API
4+
-export([run/1]).
5+
-export([settings/0]).
6+
7+
%--- Types ---------------------------------------------------------------------
8+
9+
% {Name, {Long, Short}, {Type, Default}, Description}
10+
-type settings() :: {string(),
11+
{string(), char()},
12+
{string, string()} | {boolean, boolean()},
13+
string()}.
14+
15+
-type settings_options() :: {string(),
16+
{string(), char()},
17+
{string, string()} | {boolean, boolean()},
18+
string(),
19+
function()} | settings().
20+
21+
%--- API -----------------------------------------------------------------------
22+
23+
run(State) ->
24+
Options = settings_options(),
25+
do_ask(State, Options).
26+
27+
do_ask(State, Options) ->
28+
lists:foldl(
29+
fun(Setting, AccState) ->
30+
ask(AccState, Setting)
31+
end,
32+
State,
33+
Options).
34+
35+
ask(#{flags := #{interactive := false}} = State, _) ->
36+
State; % Skipping ask in non-interactive mode
37+
ask(#{user_opts := UserOpts} = State, {_, {Key, _}, _, _})
38+
when is_map_key(Key, UserOpts) ->
39+
user_provided_event(State, Key, UserOpts),
40+
State; % Skipping if user provided the option in the command args
41+
ask(#{user_opts := UOpts, flags := Flags} = State, {_, {Key, _}, _, _, OptsGen})
42+
when is_map_key(Key, UOpts) ->
43+
user_provided_event(State, Key, UOpts),
44+
case maps:get(Key, UOpts) of
45+
true -> do_ask(State#{flags := Flags#{Key => true}}, OptsGen());
46+
_ -> State
47+
end;
48+
ask(#{flags := Flags} = State, {Prompt, {Key, _}, {Type, _}, _}) ->
49+
Default = maps:get(Key, Flags),
50+
Value = grisp_tools_io:ask(State, Prompt, Type, Default),
51+
State#{flags => Flags#{Key => Value}};
52+
ask(#{flags := Flags} = State, {Prompt, {Key, _}, {boolean, _}, _, OptsGen}) ->
53+
Default = maps:get(Key, Flags),
54+
case grisp_tools_io:ask(State, Prompt, boolean, Default) of
55+
true -> do_ask(State#{flags := Flags#{Key => true}}, OptsGen());
56+
_ -> State#{flags := Flags#{Key => false}}
57+
end.
58+
59+
user_provided_event(State, {Key, _}, UserOpts) ->
60+
Value = maps:get(Key, UserOpts),
61+
Prompt = io_lib:format("Value ~p provided for ~p. Skipping question",
62+
[Value, Key]),
63+
grisp_tools_util:event(State, {info, Prompt}).
64+
65+
-spec settings() -> [settings()].
66+
settings() ->
67+
{{Year, _, _}, _} = calendar:universal_time(),
68+
{AuthorName, AuthorEmail} = default_author_and_email(),
69+
[{"Interactive", {interactive, $i}, {boolean, true},
70+
"Activates the interactive mode"},
71+
{"Description", {desc, undefined}, {string, "A GRiSP application"},
72+
"Short description of the app"},
73+
{"Copyright year", {copyright_year, undefined}, {string, Year},
74+
"The copyright year"},
75+
{"Author name", {author_name, undefined}, {string, AuthorName},
76+
"The name of the author"},
77+
{"Author email", {author_email, undefined}, {string, AuthorEmail},
78+
"The email of the author"}
79+
] ++ format_settings_options(settings_options(), []).
80+
81+
-spec settings_options() -> [settings_options()].
82+
settings_options() -> [
83+
{"App name", {name, undefined}, {string, "robot"},
84+
"The name of the OTP application"},
85+
{"Erlang version", {otp_version, $o}, {string, "25"},
86+
"The OTP version of the GRiSP app"},
87+
{"SD Card Path", {dest, $d}, {string, "/path/to/SD-card"},
88+
"The path to the SD card where you want to deploy the GRISP app"},
89+
{"Use Network ?", {network, $n}, {boolean, false},
90+
"Network configuration files generation", fun network_options/0}
91+
].
92+
93+
-spec network_options() -> [settings_options()].
94+
network_options() -> [
95+
{"Use Wifi ?", {wifi, $w}, {boolean, false},
96+
"Wifi configuration", fun wifi_options/0},
97+
{"Enable GRiSP.io integration ?", {grisp_io, $g}, {boolean, false},
98+
"GRiSP.io configuration", fun grisp_io_options/0},
99+
{"Enable Distributed Erlang ?", {epmd, $e}, {boolean, false},
100+
"Distributed Erlang configuration generation", fun epmd_options/0}
101+
].
102+
103+
-spec wifi_options() -> [settings_options()].
104+
wifi_options() -> [
105+
{"Wifi Name", {ssid, $p}, {string, "My Wifi"}, "The SSID of your Wifi"},
106+
{"Wifi Password", {psk, $p}, {string, "..."}, "The PSK of your Wifi"}
107+
].
108+
109+
-spec grisp_io_options() -> [settings_options()].
110+
grisp_io_options() -> [
111+
{"Do you need to link your GRiSP-2 board ?", {grisp_io_linking, $l},
112+
{boolean, false}, "GRiSP.io device linking", fun grisp_io_link_options/0}
113+
].
114+
115+
-spec grisp_io_link_options() -> [settings_options()].
116+
grisp_io_link_options() -> [
117+
{"Please enter your personal device linking token", {token, $t},
118+
{string, "..."}, "Your private GRiSP.io token"}
119+
].
120+
121+
-spec epmd_options() -> [settings_options()].
122+
epmd_options() -> [
123+
{"Erlang Cookie", {cookie, $c}, {string, "grisp"},
124+
"The distributed Erlang cookie"}
125+
].
126+
127+
-spec format_settings_options([settings_options()], [settings()]) -> settings().
128+
format_settings_options([], Acc) ->
129+
Acc;
130+
format_settings_options([{_, _, _, _} = Opt | Tail], Acc) ->
131+
format_settings_options(Tail, [Opt | Acc]);
132+
format_settings_options([{Prompt, Key, Type, Descr, FollowUp} | Tail], Acc) ->
133+
format_settings_options(Tail ++ FollowUp(),
134+
[{Prompt, Key, Type, Descr} | Acc]).
135+
136+
default_author_and_email() ->
137+
%% See if we can get a git user and email to use as defaults
138+
case rebar_utils:sh("git config --global user.name", [return_on_error]) of
139+
{ok, Name} ->
140+
case rebar_utils:sh("git config --global user.email",
141+
[return_on_error]) of
142+
{ok, Email} ->
143+
{rebar_string:trim(Name, both, "\n"),
144+
rebar_string:trim(Email, both, "\n")};
145+
{error, _} ->
146+
%% Use neither if one doesn't exist
147+
{"Anonymous", "anonymous@example.org"}
148+
end;
149+
{error, _} ->
150+
%% Ok, try mecurial
151+
case rebar_utils:sh("hg showconfig ui.username",
152+
[return_on_error]) of
153+
{ok, NameEmail} ->
154+
case re:run(NameEmail, "^(.*) <(.*)>$",
155+
[{capture, [1, 2], list}, unicode]) of
156+
{match, [Name, Email]} ->
157+
{Name, Email};
158+
_ ->
159+
{"Anonymous", "anonymous@example.org"}
160+
end;
161+
{error, _} ->
162+
{"Anonymous", "anonymous@example.org"}
163+
end
164+
end.

src/grisp_tools_io.erl

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
-module(grisp_tools_io).
2+
3+
-export([
4+
ask/3,
5+
ask/4
6+
]).
7+
8+
ask(State, Prompt, Type) ->
9+
ask_convert(State, Prompt, fun get/2, Type, none).
10+
11+
ask(State, Prompt, Type, Default) ->
12+
ask_convert(State, Prompt, fun get/2, Type, Default).
13+
14+
ask_convert(State, Prompt, TransFun, Type, Default) ->
15+
DefaultPrompt = erlang:iolist_to_binary([Prompt, default(Default), "> "]),
16+
NewPrompt = erlang:binary_to_list(DefaultPrompt),
17+
Data = trim(trim(io:get_line(NewPrompt)), both, [$\n]),
18+
case TransFun(Type, Data) of
19+
no_data ->
20+
maybe_continue(State, Prompt, TransFun, Type, Default);
21+
no_clue ->
22+
continue(State, Prompt, TransFun, Type, Default);
23+
Ret ->
24+
Ret
25+
end.
26+
27+
maybe_continue(State, Prompt, TransFun, Type, Default) ->
28+
case Default of
29+
none ->
30+
continue(State, Prompt, TransFun, Type, Default);
31+
Default ->
32+
TransFun(Type, Default)
33+
end.
34+
35+
continue(State, Prompt, TransFun, Type, Default) ->
36+
say(State, "Wrong input type. A ~p is expected.~n", [Type]),
37+
ask_convert(State, Prompt, TransFun, Type, Default).
38+
39+
default(none) ->
40+
[];
41+
default(false) ->
42+
"(y/N)";
43+
default(true) ->
44+
"(Y/n)";
45+
default(Default) ->
46+
[" (", io_lib:format("~p", [Default]) , ")"].
47+
48+
get(boolean, []) ->
49+
no_data;
50+
get(boolean, [In | _]) when In =:= $Y orelse In =:= $y ->
51+
true;
52+
get(boolean, [In | _]) when In =:= $N orelse In =:= $n ->
53+
false;
54+
get(boolean, true) ->
55+
true;
56+
get(boolean, false) ->
57+
false;
58+
get(boolean, _) ->
59+
no_clue;
60+
get(integer, []) ->
61+
no_data;
62+
get(number, String) ->
63+
get(integer, String);
64+
get(integer, String) ->
65+
case (catch list_to_integer(String)) of
66+
{'Exit', _} ->
67+
no_clue;
68+
Integer ->
69+
Integer
70+
end;
71+
get(string, []) ->
72+
no_data;
73+
get(string, String) ->
74+
case is_list(String) of
75+
true ->
76+
String;
77+
false ->
78+
no_clue
79+
end.
80+
81+
-ifdef(unicode_str).
82+
trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
83+
trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
84+
trim(Str, both, Chars) -> string:trim(Str, both, Chars).
85+
-else.
86+
trim(Str) -> string:strip(rebar_utils:to_list(Str)).
87+
trim(Str, Dir, [Chars|_]) -> string:strip(rebar_utils:to_list(Str), Dir, Chars).
88+
-endif.
89+
90+
say(State, Say) ->
91+
Event = {say, io_lib:format(lists:flatten([Say, "~n"]), [])},
92+
grisp_tools_util:event(State, Event).
93+
94+
-spec say(string(), [term()] | term()) -> ok.
95+
say(State, Say, Args) when is_list(Args) ->
96+
Event = {say, io_lib:format(lists:flatten([Say, "~n"]), Args)},
97+
grisp_tools_util:event(State, Event);
98+
say(State, Say, Args) ->
99+
Event = {say, io_lib:format(lists:flatten([Say, "~n"]), [Args])},
100+
grisp_tools_util:event(State, Event).

0 commit comments

Comments
 (0)