Packages

A comprehensive library for working with both static and dynamic enum structures in Elixir.

Current section

Files

Jump to
enumex lib mix tasks enumex.static.gen.enum.ex
Raw

lib/mix/tasks/enumex.static.gen.enum.ex

# credo:disable-for-next-line Credo.Check.Refactor.ModuleDependencies
defmodule Mix.Tasks.Enumex.Static.Gen.Enum do
@shortdoc "Generates an enum module with static values."
args = [
%{help: "Specifies an enum module", value_name: "MODULE"},
%{help: "Specifies an enum name", value_name: "ENUM_NAME"},
%{help: "Values in format 'name' or 'name:index'", value_name: "VALUE_1 VALUE_2 … VALUE_N"}
]
flags = [
%{help: "Specifies file path for enum", long: "--file", short: "-f", value_name: "FILE_PATH"}
]
required_args = Enum.map(args, &Map.put(&1, :required, true))
task = Enumex.Generator.Task.new("enumex.static.gen.enum", required_args, flags)
if Mix.env() == :test do
try do
:meck.new(:io, [:unstick, :passthrough])
rescue
_exception -> :ok
end
:meck.expect(:io, :columns, fn -> {:ok, 80} end)
end
help = Enumex.Generator.Task.help(task, @shortdoc)
@moduledoc Enumex.Generator.Task.moduledoc(help)
use Mix.Task
alias Enumex.Generator.Task
alias Enumex.Value
alias Mix.Generator
require Generator
@help help
@opts [aliases: Task.aliases([:file]), strict: Task.options(file: :string)]
@task_name task.name
@version Task.version(task)
@impl Mix.Task
def run(args) do
Task.run(@task_name, args, @help, @version, @opts, fn
[_module], _opts -> Task.missing_args(@task_name, ["ENUM_NAME", "VALUE_1 VALUE_2 … VALUE_N"])
[_module, _name], _opts -> Task.missing_args(@task_name, ["VALUE_1 VALUE_2 … VALUE_N"])
[module, name | values], opts -> generate_enum(module, name, values, opts[:file])
end)
end
@spec generate_enum(String.t(), String.t(), [], String.t()) :: :ok
@spec generate_enum(String.t(), String.t(), [String.t()], String.t()) :: :ok
defp generate_enum(_module, _name, values, _file) when length(values) < 2, do: not_enough_values()
@spec generate_enum(String.t(), String.t(), OptionParser.argv(), String.t()) :: boolean()
defp generate_enum(module, name, values, file) do
Mix.Task.run("loadpaths")
file_path = file || gen_file_path(module)
values_string = Enum.map_join(values, "\n ", &prepare_value/1)
# credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom
contents = enum_template(module: module, name: String.to_atom(name), values: values_string)
file_path
|> tap(fn path ->
path
|> Path.dirname()
|> Generator.create_directory()
end)
|> tap(&Generator.create_file(&1, contents))
|> List.wrap()
end
@spec gen_file_path(String.t()) :: Path.t()
defp gen_file_path(module) do
module
|> Macro.underscore()
|> then(&Path.join(["lib", &1 <> ".ex"]))
end
@spec prepare_value(String.t()) :: String.t()
defp prepare_value(value) do
case String.split(value, ":") do
# credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom
[id] -> value_template(id: String.to_atom(id))
# credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom
[id, index] -> value_with_index_template(id: String.to_atom(id), index: index)
end
end
@spec not_enough_values :: :ok
defp not_enough_values, do: Task.print_errors(@task_name, ["expected at least 2 values"])
@spec enum_template(module: String.t(), name: atom(), values: String.t()) :: String.t()
Generator.embed_template(:enum, """
defmodule <%= @module %> do
use Enumex.Static, components: [
# Enumex.Static.Components.AbsinthePhase,
# Enumex.Static.Components.Constant,
# Enumex.Static.Components.EctoType,
# Enumex.Static.Components.Guards,
# Enumex.Static.Components.Index,
# Enumex.Static.Components.List,
# Enumex.Static.Components.Sort,
# Enumex.Static.Components.Typespecs
]
enum <%= inspect(@name) %> do
<%= @values %>
end
end
""")
@spec value_template(id: Value.id()) :: String.t()
Generator.embed_template(:value, ~s[value <%= inspect(@id) %>])
@spec value_template(id: Value.id(), index: Value.index()) :: String.t()
Generator.embed_template(:value_with_index, ~s[value <%= inspect(@id) %>, <%= @index %>])
end