diff options
Diffstat (limited to 'doc/plugins.md')
-rw-r--r-- | doc/plugins.md | 272 |
1 files changed, 0 insertions, 272 deletions
diff --git a/doc/plugins.md b/doc/plugins.md deleted file mode 100644 index c8afc91..0000000 --- a/doc/plugins.md +++ /dev/null @@ -1,272 +0,0 @@ -# Plugins # - -Rebar3's system is based on the concept of *[providers](https://github.com/tsloughter/providers)*. A provider has three callbacks: - -- `init(State) -> {ok, NewState}`, which helps set up the state required, state dependencies, etc. -- `do(State) -> {ok, NewState} | {error, Error}`, which does the actual work. -- `format_error(Error) -> String`, which allows to print errors when they happen, and to filter out sensitive elements from the state. - -A provider should also be an OTP Library application, which can be fetched as any other Erlang dependency, except for Rebar3 rather than your own system or application. - -This document contains the following elements: - -- [Using a Plugin](#using-a-plugin) -- [Reference](#reference) - - [Provider Interface](#provider-interface) - - [List of Possible Dependencies](#list-of-possible-dependencies) - - [Rebar State Manipulation](#rebar-state-manipulation) -- [Tutorial](#tutorial) - -## Using a Plugin ## - -To use the a plugin, add it to the rebar.config: - -```erlang -{plugins, [ - {plugin_name, ".*", {git, "git@host:user/name-of-plugin.git", {tag, "v1.0.0"}}} -]}. -``` - -Then you can just call it directly: - -``` -→ rebar3 plugin_name -===> Fetching plugin_name -===> Compiling plugin_name -<PLUGIN OUTPUT> -``` - -## Reference ## - -TODO - -### Provider Interface ### - -TODO - -### List of Possible Dependencies ### - -TODO - -### Rebar State Manipulation ### - -TODO - - -## Tutorial ## - -### First version ### - -In this tutorial, we'll show how to start from scratch, and get a basic plugin written. The plugin will be quite simple: it will look for instances of 'TODO:' lines in comments and report them as warnings. The final code for the plugin can be found on [bitbucket](https://bitbucket.org/ferd/rebar3-todo-plugin). - -The first step is to create a new OTP Application that will contain the plugin: - - → rebar3 new plugin provider_todo desc="example rebar3 plugin" - ... - → cd provider_todo - → git init - Initialized empty Git repository in /Users/ferd/code/self/provider_todo/.git/ - -Open up the `src/provider_todo.erl` file and make sure you have the following skeleton in place: - -```erlang --module(provider_todo). --behaviour(provider). - --export([init/1, do/1, format_error/1]). - --define(PROVIDER, todo). --define(DEPS, [app_discovery]). - -%% =================================================================== -%% Public API -%% =================================================================== --spec init(rebar_state:t()) -> {ok, rebar_state:t()}. -init(State) -> - Provider = providers:create([ - {name, ?PROVIDER}, % The 'user friendly' name of the task - {module, ?MODULE}, % The module implementation of the task - {bare, true}, % The task can be run by the user, always true - {deps, ?DEPS}, % The list of dependencies - {example, "rebar provider_todo"}, % How to use the plugin - {opts, []} % list of options understood by the plugin - {short_desc, "example rebar3 plugin"}, - {desc, ""} - ]), - {ok, rebar_state:add_provider(State, Provider)}. - - --spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. -do(State) -> - {ok, State}. - --spec format_error(any()) -> iolist(). -format_error(Reason) -> - io_lib:format("~p", [Reason]). -``` - -This shows all the basic content needed. Note that we leave the `DEPS` macro to the value `app_discovery`, used to mean that the plugin should at least find the project's source code (excluding dependencies). - -In this case, we need to change very little in `init/1`. Here's the new provider description: - -```erlang - Provider = providers:create([ - {name, ?PROVIDER}, % The 'user friendly' name of the task - {module, ?MODULE}, % The module implementation of the task - {bare, true}, % The task can be run by the user, always true - {deps, ?DEPS}, % The list of dependencies - {example, "rebar todo"}, % How to use the plugin - {opts, []}, % list of options understood by the plugin - {short_desc, "Reports TODOs in source code"}, - {desc, "Scans top-level application source and find " - "instances of TODO: in commented out content " - "to report it to the user."} - ]), -``` - -Instead, most of the work will need to be done directly in `do/1`. We'll use the `rebar_state` module to fetch all the applications we need. This can be done by calling the `project_apps/1` function, which returns the list of the project's top-level applications. - -```erlang -do(State) -> - lists:foreach(fun check_todo_app/1, rebar_state:project_apps(State)), - {ok, State}. -``` - -This, on a high level, means that we'll check each top-level app one at a time (there may often be more than one top-level application when working with releases) - -The rest is filler code specific to the plugin, in charge of reading each app path, go read code in there, and find instances of 'TODO:' in comments in the code: - -```erlang -check_todo_app(App) -> - Path = filename:join(rebar_app_info:dir(App),"src"), - Mods = find_source_files(Path), - case lists:foldl(fun check_todo_mod/2, [], Mods) of - [] -> ok; - Instances -> display_todos(rebar_app_info:name(App), Instances) - end. - -find_source_files(Path) -> - [filename:join(Path, Mod) || Mod <- filelib:wildcard("*.erl", Path)]. - -check_todo_mod(ModPath, Matches) -> - {ok, Bin} = file:read_file(ModPath), - case find_todo_lines(Bin) of - [] -> Matches; - Lines -> [{ModPath, Lines} | Matches] - end. - -find_todo_lines(File) -> - case re:run(File, "%+.*(TODO:.*)", [{capture, all_but_first, binary}, global, caseless]) of - {match, DeepBins} -> lists:flatten(DeepBins); - nomatch -> [] - end. - -display_todos(_, []) -> ok; -display_todos(App, FileMatches) -> - io:format("Application ~s~n",[App]), - [begin - io:format("\t~s~n",[Mod]), - [io:format("\t ~s~n",[TODO]) || TODO <- TODOs] - end || {Mod, TODOs} <- FileMatches], - ok. -``` - -Just using `io:format/2` to output is going to be fine. - -To test the plugin, push it to a source repository somewhere. Pick one of your projects, and add something to the rebar.config: - -```erlang -{plugins, [ - {provider_todo, ".*", {git, "git@bitbucket.org:ferd/rebar3-todo-plugin.git", {branch, "master"}}} -]}. -``` - -Then you can just call it directly: - -``` -→ rebar3 todo -===> Fetching provider_todo -===> Compiling provider_todo -Application merklet - /Users/ferd/code/self/merklet/src/merklet.erl - todo: consider endianness for absolute portability -``` - -Rebar3 will download and install the plugin, and figure out when to run it. Once compiled, it can be run at any time again. - -### Optionally Search Deps ### - -Let's extend things a bit. Maybe from time to time (when cutting a release), we'd like to make sure none of our dependencies contain 'TODO:'s either. - -To do this, we'll need to go parse command line arguments a bit, and change our execution model. The `?DEPS` macro will now need to specify that the `todo` provider can only run *after* dependencies have been installed: - -```erlang --define(DEPS, [install_deps]). -``` - -We can add the option to the list we use to configure the provider in `init/1`: - -```erlang -{opts, [ % list of options understood by the plugin - {deps, $d, "deps", undefined, "also run against dependencies"} -]}, -``` - -Meaning that deps can be flagged in by using the option `-d` (or `--deps`), and if it's not defined, well, we get the default value `undefined`. The last element of the 4-tuple is documentation for the option. - -And then we can implement the switch to figure out what to search: - -```erlang -do(State) -> - Apps = case discovery_type(State) of - project -> rebar_state:project_apps(State); - deps -> rebar_state:project_apps(State) ++ rebar_state:all_deps(State) - end, - lists:foreach(fun check_todo_app/1, Apps), - {ok, State}. - -[...] - -discovery_type(State) -> - {Args, _} = rebar_state:command_parsed_args(State), - case proplists:get_value(deps, Args) of - undefined -> project; - _ -> deps - end. -``` - -The `deps` option is found using `rebar_state:command_parsed_args(State)`, which will return a proplist of terms on the command-line after 'todo', and will take care of validating whether the flags are accepted or not. The rest can remain the same. - -Push the new code for the plugin, and try it again on a project with dependencies: - -``` -→ rebar3 todo --deps -===> Fetching provider_todo -===> Compiling provider_todo -===> Fetching bootstrap -===> Fetching file_monitor -===> Fetching recon -[...] -Application dirmon - /Users/ferd/code/self/figsync/apps/dirmon/src/dirmon_tracker.erl - TODO: Peeranha should expose the UUID from a node. -Application meck - /Users/ferd/code/self/figsync/_deps/meck/src/meck_proc.erl - TODO: What to do here? - TODO: What to do here? -``` - -Rebar3 will now go pick dependencies before running the plugin on there. - -you can also see that the help will be completed for you: - -``` -→ rebar3 help todo -Scans top-level application source and find instances of TODO: in commented out content to report it to the user. - -Usage: rebar todo [-d] - - -d, --deps also run against dependencies -``` - -That's it, the todo plugin is now complete! It's ready to ship and be included in other repositories. |