Intro
This write-up talks about aspects of project skeletons that eventually end up in all my Elixir projects.
If you're interested in discussion around this and are feeling cut-and-paste'y then just read on.
If you want a automation tool that actually generates this boilerplate, consider using cookiecutter, which can be used to make all kinds of files/directory structures from templates. My elixir project template for cookiecutter includes everything discussed on this page, and is on github.
Commit hooks
Regardless of programming language, I've come to believe that linting should be done strictly, constantly, and consistently across all contributing project members. Thus, I advocate moving linters out of your text editor (or whatever) and into pre-commit hooks.
Another benefit of the hooks approach is that it works better with code-bases that are large and under construction, because hooks force developers to fix lint on files they are modifying anyway, whereas running lint elsewhere -- say from tox via a buildbot -- might force developers to de-lint the entire code base at once.
I like to use yelp's pre-commit framework for managing my hooks. The setup is simple; just make a .pre-commit-config.yaml file in your source root. For elixir code my hooks normally look something like what you see below (to see my python hooks go here).
- repo: local
hooks:
- id: mix-test
name: 'elixir: mixing tests'
entry: mix test
language: system
files: \.ex$
- id: mix-test
name: 'elixir: mixing compile'
entry: mix compile
language: system
files: \.ex$
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: master
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-yaml
Afterwards you'll want to actually use this file, so run the commands below:
$ pip install pre-commit
$ pre-commit install
$ git add .pre-commit-config.yaml
$ git commit -a -m"add pre-commit config"
$ git push
Extra dependencies
Several dependencies find their way into almost all my elixir projects, most of them related to testing and static-analysis. Inside mix.exs modify the deps function as follows:
defp deps do
[
#... your other project dependencies ...
# a linter for elixir code
{:dogma, "~> 0.1", only: :dev},
# NB: 0.1.4 is available on github but not hex currently
{:mock, "~> 0.1.4", git: "https://github.com/jjh42/mock.git"},
# a static analysis tool
{:dialyxir, "~> 0.3", only: [:dev]},
# coverage tool for tests
# https://github.com/alfert/coverex
{:coverex, "~> 1.4.9", only: :test},
#... your other project dependencies ...
]
Static analysis
After you've added the dependencies as above and run mix deps.get, you can invoke the linter with the command line below. See the dogma proejct for more details.
$ mix dogma
To use dialyxir for the first time you have to build the persistent lookup table, which takes a while.
$ mix dialyzer.plt
Thereafter, just run
$ mix dialyzer
Test coverage
Coverage with coverex requires a modification to the project function in mix.exs, as well as the coverex dependency mentioned above.
def project do
[
# ... other project-config ...
test_coverage: [
tool: Coverex.Task,
console_log: true],
# ... other project-config ...
]
end
To run tests with coverage, use the commandline below:
$ mix test --cover
Custom Mix Tasks
One ends up making custom mix tasks in any project that's not an escript commandline. It's also common that the mix task needs to invoke other mix tasks, so that's demonstrated below. If your mix task requires the app to be started, why not do the programmatic equivalent of mix app.start?
defmodule Mix.Tasks.MyMixTask do
use Mix.Task
def run(anything) do
Mix.Tasks.App.Start.run([])
main(anything)
end
def main([]) do
IO.puts("No arguments given!")
end
def main([fname]) do
IO.puts("Given a filename #{fname}")
end
end