Current section
Files
Jump to
Current section
Files
lib/phoenix_custom_generators.ex
defmodule Mix.PhoenixCustomGenerators do
# Conveniences for Phoenix tasks.
@moduledoc false
@valid_attributes [:integer, :float, :decimal, :boolean, :map, :string,
:array, :references, :text, :date, :time, :datetime,
:uuid, :binary]
@doc """
Copies files from source dir to target dir
according to the given map.
Files are evaluated against EEx according to
the given binding.
"""
def copy_from(apps, source_dir, target_dir, binding, mapping) when is_list(mapping) do
roots = Enum.map(apps, &to_app_source(&1, source_dir))
for {format, source_file_path, target_file_path} <- mapping do
source =
Enum.find_value(roots, fn root ->
source = Path.join(root, source_file_path)
if File.exists?(source), do: source
end) || raise "could not find #{source_file_path} in any of the sources"
target = Path.join(target_dir, target_file_path)
contents =
case format do
:text -> File.read!(source)
:eex -> EEx.eval_file(source, binding)
end
Mix.Generator.create_file(target, contents)
end
end
defp to_app_source(path, source_dir) when is_binary(path),
do: Path.join(path, source_dir)
defp to_app_source(app, source_dir) when is_atom(app),
do: Application.app_dir(app, source_dir)
@doc """
Inflect path, scope, alias and more from the given name.
iex> Mix.Phoenix.inflect("user")
[alias: "User",
human: "User",
base: "Phoenix",
module: "Phoenix.User",
scoped: "User",
singular: "user",
path: "user"]
iex> Mix.Phoenix.inflect("Admin.User")
[alias: "User",
human: "User",
base: "Phoenix",
module: "Phoenix.Admin.User",
scoped: "Admin.User",
singular: "user",
path: "admin/user"]
iex> Mix.Phoenix.inflect("Admin.SuperUser")
[alias: "SuperUser",
human: "Super user",
base: "Phoenix",
module: "Phoenix.Admin.SuperUser",
scoped: "Admin.SuperUser",
singular: "super_user",
path: "admin/super_user"]
"""
def inflect(singular) do
base = Mix.Phoenix.base
scoped = Phoenix.Naming.camelize(singular)
path = Phoenix.Naming.underscore(scoped)
singular = String.split(path, "/") |> List.last
module = Module.concat(base, scoped) |> inspect
alias = String.split(module, ".") |> List.last
human = Phoenix.Naming.humanize(singular)
[alias: alias,
human: human,
base: base,
module: module,
scoped: scoped,
singular: singular,
path: path]
end
@doc """
Parses the attrs as received by generators.
"""
def attrs(attrs) do
Enum.map(attrs, fn attr ->
attr
|> drop_unique()
|> String.split(":", parts: 3)
|> list_to_attr()
|> validate_attr!()
end)
end
@doc """
Fetches the unique attributes from attrs.
"""
def uniques(attrs) do
attrs
|> Enum.filter(&String.ends_with?(&1, ":unique"))
|> Enum.map(& &1 |> String.split(":", parts: 2) |> hd |> String.to_atom)
end
@doc """
Generates some sample params based on the parsed attributes.
"""
def params(attrs, opts) do
attrs
|> Enum.reject(fn
{_, {:references, _}} -> true
{_, _} -> false
end)
|> Enum.into(%{}, fn {k, t} -> {k, type_to_default(t, opts)} end)
end
def params_factory_girl(attrs, opts) do
attrs
|> Enum.reject(fn
{_, {:references, _}} -> true
{_, _} -> false
end)
|> Enum.into(%{}, fn {k, t} -> {k, type_to_default_factory_girl(t, opts)} end)
end
def params_json(attrs, opts) do
attrs
|> Enum.reject(fn
{_, {:references, _}} -> true
{_, _} -> false
end)
|> Enum.into(%{}, fn {k, t} -> {k, type_to_default_json(t)} end)
end
@doc """
Checks the availability of a given module name.
"""
def check_module_name_availability!(name) do
name = Module.concat(Elixir, name)
if Code.ensure_loaded?(name) do
Mix.raise "Module name #{inspect name} is already taken, please choose another name"
end
end
@doc """
Returns the module base name based on the configuration value.
config :my_app
namespace: My.App
"""
def base do
app = otp_app()
case Application.get_env(app, :namespace, app) do
^app -> app |> to_string |> Phoenix.Naming.camelize
mod -> mod |> inspect
end
end
@doc """
Returns the otp app from the Mix project configuration.
"""
def otp_app do
Mix.Project.config |> Keyword.fetch!(:app)
end
@doc """
Returns all compiled modules in a project.
"""
def modules do
Mix.Project.compile_path
|> Path.join("*.beam")
|> Path.wildcard
|> Enum.map(&beam_to_module/1)
end
defp beam_to_module(path) do
path |> Path.basename(".beam") |> String.to_atom()
end
defp drop_unique(info) do
prefix = byte_size(info) - 7
case info do
<<attr::size(prefix)-binary, ":unique">> -> attr
_ -> info
end
end
defp list_to_attr([key]), do: {String.to_atom(key), :string}
defp list_to_attr([key, value]), do: {String.to_atom(key), String.to_atom(value)}
defp list_to_attr([key, comp, value]) do
{String.to_atom(key), {String.to_atom(comp), String.to_atom(value)}}
end
defp type_to_default(t, opts) do
ecto_calendar_type = case Keyword.get(opts, :ecto_calendar_types, false) do
true -> :ecto_calendar_type
false -> :native
end
case {t, ecto_calendar_type} do
{{:array, _}, _} -> []
{:integer, _} -> 42
{:float, _} -> "120.5"
{:decimal, _} -> "120.5"
{:boolean, _} -> true
{:map, _} -> %{}
{:text, _} -> "some content"
{:date, :ecto_calendar_type} -> %{year: 2010, month: 4, day: 17}
{:date, :native} -> %Date{year: 2010, month: 4, day: 17}
{:time, :ecto_calendar_type} -> %{hour: 14, min: 0, sec: 0}
{:time, :native} -> %Time{hour: 14, minute: 0, second: 0, microsecond: {0, 0}}
{:datetime, :ecto_calendar_type} -> %{year: 2010, month: 4, day: 17, hour: 14, min: 0, sec: 0}
{:datetime, :native} -> "2010-04-17 14:00:00.000000Z"
{:uuid, _} -> "7488a646-e31f-11e4-aace-600308960662"
_ -> "some content"
end
end
defp type_to_default_factory_girl(t, opts) do
ecto_calendar_type = case Keyword.get(opts, :ecto_calendar_types, false) do
true -> :ecto_calendar_type
false -> :native
end
case {t, ecto_calendar_type} do
{{:array, _}, _} -> "[]"
{:integer, _} -> "42"
{:float, _} -> "120.5"
{:decimal, _} -> "Decimal.new(\"120.5\")"
{:boolean, _} -> "true"
{:map, _} -> "%{}"
{:text, _} -> "\"some content\""
{:date, :ecto_calendar_type} -> "%Ecto.Date{year: 2010, month: 4, day: 17}"
{:date, :native} -> "%Date{year: 2010, month: 4, day: 17}"
{:time, :ecto_calendar_type} -> "%Ecto.Time{hour: 14, min: 0, sec: 0}"
{:time, :native} -> "%Time{hour: 14, minute: 0, second: 0, microsecond: {0, 0}}"
{:datetime, :ecto_calendar_type} -> "%Ecto.DateTime{year: 2010, month: 4, day: 17, hour: 14, min: 0, sec: 0}"
{:datetime, :native} -> "\"2010-04-17 14:00:00.000000Z\""
{:uuid, _} -> "\"7488a646-e31f-11e4-aace-600308960662\""
_ -> "\"some content\""
end
end
defp type_to_default_json(t) do
case t do
{:array, _} -> []
:integer -> 42
:float -> 120.5
:decimal -> "120.5"
:boolean -> true
:map -> %{}
:text -> "some content"
:date -> "2010-04-17"
:time -> "14:00:00"
:datetime -> "2010-04-17T14:00:00"
:uuid -> "7488a646-e31f-11e4-aace-600308960662"
_ -> "some content"
end
end
defp validate_attr!({_name, type} = attr) when type in @valid_attributes, do: attr
defp validate_attr!({_name, {type, _}} = attr) when type in @valid_attributes, do: attr
defp validate_attr!({_, type}), do: Mix.raise "Unknown type `#{type}` given to generator"
end