Add "swp" (Service->Worker Pattern) templater

This commit is contained in:
Craig Everett 2020-09-28 22:12:43 +09:00
parent 378a30e112
commit 45fb16f5ea
54 changed files with 228 additions and 34 deletions

View File

@ -3,7 +3,7 @@
# A convenience script that will download the ZX installer, unpack it, and clean up.
# For maximum portability this script uses the gzipped package version.
version=0.11.4
version=0.11.6
zx="zx-$version"
tarball="$zx.tar.gz"

View File

@ -1 +1 @@
0.11.7
0.12.0

View File

@ -1,6 +1,6 @@
{application,zx,
[{description,"An Erlang development tool and Zomp user client"},
{vsn,"0.11.7"},
{vsn,"0.12.0"},
{applications,[stdlib,kernel]},
{modules,[zx,zx_auth,zx_conn,zx_conn_sup,zx_daemon,zx_key,
zx_lib,zx_local,zx_net,zx_peer,zx_peer_man,

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -24,7 +24,7 @@
%%% @end
-module(zx).
-vsn("0.11.7").
-vsn("0.12.0").
-behavior(application).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
@ -193,6 +193,9 @@ do(["drop", "mirror", Address, Port]) ->
do(["create", "project"]) ->
ok = connect(),
done(zx_local:create_project());
do(["template", "swp"]) ->
ok = connect(),
done(zx_local:template_swp());
do(["runlocal" | ArgV]) ->
ok = connect(),
not_done(run_local(ArgV));
@ -1253,6 +1256,7 @@ usage_user() ->
usage_dev() ->
"Developer/Packager/Maintainer Actions:~n"
" zx create project~n"
" zx template swp~n"
" zx runlocal [Args]~n"
" zx rundir Path [Args]~n"
" zx init~n"

View File

@ -9,7 +9,7 @@
%%% @end
-module(zx_auth).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -7,7 +7,7 @@
%%% @end
-module(zx_conn).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -5,7 +5,7 @@
%%% @end
-module(zx_conn_sup).
-vsn("0.11.7").
-vsn("0.12.0").
-behavior(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").

View File

@ -138,7 +138,7 @@
%%% @end
-module(zx_daemon).
-vsn("0.11.7").
-vsn("0.12.0").
-behavior(gen_server).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
@ -412,10 +412,15 @@ unsubscribe(Package = {Realm, Name}) ->
gen_server:cast(?MODULE, {unsubscribe, self(), Package}).
-spec list() -> {ok, realm_list()}.
-spec list() -> {ok, RequestID}
when RequestID :: term().
%% @doc
%% Request a list of currently configured realms. Because this call is entirely local
%% it is the only one that does not involve a round-trip
%% This query does not actually require a round trip but is included for completeness
%% and convenience when stacking zx_daemon queries into a list for use with
%% wait_results/1.
%%
%% If you only need a list of configured realms and aren't performing a list of queries
%% use zx:list/0 instead.
list() ->
request(list).

View File

@ -8,7 +8,7 @@
%%% @end
-module(zx_key).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -10,7 +10,7 @@
%%% @end
-module(zx_lib).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -6,7 +6,7 @@
%%% @end
-module(zx_local).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").
@ -21,7 +21,7 @@
set_timeout/1,
add_mirror/0, add_mirror/1, add_mirror/2,
drop_mirror/0, drop_mirror/1, drop_mirror/2,
create_project/0,
create_project/0, template_swp/0,
grow_a_pair/0, grow_a_pair/2, drop_key/1,
create_user/0, export_user/1, import_user/1,
create_realm/0, export_realm/0, export_realm/1]).
@ -1484,6 +1484,86 @@ create_project(P = #project{id = {_, Name, Version}}) ->
end.
template_swp() ->
case find_meta() of
{ok, Meta} -> template_swp(Meta);
Error -> Error
end.
template_swp(Meta) ->
Prefix = maps:get(prefix, Meta),
Modules = {_, _, _, Worker} = ask_service_name(Prefix),
{_, Name, Version} = maps:get(package_id, Meta),
{ok, VersionString} = zx_lib:version_to_string(Version),
Credit = maps:get(author, Meta),
AEmail = maps:get(a_email, Meta),
Holder = maps:get(copyright, Meta),
CEmail = maps:get(c_email, Meta),
License = maps:get(license, Meta),
Substitutions =
[{"\*PROJECT NAME\*〙", maps:get(name, Meta)},
{"\*SERVICE\*〙", Worker},
{"\*CAP SERVICE\*〙", string:titlecase(Worker)},
{"\*NAME\*〙", Name},
{"\*VERSION\*〙", VersionString},
{"\*PREFIX\*〙", Prefix},
{"\*AUTHOR\*〙", author(Credit, AEmail)},
{"\*COPYRIGHT\*〙", copyright(Holder, CEmail)},
{"\*LICENSE\*〙", license(License)}],
case template_filenames(Prefix, Modules) of
{ok, Templates} -> template_swp(Templates, Substitutions);
Error -> Error
end.
template_swp(Templates, Substitutions) ->
TemplateDir = filename:join([os:getenv("ZX_DIR"), "templates", "swp"]),
Transform =
fun({Template, Destination}) ->
TemplatePath = filename:join(TemplateDir, Template),
{ok, Raw} = file:read_file(TemplatePath),
UTF8 = unicode:characters_to_list(Raw, utf8),
Cooked = substitute(UTF8, Substitutions),
file:write_file(Destination, Cooked)
end,
lists:foreach(Transform, Templates).
template_filenames(Prefix, Modules) ->
Ts = ["workers.erl", "worker_man.erl", "worker_sup.erl", "worker.erl"],
Fs = [Prefix ++ "_" ++ S ++ ".erl" || S <- tuple_to_list(Modules)],
case lists:any(fun filelib:is_file/1, Fs) of
false ->
{ok, lists:zip(Ts, Fs)};
true ->
Format =
"The module files ~p would be created by this command.~n"
"One or more of these names already exists in the current directory.~n"
"Aborting.~n",
ok = io:format(Format, [Fs]),
{error, "File name conflict."}
end.
find_meta() ->
{ok, CWD} = file:get_cwd(),
find_meta(CWD).
find_meta(Dir) ->
case zx_lib:read_project_meta(Dir) of
{ok, Meta} -> {ok, Meta};
{error, _, 2} -> dive_down(Dir);
Error -> Error
end.
dive_down(Dir) ->
case filename:dirname(Dir) of
Dir ->
{error, "No project zomp.meta file. Wrong directory? Not initialized?", 2};
ParentDir ->
find_meta(ParentDir)
end.
munge_sources(#project{type = lib,
id = {_, Name, _},
name = ProjectName,
@ -1585,7 +1665,8 @@ munge_sources(#project{type = Type,
transform([Template | Rest], Prefix, Substitutions) ->
{ok, Raw} = file:read_file(Template),
Data = substitute(Raw, Substitutions),
UTF8 = unicode:characters_to_list(Raw, utf8),
Data = substitute(UTF8, Substitutions),
Path = Prefix ++ filename:basename(Template),
ok = file:write_file(Path, Data),
transform(Rest, Prefix, Substitutions);
@ -1740,8 +1821,8 @@ ask_prefix(Name) ->
"in the project are named things like \"es_client\".~n"
"Enter the prefix you would like to use below (or enter for the generated "
"default).~n",
ok = io:format(Instructions),
case zx_tty:get_input("[\"~ts\"]", [Default]) of
Prompt = io_lib:format("[\"~ts\"]", [Default]),
case zx_tty:get_input(Instructions, [], Prompt) of
"" ->
Default;
String ->
@ -2292,6 +2373,102 @@ pick_realm() ->
end.
-spec ask_service_name(Prefix) -> Modules
when Prefix :: string(),
Modules :: {Sup :: string(),
Man :: string(),
WorkerSup :: string(),
Worker :: string()}.
ask_service_name(Prefix) ->
Instructions =
"~nSERVICE NAME~n"
"Enter the name of the service.~n"
"Must be a legal module name.~n",
case zx_tty:get_input(Instructions) of
"" ->
ok = io:format("Please enter a service name."),
ask_service_name(Prefix);
String ->
Name = unicode:characters_to_list(String, utf8),
ask_service_name2(Prefix, Name)
end.
ask_service_name2(Prefix, Name) ->
case zx_lib:valid_lower0_9(Name) of
true ->
ask_service_name3(Prefix, Name);
false ->
Message = "Invalid characters. Try something in the range [:a-z0-9:].~n",
ok = io:format(Message),
ask_service_name(Prefix)
end.
ask_service_name3(Prefix, Worker) ->
Sup = Worker ++ "s",
Man = Worker ++ "_man",
WorkerSup = Worker ++ "_sup",
Modules = [Sup, Man, WorkerSup, Worker],
Prefixed = [Prefix ++ "_" ++ M || M <- Modules],
{ok, Realms} = zx:list(),
Combinations = [{R, M} || R <- Realms, M <- Prefixed],
Query =
fun({Realm, Module}) ->
{ok, ID} = zx_daemon:provides(Realm, Module),
{ID, Realm, Module}
end,
Pending = lists:map(Query, Combinations),
{ok, Results} = zx_daemon:wait_results([ID || {ID, _, _} <- Pending]),
case lists:all(fun({_, {ok, Ps}}) -> Ps == [] end, Results) of
true ->
list_to_tuple(Modules);
false ->
Trouble = match(Pending, Results, Prefixed),
confirm_service_name(Prefix, Modules, Trouble)
end.
match(Pending, Results, Modules) ->
Index = maps:from_list([{M, []} || M <- Modules]),
Merge =
fun({ID, {ok, Conflicts}}, I) ->
{value, {_, Realm, Module}} = lists:keysearch(ID, 1, Pending),
maps:put(Module, [{Realm, Conflicts} | maps:get(Module, I)], I)
end,
lists:foldl(Merge, Index, Results).
confirm_service_name(Prefix, Modules, Trouble) ->
ok = io:format("~nModule name conflicts! OH NOES!!!~n"),
ok = lists:foreach(fun show_conflict/1, maps:to_list(Trouble)),
Instructions =
"~nDECISIONS DECISIONS!~n"
"Do you want to use these names anyway?~n"
"The name conflict won't be a problem unless you need to include any of "
"the conflicting packages as a dependency (now or in the future).~n",
case zx_tty:get_input(Instructions, [], "[Y/N]") of
"Y" -> list_to_tuple(Modules);
"y" -> list_to_tuple(Modules);
_ -> ask_service_name(Prefix)
end.
show_conflict({_, []}) ->
ok;
show_conflict({Module, Packages}) ->
Message = "The module name ~tp shows a conflict with the following packages:~n",
ok = io:format(Message, [Module]),
show_conflict2(Packages).
show_conflict2([{Realm, Packages} | Rest]) ->
Show = fun({Name, Version}) -> show_package({Realm, Name, Version}) end,
ok = lists:foreach(Show, Packages),
show_conflict2(Rest);
show_conflict2([]) ->
ok.
show_package(ID) ->
{ok, PackageString} = zx_lib:package_string(ID),
io:format("~ts~n", [PackageString]).
-spec ask_project_name() -> string().
ask_project_name() ->
@ -2487,7 +2664,7 @@ edit_tags(Set) ->
{"Return", 3}],
case zx_tty:select(Options) of
1 ->
New = string:lowercase(zx_tty:get_input("~nEnter a new tag~n")),
New = string:casefold(zx_tty:get_input("~nEnter a new tag~n")),
edit_tags(sets:add_element(New, Set));
2 ->
edit_tags(rem_item(Set));

View File

@ -5,7 +5,7 @@
%%% @end
-module(zx_net).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -8,7 +8,7 @@
%%% @end
-module(zx_peer).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -9,7 +9,7 @@
%%% @end
-module(zx_peer_man).
-vsn("0.11.7").
-vsn("0.12.0").
-behavior(gen_server).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").

View File

@ -6,7 +6,7 @@
%%% @end
-module(zx_peer_sup).
-vsn("0.11.7").
-vsn("0.12.0").
-behaviour(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").

View File

@ -10,7 +10,7 @@
%%% @end
-module(zx_peers).
-vsn("0.11.7").
-vsn("0.12.0").
-behavior(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").

View File

@ -5,7 +5,7 @@
%%% @end
-module(zx_proxy).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -5,7 +5,7 @@
%%% @end
-module(zx_sup).
-vsn("0.11.7").
-vsn("0.12.0").
-behavior(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").

View File

@ -6,7 +6,7 @@
%%% @end
-module(zx_tty).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -5,7 +5,7 @@
%%% @end
-module(zx_userconf).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -7,7 +7,7 @@
%%% @end
-module(zx_zsp).
-vsn("0.11.7").
-vsn("0.12.0").
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0").

View File

@ -61,6 +61,14 @@ handle_info(Unexpected, State) ->
{noreply, State}.
-spec code_change(OldVersion, State, Extra) -> {ok, NewState} | {error, Reason}
when OldVersion :: Version | {old, Version},
Version :: term(),
State :: state(),
Extra :: term(),
NewState :: term(),
Reason :: term().
code_change(_, State, _) ->
{ok, State}.

View File

@ -80,11 +80,11 @@ handle_info(Unexpected, State) ->
{noreply, State}.
handle_down(Mon, PID, Reason, State = #s{clients = Clients}) ->
case lists:member(PID, Clients) of
handle_down(Mon, PID, Reason, State = #s{*SERVICE*s = *CAP SERVICE*s}) ->
case lists:member(PID, *CAP SERVICE*s) of
true ->
NewClients = lists:delete(PID, Clients),
State#s{clients = NewClients};
New*CAP SERVICE*s = lists:delete(PID, *CAP SERVICE*s),
State#s{*SERVICE*s = New*CAP SERVICE*s};
false ->
Unexpected = {'DOWN', Mon, process, PID, Reason},
ok = log(warning, "Unexpected info: ~tp", [Unexpected]),

View File

@ -9,7 +9,7 @@
{license,"MIT"}.
{modules,[]}.
{name,"zx"}.
{package_id,{"otpr","zx",{0,11,7}}}.
{package_id,{"otpr","zx",{0,12,0}}}.
{prefix,"zx_"}.
{repo_url,"https://gitlab.com/zxq9/zx"}.
{tags,["tools","package manager","erlang"]}.