Merge pull request #5 from zxq9/dev

Adjust dev scripts. Make interaction better.

Say, y'all, this here's sharp!
This commit is contained in:
Craig Everett 2018-06-01 18:48:59 +09:00 committed by GitHub
commit 5c0f5e0e89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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, -export_type([serial/0, package_id/0, package/0, realm/0, name/0, version/0,
identifier/0, identifier/0,
host/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, user_id/0, user_name/0, contact_info/0, user_data/0,
lower0_9/0, label/0, lower0_9/0, label/0,
package_meta/0, package_meta/0,
@ -50,16 +50,13 @@
-type host() :: {string() | inet:ip_address(), inet:port_number()}. -type host() :: {string() | inet:ip_address(), inet:port_number()}.
-type key_id() :: {realm(), key_name()}. -type key_id() :: {realm(), key_name()}.
-type key_name() :: lower0_9(). -type key_name() :: lower0_9().
-type key_data() :: {ID :: key_id(),
Public :: binary() | <<>>,
Private :: binary() | <<>>}.
-type user_id() :: {realm(), user_name()}. -type user_id() :: {realm(), user_name()}.
-type user_name() :: label(). -type user_name() :: label().
-type contact_info() :: {Type :: string(), Data :: string()}. -type contact_info() :: {Type :: string(), Data :: string()}.
-type user_data() :: {ID :: user_id(), -type user_data() :: {ID :: user_id(),
RealName :: string(), RealName :: string(),
Contact :: contact_info(), Contact :: [contact_info()],
KeyData :: [key_data()]}. Keys :: [key_name()]}.
-type lower0_9() :: [$a..$z | $0..$9 | $_]. -type lower0_9() :: [$a..$z | $0..$9 | $_].
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.]. -type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
-type package_meta() :: #{package_id := package_id(), -type package_meta() :: #{package_id := package_id(),
@ -136,8 +133,8 @@ do(["package", TargetDir]) ->
end; end;
do(["dialyze"]) -> do(["dialyze"]) ->
done(zx_local:dialyze()); done(zx_local:dialyze());
do(["create", "user", Realm, Name]) -> do(["create", "user"]) ->
done(zx_local:create_user(Realm, Name)); done(zx_local:create_user());
do(["create", "keypair"]) -> do(["create", "keypair"]) ->
done(zx_local:grow_a_pair()); done(zx_local:grow_a_pair());
do(["drop", "key", Realm, KeyName]) -> do(["drop", "key", Realm, KeyName]) ->
@ -646,7 +643,7 @@ usage() ->
" zx add key Realm KeyName~n" " zx add key Realm KeyName~n"
" zx get key Realm KeyName~n" " zx get key Realm KeyName~n"
" zx rem 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 userfiles Realm UserName~n"
" zx create keypair Realm~n" " zx create keypair Realm~n"
" zx export user UserID~n" " zx export user UserID~n"

View File

@ -17,11 +17,26 @@
takeover/1, abdicate/1, takeover/1, abdicate/1,
create_plt/0, dialyze/0, create_plt/0, dialyze/0,
grow_a_pair/0, drop_key/1, 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"). -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 %%% Functions
@ -57,7 +72,7 @@ initialize2(Type, RawPackageString) ->
initialize3(Type, PackageID) -> initialize3(Type, PackageID) ->
case package_exists(PackageID) of case package_exists(PackageID) of
false -> false ->
Prefix = solicit_prefix(), Prefix = ask_prefix(),
initialize(Type, PackageID, Prefix); initialize(Type, PackageID, Prefix);
true -> true ->
Message = "Package already exists. Try another.", Message = "Package already exists. Try another.",
@ -139,11 +154,11 @@ package_exists(PackageID) ->
false. false.
-spec solicit_prefix() -> string(). -spec ask_prefix() -> string().
%% @private %% @private
%% Get a valid module prefix to use as a namespace for new modules. %% Get a valid module prefix to use as a namespace for new modules.
solicit_prefix() -> ask_prefix() ->
Instructions = Instructions =
"~nPICKING A PREFIX~n" "~nPICKING A PREFIX~n"
"Most Erlang applications have a prefix on the front of their modules. This " "Most Erlang applications have a prefix on the front of their modules. This "
@ -169,7 +184,7 @@ solicit_prefix() ->
false -> false ->
Message = "The string \"~tp\" is problematic. Try \"[a-z]*_\".~n", Message = "The string \"~tp\" is problematic. Try \"[a-z]*_\".~n",
ok = io:format(Message, [Prefix]), ok = io:format(Message, [Prefix]),
solicit_prefix() ask_prefix()
end end
end. end.
@ -805,18 +820,6 @@ drop_key({Realm, KeyName}) ->
end. 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. -spec create_realm() -> ok.
%% @private %% @private
%% Prompt the user to input the information necessary to create a new zomp realm, %% Prompt the user to input the information necessary to create a new zomp realm,
@ -825,211 +828,58 @@ create_user(Realm, Username) ->
create_realm() -> create_realm() ->
ok = log(info, "WOOHOO! Making a new 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 = Instructions =
"~nNAMING~n" "~nREALM DATA CONFIRMATION~n"
"Enter a name for your new realm.~n" "That's everything! Please confirm these settings:~n"
"Names can contain only lower-case letters, numbers and the underscore.~n" "[1] Realm Name : ~ts ~n"
"Names must begin with a lower-case letter.~n", "[2] Host Address: ~ts~n"
ok = io:format(Instructions), "[3] Port Number : ~w~n"
Realm = zx_tty:get_input(), "[4] URL : ~ts~n"
case zx_lib:valid_lower0_9(Realm) of "Press a number to select something to change, or [ENTER] to continue.~n",
true -> ok = io:format(Instructions, [Realm, Addr, Port, URL]),
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),
case zx_tty:get_input() of case zx_tty:get_input() of
"" -> "1" -> create_realm(R#realm_init{realm = none});
ok = io:format("You need to enter an address.~n"), "2" -> create_realm(R#realm_init{addr = none});
prompt_address(); "3" -> create_realm(R#realm_init{port = none});
String -> "4" -> create_realm(R#realm_init{url = none});
parse_address(String) "" -> store_realm(R);
_ ->
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
create_realm(R)
end. end.
-spec parse_address(string()) -> inet:hostname() | inet:ip_address(). store_realm(#realm_init{realm = Realm,
addr = Addr,
parse_address(String) -> port = Port,
case inet:parse_address(String) of url = URL,
{ok, Address} -> Address; sysop = Sysop = #user_data{username = UserName,
{error, einval} -> String keys = [KeyName]}}) ->
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",
ok = make_realm_dirs(Realm), ok = make_realm_dirs(Realm),
ok = zx_key:generate_rsa({Realm, KeyName}), Address = parse_maybe_address(Addr),
RealmConf = RealmConf =
[{realm, Realm}, [{realm, Realm},
{prime, {Address, Port}}, {prime, {Address, Port}},
{sysop, UserName}, {sysop, UserName},
{key, KeyName}], {key, KeyName},
UserConf = {url, URL}],
[{realm, Realm}, ok = store_user(Sysop),
{username, UserName},
{realmname, RealName},
{contact_info, [{"email", Email}]},
{keys, [KeyName]}],
RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"), RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"),
ok = zx_lib:write_terms(RealmConfPath, RealmConf), 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, "."), ok = create_realmfile(Realm, "."),
ZRF = Realm ++ ".zrf", ZRF = Realm ++ ".zrf",
Message = Message =
@ -1054,6 +904,308 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
io:format(Message, Substitutions). 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(). -spec realm_exists(zx:realm()) -> boolean().
%% @private %% @private
%% Checks for remnants of a realm. %% Checks for remnants of a realm.
@ -1111,8 +1263,7 @@ drop_realm(Realm) ->
case realm_exists(Realm) of case realm_exists(Realm) of
true -> true ->
Message = Message =
"~n" "~nWARNING: Are you SURE you want to remove realm ~ts?~n"
" WARNING: Are you SURE you want to remove realm ~ts?~n"
"(Only \"Y\" will confirm this action.)~n", "(Only \"Y\" will confirm this action.)~n",
ok = io:format(Message, [Realm]), ok = io:format(Message, [Realm]),
case zx_tty:get_input() of case zx_tty:get_input() of

View File

@ -10,7 +10,7 @@
-copyright("Craig Everett <zxq9@zxq9.com>"). -copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0"). -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 %%% Type Definitions
@ -20,12 +20,38 @@
%%% User menu interface (terminal) %%% User menu interface (terminal)
-spec get_input() -> string(). -spec get_input() -> string().
%% @private %% @private
%% Provide a standard input prompt and newline sanitized return value. %% Provide a standard input prompt and newline sanitized return value.
get_input() -> 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 -spec select(Options) -> Selected
@ -37,13 +63,18 @@ get_input() ->
select(Options) -> select(Options) ->
Max = show(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 -> error ->
ok = hurr(), ok = hurr(),
select(Options); select(Options);
I -> Index ->
{_, Value} = lists:nth(I, Options), {_, Value} = lists:nth(Index, Options),
Value Value
end
end. end.
@ -78,7 +109,7 @@ show([], I) ->
I; I;
show([{Label, _} | Rest], I) -> show([{Label, _} | Rest], I) ->
Z = I + 1, Z = I + 1,
ok = io:format(" ~2w - ~ts~n", [Z, Label]), ok = io:format("[~2w] ~ts~n", [Z, Label]),
show(Rest, Z). show(Rest, Z).
@ -99,3 +130,12 @@ pick(_, _) -> error.
%% Present an appropriate response when the user derps on selection. %% Present an appropriate response when the user derps on selection.
hurr() -> io:format("That isn't an option.~n"). 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 #!/bin/sh
ZOMP_DIR="$HOME/.zomp" export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}"
VERSION=$(cat "$ZOMP_DIR/etc/version.txt") version=$(cat "$ZOMP_DIR/etc/version.txt")
ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION" export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version"
pushd "$ZX_DIR" > /dev/null start_dir="$PWD"
cd "$ZX_DIR"
./make_zx ./make_zx
popd > /dev/null cd "$start_dir"
erl -pa "$ZX_DIR/ebin" -run zx run $@ 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 $@