defmodule ExFactor do
@moduledoc """
`ExFactor` is a refactoring helper.
By identifying a source module, function name, and arity, it will identify all non-test usages
and extract them to the target module.
If the target module exists, it adds the function to the end of the file and changes all calls to the
new module namespace. Otherwise ExFactor will create the target module at the target_path or
at a (resonably) expected location by the module namespace
"""
_docp = "results struct"
defstruct [:module, :path, :message, :file_contents, :state]
alias ExFactor.Changer
alias ExFactor.Extractor
alias ExFactor.Formatter
alias ExFactor.Remover
@doc """
Call Extractor, Remover, and Formatter modules
"""
def refactor(opts) do
source_module = Keyword.fetch!(opts, :source_module)
target_module = Keyword.fetch!(opts, :target_module)
dry_run = Keyword.get(opts, :dry_run, false)
opts =
opts
|> Keyword.put_new(:target_path, path(target_module))
|> Keyword.put_new(:source_path, path(source_module))
emplace = Extractor.emplace(opts)
changes = Changer.change(opts)
# remove should be last (before format)
removals = Remover.remove(opts)
format(%{additions: emplace, changes: changes, removals: removals}, dry_run, opts)
end
def refactor_module(opts) do
source_module = Keyword.fetch!(opts, :source_module)
target_module = Keyword.fetch!(opts, :target_module)
dry_run = Keyword.get(opts, :dry_run, false)
opts =
opts
|> Keyword.put_new(:target_path, path(target_module))
|> Keyword.put_new(:source_path, path(source_module))
changes = Changer.change(opts)
format(%{additions: %ExFactor{}, changes: changes, removals: %ExFactor{}}, dry_run, opts)
end
def path(module) do
Path.join(["lib", Macro.underscore(module) <> ".ex"])
end
defp format(%{path: nil} = struct, _dry_run, _format), do: struct
defp format(output, true, _format), do: output
defp format(%{additions: adds, changes: changes, removals: removals} = output, false, opts) do
%{
additions: format(adds, opts),
changes: format(changes, opts),
removals: format(removals, opts)
}
output
end
defp format(list, opts) when is_list(list) do
Enum.map(list, fn elem ->
format(elem, opts)
Map.get_and_update(elem, :state, fn val -> {val, [:formatted | val]} end)
end)
end
defp format(%{state: [:unchanged]} = struct, _opts), do: struct
defp format(struct, opts) do
Formatter.format([struct.path], opts)
Map.get_and_update(struct, :state, fn val -> {val, [:formatted | val]} end)
end
end