Composer.Cookbook (composer v0.2.0)

Copy Markdown View Source

Defines a cookbook: one package's tree of steps and recipes.

Every cookbook is laid out the same way:

lib/composer/composer_lab.ex                    # the cookbook module
lib/composer/composer_lab/steps/astral/nav1.ex  # a step (use Composer.Step)
lib/composer/composer_lab/steps/astral/_nav1/   # its templates
lib/composer/composer_lab/recipes/*.exs         # recipe groups (see Composer.Recipe.Group)
lib/composer/composer_lab/manifest.exs          # written by mix composer.cookbook.manifest

A step's templates live in the directory named after its file (see Composer.Templates). There is no cookbook-level templates/ directory.

and declared with:

defmodule Composer.ComposerLab do
  use Composer.Cookbook,
    summary: "Andy's composer steps",
    homepage: "https://github.com/andyl/composer_lab"
end

Naming

The cookbook name comes from the module (Composer.ComposerLab is composer_lab) and must equal the package's OTP app name, so one package defines at most one cookbook and Hex's package uniqueness keeps cookbook names unique. The one exception is composer's own built-in cookbook, core.

These names are reserved and rejected at compile time: core, hex, local and every top-level Composer.* module name (see reserved/0).

Compile-time checks

use Composer.Cookbook fails compilation, with a message that says how to fix it, when:

  • the module is nested (Composer.Foo.Bar) or its name does not match the app
  • the name is reserved
  • the module is not defined in lib/composer/<name>.ex
  • a group file under recipes/ has an invalid name or does not parse
  • a .ex file under steps/ is not a step (a template stored as bare .ex would be compiled as part of the package; rename it .ex.eex)

Group files are external resources, and adding or removing a group or a file under steps/ triggers a recompile. The .ex check is textual (the file must use Composer.Step and be named for its path), since step modules may not be compiled yet; mix composer.cookbook.lint checks the compiled modules.

Runtime

A cookbook is described at run time by a %Composer.Cookbook{} struct. kind is :code for a package's cookbook, :local for your recipes (see Composer.Local) and :virtual for hex. Recipes and templates are read from source_dir/1, the cookbook's tree in the dependency's source, so composer is a development tool: running from a release is not supported.

Summary

Functions

True when module is loaded and defines a cookbook.

Builds the struct for a cookbook module.

Returns the path of group group, or nil when it does not exist.

Returns the cookbook's group files as {group, path}, sorted by name. Files with an invalid group name are left out.

The virtual hex cookbook.

Evaluates every group file of the cookbook. Returns the valid recipes, sorted by id, and {path, reason} for each group, or recipe, that failed to load.

Returns the module a cookbook named name is defined by, e.g. Composer.ComposerLab for "composer_lab".

Returns the group file holding recipe id ("group/name"), or nil when the group or the recipe does not exist.

Returns the cookbook's recipe ids ("group/name"), sorted.

The cookbook names no package may use.

Returns the directory the cookbook's recipes and templates are read from.

Finds a step by id.

Returns the cookbook's steps, sorted by id.

Types

kind()

@type kind() :: :code | :local | :virtual

t()

@type t() :: %Composer.Cookbook{
  app: atom() | nil,
  homepage: String.t() | nil,
  kind: kind(),
  module: module() | nil,
  name: String.t(),
  source_dir: Path.t() | nil,
  summary: String.t() | nil
}

Functions

cookbook_module?(module)

@spec cookbook_module?(module()) :: boolean()

True when module is loaded and defines a cookbook.

from_module(module)

@spec from_module(module()) :: t()

Builds the struct for a cookbook module.

group_path(cookbook, group)

@spec group_path(t(), String.t()) :: Path.t() | nil

Returns the path of group group, or nil when it does not exist.

groups(cookbook)

@spec groups(t()) :: [{String.t(), Path.t()}]

Returns the cookbook's group files as {group, path}, sorted by name. Files with an invalid group name are left out.

hex()

@spec hex() :: t()

The virtual hex cookbook.

load_recipes(cookbook)

@spec load_recipes(t()) :: {[Composer.Recipe.t()], [{Path.t(), term()}]}

Evaluates every group file of the cookbook. Returns the valid recipes, sorted by id, and {path, reason} for each group, or recipe, that failed to load.

module_for(name)

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

Returns the module a cookbook named name is defined by, e.g. Composer.ComposerLab for "composer_lab".

recipe_path(cookbook, id)

@spec recipe_path(t(), String.t()) :: Path.t() | nil

Returns the group file holding recipe id ("group/name"), or nil when the group or the recipe does not exist.

recipes(cookbook)

@spec recipes(t()) :: [String.t()]

Returns the cookbook's recipe ids ("group/name"), sorted.

Ids are read from each group file's syntax tree, without evaluating it. A group file that cannot be read this way is skipped; load_recipes/1 reports it.

reserved()

@spec reserved() :: [String.t()]

The cookbook names no package may use.

source_dir(cookbook)

@spec source_dir(t()) :: Path.t() | nil

Returns the directory the cookbook's recipes and templates are read from.

For a package cookbook, in order: the dependency's source path, the project root when the cookbook belongs to the current project, then the directory recorded at compile time.

step(cookbook, id)

@spec step(t(), String.t()) :: {:ok, Composer.Step.Meta.t()} | :error

Finds a step by id.

steps(cookbook)

@spec steps(t()) :: [Composer.Step.Meta.t()]

Returns the cookbook's steps, sorted by id.

The steps are the modules of the cookbook's app named Composer.<Cookbook>.Steps.* that define __composer_step__/0. Only the app's own module list is read; nothing is scanned.