Composer.Step behaviour (composer v0.2.0)

Copy Markdown View Source

Defines a composer step.

A step is an Igniter.Mix.Task that lives in a cookbook's steps/ directory instead of under Mix.Tasks, so it composes into a run's single diff but never shows up in mix help.

defmodule Composer.ComposerLab.Steps.Ash.Postgres do   # lib/composer/composer_lab/steps/ash/postgres.ex
  use Composer.Step,
    summary: "Adds AshPostgres",
    action: :add,
    frameworks: [:ash],
    capability: [:database],
    stability: :stable,
    requires: ["composer_lab:ash", {:executable, "psql"}]

  @impl Igniter.Mix.Task
  def igniter(igniter), do: ...
end

Identity

The step's id is its path under steps/ ("ash/postgres"), and its cookbook is the directory above steps/ (composer_lab). Its reference is "composer_lab:ash/postgres". The module name must match that path — Composer.<Cookbook>.Steps.<Path...> — or compilation fails.

Options

Every option is metadata; see Composer.Facets for the vocabulary.

  • :summary - a one-line description, also the default @moduledoc
  • :action, :phase, :stability - one facet value each
  • :frameworks, :capability - lists of facet values
  • :tags - free atoms or strings
  • :requires - step references, {:executable, name} and {:package, name, requirement} checked before a run
  • :provides, :conflicts - capability atoms used for overlap warnings
  • :author, :license, :homepage - strings

Unknown facet values are compile-time warnings. A wrong type is a compile error.

Templates

A step's templates live in the directory named after its file with a leading underscore, and template/1 takes a path relative to it:

steps/cluster/gigalixir.ex               # Composer.Core.Steps.Cluster.Gigalixir
steps/cluster/_gigalixir/some.conf.eex   # template("some.conf.eex") in Gigalixir
steps/cluster/gigalixir/libcluster.ex    # a child step, Composer.Core.Steps.Cluster.Gigalixir.Libcluster

Composer.Templates.copy_file(igniter, template("some.conf.eex"), "config/some.conf")

The underscore keeps templates apart from child steps, so a step can have both. Step path segments never start with _. Every .ex file under steps/ is compiled with the package, so Elixir source templates are stored as .ex.eex. template/1 checks at compile time that the file or directory exists, and registers it as an external resource, so a typo fails mix compile and an edit recompiles the step. A path computed at run time (template("components/#{name}.astral")) is only checked when the step runs. A path may not leave the step's directory: templates are never shared between steps.

Summary

Callbacks

Returns the step's metadata.

Functions

Returns the template directory of the step defined in file: the path without its .ex, with its last segment prefixed by _, e.g. steps/cluster/_gigalixir/ for steps/cluster/gigalixir.ex.

Returns the module a step at id in cookbook must be named, e.g. Composer.ComposerLab.Steps.Ash.Postgres for {"composer_lab", "ash/postgres"}.

Splits a step source path into {cookbook, id}, e.g. ".../lib/composer/composer_lab/steps/ash/postgres.ex" gives {"composer_lab", "ash/postgres"}. Returns :error when the file is not under a lib/composer/<cookbook>/steps/ directory.

Returns a template reference, {cookbook, step_id, path}, to path in the calling step's template directory (steps/_<id>/, see template_dir/1).

Returns the template directory of the step id, relative to steps/: its last segment prefixed by _, e.g. "ash/_postgres" for "ash/postgres". Also accepts a full path without the .ex.

Callbacks

__composer_step__()

@callback __composer_step__() :: Composer.Step.Meta.t()

Returns the step's metadata.

Functions

dir(file)

@spec dir(Path.t()) :: Path.t()

Returns the template directory of the step defined in file: the path without its .ex, with its last segment prefixed by _, e.g. steps/cluster/_gigalixir/ for steps/cluster/gigalixir.ex.

module_for(cookbook, id)

@spec module_for(String.t(), String.t()) :: module()

Returns the module a step at id in cookbook must be named, e.g. Composer.ComposerLab.Steps.Ash.Postgres for {"composer_lab", "ash/postgres"}.

path_id(file)

@spec path_id(Path.t()) :: {:ok, {String.t(), String.t()}} | :error

Splits a step source path into {cookbook, id}, e.g. ".../lib/composer/composer_lab/steps/ash/postgres.ex" gives {"composer_lab", "ash/postgres"}. Returns :error when the file is not under a lib/composer/<cookbook>/steps/ directory.

template(path)

(macro)

Returns a template reference, {cookbook, step_id, path}, to path in the calling step's template directory (steps/_<id>/, see template_dir/1).

A literal path must exist at compile time; see "Templates" above.

template_dir(id)

@spec template_dir(String.t()) :: String.t()

Returns the template directory of the step id, relative to steps/: its last segment prefixed by _, e.g. "ash/_postgres" for "ash/postgres". Also accepts a full path without the .ex.