Catalyst.Actions.PatchFile (Catalyst v1.0.0-beta.0)

Copy Markdown View Source

Generic action for inserting arbitrary Elixir AST/source into a .ex or .exs file using Sourceror. Handles anything with a do...end block - def/defp (guarded or not, block or one-liner), and any other macro call that takes a do: body (schema, pipeline, scope, etc) - and also handles the body itself being a list literal (e.g. a supervisor's children = [...] or def children, do: [...]), splicing into the list's elements rather than appending next to it.

Not intended for mix.exs dependency lists - use AddDependency for that.

Options

  • :path - file to patch. Defaults to Execution.mix_file/1.
  • :content - required. A source string (parsed with Sourceror.parse_string!/1) or an already-quoted AST fragment.
  • :target - where to insert:
    • nil (default) - the top level of the file
    • an atom - the body of the first def/defp/macro call found with that name
    • {:assign, var_name} - the value of the first var_name = ... assignment found anywhere in the file
    • a 1-arity function zipper -> zipper | nil - custom locator, must return a zipper positioned on the block/list/expression to patch directly (not a call node, not an assignment node)

  • :position - one of:
    • :end (default) - append as the last child
    • :start - prepend as the first child
    • :after / :before - needs :anchor, a 1-arity function receiving each existing child node (raw AST) and returning true for the node to anchor the insertion to
  • :anchor - matcher function, used with :after / :before

Examples

# Append a statement into a function body
run(target: :handle_call, content: "IO.inspect(msg)", position: :start)

# Append into a list literal body
run(target: :children, content: "MyApp.NewWorker", position: :end)

# Append into a variable assigned to a list, e.g. `children = [...]`
# inside a multi-statement function body
run(target: {:assign, :children}, content: "MyApp.NewWorker", position: :end)

# Custom locator - find and splice into a list found anywhere
run(
  target: fn root_zipper ->
    root_zipper
    |> Zipper.find(fn
      {:=, _, [{:children, _, ctx}, _rhs]} when is_atom(ctx) or is_nil(ctx) -> true
      _ -> false
    end)
    |> case do
      nil -> nil
      assign_zipper -> assign_zipper |> Zipper.down() |> Zipper.right()
    end
  end,
  content: "{MyApp.NewWorker, []}",
  position: :end
)