|
| 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. |
0 commit comments