Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ erl_crash.dump
*.ez
*.swp
*.swo
rename-*.tar
4 changes: 3 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
use Mix.Config
import Config

import_config("#{Mix.env()}.exs")
Empty file added config/dev.exs
Empty file.
Empty file added config/prod.exs
Empty file.
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Config

config :rename, Rename.Repo, foo: :bar
56 changes: 30 additions & 26 deletions lib/mix/tasks/rename.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,46 @@ defmodule Mix.Tasks.Rename do
use Mix.Task

def run(args \\ [])
def run([old_name, new_name, old_otp, new_otp | extra_options]) do

def run([old_name, new_name, old_otp, new_otp | options]) do
{options, _argv, _errors} =
options
|> OptionParser.parse(
strict: [
ignore_directories: :keep,
ignore_files: :keep,
starting_directory: :string,
include_extensions: :keep,
include_files: :keep
]
)

options =
options
|> Enum.group_by(fn {k, _v} -> k end, fn {_k, v} -> v end)
|> Map.to_list()
|> maybe_put_starting_directory(Keyword.get(options, :starting_directory))

Rename.run(
{old_name, new_name},
{old_otp, new_otp},
run_options(extra_options)
{old_name, new_name},
{old_otp, new_otp},
options
)
end

def run(_bad_args) do
IO.puts """
IO.puts("""
Did not provide required app and otp names
Call should look like:
mix rename OldName NewName old_name NewName
"""
""")

{:error, :bad_params}
end

def run_options(extra_options, options \\ [])
def run_options([], options) do
ignore_files = options[:ignore_files] || []
options
|> Enum.reject(fn {key, _val} -> key == :ignore_files end)
|> Enum.concat([ignore_files: ignore_files])
end
def run_options([key, val | rest], options) do
rest
|> run_options(
options
|> Enum.concat([{parsed_key(key), val}])
)
end
defp maybe_put_starting_directory(options, nil), do: options

def parsed_key(key) do
key
|> String.slice(2..-1)
|> String.replace("-", "_")
|> String.to_atom
defp maybe_put_starting_directory(options, starting_directory) do
Keyword.put(options, :starting_directory, starting_directory)
end

end
144 changes: 100 additions & 44 deletions lib/rename.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
defmodule Rename do

@moduledoc """
The single module that does all the renaming
Talk about pressure
Expand All @@ -9,91 +8,148 @@ defmodule Rename do
So it's fine
If you're mad about it, submit a PR.
"""
@default_extensions ~w(
.ex
.exs
.eex
.md
)

@default_extensions [
"ex",
"exs",
"eex",
"md",
]

@default_ignore_directories [
"_build",
"deps",
"assets",
]
@default_ignore_directories ~w(
.elixir_ls
_build
deps
assets
)

@default_starting_directory "."

@default_ignore_files []

@default_include_files ~w(
mix.exs
)

@doc """
The public function you use to rename your app.
Call looks like: run({"OldName", "NewName"}, {"old_otp", "new_otp"}, options)
"""

def run(names, opts, options \\ [])
def run(names = {_old_name, _new_name}, otps = {_old_otp, _new_otp}, options) do
def run(names, otps, options \\ [])

def run({_old_name, _new_name} = names, {_old_otp, _new_otp} = otps, options) do
options =
options
|> Enum.reduce(defaults(), fn
{key, val}, acc when is_list(val) ->
Keyword.put(acc, key, merge_with_default({key, val}, acc))

{k, v}, acc ->
Keyword.put(acc, k, v)
end)

names
|> rename_in_directory(
otps,
options[:starting_directory] || @default_starting_directory,
options[:starting_directory],
options
)
end

def run(_names, _otp, _options), do: {:error, "bad params"}

defp defaults do
[
ignore_directories: @default_ignore_directories,
ignore_files: @default_ignore_files,
include_extensions: @default_extensions,
starting_directory: @default_starting_directory,
include_files: @default_include_files
]
end

defp rename_in_directory(names = {old_name, new_name}, otps = {old_otp, new_otp}, cwd, options) do
cwd
|> File.ls!
|> Enum.each(fn path ->
file_or_dir = cwd <> "/" <> path
|> File.ls!()
|> Enum.reject(&ignored_directory?(&1, options))
|> Enum.each(fn file_or_dir ->
path = Path.join([cwd, file_or_dir])

cond do
is_valid_directory?(file_or_dir, options) ->
rename_in_directory(names, otps, file_or_dir, options)
File.dir?(path) ->
rename_in_directory(names, otps, path, options)
true
is_valid_file?(file_or_dir, options) ->
file_or_dir
|> File.read

is_valid_file?(path, options) ->
path
|> File.read()
|> case do
{:ok, file} ->
updated_file = file
|> String.replace(old_name, new_name)
|> String.replace(old_otp, new_otp)
File.write(file_or_dir, updated_file)
{:ok, file} ->
updated_file =
file
|> String.replace(old_name, new_name)
|> String.replace(old_otp, new_otp)
|> String.replace(dasherised(old_otp), dasherised(new_otp))

File.write(path, updated_file)
true

_ ->
false
end
true ->

true ->
false
end
|> case do
true ->
file_or_dir
|> File.rename(String.replace(file_or_dir, old_otp, new_otp))
_ -> :nothing
true ->
rename_file(path, old_otp, new_otp)

_ ->
:ok
end
end)
end

defp is_valid_directory?(dir, options) do
File.dir?(dir) and
dir in (options[:ignore_directories] || @default_ignore_directories) == false
defp rename_file(path, old_otp, new_otp) do
File.rename(path, String.replace(path, old_otp, new_otp))
end

defp ignored_directory?(dir, options) do
File.dir?(dir) and dir in options[:ignore_directories]
end

defp is_valid_file?(file, options) do
File.exists?(file) and
file in (options[:ignore_files] || @default_ignore_files) == false and
has_valid_extension?(file, options)
File.exists?(file) &&
has_valid_extension?(file, options) &&
(include?(file, options) || !ignore?(file, options))
end

def include?(file, options) do
Path.basename(file) in options[:include_files]
end

def ignore?(file, options) do
Path.basename(file) in options[:ignore_files]
end

defp has_valid_extension?(file, options) do
extension = file
|> String.split(".")
|> List.last
extension in (options[:valid_extensions] || @default_extensions)
file
|> Path.extname()
|> case do
"" ->
true

ext ->
ext in options[:include_extensions]
end
end

defp dasherised(name), do: String.replace(name, "_", "-")

defp merge_with_default({key, val}, acc) do
acc
|> Keyword.get(key)
|> then(&(&1 ++ val))
end
end
Empty file added lib/rename/.keep
Empty file.
13 changes: 8 additions & 5 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ defmodule Rename.Mixfile do
app: :rename,
version: "0.1.0",
elixir: "~> 1.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
description: description(),
elixirc_paths: elixirc_paths(Mix.env()),
package: package(),
deps: deps(),
test_coverage: [tool: ExCoveralls],
preferred_cli_env: [
"coveralls": :test,
coveralls: :test,
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test,
"coveralls.html": :test
]
]
end
Expand All @@ -38,8 +39,10 @@ defmodule Rename.Mixfile do
defp deps do
[
{:excoveralls, "~> 0.6", only: :test},
{:ex_doc, ">= 0.0.0", only: :dev},
{:ex_doc, ">= 0.0.0", only: :dev}
]
end

defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
end
24 changes: 13 additions & 11 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
%{"certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], []},
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"excoveralls": {:hex, :excoveralls, "0.6.3", "894bf9254890a4aac1d1165da08145a72700ff42d8cb6ce8195a584cb2a4b374", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]},
"exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]},
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
"jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}}
%{
"certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], [], "hexpm", "44a5aa4261490a7d7fa6909ab4bcf14bff928a4fef49e80fc1e7a8fdb7b45f79"},
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], [], "hexpm", "6709251dd10e70cca0d50be8a25adc38c701b39eac2da3b1c166e3e7e4d358ed"},
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm", "e5ea59f50ecdfe4cc755808450dafe35221d5a0f4a31c42e80a7188eca570e4c"},
"excoveralls": {:hex, :excoveralls, "0.6.3", "894bf9254890a4aac1d1165da08145a72700ff42d8cb6ce8195a584cb2a4b374", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "33b8e869a0afe422e2a05794ac67bed6570498173a02a80f0c88bc0fe3580287"},
"exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "b55727b206dab96feb025267e5c122ddb448f55b6648f9156b8d481215d80290"},
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "4.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "ec8309cb6d42251513492ef683d212c614d78b20594e5f4d89a05d8411dd0dea"},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], [], "hexpm", "f1b699f7275728538da7b5e35679f9e0f41ad8e0a49896e6a27b61867ed344eb"},
"jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm", "b4c5d3230b397c8d95579e4a3d72826bb6463160130ccf4182f5be8579b5f44c"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm", "7a4c8e1115a2732a67d7624e28cf6c9f30c66711a9e92928e745c255887ba465"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm", "4f8805eb5c8a939cf2359367cb651a3180b27dfb48444846be2613d79355d65e"},
}
50 changes: 50 additions & 0 deletions test/mix/tasks/rename_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule Mix.Tasks.RenameTest do
use Rename.UnitCase

@test_copy_dir "test_copy"
@old_app_name "Rename"
@old_app_otp "rename"
@new_app_name "ToDoTwitterClone"
@new_app_otp "to_do_twitter_clone"

describe "run/1" do
test "rename mix task works" do
create_copy_of_app(@test_copy_dir)

Mix.Tasks.Rename.run([
@old_app_name,
@new_app_name,
@old_app_otp,
@new_app_otp,
"--starting-directory",
@test_copy_dir,
"--ignore-directories",
"foo"
])

mix_file = File.read!(@test_copy_dir <> "/mix.exs")
assert mix_file |> String.contains?(@new_app_name)
assert mix_file |> String.contains?(@new_app_otp)
assert mix_file |> String.contains?(@old_app_name) == false
main_module = File.read!(@test_copy_dir <> "/lib/" <> @new_app_otp <> ".ex")
assert main_module |> String.contains?(@new_app_name)
assert main_module |> String.contains?(@old_app_name) == false
readme = File.read!(@test_copy_dir <> "/README.md")
assert readme |> String.contains?(@new_app_name)
assert readme |> String.contains?(@new_app_otp)
assert readme |> String.contains?(@old_app_name) == false

delete_copy_of_app(@test_copy_dir)
end
end

test "rename mix task should give proper error for bad params" do
{stdout, 0} = System.cmd("mix", ["rename", "not", "enought", "params"])

assert stdout =~ """
Did not provide required app and otp names
Call should look like:
mix rename OldName NewName old_name NewName
"""
end
end
Loading