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