From 312db0e63c14f97496b707b8015cbc8f2c997a99 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sun, 2 Jul 2023 17:51:16 +0900 Subject: [PATCH] Add ISC license and Cowboy based web service template --- zomp/lib/otpr/zx/0.13.0/src/zx.erl | 57 +++++++-- zomp/lib/otpr/zx/0.13.0/src/zx_local.erl | 49 ++++++-- .../templates/cowboy_example/appmod.erl | 59 +++++++++ .../templates/cowboy_example/src/_state.erl | 119 ++++++++++++++++++ .../templates/cowboy_example/src/_sup.erl | 44 +++++++ .../templates/cowboy_example/src/_top.erl | 38 ++++++ .../otpr/zx/0.13.0/templates/licenses/cc0.txt | 2 +- .../otpr/zx/0.13.0/templates/licenses/isc.txt | 14 +++ 8 files changed, 358 insertions(+), 24 deletions(-) create mode 100644 zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/appmod.erl create mode 100644 zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_state.erl create mode 100644 zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_sup.erl create mode 100644 zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_top.erl create mode 100644 zomp/lib/otpr/zx/0.13.0/templates/licenses/isc.txt diff --git a/zomp/lib/otpr/zx/0.13.0/src/zx.erl b/zomp/lib/otpr/zx/0.13.0/src/zx.erl index af0f9bc..6c94db3 100644 --- a/zomp/lib/otpr/zx/0.13.0/src/zx.erl +++ b/zomp/lib/otpr/zx/0.13.0/src/zx.erl @@ -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(), - ok = file:set_cwd(ExecDir), - execute(Type, PackageID, Meta, ProjectDir, NewArgs); + 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(), - ok = file:set_cwd(WorkingDir), - make(Rest); + 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 diff --git a/zomp/lib/otpr/zx/0.13.0/src/zx_local.erl b/zomp/lib/otpr/zx/0.13.0/src/zx_local.erl index 0cfba46..4b6e32d 100644 --- a/zomp/lib/otpr/zx/0.13.0/src/zx_local.erl +++ b/zomp/lib/otpr/zx/0.13.0/src/zx_local.erl @@ -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}, diff --git a/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/appmod.erl b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/appmod.erl new file mode 100644 index 0000000..6fcc36d --- /dev/null +++ b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/appmod.erl @@ -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. diff --git a/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_state.erl b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_state.erl new file mode 100644 index 0000000..a2b38f6 --- /dev/null +++ b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_state.erl @@ -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). diff --git a/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_sup.erl b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_sup.erl new file mode 100644 index 0000000..429162c --- /dev/null +++ b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_sup.erl @@ -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}}. diff --git a/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_top.erl b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_top.erl new file mode 100644 index 0000000..126321c --- /dev/null +++ b/zomp/lib/otpr/zx/0.13.0/templates/cowboy_example/src/_top.erl @@ -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}. diff --git a/zomp/lib/otpr/zx/0.13.0/templates/licenses/cc0.txt b/zomp/lib/otpr/zx/0.13.0/templates/licenses/cc0.txt index 8173a8d..43d06fd 100644 --- a/zomp/lib/otpr/zx/0.13.0/templates/licenses/cc0.txt +++ b/zomp/lib/otpr/zx/0.13.0/templates/licenses/cc0.txt @@ -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/ diff --git a/zomp/lib/otpr/zx/0.13.0/templates/licenses/isc.txt b/zomp/lib/otpr/zx/0.13.0/templates/licenses/isc.txt new file mode 100644 index 0000000..a8582d3 --- /dev/null +++ b/zomp/lib/otpr/zx/0.13.0/templates/licenses/isc.txt @@ -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. +