Add ISC license and Cowboy based web service template

This commit is contained in:
Craig Everett 2023-07-02 17:51:16 +09:00
parent 7ce93df72e
commit 312db0e63c
8 changed files with 358 additions and 24 deletions

View File

@ -332,7 +332,12 @@ done({error, Info, Code}) ->
ok = zx_daemon:idle(),
Message = "Operation failed with: ~160tp",
ok = tell(error, Message, [Info]),
init:stop(Code).
init:stop(Code);
done({error, Class, Error, Stacktrace}) ->
ok = zx_daemon:idle(),
Message = "Execution failed with: ~tp: ~tp~nStacktrace:~n~tp",
ok = tell(error, Message, [Class, Error, Stacktrace]),
init:stop(1).
-spec not_done(outcome()) -> ok | no_return().
@ -442,7 +447,10 @@ start(LogPath) ->
overload_kill_qlen => 20000,
overload_kill_restart_after => 5000,
sync_mode_qlen => 10,
type => {file, LogPath}},
type => file,
file => LogPath,
max_no_bytes => 10000000,
max_no_files => 10},
filter_default =>
stop,
filters =>
@ -468,8 +476,8 @@ trim_logs(LogDir) ->
{ok, Origin} = file:get_cwd(),
ok = file:set_cwd(LogDir),
{ok, Files} = file:list_dir("."),
Desc = fun(A, B) -> A > B end,
Sorted = lists:sort(Desc, Files),
Descending = fun(A, B) -> A > B end,
Sorted = lists:sort(Descending, Files),
ok =
case length(Sorted) =< 10 of
true -> ok;
@ -873,6 +881,11 @@ search_xdg([]) ->
%% procedure the runtime will halt with an error message.
run(PackageString, RunArgs) ->
try run1(PackageString, RunArgs)
catch C:E:S -> {error, C, E, S}
end.
run1(PackageString, RunArgs) ->
case zx_lib:package_id(PackageString) of
{ok, {"otpr", "zomp", Version}} -> run2_maybe(Version, RunArgs);
{ok, FuzzyID} -> run2(FuzzyID, RunArgs);
@ -969,6 +982,11 @@ resolve_version(PackageID = {Realm, Name, _}) ->
%% and use zx commands to add or drop dependencies made available via zomp.
run_local(RunArgs) ->
try run_local1(RunArgs)
catch C:E:S -> {error, C, E, S}
end.
run_local1(RunArgs) ->
{ok, ProjectDir} = file:get_cwd(),
run_project(ProjectDir, ProjectDir, RunArgs).
@ -978,6 +996,11 @@ run_local(RunArgs) ->
RunArgs :: [string()].
run_dir(TargetDir, RunArgs) ->
try run_dir1(TargetDir, RunArgs)
catch C:E:S -> {error, C, E, S}
end.
run_dir1(TargetDir, RunArgs) ->
{ok, ExecDir} = file:get_cwd(),
case file:set_cwd(TargetDir) of
ok ->
@ -1009,9 +1032,13 @@ run_project(ProjectDir, ExecDir, RunArgs, Meta) ->
case prepare(Deps) of
ok ->
ok = file:set_cwd(ProjectDir),
ok = zx_lib:build(),
case zx_lib:build() of
ok ->
ok = file:set_cwd(ExecDir),
execute(Type, PackageID, Meta, ProjectDir, NewArgs);
error ->
{error, build_failed}
end;
Error ->
Error
end;
@ -1077,8 +1104,10 @@ is_local(_, []) -> false.
%% Execution prep common to all packages.
prepare(Deps) ->
ok = ensure(Deps),
make(Deps).
case ensure(Deps) of
ok -> make(Deps);
Error -> Error
end.
ensure([{fetch, Dep} | Rest]) ->
case filelib:is_dir(zx_lib:ppath(lib, Dep)) of
@ -1115,9 +1144,13 @@ make([{local, _, Dir} | Rest]) ->
{ok, WorkingDir} = file:get_cwd(),
case file:set_cwd(Dir) of
ok ->
ok = zx_lib:build(),
case zx_lib:build() of
ok ->
ok = file:set_cwd(WorkingDir),
make(Rest);
error ->
{error, build_failed}
end;
Error = {error, enoent} ->
ok = tell(error, "Dir ~p does not exist!", [Dir]),
Error

View File

@ -92,10 +92,14 @@ initialize(P = #project{id = none}) ->
initialize(P#project{id = ID, appmod = Name});
initialize(P = #project{type = app, id = {_, Name, _}, prefix = none}) ->
initialize(P#project{prefix = ask_prefix(Name)});
initialize(P = #project{type = web, id = {_, Name, _}, prefix = none}) ->
initialize(P#project{prefix = ask_prefix(Name)});
initialize(P = #project{type = gui, id = {_, Name, _}, prefix = none}) ->
initialize(P#project{prefix = ask_prefix(Name)});
initialize(P = #project{type = app, appmod = none}) ->
initialize(P#project{appmod = ask_appmod()});
initialize(P = #project{type = web, appmod = none}) ->
initialize(P#project{appmod = ask_appmod()});
initialize(P = #project{type = cli, appmod = none}) ->
initialize(P#project{appmod = ask_appmod()});
initialize(P = #project{type = gui, appmod = none}) ->
@ -190,6 +194,7 @@ initialize(P = #project{type = Type,
TS =
case Type of
app -> "Erlang application";
web -> "Web service";
cli -> "CLI/terminal program";
gui -> "GUI application"
end,
@ -304,9 +309,10 @@ zompify(P = #project{type = Type,
prefix => Prefix,
tags => []},
Data =
case Type == cli of
true -> maps:put(mod, Module, Simple);
false -> Simple
case Type of
cli -> maps:put(mod, Module, Simple);
web -> Simple#{type := app, deps => cowboy_deps()};
_ -> Simple
end,
ok = zx_lib:write_project_meta(Data),
ok = ensure_emakefile(),
@ -315,6 +321,17 @@ zompify(P = #project{type = Type,
{ok, PackageString} = zx_lib:package_string(ID),
tell("Project ~ts initialized.", [PackageString]).
cowboy_deps() ->
Apps = [{"otpr", N} || N <- ["cowboy", "ranch", "cowlib"]],
cowboy_deps(Apps).
cowboy_deps([App = {Realm, Name} | Apps]) ->
{ok, Ref} = zx_daemon:latest(App),
{ok, Ver} = zx_daemon:wait_result(Ref),
[{Realm, Name, Ver} | cowboy_deps(Apps)];
cowboy_deps([]) ->
[].
-spec ensure_emakefile() -> ok.
@ -347,6 +364,7 @@ ensure_license(#project{name = Name,
"LGPL-3.0-only" -> "lgpl3.txt";
"LGPL-3.0-or-later" -> "lgpl3.txt";
"MIT" -> "mit.txt";
"ISC" -> "isc.txt";
"MPL-2.0" -> "mpl2.txt";
"CC0" -> "cc0.txt"
end,
@ -1210,10 +1228,14 @@ create(P = #project{id = none}) ->
create(P#project{id = ID, appmod = AppMod});
create(P = #project{type = app, id = {_, Name, _}, prefix = none}) ->
create(P#project{prefix = ask_prefix(Name)});
create(P = #project{type = web, id = {_, Name, _}, prefix = none}) ->
create(P#project{prefix = ask_prefix(Name)});
create(P = #project{type = gui, id = {_, Name, _}, prefix = none}) ->
create(P#project{prefix = ask_prefix(Name)});
create(P = #project{type = app, appmod = none}) ->
create(P#project{appmod = ask_appmod()});
create(P = #project{type = web, appmod = none}) ->
create(P#project{appmod = ask_appmod()});
create(P = #project{type = gui, appmod = none}) ->
create(P#project{appmod = ask_appmod()});
create(P = #project{type = cli, module = none}) ->
@ -1402,6 +1424,7 @@ create(P = #project{type = Type,
TS =
case Type of
app -> "Erlang application";
web -> "Web service";
gui -> "GUI application"
end,
Instructions =
@ -1648,6 +1671,7 @@ munge_sources(#project{type = Type,
Template =
case Type of
app -> "example_server";
web -> "cowboy_example";
gui -> "hellowx"
end,
TemplateDir = filename:join([os:getenv("ZX_DIR"), "templates", Template]),
@ -1716,17 +1740,19 @@ ask_project_type() ->
"to work, even if it acts in a supporting role.~n"
"(Note that escripts cannot be packaged.)~n"
"[1] Traditional Erlang service application~n"
"[2] Library~n"
"[3] End-user GUI application~n"
"[4] End-user CLI application~n"
"[5] Escript~n",
"[2] Cowboy-based web service~n"
"[3] Library~n"
"[4] End-user GUI application~n"
"[5] End-user CLI application~n"
"[6] Escript~n",
ok = io:format(Instructions),
case zx_tty:get_input() of
"1" -> app;
"2" -> lib;
"3" -> gui;
"4" -> cli;
"5" -> escript;
"2" -> web;
"3" -> lib;
"4" -> gui;
"5" -> cli;
"6" -> escript;
_ ->
ok = zx_tty:derp(),
ask_project_type()
@ -2551,6 +2577,7 @@ ask_license() ->
{"GNU Library (LGPL) v3.0 only", "LGPL-3.0-only"},
{"GNU Library (LGPL) v3.0 or later", "LGPL-3.0-or-later"},
{"MIT license", "MIT"},
{"ISC license", "ISC"},
{"Mozilla Public License 2.0", "MPL-2.0"},
{"Public Domain/Creative Commons Zero notice", "CC0"},
{"[proprietary]", proprietary},

View File

@ -0,0 +1,59 @@
%%% @doc
%%% *PROJECT NAME*
%%% @end
-module(*APP MOD*).
-vsn("〘*VERSION*〙").
*AUTHOR*
*COPYRIGHT*
*LICENSE*
-export([start/0, start/1]).
-export([start/2, stop/1]).
-spec start() -> ok.
%% @doc
%% Start the server in an "ignore" state.
start() ->
ok = application:start(hello_web),
io:format("Starting...").
-spec start(PortNum) -> ok
when PortNum :: inet:port_number().
%% @doc
%% Start the server and begin listening immediately. Slightly more convenient when
%% playing around in the shell.
start(PortNum) ->
ok = start(),
io:format("Startup complete, listening on ~w~n", [PortNum]).
-spec start(normal, term()) -> {ok, pid()}.
%% @private
%% Called by OTP to kick things off. This is for the use of the "application" part of
%% OTP, not to be called by user code.
%% See: http://erlang.org/doc/apps/kernel/application.html
start(normal, _Args) ->
ok = application:ensure_started(sasl),
{ok, Started} = application:ensure_all_started(cowboy),
ok = io:format("Started: ~p~n", [Started]),
Routes = [{'_', [{"/", *PREFIX*_top, []}]}],
Dispatch = cowboy_router:compile(Routes),
Env = #{env => #{dispatch => Dispatch}},
{ok, _} = cowboy:start_clear(*PREFIX*_listener, [{port, 8080}], Env),
*PREFIX*_sup:start_link().
-spec stop(term()) -> ok.
%% @private
%% Similar to start/2 above, this is to be called by the "application" part of OTP,
%% not client code. Causes a (hopefully graceful) shutdown of the application.
stop(_State) ->
ok.

View File

@ -0,0 +1,119 @@
%%% @doc
%%% *PROJECT NAME* State
%%%
%%% This template has been generated by the `zx create project' command and is
%%% dead simple. You can have it save a value and you can read that value back out.
%%% Obviously you will probably want more from a web server than this,
%%% so make it your own.
%%% @end
-module(*PREFIX*_state).
-vsn("〘*VERSION*〙").
-behavior(gen_server).
*AUTHOR*
*COPYRIGHT*
*LICENSE*
-export([save/2, read/1]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
%%% Type and Record Definitions
-record(s,
{data = #{} :: #{Key :: term() := Value :: term()}}).
-type state() :: #s{}.
%%% Service Interface
-spec save(Key, Value) -> ok
when Key :: term(),
Value :: term().
%% @doc
%% Save a value.
save(Key, Value) ->
gen_server:cast(?MODULE, {save, Key, Value}).
-spec read(Key) -> {ok, Value} | error
when Key :: term(),
Value :: term().
%% @doc
%% Read a value.
read(ConfKey) ->
gen_server:call(?MODULE, {read, ConfKey}).
%%% Startup Functions
-spec start_link() -> Result
when Result :: {ok, pid()}
| {error, Reason :: term()}.
%% @private
%% This should only ever be called by hw_sup (the service-level supervisor).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).
-spec init(none) -> {ok, state()}.
%% @private
%% Called by the supervisor process to give the process a chance to perform any
%% preparatory work necessary for proper function.
init(none) ->
State = #s{},
{ok, State}.
%%% gen_server Message Handling Callbacks
handle_call({read, ConfKey}, _, State) ->
Value = do_read(ConfKey, State),
{reply, Value, State};
handle_call(Unexpected, From, State) ->
ok = io:format("~p Unexpected call from ~tp: ~tp~n", [self(), From, Unexpected]),
{noreply, State}.
handle_cast({save, ConfKey, Value}, State) ->
NewState = do_save(ConfKey, Value, State),
{noreply, NewState};
handle_cast(Unexpected, State) ->
ok = io:format("~p Unexpected cast: ~tp~n", [self(), Unexpected]),
{noreply, State}.
handle_info(Unexpected, State) ->
ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]),
{noreply, State}.
%%% OTP Service Functions
code_change(_, State, _) ->
{ok, State}.
terminate(_, _) ->
ok.
%%% Doer Functions
do_save(Key, Value, State = #s{data = Data}) ->
NewData = maps:put(Key, Value, Data),
State#s{data = NewData}.
do_read(Key, #s{data = Data}) ->
maps:find(Key, Data).

View File

@ -0,0 +1,44 @@
%%% @doc
%%% *PROJECT NAME* Top-level Supervisor
%%%
%%% The very top level supervisor in the system.
%%% There is only one stateful worker defined by default here, simple
%%% called [project]_state. Make it yours.
%%%
%%% See: http://erlang.org/doc/design_principles/applications.html
%%% See: http://zxq9.com/archives/1311
%%% @end
-module(*PREFIX*_sup).
-vsn("〘*VERSION*〙").
-behaviour(supervisor).
*AUTHOR*
*COPYRIGHT*
*LICENSE*
-export([start_link/0]).
-export([init/1]).
-spec start_link() -> {ok, pid()}.
%% @private
%% This supervisor's own start function.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
%% @private
%% The OTP init/1 function.
init([]) ->
RestartStrategy = {one_for_one, 1, 60},
State = {*PREFIX*_state,
{*PREFIX*_state, start_link, []},
permanent,
5000,
worker,
[*PREFIX*_state]},
Children = [State],
{ok, {RestartStrategy, Children}}.

View File

@ -0,0 +1,38 @@
%%% @doc
%%% *PROJECT NAME* top-level HTTP request handler.
%%% @end
-module(*PREFIX*_top).
-vsn("〘*VERSION*〙").
-behavior(cowboy_handler).
*AUTHOR*
*COPYRIGHT*
*LICENSE*
-export([init/2]).
-spec init(Req, State) -> Result
when Req :: cowboy_req:req(),
State :: any(),
Result :: {ok, Reply, NewState}
| {module(), Reply, NewState, Options},
Reply :: cowboy_req:req(),
NewState :: any(),
Options :: any().
init(Req, State) ->
Hits =
case *PREFIX*_state:read(hits) of
{ok, N} -> N;
error -> 1
end,
ok = *PREFIX*_state:save(hits, Hits + 1),
Code = 200,
Headers = #{<<"content-type">> => <<"text/plain">>},
TextHits = integer_to_binary(Hits),
Body =
<<"Hello, World!\r\n",
"We've had ", TextHits/binary, " hits so far.">>,
Reply = cowboy_req:reply(Code, Headers, Body, Req),
{ok, Reply, State}.

View File

@ -1,4 +1,4 @@
To the extent possible under law, 〘\*COPYRIGHT HOLDER\*〙 has waived all copyright and related or neighboring rights to 〘\*PROJECT NAME\*〙.
To the extent possible under law, 〘*COPYRIGHT HOLDER*〙 has waived all copyright and related or neighboring rights to 〘*PROJECT NAME*〙.
A more complete reference for the reasoning and formulation of this waiver of property rights is available at the Creative Commons Zero ("CC0") page:
https://creativecommons.org/share-your-work/public-domain/cc0/

View File

@ -0,0 +1,14 @@
Copyright 〘*YEAR*〙 〘*COPYRIGHT HOLDER*〙
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.