Adjust dev scripts. Make interaction better.

This commit is contained in:
Craig Everett 2018-06-01 18:46:37 +09:00
parent aaf4ec124c
commit 1e6132f7e5
9 changed files with 460 additions and 271 deletions

10
test_prep Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
pushd $(dirname $BASH_SOURCE) > /dev/null
ZX_DEV_ROOT=$PWD
popd > /dev/null
ZOMP_DIR="$ZX_DEV_ROOT/tester"
rm -rf "$ZOMP_DIR"
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
echo "Done. Make sure to export \"export ZOMP_DIR=$ZOMP_DIR\" before running zx"

View File

@ -27,7 +27,7 @@
-export_type([serial/0, package_id/0, package/0, realm/0, name/0, version/0,
identifier/0,
host/0,
key_id/0, key_name/0, key_data/0,
key_id/0, key_name/0,
user_id/0, user_name/0, contact_info/0, user_data/0,
lower0_9/0, label/0,
package_meta/0,
@ -50,16 +50,13 @@
-type host() :: {string() | inet:ip_address(), inet:port_number()}.
-type key_id() :: {realm(), key_name()}.
-type key_name() :: lower0_9().
-type key_data() :: {ID :: key_id(),
Public :: binary() | <<>>,
Private :: binary() | <<>>}.
-type user_id() :: {realm(), user_name()}.
-type user_name() :: label().
-type contact_info() :: {Type :: string(), Data :: string()}.
-type user_data() :: {ID :: user_id(),
RealName :: string(),
Contact :: contact_info(),
KeyData :: [key_data()]}.
Contact :: [contact_info()],
Keys :: [key_name()]}.
-type lower0_9() :: [$a..$z | $0..$9 | $_].
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
-type package_meta() :: #{package_id := package_id(),
@ -136,8 +133,8 @@ do(["package", TargetDir]) ->
end;
do(["dialyze"]) ->
done(zx_local:dialyze());
do(["create", "user", Realm, Name]) ->
done(zx_local:create_user(Realm, Name));
do(["create", "user"]) ->
done(zx_local:create_user());
do(["create", "keypair"]) ->
done(zx_local:grow_a_pair());
do(["drop", "key", Realm, KeyName]) ->
@ -646,7 +643,7 @@ usage() ->
" zx add key Realm KeyName~n"
" zx get key Realm KeyName~n"
" zx rem key Realm KeyName~n"
" zx create user Realm~n"
" zx create user~n"
" zx create userfiles Realm UserName~n"
" zx create keypair Realm~n"
" zx export user UserID~n"

View File

@ -17,11 +17,26 @@
takeover/1, abdicate/1,
create_plt/0, dialyze/0,
grow_a_pair/0, drop_key/1,
create_user/2, create_realm/0, create_realmfile/2]).
create_user/0, create_realm/0, create_realmfile/2]).
-include("zx_logger.hrl").
-record(user_data,
{realm = none :: none | zx:realm(),
username = none :: none | zx:user_name(),
realname = none :: none | string(),
contact_info = none :: none | [zx:contact_info()],
keys = none :: none | [zx:key_name()]}).
-record(realm_init,
{realm = none :: none | zx:realm(),
addr = none :: none | string() | inet:ip_address(),
port = none :: none | inet:port_number(),
sysop = none :: none | #user_data{},
url = none :: none | string()}).
%%% Functions
@ -57,7 +72,7 @@ initialize2(Type, RawPackageString) ->
initialize3(Type, PackageID) ->
case package_exists(PackageID) of
false ->
Prefix = solicit_prefix(),
Prefix = ask_prefix(),
initialize(Type, PackageID, Prefix);
true ->
Message = "Package already exists. Try another.",
@ -118,9 +133,9 @@ initialize(Type, PackageID, Prefix, AppStart) ->
ok = log(info, "Project ~tp initialized.", [PackageString]),
Message =
"~nNOTICE:~n"
" This project is currently listed as having no dependencies.~n"
" If this is not true then run `zx set dep DepID` for each current dependency.~n"
" (run `zx help` for more information on usage)~n",
"This project is currently listed as having no dependencies.~n"
"If this is not true then run `zx set dep DepID` for each current dependency.~n"
"(run `zx help` for more information on usage)~n",
io:format(Message).
@ -139,11 +154,11 @@ package_exists(PackageID) ->
false.
-spec solicit_prefix() -> string().
-spec ask_prefix() -> string().
%% @private
%% Get a valid module prefix to use as a namespace for new modules.
solicit_prefix() ->
ask_prefix() ->
Instructions =
"~nPICKING A PREFIX~n"
"Most Erlang applications have a prefix on the front of their modules. This "
@ -169,7 +184,7 @@ solicit_prefix() ->
false ->
Message = "The string \"~tp\" is problematic. Try \"[a-z]*_\".~n",
ok = io:format(Message, [Prefix]),
solicit_prefix()
ask_prefix()
end
end.
@ -805,18 +820,6 @@ drop_key({Realm, KeyName}) ->
end.
-spec create_user(zx:realm(), zx:user_name()) -> ok.
%% @private
%% Validate the realm and username provided, prompt the user to either select a keypair
%% to use or generate a new one, and bundle a .zuser file for conveyance of the user
%% data and his relevant keys (for import into an existing zomp server via `add'
%% command like "add packager", "add maintainer" and "add sysop".
create_user(Realm, Username) ->
Message = "Would be generating a user file for {~160tp, ~160to}.",
log(info, Message, [Realm, Username]).
-spec create_realm() -> ok.
%% @private
%% Prompt the user to input the information necessary to create a new zomp realm,
@ -825,211 +828,58 @@ create_user(Realm, Username) ->
create_realm() ->
ok = log(info, "WOOHOO! Making a new realm!"),
create_realm(#realm_init{}).
create_realm(R = #realm_init{realm = none}) ->
create_realm(R#realm_init{realm = ask_realm()});
create_realm(R = #realm_init{addr = none}) ->
create_realm(R#realm_init{addr = ask_addr()});
create_realm(R = #realm_init{port = none}) ->
create_realm(R#realm_init{port = ask_port()});
create_realm(R = #realm_init{url = none}) ->
create_realm(R#realm_init{url = ask_url()});
create_realm(R = #realm_init{realm = Realm, sysop = none}) ->
create_realm(R#realm_init{sysop = create_sysop(#user_data{realm = Realm})});
create_realm(R = #realm_init{realm = Realm, addr = Addr, port = Port, url = URL}) ->
Instructions =
"~nNAMING~n"
"Enter a name for your new realm.~n"
"Names can contain only lower-case letters, numbers and the underscore.~n"
"Names must begin with a lower-case letter.~n",
ok = io:format(Instructions),
Realm = zx_tty:get_input(),
case zx_lib:valid_lower0_9(Realm) of
true ->
case realm_exists(Realm) of
false ->
create_realm(Realm);
true ->
ok = io:format("That realm already exists. Be more original.~n"),
create_realm()
end;
false ->
ok = io:format("Bad realm name \"~ts\". Try again.~n", [Realm]),
create_realm()
end.
-spec create_realm(Realm) -> ok
when Realm :: zx:realm().
create_realm(Realm) ->
Address = prompt_address(),
create_realm(Realm, Address).
-spec prompt_address() -> Result
when Result :: inet:hostname() | inet:ip_address().
prompt_address() ->
Message =
"~nHOST ADDRESS~n"
"Enter a static, valid hostname or IPv4 or IPv6 address at which this host "
"can be reached from the public internet (or internal network if it will never "
"need to be reached from the internet).~n"
"DO NOT INCLUDE A PORT NUMBER IN THIS STEP~n",
ok = io:format(Message),
"~nREALM DATA CONFIRMATION~n"
"That's everything! Please confirm these settings:~n"
"[1] Realm Name : ~ts ~n"
"[2] Host Address: ~ts~n"
"[3] Port Number : ~w~n"
"[4] URL : ~ts~n"
"Press a number to select something to change, or [ENTER] to continue.~n",
ok = io:format(Instructions, [Realm, Addr, Port, URL]),
case zx_tty:get_input() of
"" ->
ok = io:format("You need to enter an address.~n"),
prompt_address();
String ->
parse_address(String)
"1" -> create_realm(R#realm_init{realm = none});
"2" -> create_realm(R#realm_init{addr = none});
"3" -> create_realm(R#realm_init{port = none});
"4" -> create_realm(R#realm_init{url = none});
"" -> store_realm(R);
_ ->
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
create_realm(R)
end.
-spec parse_address(string()) -> inet:hostname() | inet:ip_address().
parse_address(String) ->
case inet:parse_address(String) of
{ok, Address} -> Address;
{error, einval} -> String
end.
-spec create_realm(Realm, Address) -> ok
when Realm :: zx:realm(),
Address :: inet:hostname() | inet:ip_address().
create_realm(Realm, Address) ->
Message =
"~nPUBLIC PORT NUMBER~n"
"Enter the public (external) port number at which this service should be "
"available. (This might be different from the local port number if you are "
"forwarding ports or have a complex network layout.)~n",
ok = io:format(Message),
Port = prompt_port_number(11311),
create_realm(Realm, Address, Port).
-spec prompt_port_number(Current) -> Result
when Current :: inet:port_number(),
Result :: inet:port_number().
prompt_port_number(Current) ->
Instructions =
"A valid port is any number from 1 to 65535."
"[Press enter to accept the current setting: ~tw]~n",
ok = io:format(Instructions, [Current]),
case zx_tty:get_input() of
"" ->
Current;
S ->
try
case list_to_integer(S) of
Port when 16#ffff >= Port, Port > 0 ->
Port;
Illegal ->
Whoops = "Whoops! ~tw is out of bounds (1~65535). Try again.~n",
ok = io:format(Whoops, [Illegal]),
prompt_port_number(Current)
end
catch error:badarg ->
ok = io:format("~tp is not a port number. Try again...", [S]),
prompt_port_number(Current)
end
end.
-spec create_realm(Realm, Address, Port) -> ok
when Realm :: zx:realm(),
Address :: inet:hostname() | inet:ip_address(),
Port :: inet:port_number(),
Port :: inet:port_number().
create_realm(Realm, Address, Port) ->
Instructions =
"~nSYSOP USERNAME~n"
"Enter a username for the realm sysop.~n"
"Names can contain only lower-case letters, numbers and the underscore.~n"
"Names must begin with a lower-case letter.~n",
ok = io:format(Instructions),
UserName = zx_tty:get_input(),
case zx_lib:valid_lower0_9(UserName) of
true ->
create_realm(Realm, Address, Port, UserName);
false ->
ok = io:format("Bad username ~tp. Try again.~n", [UserName]),
create_realm(Realm, Address, Port)
end.
-spec create_realm(Realm, Address, Port, UserName) -> ok
when Realm :: zx:realm(),
Address :: inet:hostname() | inet:ip_address(),
Port :: inet:port_number(),
UserName :: string().
create_realm(Realm, Address, Port, UserName) ->
Instructions =
"~nSYSOP EMAIL~n"
"Enter an email address for the realm sysop.~n"
"Valid email address rules apply though the checking done here is quite "
"minimal. Check the address you enter carefully. The only people who will "
"suffer from an invalid address are your users.~n",
ok = io:format(Instructions),
Email = zx_tty:get_input(),
[User, Host] = string:lexemes(Email, "@"),
case {zx_lib:valid_lower0_9(User), zx_lib:valid_label(Host)} of
{true, true} ->
create_realm(Realm, Address, Port, UserName, Email);
{false, true} ->
Message = "The user part of the email address seems invalid. Try again.~n",
ok = io:format(Message),
create_realm(Realm, Address, Port, UserName);
{true, false} ->
Message = "The host part of the email address seems invalid. Try again.~n",
ok = io:format(Message),
create_realm(Realm, Address, Port, UserName);
{false, false} ->
Message = "This email address seems like its totally bonkers. Try again.~n",
ok = io:format(Message),
create_realm(Realm, Address, Port, UserName)
end.
-spec create_realm(Realm, Address, Port, UserName, Email) -> ok
when Realm :: zx:realm(),
Address :: inet:hostname() | inet:ip_address(),
Port :: inet:port_number(),
UserName :: string(),
Email :: string().
create_realm(Realm, Address, Port, UserName, Email) ->
Instructions =
"~nSYSOP REAL NAME~n"
"Enter the real name (or whatever name people recognize) for the sysop.~n"
"There are no rules for this one. Any valid UTF-8 printables are legal.~n",
ok = io:format(Instructions),
RealName = zx_tty:get_input(),
create_realm(Realm, Address, Port, UserName, Email, RealName).
-spec create_realm(Realm, Address, Port, UserName, Email, RealName) -> ok
when Realm :: zx:realm(),
Address :: inet:hostname() | inet:ip_address(),
Port :: inet:port_number(),
UserName :: string(),
Email :: string(),
RealName :: string().
create_realm(Realm, Address, Port, UserName, Email, RealName) ->
ok = io:format("~nGenerating keys. This might take a while, so settle in...~n"),
KeyName = UserName ++ "-root",
store_realm(#realm_init{realm = Realm,
addr = Addr,
port = Port,
url = URL,
sysop = Sysop = #user_data{username = UserName,
keys = [KeyName]}}) ->
ok = make_realm_dirs(Realm),
ok = zx_key:generate_rsa({Realm, KeyName}),
Address = parse_maybe_address(Addr),
RealmConf =
[{realm, Realm},
{prime, {Address, Port}},
{sysop, UserName},
{key, KeyName}],
UserConf =
[{realm, Realm},
{username, UserName},
{realmname, RealName},
{contact_info, [{"email", Email}]},
{keys, [KeyName]}],
{key, KeyName},
{url, URL}],
ok = store_user(Sysop),
RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"),
ok = zx_lib:write_terms(RealmConfPath, RealmConf),
UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"),
ok = zx_lib:write_terms(UserConfPath, UserConf),
ok = create_realmfile(Realm, "."),
ZRF = Realm ++ ".zrf",
Message =
@ -1054,6 +904,308 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
io:format(Message, Substitutions).
-spec ask_realm() -> string().
ask_realm() ->
Instructions =
"~nNAMING~n"
"Enter a name for your new realm.~n"
"Names can contain only lower-case letters, numbers and the underscore.~n"
"Names must begin with a lower-case letter.~n",
ok = io:format(Instructions),
Realm = zx_tty:get_input(),
case zx_lib:valid_lower0_9(Realm) of
true ->
case realm_exists(Realm) of
false ->
Realm;
true ->
ok = io:format("That realm already exists. Be more original.~n"),
ask_realm()
end;
false ->
ok = io:format("Bad realm name \"~ts\". Try again.~n", [Realm]),
ask_realm()
end.
-spec ask_addr() -> string().
ask_addr() ->
Message =
"~nHOST ADDRESS~n"
"Enter a static, valid hostname or IPv4 or IPv6 address at which this host "
"can be reached from the public internet (or internal network if it will never "
"need to be reached from the internet).~n"
"DO NOT INCLUDE A PORT NUMBER IN THIS STEP~n",
ok = io:format(Message),
case zx_tty:get_input() of
"" ->
ok = io:format("You need to enter an address.~n"),
ask_addr();
String ->
String
end.
-spec parse_maybe_address(string()) -> inet:hostname() | inet:ip_address().
parse_maybe_address(String) ->
case inet:parse_address(String) of
{ok, Address} -> Address;
{error, einval} -> String
end.
-spec ask_port() -> inet:port_number().
ask_port() ->
Message =
"~nPUBLIC PORT NUMBER~n"
"Enter the publicly visible (external) port number at which this service "
"should be available.~n"
"(This might be different from the local port number if you are forwarding "
"ports or have a complex network layout.)~n",
ok = io:format(Message),
prompt_port_number(11311).
-spec prompt_port_number(Current) -> Result
when Current :: inet:port_number(),
Result :: inet:port_number().
prompt_port_number(Current) ->
ok = io:format("A valid port is any number from 1 to 65535.~n"),
case zx_tty:get_input("[~tw]", [Current]) of
"" ->
Current;
S ->
try
case list_to_integer(S) of
Port when 16#ffff >= Port, Port > 0 ->
Port;
Illegal ->
Whoops = "Whoops! ~tw is out of bounds (1~65535). Try again.~n",
ok = io:format(Whoops, [Illegal]),
prompt_port_number(Current)
end
catch error:badarg ->
ok = io:format("~tp is not a port number. Try again...", [S]),
prompt_port_number(Current)
end
end.
-spec ask_url() -> string().
ask_url() ->
Message =
"~nURL~n"
"Most public realms have a website, IRC channel, or similar location where its "
"community (or customers) communicate. If you have such a URL enter it here.~n"
"NOTE: No checking is performed on the input here. Confuse your users at "
"your own peril!~n",
ok = io:format(Message),
zx_tty:get_input("[ENTER] to leave blank").
-spec create_sysop(InitUser) -> FullUser
when InitUser :: #user_data{},
FullUser :: #user_data{}.
create_sysop(U = #user_data{username = none}) ->
UserName = ask_username(),
KeyName = UserName ++ ".root",
create_sysop(U#user_data{username = UserName, keys = [KeyName]});
create_sysop(U = #user_data{realname = none}) ->
create_sysop(U#user_data{realname = ask_realname()});
create_sysop(U = #user_data{contact_info = none}) ->
create_sysop(U#user_data{contact_info = [ask_email()]});
create_sysop(U = #user_data{username = UserName,
realname = RealName,
contact_info = [{"email", Email}]}) ->
Instructions =
"~nSYSOP DATA CONFIRMATION~n"
"Please correct or confirm the sysop's data:~n"
"[1] Username : ~ts~n"
"[2] Real Name: ~ts~n"
"[3] Email : ~ts~n"
"Press a number to select something to change, or [ENTER] to accept.~n",
ok = io:format(Instructions, [UserName, RealName, Email]),
case zx_tty:get_input() of
"1" -> create_sysop(U#user_data{username = none});
"2" -> create_sysop(U#user_data{realname = none});
"3" -> create_sysop(U#user_data{contact_info = none});
"" -> U;
_ ->
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
create_sysop(U)
end.
-spec create_user() -> zx:outcome().
create_user() ->
create_user(#user_data{}).
-spec create_user(#user_data{}) -> zx:outcome().
create_user(U = #user_data{realm = none}) ->
case pick_realm() of
{ok, Realm} -> create_user(U#user_data{realm = Realm});
{error, no_realms} -> {error, "No realms configured.", 1}
end;
create_user(U = #user_data{username = none}) ->
UserName = ask_username(),
KeyName = UserName ++ ".1",
create_user(U#user_data{username = UserName, keys = [KeyName]});
create_user(U = #user_data{realname = none}) ->
create_user(U#user_data{realname = ask_realname()});
create_user(U = #user_data{contact_info = none}) ->
create_user(U#user_data{contact_info = [ask_email()]});
create_user(U = #user_data{realm = Realm,
username = UserName,
realname = RealName,
contact_info = [{"email", Email}]}) ->
Instructions =
"~nUSER DATA CONFIRMATION~n"
"Please correct or confirm the user's data:~n"
"[1] Realm : ~ts~n"
"[2] Username : ~ts~n"
"[3] Real Name: ~ts~n"
"[4] Email : ~ts~n"
"Press a number to select something to change, or [ENTER] to accept.~n",
ok = io:format(Instructions, [Realm, UserName, RealName, Email]),
case zx_tty:get_input() of
"1" -> create_user(U#user_data{realm = none});
"2" -> create_user(U#user_data{username = none});
"3" -> create_user(U#user_data{realname = none});
"4" -> create_user(U#user_data{contact_info = none});
"" -> store_user(U);
_ ->
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
create_user(U)
end.
-spec store_user(#user_data{}) -> ok.
store_user(#user_data{realm = Realm,
username = UserName,
realname = RealName,
contact_info = ContactInfo,
keys = KeyNames}) ->
UserConf =
[{realm, Realm},
{username, UserName},
{realname, RealName},
{contact_info, ContactInfo},
{keys, KeyNames}],
ok = gen_keys(Realm, KeyNames),
UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"),
ok = zx_lib:write_terms(UserConfPath, UserConf),
log(info, "User ~tp created.", [{Realm, UserName}]).
-spec gen_keys(Realm, KeyNames) -> ok
when Realm :: zx:realm(),
KeyNames :: [zx:key_names()].
gen_keys(Realm, KeyNames) ->
ok = io:format("Generating keys. This might take a while, so settle in...~n"),
GenRSA = fun(KeyName) -> zx_key:generate_rsa({Realm, KeyName}) end,
lists:foreach(GenRSA, KeyNames).
-spec pick_realm() -> Result
when Result :: {ok, zx:realm()}
| {error, no_realms}.
pick_realm() ->
case zx_lib:list_realms() of
[] ->
ok = log(warning, "No realms configured! Exiting..."),
{error, no_realms};
Realms ->
ok = io:format("Select a realm:~n"),
Options = lists:zip(Realms, Realms),
Realm = zx_tty:select(Options),
{ok, Realm}
end.
-spec ask_username() -> zx:user_name().
ask_username() ->
Instructions =
"~nUSERNAME~n"
"Enter a username.~n"
"Names can contain only lower-case letters, numbers and the underscore.~n"
"Names must begin with a lower-case letter.~n",
ok = io:format(Instructions),
case zx_tty:get_input() of
"" ->
Message = "You have to be called *something*. Let's try that again.~n",
ok = io:format(Message),
ask_username();
UserName ->
case zx_lib:valid_lower0_9(UserName) of
true ->
UserName;
false ->
ok = io:format("Bad username ~tp. Try again.~n", [UserName]),
ask_username()
end
end.
-spec ask_realname() -> string().
ask_realname() ->
Instructions =
"~nREAL NAME~n"
"Enter the user's real name (or whatever name people recognize).~n"
"There are no rules for this one. Any valid UTF-8 printables are legal.~n",
ok = io:format(Instructions),
zx_tty:get_input().
-spec ask_email() -> {Type :: string(), Email :: string()}.
ask_email() ->
Instructions =
"~nEMAIL~n"
"Enter an email address.~n"
"Valid email address rules apply though the checking done here is quite "
"minimal. Check the address you enter carefully. The only people who will "
"suffer from an invalid address are other realm users.~n",
ok = io:format(Instructions),
Email = zx_tty:get_input(),
case string:lexemes(Email, "@") of
[User, Host] ->
case {zx_lib:valid_lower0_9(User), zx_lib:valid_label(Host)} of
{true, true} ->
{"email", Email};
{false, true} ->
Message = "The user part of the email address seems invalid.~n",
ok = io:format(Message),
ask_email();
{true, false} ->
Message = "The host part of the email address seems invalid.~n",
ok = io:format(Message),
ask_email();
{false, false} ->
Message = "This email address is totally bonkers. Try again.~n",
ok = io:format(Message),
ask_email()
end;
_ ->
ok = io:format("Don't get fresh with me. Try again. For real this time.~n"),
ask_email()
end.
-spec realm_exists(zx:realm()) -> boolean().
%% @private
%% Checks for remnants of a realm.
@ -1111,9 +1263,8 @@ drop_realm(Realm) ->
case realm_exists(Realm) of
true ->
Message =
"~n"
" WARNING: Are you SURE you want to remove realm ~ts?~n"
" (Only \"Y\" will confirm this action.)~n",
"~nWARNING: Are you SURE you want to remove realm ~ts?~n"
"(Only \"Y\" will confirm this action.)~n",
ok = io:format(Message, [Realm]),
case zx_tty:get_input() of
"Y" ->

View File

@ -10,7 +10,7 @@
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").
-export([get_input/0, select/1, select_string/1]).
-export([get_input/0, get_input/1, get_input/2, select/1, select_string/1]).
%%% Type Definitions
@ -20,12 +20,38 @@
%%% User menu interface (terminal)
-spec get_input() -> string().
%% @private
%% Provide a standard input prompt and newline sanitized return value.
get_input() ->
string:trim(io:get_line("(^C to quit): ")).
case string:trim(io:get_line("(or \"QUIT\"): ")) of
"QUIT" -> what_a_quitter();
String -> String
end.
-spec get_input(Prompt :: string()) -> string().
%% @private
%% Introduce
get_input(Prompt) ->
get_input(Prompt, []).
-spec get_input(Format, Args) -> string()
when Format :: string(),
Args :: [term()].
%% @private
%% Allow the caller to use io format strings and args to create a prompt.
get_input(Format, Args) ->
Prompt = io_lib:format(Format, Args),
case string:trim(io:get_line(["(or \"QUIT\") ", Prompt, ": "])) of
"QUIT" -> what_a_quitter();
String -> String
end.
-spec select(Options) -> Selected
@ -37,13 +63,18 @@ get_input() ->
select(Options) ->
Max = show(Options),
case pick(string:to_integer(io:get_line("(or ^C to quit)~n ? ")), Max) of
case string:trim(io:get_line("(or \"QUIT\"): ")) of
"QUIT" ->
what_a_quitter();
String ->
case pick(string:to_integer(String), Max) of
error ->
ok = hurr(),
select(Options);
I ->
{_, Value} = lists:nth(I, Options),
Index ->
{_, Value} = lists:nth(Index, Options),
Value
end
end.
@ -78,7 +109,7 @@ show([], I) ->
I;
show([{Label, _} | Rest], I) ->
Z = I + 1,
ok = io:format(" ~2w - ~ts~n", [Z, Label]),
ok = io:format("[~2w] ~ts~n", [Z, Label]),
show(Rest, Z).
@ -99,3 +130,12 @@ pick(_, _) -> error.
%% Present an appropriate response when the user derps on selection.
hurr() -> io:format("That isn't an option.~n").
-spec what_a_quitter() -> no_return().
%% @private
%% Halt the runtime if the user decides to quit.
what_a_quitter() ->
ok = io:format("User abort: \"QUIT\".~nHalting.~n"),
halt(0).

13
zomp/zx
View File

@ -1,10 +1,11 @@
#!/bin/sh
ZOMP_DIR="$HOME/.zomp"
VERSION=$(cat "$ZOMP_DIR/etc/version.txt")
ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION"
export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}"
version=$(cat "$ZOMP_DIR/etc/version.txt")
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version"
pushd "$ZX_DIR" > /dev/null
start_dir="$PWD"
cd "$ZX_DIR"
./make_zx
popd > /dev/null
erl -pa "$ZX_DIR/ebin" -run zx run $@
cd "$start_dir"
erl -noshell -pa "$ZX_DIR/ebin" -run zx do $@

11
zomp/zxh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}"
version=$(cat "$ZOMP_DIR/etc/version.txt")
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version"
start_dir="$PWD"
cd "$ZX_DIR"
./make_zx
cd "$start_dir"
erl -pa "$ZX_DIR/ebin" -run zx do $@

9
zomp/zxh.cmd Normal file
View File

@ -0,0 +1,9 @@
REM Prepare the environment for ZX and launch it
set ZOMP_DIR="%LOCALAPPDATA%\zomp"
set VERSION=<"%ZOMP_DIR%\etc\version.txt"
set ZX_DIR="%ZOMP_DIR%\lib\otpr\zx\%VERSION%"
pushd "%ZX_DIR%"
escript.exe make_zx
popd
erl.exe -pa "%ZX_DIR%/ebin" -run zx run "%*"

15
zx_dev
View File

@ -1,15 +0,0 @@
#!/bin/bash
pushd $(dirname $BASH_SOURCE) > /dev/null
ZX_DEV_ROOT=$PWD
popd > /dev/null
export ZOMP_DIR="$ZX_DEV_ROOT/tester"
rm -rf "$ZOMP_DIR"
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
VERSION=$(cat "$ZOMP_DIR/etc/version.txt")
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION"
pushd "$ZX_DIR" > /dev/null
./make_zx
popd > /dev/null
erl -noshell -pa "$ZX_DIR/ebin" -run zx do $@

15
zxh_dev
View File

@ -1,15 +0,0 @@
#!/bin/bash
pushd $(dirname $BASH_SOURCE) > /dev/null
ZX_DEV_ROOT=$PWD
popd > /dev/null
export ZOMP_DIR="$ZX_DEV_ROOT/tester"
rm -rf "$ZOMP_DIR"
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
VERSION=$(cat "$ZOMP_DIR/etc/version.txt")
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION"
pushd "$ZX_DIR" > /dev/null
./make_zx
popd > /dev/null
erl -pa "$ZX_DIR/ebin" -run zx do $@