gen_template_project v0.1.0 Mix.Gen.Template.Project

Project: a new mix template for projects

This is an alternative for mix new, creating what I feel is an easier to read and maintain set of basic files.

You use it in combination with the mix gen mix task, which you will need to install.

Installation

This template is installed using the template.install mix task. Projects are generated from it using the mix gen task.

So, before using templates for the first time, you need to install these two tasks:

$ mix archive.install mix_templates
$ mix archive.install mix_generator

Then you can install this template using

$ mix template.install gen_template_project

Usage

To create a basic project, with no supervision and no application, run:

$ mix gen project «project_name»

This will create a subdirectory called project_name containing your seed project. By default this project will be created under your current directory; you can change this with the --to option:

$ mix gen project «project_name» --to some/other/dir

Variants

To create a project with a top-level supervisor contained in an application callback, use the --sup option.

$ mix gen project «project_name» --sup

Background

You probably use mix new and/or mix phoenix.new to create new Elixir projects.

I never really likes the code these generators created. The layout made things hard to read, and even harder to change. For example, the mix.exs file looked like this:

defmodule Myapp.Mixfile do
  use Mix.Project

  def project do
    [app: :myapp,
     version: "0.1.0",
     elixir: "~> 1.4",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    # Specify extra applications you'll use from Erlang/Elixir
    [extra_applications: [:logger]]
  end

  # Dependencies can be Hex packages:
  #
  #   {:my_dep, "~> 0.3.0"}
  #
  # Or git/path repositories:
  #
  #   {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
  #
  # Type "mix help deps" for more examples and options
  defp deps do
    []
  end
end

Let’s look at a couple of problems.

The various lists are written in a really compact form:

[app: :myapp,
 version: "0.1.0",
 elixir: "~> 1.4",
 build_embedded: Mix.env == :prod,
 start_permanent: Mix.env == :prod,
 deps: deps()]

The indention is a unconventional single space, the entries are in an apparently random order, and the layout makes it hard to distinguish keys from values.

So, as a first iteration, I’d change this to:

[
  app:     :myapp,
  version: "0.1.0",
  deps:    deps(),
  elixir:  "~> 1.4",
  build_embedded:  Mix.env == :prod,
  start_permanent: Mix.env == :prod,
]

This moves the most commonly changed things to the top and aligns the values. It also has the list ending with a comma. This makes no difference to the internal representation of the list, but makes it a lot easier to change the order of entries and add new lines.

Next, let’s remove the duplication:

in_production = Mix.env == :prod
[
  app:     :myapp,
  version: "0.1.0",
  deps:    deps(),
  elixir:  "~> 1.4",
  build_embedded:  in_production,
  start_permanent: in_production,
]

All very well, but why are we exposing people to build_embedded and start_permanent as it they were as important as the application name and version? In fact, do you even know what they do? (I didn’t until I started looking into all this.)

So let’s split out the important (and changeable) stuff, and give it its own section in the mixfile.

defmodule Myapp.Mixfile do
  use Mix.Project

  @app     :myapp
  @version "0.1.0"
  
  @deps    []
  
  # ------------------------------------------------------------
  
  def project do
    in_production = Mix.env == :prod
    [
      app:     :app,
      version: @version,
      deps:    @deps,
      elixir:  "~> 1.4",
      build_embedded:  in_production,
      start_permanent: in_production,
    ]
  end

  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    # Specify extra applications you'll use from Erlang/Elixir
    [extra_applications: [:logger]]
  end
end

Now let’s think about the application function. Every application out there has the Logger started. How many use it? I’m guessing less than 50%. But we normally just leave it in because it seems like it’s needed. It also doesn’t help that the comments don’t really make it clear what extra_applications is actually for.

So let’s cut this down a little:

# Type "mix help compile.app" for more information
def application do
  [
    # extra_applications: [:logger]
  ]
end

Summary

Functions

Return the name of this template as an atom. This is the name passed to the gen command

Override this function to process command line options and set values passed into the template via assigns

Return the short description of this template, or nil

Return the absolute path to the tree that is to be copied when instantiating this template. This top-level dir will typically just contain a directory called $APP_NAME$

Functions

name()

Return the name of this template as an atom. This is the name passed to the gen command.

populate_assigns(assigns, options)

Override this function to process command line options and set values passed into the template via assigns.

short_desc()

Return the short description of this template, or nil.

source_dir()

Return the absolute path to the tree that is to be copied when instantiating this template. This top-level dir will typically just contain a directory called $APP_NAME$.