├── README.adoc ├── cowboy ├── app.erl ├── app.src └── sup.erl ├── cowboyapp.template ├── ctsuite.erl ├── ctsuite.template ├── ejabberdmod.erl ├── ejabberdmod.template ├── escript ├── escript.template ├── eunit.erl ├── eunit.template ├── finapp.template ├── finapp ├── finapp.app.src ├── finapp_app.erl └── finapp_sup.erl ├── finevt.erl ├── finevt.template ├── finsrv.erl ├── finsrv.template ├── finsrv_proper_statem.erl ├── finsrv_proper_statem.template ├── finsup.erl ├── finsup.template ├── globals ├── project.template ├── project_Emakefile ├── project_Makefile ├── project_README ├── project_gitignore ├── project_rebar.config ├── rebar_compiler.erl ├── rebar_compiler.template ├── rebar_compiler_SUITE.erl ├── riak_pipe_vnode_worker.erl ├── riak_pipe_vnode_worker.template ├── webmachine_resource.erl └── webmachine_resource.template /README.adoc: -------------------------------------------------------------------------------- 1 | == Rebar Templates 2 | 3 | === Overview 4 | 5 | This might really only be useful for me, but I thought I would share the 6 | Rebar templates that I have written to streamline my Erlang development. 7 | 8 | So far it only consists of templates for the following: 9 | 10 | * ejabberd module 11 | * OTP gen_server callback modules 12 | * OTP gen_event callback modules 13 | * simple rebar project layout 14 | * escript starter script 15 | * OTP application and supervisor callback modules 16 | * Nitrogen page handler (for Nitrogen v2+) 17 | 18 | === Status 19 | 20 | Many of these templates are from a past life in Erlang which are no longer 21 | maintained. I am in the process of culling/curating these templates while 22 | I upgrade to rebar3 and return to more active Erlang development. 23 | 24 | Please be patient during this transition period. 25 | 26 | === rebar3 27 | 28 | If you don't already you can clone this Git repository in your 29 | `${HOME}/.config/rebar3/templates` directory, like so: 30 | 31 | [source,shell] 32 | ---- 33 | $ git clone git://github.com/mbbx6spp/rebar-templates.git \ 34 | "${HOME}/.config/rebar3/templates" 35 | ---- 36 | 37 | Be sure to update the `globals` file with your preferred global values. 38 | 39 | === pre-rebar3 40 | 41 | If you don't already you can clone this Git repository in your 42 | `${HOME}/.rebar/templates` directory, like so: 43 | 44 | [source,shell] 45 | ---- 46 | $ git clone git://github.com/mbbx6spp/rebar-templates.git \ 47 | "${HOME}/.rebar/templates" 48 | ---- 49 | 50 | (Thanks link:http://twitter.com/dizzyd[@dizzyd] for the pointer.) 51 | 52 | Alternatively you can add this as a submodule under 53 | `${PROJECT_ROOT}/priv/templates` if you want to use these templates just for 54 | a particular project. 55 | 56 | === Developer(s) 57 | 58 | * link:http://twitter.com/SusanPotter[Susan Potter] 59 | 60 | === Contributor(s) 61 | 62 | * link:http://twitter.com/spawn_think[Ahmed D. Omar] - bug fix in ejabberdmod 63 | template (`s/init\/7/init\/2/` in exports). 64 | 65 | === Examples 66 | 67 | === Rebar Project Layout 68 | 69 | If you want to create a new rebar directory layout for your new project you 70 | can do the following: 71 | 72 | [source,shell] 73 | ---- 74 | $ mkdir project_name 75 | $ cd project_name 76 | $ rebar create template=project \ 77 | project_name=project_name \ 78 | description="Describe your project here." 79 | ---- 80 | 81 | This will generate 6 directories and 4 files by default that are organized 82 | like the following: 83 | 84 | 85 | [source,shell] 86 | ---- 87 | . 88 | ├── deps/ 89 | ├── ebin/ 90 | ├── Emakefile 91 | ├── include/ 92 | ├── Makefile 93 | ├── priv/ 94 | ├── README 95 | ├── rebar.config 96 | ├── src/ 97 | └── tests/ 98 | ---- 99 | 100 | === ejabberd Module 101 | 102 | To generate a stub of an ejabberd extension module that intercepts sent and 103 | received packets you can do the following: 104 | 105 | [source,shell] 106 | ---- 107 | # inside your rebar based project directory 108 | $ rebar create template=ejabberdmod name=mod_name_here 109 | ---- 110 | 111 | HTH 112 | link:http://twitter.com/SusanPotter[@SusanPotter] 113 | 114 | -------------------------------------------------------------------------------- /cowboy/app.erl: -------------------------------------------------------------------------------- 1 | %-*- mode: erlang -*- 2 | %%%' HEADER 3 | %%% @author {{author_name}} <{{author_email}}> 4 | %%% @copyright {{copyright_year}} {{author_name}} 5 | %%% @doc {{description}} 6 | %%% @end 7 | 8 | -module({{name}}_app). 9 | -behaviour(application). 10 | 11 | -export([start/2, stop/1]). 12 | %%%. 13 | %%%' CALLBACKS 14 | start(_StartType, _StartArgs) -> 15 | Dispatch = cowboy_router:compile(dispatch()), 16 | Port = application:get_env(port, 9292), 17 | {ok, _} = cowboy:start_http(http, 100, [{port, Port}], [ 18 | {env, [{dispatch, Dispatch}]} 19 | ]), 20 | {{name}}_sup:start_link(). 21 | 22 | stop(_State) -> 23 | ok. 24 | %%%. 25 | %%%' PRIVATE FUNCTIONS 26 | dispatch() -> 27 | [{'_', 28 | [ 29 | % { "/path", some_handler, []} 30 | ] 31 | }]. 32 | 33 | %%%. 34 | % vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 35 | -------------------------------------------------------------------------------- /cowboy/app.src: -------------------------------------------------------------------------------- 1 | %-*- mode: erlang -*- 2 | {application, {{name}}, 3 | [ 4 | {description, "{{description}}"}, 5 | {vsn, git}, 6 | {registered, []}, 7 | {applications, 8 | [ kernel 9 | , stdlib 10 | , cowboy 11 | ] 12 | }, 13 | {mod, { {{name}}_app, []}}, 14 | {env, []} 15 | ]}. 16 | % vim: set filetype=erlang tabstop=2: 17 | -------------------------------------------------------------------------------- /cowboy/sup.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @since {{date}} 4 | %%% @copyright {{copyright_year}} {{author_name}} 5 | %%% @doc {{description}} 6 | %%% @end 7 | -module({{name}}_sup). 8 | 9 | -behaviour(supervisor). 10 | -export([start_link/0]). 11 | -export([init/1]). 12 | 13 | -define(SERVER, ?MODULE). 14 | %%%. 15 | %%%' PUBLIC API 16 | start_link() -> 17 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 18 | 19 | %%%. 20 | %%%' CALLBACKS 21 | init([]) -> 22 | {ok, { {one_for_one, 5, 10}, []} }. 23 | 24 | %%%. 25 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 26 | -------------------------------------------------------------------------------- /cowboyapp.template: -------------------------------------------------------------------------------- 1 | {description, "Cowboy OTP-based application."}. 2 | {variables, [ 3 | {name, "appname"}, 4 | {appdir, ".", "Path to application directory."} 5 | ]}. 6 | {dir, "{{appdir}}/src"}. 7 | {template, "cowboy/app.src", "{{appdir}}/src/{{name}}.app.src"}. 8 | {template, "cowboy/app.erl", "{{appdir}}/src/{{name}}_app.erl"}. 9 | {template, "cowboy/sup.erl", "{{appdir}}/src/{{name}}_sup.erl"}. 10 | -------------------------------------------------------------------------------- /ctsuite.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @since {{date}} 4 | %%% @copyright {{copyright_year}} {{author_name}} 5 | %%% @doc {{description}} 6 | %%% @end 7 | 8 | -module({{name}}_SUITE). 9 | %% Note: This directive should only be used in test suites. 10 | -compile(export_all). 11 | -include_lib("common_test/include/ct.hrl"). 12 | %%%. 13 | %%%' CALLBACKS 14 | 15 | %% @doc returns list of tuples to set default properties for the suite. 16 | -spec suite() -> Config 17 | when 18 | Config :: proplist(). 19 | suite() -> 20 | [{timetrap,{minutes,10}}]. 21 | 22 | %% @doc runs initialization before matching test suite is executed. 23 | %% This function may add key/value pairs to Config. 24 | -spec init_per_suite(Config0) -> 25 | Config1 | {skip, Reason} | {skip_and_save, Reason, Config1} 26 | when 27 | Config0 :: proplist(), 28 | Config1 :: proplist(), 29 | Reason :: term(). 30 | init_per_suite(Config) -> 31 | Config. 32 | 33 | %% @doc runs cleanup after matching test suite is executed. 34 | -spec end_per_suite(Config0) -> ok | {save_config, Config1} 35 | when 36 | Config0 :: proplist(), 37 | Config1 :: proplist(). 38 | end_per_suite(_Config) -> 39 | ok. 40 | 41 | %% @doc runs initialization before matching test group is executed. 42 | -spec init_per_group(GroupName, Config0) -> 43 | Config1 | {skip, Reason} | {skip_and_save, Reason, Config1} 44 | when 45 | GroupName :: string(), 46 | Config0 :: proplist(), 47 | Config1 :: proplist(), 48 | Reason :: term(). 49 | init_per_group(_GroupName, Config) -> 50 | Config. 51 | 52 | %% @doc runs cleanup after matching test group is fully executed. 53 | -spec end_per_group(GroupName, Config0) -> ok | {save_config, Config1} 54 | when 55 | GroupName :: string(), 56 | Config0 :: proplist(), 57 | Config1 :: proplist(). 58 | end_per_group(_GroupName, _Config) -> 59 | ok. 60 | 61 | %% @doc runs initialization before matching test case. Should not alter or 62 | %% remove any key/value pairs to the Config, but may add to it. 63 | -spec init_per_testcase(TestCase, Config0) -> 64 | when 65 | TestCase :: atom(), 66 | Config0 :: proplist(), 67 | Config1 :: proplist(), 68 | Reason :: term(). 69 | init_per_testcase(_TestCase, Config) -> 70 | Config. 71 | 72 | %% @doc runs cleanup for matching test case. 73 | -spec end_per_testcase(TestCase, Config0) -> 74 | ok | {save_config, Config1} | {fail, Reason} 75 | when 76 | TestCase :: atom(), 77 | Config0 :: proplist(), 78 | Config1 :: proplist(), 79 | Reason :: term(). 80 | end_per_testcase(_TestCase, _Config) -> 81 | ok. 82 | 83 | %% @doc returns list of test case group definitions. 84 | -spec groups() -> [Group] 85 | when 86 | Group :: {GroupName, Properties, Members}, 87 | GroupName :: atom(), 88 | Properties :: [parallel | sequence | Shuffle | {RepeatType, N}], 89 | Members :: [Group | {group, GroupName} | TestCase], 90 | TestCase :: atom(), 91 | Shuffle :: shuffle | {shuffle, Seed}, 92 | Seed :: {integer(), integer(), integer()}, 93 | RepeatType :: repeat | repeat_until_all_ok | repeat_until_all_fail | 94 | repeat_until_any_ok | repeat_until_any_fail 95 | N :: integer() | forever. 96 | groups() -> 97 | []. 98 | 99 | %% @doc returns list of tests (group or testcase) to be run for all of suite. 100 | -spec all() -> Tests | {skip, Reason} 101 | when 102 | Test :: {group, GroupName} | TestCase, 103 | Tests :: [Test], 104 | GroupName :: atom(), 105 | TestCase :: atom(), 106 | Reason :: term(). 107 | all() -> 108 | [my_test_case]. 109 | 110 | %%%. 111 | %%%' TESTCASES 112 | 113 | %% @doc returns list of tuples to set info properties for the test case. 114 | -spec my_test_case() -> Info 115 | when 116 | Info :: proplist(). 117 | my_test_case() -> 118 | []. 119 | 120 | %% @doc the test function. 121 | @spec TestCase(Config0) -> 122 | ok | exit() | {skip, Reason} | {comment, Comment} | 123 | {save_config, Config} | {skip_and_save, Reason, Config1} 124 | when 125 | Config0 :: Config1 :: proplist(), 126 | Reason :: term(), 127 | Comment :: term() 128 | my_test_case(_Config) -> 129 | ok. 130 | %%%. 131 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 132 | -------------------------------------------------------------------------------- /ctsuite.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "suitename"} 3 | ]}. 4 | {dir, "test"}. 5 | {template, "ctsuite.erl", "test/{{name}}_SUITE.erl"}. 6 | -------------------------------------------------------------------------------- /ejabberdmod.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %% @author {{author_name}} <{{author_email}}> 3 | %% @copyright {{copyright_year}} {{author_name}} 4 | %% @doc ejabberd module that ... listens to packets sent & received by users. 5 | %% @end 6 | 7 | -module(mod_{{name}}). 8 | -author('{{author_name}} <{{author_email}}>'). 9 | 10 | -behaviour(gen_mod). 11 | 12 | -export([start/2, init/2, stop/1, 13 | send_packet/3, receive_packet/4]). 14 | 15 | -include_lib("ejabberd/include/ejabberd.hrl"). 16 | -include("ejabberd/include/jlib.hrl"). 17 | 18 | -define(PROCNAME, ejabberd_{{name}}). 19 | 20 | -ifdef(TEST). 21 | -compile(export_all). 22 | -endif. 23 | 24 | %%%. 25 | %%%' CALLBACKS 26 | 27 | start(Host, Opts) -> 28 | Opt1 = gen_mod:get_opt(opt1, Opts, "default value"), 29 | % capture packets sent by user 30 | ejabberd_hooks:add(user_send_packet, Host, ?MODULE, send_packet, 90), 31 | % capture packets received by user 32 | ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 90), 33 | % register the module process for Host and spawn. 34 | register(gen_mod:get_module_proc(Host, ?PROCNAME), 35 | spawn(?MODULE, init, [Host, Opt1])). 36 | 37 | stop(Host) -> 38 | % remove hook for packets sent by user 39 | ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, send_packet, 90), 40 | % remove hook for packets received by user 41 | ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 90), 42 | % send stop message to process 43 | Proc = gen_mod:get_module_proc(Host, ?PROCNAME), 44 | Proc ! stop, 45 | {wait, Proc}. 46 | 47 | init(Host, Opt1) -> 48 | % do something here instead of nothing 49 | loop(Host, Opt1). 50 | 51 | loop(Host, Opt1) -> 52 | receive 53 | {persist, E} -> 54 | persist(E), 55 | loop(Host, Opt1); 56 | stop -> 57 | ok; 58 | _ -> 59 | loop(Host, Opt1) 60 | end. 61 | 62 | %%%. 63 | %%%' PUBLIC API 64 | 65 | %% @doc 66 | %% @end 67 | send_packet(FromJID, ToJID, P) -> 68 | Host = FromJID#jid.lserver, 69 | Proc = gen_mod:get_module_proc(Host, ?PROCNAME), 70 | Proc ! {persist, {send, FromJID, ToJID, P}}. 71 | 72 | %% @doc 73 | %% @end 74 | receive_packet(_JID, From, To, P) -> 75 | Host = To#jid.lserver, 76 | Proc = gen_mod:get_module_proc(Host, ?PROCNAME), 77 | Proc ! {persist, {recv, From, To, P}}. 78 | 79 | %%%. 80 | %%%' PRIVATE FUNCTIONS 81 | 82 | %% @private 83 | persist({recv, From, To, P}) -> 84 | % do something with this data...like persist it somehow 85 | ok; 86 | persist({send, FromJID, ToJID, P}) -> 87 | % do something with this data...like persist it somehow 88 | ok. 89 | %%%. 90 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 91 | -------------------------------------------------------------------------------- /ejabberdmod.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "myejabberdmod"} 3 | ]}. 4 | {template, "ejabberdmod.erl", "src/mod_{{name}}.erl"}. 5 | -------------------------------------------------------------------------------- /escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- mode: erlang -*- 3 | %%%' HEADER 4 | %% @author {{author_name}} <{{author_email}}> 5 | %% @copyright {{copyright_year}} {{author_name}} 6 | %% @doc {{description}} 7 | %% @end 8 | -export([main/1]). 9 | -define(CMD, filename:basename(escript:script_name())). 10 | %%%. 11 | %%%' PUBLIC API 12 | -spec main(Args) -> void() 13 | when 14 | Args :: list(string()). 15 | main(_)-> 16 | usage(). 17 | 18 | %%%. 19 | %%%' PRIVATE FUNCTIONS 20 | %% @private 21 | -spec usage() -> void(). 22 | usage() -> 23 | io:format("Usage: ~s ...~n", [?CMD]), 24 | halt(1). 25 | 26 | %%%. 27 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 28 | -------------------------------------------------------------------------------- /escript.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "escriptname"} 3 | ]}. 4 | {dir, "bin"}. 5 | {template, "escript", "bin/{{name}}"}. 6 | {chmod, 8#00770, "bin/{{name}}"}. 7 | -------------------------------------------------------------------------------- /eunit.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %% @author {{author_name}} <{{author_email}}> 3 | %% @copyright {{copyright_year}} {{author_name}} 4 | %% @doc EUnit test suite module {{name}}. 5 | %% @end 6 | 7 | -module({{name}}_tests). 8 | -author('{{author_name}} <{{author_email}}>'). 9 | 10 | -define(NOTEST, true). 11 | -define(NOASSERT, true). 12 | -include_lib("eunit/include/eunit.hrl"). 13 | 14 | -define(MODNAME, {{name}}). 15 | %%%. 16 | %%%' TEST GENERATOR 17 | %% @spec {{name}}_test_() -> List 18 | %% where 19 | %% List = [term()] 20 | {{name}}_test_() -> 21 | %% add your asserts in the returned list, e.g.: 22 | %% [ 23 | %% ?assert(?MODNAME:double(2) =:= 4), 24 | %% ?assertMatch({ok, Pid}, ?MODNAME:spawn_link()), 25 | %% ?assertEqual("ba", ?MODNAME:reverse("ab")), 26 | %% ?assertError(badarith, ?MODNAME:divide(X, 0)), 27 | %% ?assertExit(normal, ?MODNAME:exit(normal)), 28 | %% ?assertThrow({not_found, _}, ?MODNAME:func(unknown_object)) 29 | %% ] 30 | []. 31 | %%%. 32 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 33 | 34 | -------------------------------------------------------------------------------- /eunit.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "mymodule"} 3 | ]}. 4 | {dir, "tests"}. 5 | {template, "eunit.erl", "tests/{{name}}_tests.erl"}. 6 | -------------------------------------------------------------------------------- /finapp.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "appname"}, 3 | {appdir, "Path to application directory."} 4 | ]}. 5 | {dir, "{{appdir}}/src"}. 6 | {template, "finapp/finapp.app.src", "{{appdir}}/src/{{name}}.app.src"}. 7 | {template, "finapp/finapp_app.erl", "{{appdir}}/src/{{name}}_app.erl"}. 8 | {template, "finapp/finapp_sup.erl", "{{appdir}}/src/{{name}}_sup.erl"}. 9 | -------------------------------------------------------------------------------- /finapp/finapp.app.src: -------------------------------------------------------------------------------- 1 | %-*- mode: erlang -*- 2 | {application, {{name}}, 3 | [ 4 | {description, "{{description}}"}, 5 | {vsn, git}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | stdlib 10 | ]}, 11 | {mod, { {{name}}_app, []}}, 12 | {env, []} 13 | ]}. 14 | % vim: set filetype=erlang tabstop=2: 15 | -------------------------------------------------------------------------------- /finapp/finapp_app.erl: -------------------------------------------------------------------------------- 1 | %-*- mode: erlang -*- 2 | %%%' HEADER 3 | %%% @author {{author_name}} <{{author_email}}> 4 | %%% @copyright {{copyright_year}} {{author_name}} 5 | %%% @doc {{description}} 6 | %%% @end 7 | 8 | -module({{name}}_app). 9 | -behaviour(application). 10 | 11 | -export([start/2, stop/1]). 12 | %%%. 13 | %%%' CALLBACKS 14 | start(_StartType, _StartArgs) -> 15 | {{name}}_sup:start_link(). 16 | 17 | stop(_State) -> 18 | ok. 19 | %%%. 20 | %%%' PRIVATE FUNCTIONS 21 | 22 | %%%. 23 | % vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 24 | -------------------------------------------------------------------------------- /finapp/finapp_sup.erl: -------------------------------------------------------------------------------- 1 | %-*- mode: erlang -*- 2 | %%%' HEADER 3 | %%% @author {{author_name}} <{{author_email}}> 4 | %%% @copyright {{copyright_year}} {{author_name}} 5 | %%% @doc {{description}} 6 | %%% @end 7 | -module({{name}}_sup). 8 | 9 | -behaviour(supervisor). 10 | -export([start_link/0]). 11 | -export([init/1]). 12 | 13 | -define(SERVER, ?MODULE). 14 | %%%. 15 | %%%' PUBLIC API 16 | start_link() -> 17 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 18 | 19 | %%%. 20 | %%%' CALLBACKS 21 | init([]) -> 22 | {ok, { {one_for_one, 5, 10}, []} }. 23 | 24 | %%%. 25 | % vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 26 | -------------------------------------------------------------------------------- /finevt.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @copyright {{copyright_year}} {{author_name}} 4 | %%% @doc gen_event that {{description}} 5 | %%% @end 6 | -module({{name}}). 7 | -author('{{author_name}} <{{author_email}}>'). 8 | 9 | -behaviour(gen_event). 10 | 11 | -export([init/1, handle_event/2, terminate/2]). 12 | 13 | -ifdef(TEST). 14 | -compile(export_all). 15 | -endif. 16 | %%%. 17 | %%%' TYPE DEFINITIONS 18 | %% TODO: Add type definitions (need to export them if they should be public). 19 | -type arg() :: term(). 20 | -type event() :: term(). 21 | -type state() :: term(). 22 | -type handler() :: module() | {module(), id()}. 23 | -type id() :: term(). 24 | 25 | %%%. 26 | %%%' PUBLIC API 27 | %% TODO: Add public API methods here... 28 | 29 | %%%. 30 | %%%' CALLBACKS 31 | 32 | -spec init([arg()]) -> {ok, [arg()]}. 33 | %% @private 34 | %% @doc initializes gen_event 35 | init([]) -> 36 | {ok, []}. 37 | 38 | -spec handle_event(event(), state()) -> 39 | {ok, state()} | {ok, state(), hibernate} | 40 | {swap_handler, arg(), state(), handler(), arg()} | remove_handler. 41 | %% @private 42 | %% @doc handle/log event 43 | handle_event(_Message, State) -> 44 | {ok, State}. 45 | 46 | -type terminate_args() :: term() | {stop, term()} | stop | remove_handler | 47 | {error, {'EXIT', term()}} | {error, term()}. 48 | -spec terminate(terminate_args(), state()) -> ok. 49 | %% terminates gen_event 50 | terminate(_Args, _State) -> 51 | ok. 52 | 53 | %%%. 54 | %%%' PRIVATE FUNCTIONS 55 | %% TODO: Add private functions here... 56 | 57 | %%%. 58 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 59 | -------------------------------------------------------------------------------- /finevt.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "mylogger"} 3 | ]}. 4 | {template, "finevt.erl", "src/{{name}}.erl"}. 5 | -------------------------------------------------------------------------------- /finsrv.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @copyright {{copyright_year}} {{author_name}} 4 | %%% @doc gen_server callback module implementation: 5 | %%% {{description}} 6 | %%% @end 7 | -module({{name}}_srv). 8 | -author('{{author_name}} <{{author_email}}>'). 9 | 10 | -behaviour(gen_server). 11 | 12 | -export([start_link/0]). 13 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). 14 | -export([code_change/3]). 15 | -export([stop/0, terminate/2]). 16 | 17 | % TODO: If unnamed server, remove definition below. 18 | -define(SERVER, ?MODULE). 19 | %%%. 20 | %%%' TYPE DEFINITIONS 21 | -type start_link_error() :: {already_started, pid()} | term(). 22 | 23 | %%%. 24 | %%%' PUBLIC API 25 | 26 | %% @doc starts gen_server implementation and caller links to the process too. 27 | -spec start_link() -> {ok, pid()} | ignore | {error, start_link_error()}. 28 | start_link() -> 29 | % TODO: decide whether to name gen_server callback implementation or not. 30 | % gen_server:start_link(?MODULE, [], []). % for unnamed gen_server 31 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 32 | 33 | %% @doc stops gen_server implementation process 34 | -spec stop() -> ok. 35 | stop() -> 36 | gen_server:cast(?SERVER, stop). 37 | 38 | % TODO: add more public API here... 39 | 40 | %%%. 41 | %%%' CALLBACKS 42 | %% @callback gen_server 43 | init(State) -> 44 | {ok, State}. 45 | 46 | %% @callback gen_server 47 | handle_call(_Req, _From, State) -> 48 | {reply, State}. 49 | 50 | %% @callback gen_server 51 | handle_cast(stop, State) -> 52 | {stop, normal, State}; 53 | handle_cast(_Req, State) -> 54 | {noreply, State}. 55 | 56 | %% @callback gen_server 57 | handle_info(_Info, State) -> 58 | {noreply, State}. 59 | 60 | %% @callback gen_server 61 | code_change(_OldVsn, State, _Extra) -> 62 | {ok, State}. 63 | 64 | %% @callback gen_server 65 | terminate(normal, _State) -> 66 | ok; 67 | terminate(shutdown, _State) -> 68 | ok; 69 | terminate({shutdown, _Reason}, _State) -> 70 | ok; 71 | terminate(_Reason, _State) -> 72 | ok. 73 | 74 | %%%. 75 | %%%' PRIVATE FUNCTIONS 76 | % TODO: Add private helper functions here. 77 | 78 | %%%. 79 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 80 | -------------------------------------------------------------------------------- /finsrv.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "myserver"} 3 | ]}. 4 | {template, "finsrv.erl", "src/{{name}}_srv.erl"}. 5 | -------------------------------------------------------------------------------- /finsrv_proper_statem.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @copyright {{copyright_year}} {{author_name}} 4 | %%% @doc gen_server PropEr Abstract State Machine. 5 | %%% @end 6 | -module({{name}}_srv_asm). 7 | 8 | -behaviour(proper_statem). 9 | 10 | -include_lib("proper/include/proper.hrl"). 11 | 12 | -export([test/0, sample/0]). 13 | -export([initial_state/0, command/1, 14 | precondition/2, postcondition/3, 15 | next_state/3]). 16 | 17 | -define(SERVER, {{name}}_srv). 18 | 19 | %%%. 20 | %%%' STATEM CALLBACKS 21 | test() -> 22 | proper:quickcheck(?MODULE:prop_{{name}}()). 23 | 24 | sample() -> 25 | proper_gen:pick(commands(?MODULE)). 26 | 27 | prop_{{name}}() -> 28 | ?FORALL(Cmds, commands(?MODULE), 29 | ?TRAPEXIT( 30 | begin 31 | ?SERVER:start_link(), 32 | {History, State, Result} = run_commands(?MODULE, Cmds), 33 | ?SERVER:stop(), 34 | ?WHENFAIL( 35 | io:format("History: ~w\nState: ~w\nResult: ~w\n", 36 | [History, State, Result]), 37 | aggregate(command_names(Cmds), Result =:= ok)) 38 | end 39 | )). 40 | 41 | initial_state() -> 42 | % TODO: specify initial state 43 | []. 44 | 45 | command(State) -> 46 | % TODO: specify list of commands with appropriate frequency 47 | % weights to generate 48 | frequency([]). 49 | 50 | % TODO: flesh out precondition function clauses 51 | precondition(State, Request) -> 52 | true. 53 | 54 | % TODO: flesh out next_state logic clauses 55 | next_state(State, Value, Request) -> 56 | State. 57 | 58 | % TODO: flesh out postcondition function clauses 59 | postcondition(State, Request) -> 60 | true. 61 | 62 | %%%. 63 | %%%' PRIVATE FUNCTIONS 64 | 65 | %% @private 66 | matches(X, X) -> 1; 67 | matches(_, _) -> 0. 68 | %%%. 69 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 70 | -------------------------------------------------------------------------------- /finsrv_proper_statem.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "myserver"} 3 | ]}. 4 | {template, "finsrv_proper_statem.erl", "test/{{name}}_srv_asm.erl"}. 5 | -------------------------------------------------------------------------------- /finsup.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @since {{date}} 4 | %%% @copyright {{copyright_year}} {{author_name}} 5 | %%% @doc {{description}} 6 | %%% @end 7 | -module({{name}}_sup). 8 | 9 | -behaviour(supervisor). 10 | -export([start_link/0]). 11 | -export([init/1]). 12 | 13 | -define(SERVER, ?MODULE). 14 | %%%. 15 | %%%' PUBLIC API 16 | start_link() -> 17 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 18 | 19 | %%%. 20 | %%%' CALLBACKS 21 | init([]) -> 22 | {ok, { {one_for_one, 5, 10}, []} }. 23 | 24 | %%%. 25 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 26 | -------------------------------------------------------------------------------- /finsup.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "supname"}, 3 | {appdir, ".", "Path of the application directory."} 4 | ]}. 5 | {dir, "{{appdir}}/src"}. 6 | {template, "finsup.erl", "{{appdir}}/src/{{name}}_sup.erl"}. 7 | -------------------------------------------------------------------------------- /globals: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {copyright_year, "2016"}, 3 | {author_name, "Susan Potter"}, 4 | {author_email, "me@susanpotter.net"} 5 | ]}. 6 | -------------------------------------------------------------------------------- /project.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "myproject"}, 3 | {description, "Fill in description of project here."} 4 | ]}. 5 | {dir, "deps"}. 6 | {dir, "src"}. 7 | {dir, "ebin"}. 8 | {dir, "include"}. 9 | {dir, "test"}. 10 | {dir, "priv"}. 11 | {template, "project_README", "README"}. 12 | {file, "project_rebar.config", "rebar.config"}. 13 | {file, "project_Emakefile", "Emakefile"}. 14 | {file, "project_Makefile", "Makefile"}. 15 | {file, "project_gitignore", ".gitignore"}. 16 | -------------------------------------------------------------------------------- /project_Emakefile: -------------------------------------------------------------------------------- 1 | % -*- mode: erlang -*- 2 | {[ "*", 3 | "src/*", 4 | "src/*/*", 5 | "src/*/*/*", 6 | "src/*/*/*/*", 7 | "src/*/*/*/*/*" 8 | ], 9 | [ 10 | {i, "include"}, 11 | {outdir, "ebin"}, 12 | debug_info 13 | ] 14 | }. 15 | -------------------------------------------------------------------------------- /project_Makefile: -------------------------------------------------------------------------------- 1 | ERL ?= erl 2 | ERLC = erlc 3 | EBIN_DIRS := $(wildcard deps/*/ebin) 4 | APPS := $(shell dir apps) 5 | REL_DIR = rel 6 | NODE = {{name}} 7 | REL = {{name}} 8 | SCRIPT_PATH := $(REL_DIR)/$(NODE)/bin/$(REL) 9 | 10 | .PHONY: rel deps 11 | 12 | all: deps compile 13 | 14 | compile: deps 15 | @rebar compile 16 | 17 | deps: 18 | @rebar get-deps 19 | @rebar check-deps 20 | 21 | clean: 22 | @rebar clean 23 | 24 | realclean: clean 25 | @rebar delete-deps 26 | 27 | test: 28 | @rebar skip_deps=true ct 29 | 30 | rel: deps 31 | @rebar compile generate 32 | 33 | start: $(SCRIPT_PATH) 34 | @./$(SCRIPT_PATH) start 35 | 36 | stop: $(SCRIPT_PATH) 37 | @./$(SCRIPT_PATH) stop 38 | 39 | ping: $(SCRIPT_PATH) 40 | @./$(SCRIPT_PATH) ping 41 | 42 | attach: $(SCRIPT_PATH) 43 | @./$(SCRIPT_PATH) attach 44 | 45 | console: $(SCRIPT_PATH) 46 | @./$(SCRIPT_PATH) console 47 | 48 | restart: $(SCRIPT_PATH) 49 | @./$(SCRIPT_PATH) restart 50 | 51 | reboot: $(SCRIPT_PATH) 52 | @./$(SCRIPT_PATH) reboot 53 | 54 | doc: 55 | rebar skip_deps=true doc 56 | for app in $(APPS); do \ 57 | cp -R apps/$${app}/doc doc/$${app}; \ 58 | done; 59 | 60 | dev: 61 | @erl -pa ebin include deps/*/ebin deps/*/include ebin include -boot start_sasl 62 | 63 | analyze: checkplt 64 | @rebar skip_deps=true dialyze 65 | 66 | buildplt: 67 | @rebar skip_deps=true build-plt 68 | 69 | checkplt: buildplt 70 | @rebar skip_deps=true check-plt 71 | -------------------------------------------------------------------------------- /project_README: -------------------------------------------------------------------------------- 1 | h1. {{name}} 2 | 3 | {{description}} 4 | 5 | h2. Author(s) 6 | 7 | * {{author_name}} <{{author_email}}> 8 | 9 | h2. Copyright 10 | 11 | Copyright (c) {{copyright_year}} {{author_name}} <{{author_email}}>. All rights reserved. 12 | -------------------------------------------------------------------------------- /project_gitignore: -------------------------------------------------------------------------------- 1 | ebin/* 2 | *.beam 3 | deps/* 4 | 5 | -------------------------------------------------------------------------------- /project_rebar.config: -------------------------------------------------------------------------------- 1 | {lib_dirs, []}. 2 | {erl_first_files, []}. 3 | {erl_opts, [{i, "include"}, {src_dirs, ["src"]}]}. 4 | {erlydtl_opts, []}. 5 | {cover_enabled, true}. 6 | {clean_files, ["ebin/*.beam", "priv/log/*", "rel/*"]}. 7 | {target, "rel"}. 8 | {app_bin, []}. 9 | {deps_dir, ["deps"]}. 10 | {deps, [ 11 | {edown, ".*", {git, "git://github.com/esl/edown.git", "HEAD"}} 12 | ]}. 13 | {edoc_opts, [{doclet, edown_doclet}]}. 14 | {sub_dirs, []}. 15 | -------------------------------------------------------------------------------- /rebar_compiler.erl: -------------------------------------------------------------------------------- 1 | %%%---------------------------------------------------------------------- 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @copyright {{copyright_year}} {{author_name}} 4 | %%% @doc rebar compiler to {{description}} 5 | %%% @end 6 | %%%---------------------------------------------------------------------- 7 | -module({{name}}). 8 | -author('{{author_name}} <{{author_email}}>'). 9 | 10 | -export([compile/2]). 11 | 12 | -ifdef(TEST). 13 | -compile(export_all). 14 | -endif. 15 | 16 | -include_lib("rebar/include/rebar.hrl"). 17 | 18 | %% =================================================================== 19 | %% Public API 20 | %% =================================================================== 21 | 22 | %% @spec compile(Config, AppFile) -> ok | skipped 23 | %% @doc Compiles files compiler is *interested* in. 24 | compile(Config, _AppFile) -> 25 | ok 26 | 27 | %% =================================================================== 28 | %% Internal functions 29 | %% =================================================================== 30 | 31 | %% @private 32 | %% @doc returns default value for matching compiler option 33 | %% @end 34 | %% TODO: Add clauses to default/1 below for each option with a default. 35 | default(CompilerOpt) -> "". 36 | 37 | %% TODO: Add other internal functions below here... 38 | 39 | -------------------------------------------------------------------------------- /rebar_compiler.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "compiler_name"} 3 | ]}. 4 | {dir, "src"}. 5 | {template, "rebar_compiler.erl", "src/{{name}}.erl"}. 6 | {dir, "test"}. 7 | {template, "rebar_compiler_SUITE.erl", "test/{{name}}_SUITE.erl"}. 8 | -------------------------------------------------------------------------------- /rebar_compiler_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @author {{author_name}} <{{author_email}}> 2 | %%% @copyright {{copyright_year}} {{author_name}} 3 | %%% @doc Test suite for {{name}} module which {{description}} 4 | %%% @end 5 | 6 | -module({{name}}_SUITE). 7 | %% Note: This directive should only be used in test suites. 8 | -compile(export_all). 9 | -include_lib("common_test/include/ct.hrl"). 10 | 11 | %%-------------------------------------------------------------------- 12 | %% COMMON TEST CALLBACK FUNCTIONS 13 | %%-------------------------------------------------------------------- 14 | 15 | %% @spec suite() -> Config 16 | %% where 17 | %% Config = [tuple()] 18 | %% @doc returns list of tuples to set default properties for the suite. 19 | suite() -> 20 | [{timetrap,{minutes,10}}]. 21 | 22 | %% @spec init_per_suite(Config0) -> 23 | %% Config1 | {skip, Reason} | {skip_and_save, Reason, Config1} 24 | %% where 25 | %% Config0 = Config1 = [tuple()], 26 | %% Reason = term() 27 | %% @doc runs initialization before matching test suite is executed. 28 | %% This function may add key/value pairs to Config. 29 | init_per_suite(Config) -> 30 | Config. 31 | 32 | %% @spec end_per_suite(Config0) -> ok | {save_config, Config1} 33 | %% where 34 | %% Config0 = Config1 = [tuple()] 35 | %% @doc runs cleanup after matching test suite is executed. 36 | end_per_suite(_Config) -> 37 | ok. 38 | 39 | %% @spec init_per_group(GroupName, Config0) -> 40 | %% Config1 | {skip, Reason} | {skip_and_save, Reason, Config1} 41 | %% where 42 | %% GroupName = atom(), 43 | %% Config0 = Config1 = [tuple()], 44 | %% Reason = term() 45 | %% @doc runs initialization before matching test group is executed. 46 | init_per_group(_GroupName, Config) -> 47 | Config. 48 | 49 | %% @spec end_per_group(GroupName, Config0) -> ok | {save_config, Config1} 50 | %% where 51 | %% GroupName = atom(), 52 | %% Config0 = Config1 = [tuple()] 53 | %% @doc runs cleanup after matching test group is fully executed. 54 | end_per_group(_GroupName, _Config) -> 55 | ok. 56 | 57 | %% @spec init_per_testcase(TestCase, Config0) -> 58 | %% Config1 | {skip, Reason} | {skip_and_save, Reason, Config1} 59 | %% where 60 | %% TestCase = atom(), 61 | %% Config0 = Config1 = [tuple], 62 | %% Reason = term() 63 | %% @doc runs initialization before matching test case. Should not alter or 64 | %% remove any key/value pairs to the Config, but may add to it. 65 | init_per_testcase(_TestCase, Config) -> 66 | Config. 67 | 68 | %% @spec end_per_testcase(TestCase, Config0) -> 69 | %% ok | {save_config, Config1} | {fail, Reason} 70 | %% where 71 | %% TestCase = atom(), 72 | %% Config0 = Config1 = [tuple()], 73 | %% Reason = term() 74 | %% @doc runs cleanup for matching test case. 75 | end_per_testcase(_TestCase, _Config) -> 76 | ok. 77 | 78 | %% @spec groups() -> [Group] 79 | %% where 80 | %% Group = {GroupName, Properties, Members}, 81 | %% GroupName = atom(), 82 | %% Properties = [parallel | sequence | Shuffle | {RepeatType, N}], 83 | %% Members = [Group | {group, GroupName} | TestCase], 84 | %% TestCase = atom(), 85 | %% Shuffle = shuffle | {shuffle, Seed}, 86 | %% Seed = {integer(), integer(), integer()}, 87 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 88 | %% repeat_until_any_ok | repeat_until_any_fail 89 | %% N = integer() | forever 90 | %% @doc returns list of test case group definitions. 91 | groups() -> 92 | []. 93 | 94 | %% @spec all() -> Tests | {skip, Reason} 95 | %% where 96 | %% Tests = [{group, GroupName} | TestCase], 97 | %% GroupName = atom(), 98 | %% TestCase = atom(), 99 | %% Reason = term() 100 | %% @doc returns list of tests (group or testcase) to be run for all of suite. 101 | all() -> 102 | [test_compile_2_ok]. 103 | 104 | %%-------------------------------------------------------------------- 105 | %% TEST CASES 106 | %%-------------------------------------------------------------------- 107 | 108 | %% @spec TestCase() -> Info 109 | %% where 110 | %% Info = [tuple()] 111 | %% @doc returns list of tuples to set properties for the test case. 112 | test_compile_2_ok() -> 113 | %% TODO: modify proplist below for success case. 114 | [{compiler_config, []}, {compiler_appfile, ""}]. 115 | 116 | %% @spec TestCase(Config0) -> 117 | %% ok | exit() | {skip, Reason} | {comment, Comment} | 118 | %% {save_config, Config} | {skip_and_save, Reason, Config1} 119 | %% where 120 | %% Config0 = Config1 = [tuple()], 121 | %% Reason = term(), 122 | %% Comment = term() 123 | %% @doc runs the test case function. Verifies the cases where 124 | %% {{name}}_compiler:compile/2 should return ok. 125 | test_compile_2_ok(Config) -> 126 | CompilerConfig = proplists:get_value(compiler_config, Config), 127 | CompilerAppfile = proplists:get_value(compiler_appfile, Config), 128 | ok = {{name}}:compile(CompilerConfig, CompilerAppfile), 129 | ok. 130 | 131 | %% @spec TestCase() -> Info 132 | %% where 133 | %% Info = [tuple()] 134 | %% @doc returns list of tuples to set properties for the test case. 135 | test_compile_2_skipped() -> 136 | %% TODO: modify proplist below for skipped case. 137 | [{compiler_config, []}, {compiler_appfile, ""}]. 138 | 139 | %% @spec TestCase(Config0) -> 140 | %% ok | exit() | {skip, Reason} | {comment, Comment} | 141 | %% {save_config, Config} | {skip_and_save, Reason, Config1} 142 | %% where 143 | %% Config0 = Config1 = [tuple()], 144 | %% Reason = term(), 145 | %% Comment = term() 146 | %% @doc runs the test case function. Verifies the cases where 147 | %% {{name}}_compiler:compile/2 should return ok. 148 | test_compile_2_skipped(Config) -> 149 | CompilerConfig = proplists:get_value(compiler_config, Config), 150 | CompilerAppfile = proplists:get_value(compiler_appfile, Config), 151 | skipped = {{name}}:compile(CompilerConfig, CompilerAppfile), 152 | ok. 153 | 154 | -------------------------------------------------------------------------------- /riak_pipe_vnode_worker.erl: -------------------------------------------------------------------------------- 1 | %%% -*- mode: erlang -*- 2 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 3 | %%%' HEADER 4 | %%% @copyright {{copyright_year}} {{author_name}} <{{author_email}}> 5 | %%% @license {{license}} 6 | %%% @doc 7 | %%% {{description}} 8 | %%% @end 9 | -module({{name}}_vnode_worker). 10 | 11 | -behavior(riak_pipe_vnode_worker). 12 | 13 | -export([ 14 | init/2, 15 | process/3, 16 | done/1 17 | ]). 18 | 19 | -include("riak_pipe.hrl"). 20 | -include("riak_pipe_log.hrl"). 21 | 22 | -record(state, 23 | { partition :: riak_pipe_vnode:partition() 24 | , fitting_details :: riak_pipe_fitting:details() 25 | } 26 | ). 27 | -opaque state() :: #state{}. 28 | 29 | % TODO: Add macros or records here if pertinent 30 | 31 | 32 | %%%. 33 | %%%' PUBLIC API 34 | 35 | %% @doc Initialization stores the partition and fitting details 36 | %% in the module's state record so it can send outputs from 37 | %% {@link process/3}. 38 | -spec init(Partition, FittingDetails) -> {ok, state()} 39 | when 40 | Partition :: riak_pipe_vnode:partition(), 41 | FittingDetails :: riak_pipe_vnode:details(). 42 | init(_Partition, _FittingDetails) -> 43 | % TODO: Implement 44 | not_implemented. 45 | 46 | %% @doc 47 | -spec process(Input, Last, State) -> {ok, NewState} 48 | when 49 | Input :: any(), 50 | Last :: any(), 51 | State :: state(), 52 | NewState :: state(). 53 | process(_Input, _Last, _State) -> 54 | % TODO: Implement 55 | not_implemented. 56 | 57 | -spec done(State :: state()) -> ok. 58 | done(_State) -> 59 | % TODO: Implement 60 | not_implemented. 61 | 62 | %%%. 63 | %%%' PRIVATE FUNCTIONS 64 | % TODO: Add private helper functions here. 65 | 66 | %%%. 67 | -------------------------------------------------------------------------------- /riak_pipe_vnode_worker.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "mymodule"}, 3 | {description, "A riak_pipe vnode worker callback module."} 4 | ]}. 5 | {template, "riak_pipe_vnode_worker.erl", "{{name}}_vnode_worker.erl"}. 6 | -------------------------------------------------------------------------------- /webmachine_resource.erl: -------------------------------------------------------------------------------- 1 | %%%' HEADER 2 | %%% @author {{author_name}} <{{author_email}}> 3 | %%% @copyright {{copyright_year}} {{author_name}}. 4 | %%% @doc {{description}} 5 | 6 | -module({{app_name}}_{{name}}_resource). 7 | -author('{{author_name}} <{{author_email}}>'). 8 | 9 | % Webmachine callback exports 10 | -export([init/1, resource_exists/2, is_authorized/2, forbidden/2]). 11 | -export([malformed_request/2, allowed_methods/2, content_types_provided/2]). 12 | -export([content_types_accepted/2, charsets_provided/2, encodings_provided/2]). 13 | -export([previously_existed/2, moved_permanently/2, moved_temporarily/2]). 14 | -export([last_modified/2, expires/2, generate_etag/2, finish_request/2]). 15 | -export([service_available/2]). 16 | % to_X callback exports 17 | -export([to_json/2, to_plain_text/2, to_html/2]). 18 | 19 | -include_lib("webmachine/include/webmachine.hrl"). 20 | 21 | -define(DEFAULT_ENCODINGS, [{"identity", fun (X) -> X end}]). 22 | -define(DEFAULT_CONTENT_TYPES, [ 23 | {"application/json", to_json}, 24 | {"text/plain", to_plain_text}, 25 | {"text/html", to_html} 26 | ]). 27 | 28 | -ifdef(TEST). 29 | -compile(export_all). 30 | -endif. 31 | %%%. 32 | %%%' WEBMACHINE CALLBACKS 33 | -spec init(InitArgs) -> {ok, State} | {error, Reason} 34 | when 35 | InitArgs :: list(), 36 | State :: any(), 37 | Reason :: term(). 38 | init([]) -> {ok, undefined}. 39 | 40 | -spec resource_exists(ReqData, State) -> 41 | {Result, NewReqData, NewState} 42 | when 43 | ReqData :: #wm_reqdata{}, 44 | State :: any(), 45 | Result :: boolean() | {error, term()} | {halt, integer()}, 46 | NewReqData :: #wm_reqdata{}, 47 | NewState :: any(). 48 | resource_exists(ReqData, State) -> 49 | {true, ReqData, State}. 50 | 51 | -spec service_available(ReqData, State) -> 52 | {Result, NewReqData, NewState} 53 | when 54 | ReqData :: #wm_reqdata{}, 55 | State :: any(), 56 | Result :: boolean() | {error, term()} | {halt, integer()}, 57 | NewReqData :: #wm_reqdata{}, 58 | NewState :: any(). 59 | service_available(ReqData, State) -> 60 | {true, ReqData, State}. 61 | 62 | -spec is_authorized(ReqData, State) -> 63 | {Result, NewReqData, NewState} 64 | when 65 | ReqData :: #wm_reqdata{}, 66 | State :: any(), 67 | Result :: boolean() | {error, term()} | {halt, integer()}, 68 | NewReqData :: #wm_reqdata{}, 69 | NewState :: any(). 70 | is_authorized(ReqData, State) -> 71 | {true, ReqData, State}. 72 | 73 | -spec forbidden(ReqData, State) -> 74 | {Result, NewReqData, NewState} 75 | when 76 | ReqData :: #wm_reqdata{}, 77 | State :: any(), 78 | Result :: boolean() | {error, term()} | {halt, integer()}, 79 | NewReqData :: #wm_reqdata{}, 80 | NewState :: any(). 81 | forbidden(ReqData, State) -> 82 | {false, ReqData, State}. 83 | 84 | -spec malformed_request(ReqData, State) -> 85 | {Result, NewReqData, NewState} 86 | when 87 | ReqData :: #wm_reqdata{}, 88 | State :: any(), 89 | Result :: boolean() | {error, term()} | {halt, integer()}, 90 | NewReqData :: #wm_reqdata{}, 91 | NewState :: any(). 92 | malformed_request(ReqData, State) -> 93 | {false, ReqData, State}. 94 | 95 | -spec allowed_methods(ReqData, State) -> 96 | {Result, NewReqData, NewState} 97 | when 98 | ReqData :: #wm_reqdata{}, 99 | State :: any(), 100 | Result :: list(atom()) | {error, term()} | {halt, integer()}, 101 | NewReqData :: #wm_reqdata{}, 102 | NewState :: any(). 103 | allowed_methods(ReqData, State) -> 104 | % TODO: Determine what methods are relevant to incoming request. 105 | Methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], 106 | % NOTE By default will return empty list to force developer to think about this!:) 107 | {[], ReqData, State}. 108 | 109 | -spec content_types_provided(ReqData, State) -> 110 | {Result, NewReqData, NewState} 111 | when 112 | ReqData :: #wm_reqdata{}, 113 | State :: any(), 114 | Result :: [{MimeType :: string(), MimeTypeConverter :: function()}] | 115 | [] | {error, term()} | {halt, integer()}, 116 | NewReqData :: #wm_reqdata{}, 117 | NewState :: any(). 118 | content_types_provided(ReqData, State) -> 119 | ContentTypes = ?DEFAULT_CONTENT_TYPES, 120 | {ContentTypes, ReqData, State}. 121 | 122 | -spec content_types_accepted(ReqData, State) -> 123 | {Result, NewReqData, NewState} 124 | when 125 | ReqData :: #wm_reqdata{}, 126 | State :: any(), 127 | Result :: [{MimeType :: string(), MimeTypeConverter :: function()}] | 128 | [] | {error, term()} | {halt, integer()}, 129 | NewReqData :: #wm_reqdata{}, 130 | NewState :: any(). 131 | content_types_accepted(ReqData, State) -> 132 | ContentTypes = ?DEFAULT_CONTENT_TYPES, 133 | {ContentTypes, ReqData, State}. 134 | 135 | -spec charsets_provided(ReqData, State) -> 136 | {Result, NewReqData, NewState} 137 | when 138 | ReqData :: #wm_reqdata{}, 139 | State :: any(), 140 | Result :: [{Charset :: string(), CharsetConverter :: function()}] | 141 | no_charset | {error, term()} | {halt, integer()}, 142 | NewReqData :: #wm_reqdata{}, 143 | NewState :: any(). 144 | charsets_provided(ReqData, State) -> 145 | Charsets = [ 146 | ], 147 | {Charsets, ReqData, State}. 148 | 149 | -spec encodings_provided(ReqData, State) -> 150 | {Result, NewReqData, NewState} 151 | when 152 | ReqData :: #wm_reqdata{}, 153 | State :: any(), 154 | Result :: [{Charset :: string(), CharsetConverter :: function()}] | 155 | no_charset | {error, term()} | {halt, integer()}, 156 | NewReqData :: #wm_reqdata{}, 157 | NewState :: any(). 158 | encodings_provided(ReqData, State) -> 159 | Encodings = case wrq:method(ReqData) of 160 | 'GET' -> 161 | [ {"gzip", fun (X) -> zlib:gzip(X) end} | ?DEFAULT_ENCODINGS ]; 162 | _OtherMethod -> 163 | ?DEFAULT_ENCODINGS 164 | end, 165 | {Encodings, ReqData, State}. 166 | 167 | -spec previously_existed(ReqData, State) -> 168 | {Result, NewReqData, NewState} 169 | when 170 | ReqData :: #wm_reqdata{}, 171 | State :: any(), 172 | Result :: boolean() | {error, term()} | {halt, integer()}, 173 | NewReqData :: #wm_reqdata{}, 174 | NewState :: any(). 175 | previously_existed(ReqData, State) -> 176 | {false, ReqData, State}. 177 | 178 | -spec moved_permanently(ReqData, State) -> 179 | {Result, NewReqData, NewState} 180 | when 181 | ReqData :: #wm_reqdata{}, 182 | State :: any(), 183 | Result :: boolean() | {error, term()} | {halt, integer()}, 184 | NewReqData :: #wm_reqdata{}, 185 | NewState :: any(). 186 | moved_permanently(ReqData, State) -> 187 | {false, ReqData, State}. 188 | 189 | -spec moved_temporarily(ReqData, State) -> 190 | {Result, NewReqData, NewState} 191 | when 192 | ReqData :: #wm_reqdata{}, 193 | State :: any(), 194 | Result :: boolean() | {error, term()} | {halt, integer()}, 195 | NewReqData :: #wm_reqdata{}, 196 | NewState :: any(). 197 | moved_temporarily(ReqData, State) -> 198 | {false, ReqData, State}. 199 | 200 | -spec last_modified(ReqData, State) -> 201 | {Result, NewReqData, NewState} 202 | when 203 | ReqData :: #wm_reqdata{}, 204 | State :: any(), 205 | Year :: integer(4), 206 | Month :: integer(2), 207 | Date :: integer(2), 208 | Hour :: integer(2), 209 | Minute :: integer(2), 210 | Second :: integer(2), 211 | Result :: undefined | { {Year, Month, Date}, {Hour, Minute, Second} } | 212 | {error, term()} | {halt, integer()}, 213 | NewReqData :: #wm_reqdata{}, 214 | NewState :: any(). 215 | last_modified(ReqData, State) -> 216 | % TODO: Figure out way to set this appropriately for resource 217 | {undefined, ReqData, State}. 218 | 219 | -spec expires(ReqData, State) -> 220 | {Result, NewReqData, NewState} 221 | when 222 | ReqData :: #wm_reqdata{}, 223 | State :: any(), 224 | Year :: integer(4), 225 | Month :: integer(2), 226 | Date :: integer(2), 227 | Hour :: integer(2), 228 | Minute :: integer(2), 229 | Second :: integer(2), 230 | Result :: undefined | { {Year, Month, Date}, {Hour, Minute, Second} } | 231 | {error, term()} | {halt, integer()}, 232 | NewReqData :: #wm_reqdata{}, 233 | NewState :: any(). 234 | expires(ReqData, State) -> 235 | % TODO: Figure out way to set this appropriately for resource 236 | {undefined, ReqData, State}. 237 | 238 | -spec generate_etag(ReqData, State) -> 239 | {Result, NewReqData, NewState} 240 | when 241 | ReqData :: #wm_reqdata{}, 242 | State :: any(), 243 | Result :: undefined | string() | {error, term()} | {halt, integer()}, 244 | NewReqData :: #wm_reqdata{}, 245 | NewState :: any(). 246 | generate_etag(ReqData, State) -> 247 | % TODO: Figure out way to set this appropriately for resource 248 | {undefined, ReqData, State}. 249 | 250 | -spec finish_request(ReqData, State) -> 251 | {Result, NewReqData, NewState} 252 | when 253 | ReqData :: #wm_reqdata{}, 254 | State :: any(), 255 | Result :: undefined | string() | {error, term()} | {halt, integer()}, 256 | NewReqData :: #wm_reqdata{}, 257 | NewState :: any(). 258 | finish_request(ReqData, State) -> 259 | % TODO: Modify ReqData object returned to effect processing of HTTP request 260 | % since value returned is actually ignored by Webmachine. 261 | {true, ReqData, State}. 262 | %%%. 263 | %%%' OUTPUT FUNCTIONS 264 | to_html(ReqData, State) -> 265 | {"", ReqData, State}. 266 | 267 | to_plain_text(ReqData, State) -> 268 | {"OK", ReqData, State}. 269 | 270 | to_json(ReqData, State) -> 271 | {"{'status': 200, 'text': 'OK'}", ReqData, State}. 272 | %%%. 273 | %%%' PRIVATE FUNCTIONS 274 | % TODO: Add private functions here... 275 | 276 | %%%. 277 | %%% vim: set filetype=erlang tabstop=2 foldmarker=%%%',%%%. foldmethod=marker: 278 | -------------------------------------------------------------------------------- /webmachine_resource.template: -------------------------------------------------------------------------------- 1 | {variables, [ 2 | {name, "my_resource"}, 3 | {app_name, "wm_app"} 4 | ]}. 5 | {template, "webmachine_resource.erl", "src/{{app_name}}_{{name}}_resource.erl"}. 6 | --------------------------------------------------------------------------------