Packages
surface
0.11.2
0.12.3
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha.0
A component based library for Phoenix LiveView
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/surface/surface.init/patcher.ex
defmodule Mix.Tasks.Surface.Init.Patcher do
@moduledoc false
alias Mix.Tasks.Surface.Init.ExPatcher
@template_folder "priv/templates/surface.init"
@result_precedence %{
:unpatched => 0,
:patched => 1,
:already_patched => 2,
:maybe_already_patched => 3,
:cannot_patch => 4
}
def patch_code(code, patch_spec) do
patch_spec.patch
|> List.wrap()
|> run_patch_funs(code)
end
def patch_file(file, patches, assigns) do
log(:patching, file, fn ->
case File.read(file) do
{:ok, code} ->
{updated_code, results} =
Enum.reduce(patches, {code, []}, fn patch_spec, {code, results} ->
{result, updated_code} = patch_code(code, patch_spec)
{updated_code, [{result, file, patch_spec} | results]}
end)
unless assigns.dry_run do
File.write!(file, updated_code)
end
Enum.reverse(results)
{:error, :enoent} ->
to_results(patches, :file_not_found, file)
{:error, _reason} ->
to_results(patches, :cannot_read_file, file)
end
end)
end
def delete_file(file, assigns) do
patch_spec = %{name: "Delete #{file}", instructions: ""}
log(:deleting, file, fn ->
if File.exists?(file) do
unless assigns.dry_run do
File.rm!(file)
end
{:patched, file, patch_spec}
else
{:already_patched, file, patch_spec}
end
end)
end
def create_file(source_file_path, target, assigns) do
root = Application.app_dir(:surface, @template_folder)
source = Path.join(root, source_file_path)
patch_spec = %{name: "Create #{target}", instructions: ""}
log(:creating, target, fn ->
contents = EEx.eval_file(source, Map.to_list(assigns))
if overwrite?(target, contents, assigns) do
unless assigns.dry_run do
File.mkdir_p!(Path.dirname(target))
File.write!(target, contents)
end
{:patched, target, patch_spec}
else
{:already_patched, target, patch_spec}
end
end)
end
def overwrite?(path, contents, assigns) do
case File.read(path) do
{:ok, binary} ->
if binary == IO.iodata_to_binary(contents) do
false
else
if assigns.yes do
true
else
full = Path.expand(path)
Mix.shell().yes?(Path.relative_to_cwd(full) <> " already exists, overwrite?")
end
end
_ ->
true
end
end
defp run_patch_funs(funs, code) do
run_patch_funs(funs, code, code, :unpatched)
end
defp run_patch_funs([], _original_code, last_code, last_result) do
{last_result, last_code}
end
defp run_patch_funs([fun | funs], original_code, last_code, last_result) do
{result, patched_code} = last_code |> fun.() |> convert_patch_result()
result = Enum.max_by([result, last_result], fn item -> @result_precedence[item] end)
code =
if result == :patched do
patched_code
else
original_code
end
if result == :cannot_patch do
{result, code}
else
run_patch_funs(funs, original_code, code, result)
end
end
defp convert_patch_result(%ExPatcher{result: result, code: code}) do
{result, code}
end
defp convert_patch_result(result) do
result
end
defp to_results(patches, status, file) do
Enum.map(patches, fn patch_spec ->
{status, file, patch_spec}
end)
end
defp log(action, file, fun) do
prefix = "* #{action} "
Mix.shell().info([:green, prefix, :reset, file])
result = fun.()
skipped_postfix =
case result |> List.wrap() |> Enum.split_with(&match?({:patched, _, _}, &1)) do
{[], _not_patched} ->
[:yellow, " (skipped)", :reset]
{_patched, []} ->
[]
{patched, not_patched} ->
n_not_patched = length(not_patched)
total = n_not_patched + length(patched)
[:yellow, " (skipped #{n_not_patched} of #{total} changes)", :reset]
end
if skipped_postfix != [] do
Mix.shell().info([IO.ANSI.cursor_up(), :clear_line, :yellow, prefix, :reset, file] ++ skipped_postfix)
end
result
end
end