Current section
Files
Jump to
Current section
Files
lib/tux_new.ex
defmodule TuxNew do
@moduledoc """
Generator functions to create a tux-based CLI Elixir project.
"""
def validate_args([_path, "--sup"]), do: :ok
def validate_args([_path]), do: :ok
def validate_args(_), do: {:error, :invalid_args}
def validate_path(path) do
case File.exists?(path) do
true -> {:error, "Directory #{path} already exists. Aborting..."}
false -> :ok
end
end
def generate_app(args) do
case System.cmd("mix", ["new" | args]) do
{output, 0} ->
IO.puts(output)
:ok
{output, _} ->
IO.puts(output)
{:error, :mix_new_failed}
end
end
@doc """
Patch the application generated by `mix new`:
* add the `:tux` dependency
* generate tests
* add a ping command
* import formatting rules
* and more ...
"""
def patch_app([path | _]) do
main_module =
path
|> Path.basename()
|> Macro.camelize()
with :ok <- gen_main_module(path, main_module),
:ok <- gen_test_module(path, main_module),
:ok <- gen_cli_module(path, main_module),
:ok <- gen_cmd_module(path, main_module, "ping"),
:ok <- gen_cmd_module(path, main_module, "hello"),
:ok <- update_mix(path, main_module),
:ok <- update_formatter(path) do
show_next_steps(path)
end
end
defp eval_template(name, main_module) do
:code.priv_dir(:tux_new)
|> Path.join("templates")
|> Path.join(name)
|> EEx.eval_file(assigns: [main_module: main_module])
end
# Execute the given anonymous function and augment its
# returning error to include the current function name
defmacrop exec_fun(fun) do
quote do
case unquote(fun).() do
:ok ->
:ok
{:error, error} ->
{fname, _arity} = __ENV__.function()
{:error, "* invoking #{fname} returned: #{error}"}
end
end
end
# Generate the content for the app's main module
defp gen_main_module(path, main_module) do
main_file =
path
|> Path.basename()
|> then(&"#{&1}.ex")
|> then(&Path.join([path, "lib", &1]))
main_contents = eval_template("main.ex", main_module)
IO.puts("* updating #{main_file}")
File.write!(main_file, main_contents)
end
# Generate the project's main dispatcher
defp gen_cli_module(path, main_module) do
cli_file =
path
|> Path.basename()
|> then(&Path.join([path, "lib", &1, "cli.ex"]))
cli_file
|> Path.dirname()
|> File.mkdir_p!()
cli_contents = eval_template("cli.ex", main_module)
IO.puts("* creating #{cli_file}")
exec_fun(fn -> File.write(cli_file, cli_contents) end)
end
# Generate a module for testing the commands
defp gen_test_module(path, main_module) do
name = Path.basename(path)
test_file = Path.join([path, "test", "#{name}_test.exs"])
test_contents = eval_template("main_test.exs", main_module)
IO.puts("* updating #{test_file}")
exec_fun(fn -> File.write(test_file, test_contents) end)
end
# Update project's dependecies and set escript in mix.exs
defp update_mix(path, main_module) do
mix_file = Path.join(path, "mix.exs")
mix_contents = File.read!(mix_file)
pattern1 = "deps: deps()"
repl1 = "deps: deps(),\n escript: [main_module: #{main_module}]"
pattern2 = "# {:dep_from_hexpm, \"~> 0.3.0\"},"
repl2 = "{:tux, \"~> 0.4\"},\n {:ex_doc, \">= 0.0.0\", only: :docs}"
mix_contents
|> String.replace(pattern1, repl1)
|> String.replace(pattern2, repl2)
|> then(&exec_fun(fn -> File.write(mix_file, &1) end))
end
# Generate a command module inside the project
defp gen_cmd_module(path, main_module, cmd_name) do
name = Path.basename(path)
cmd_file = Path.join([path, "lib", name, "cli", "#{cmd_name}.ex"])
cmd_contents = eval_template(Path.join("cli", "#{cmd_name}.ex"), main_module)
IO.puts("* creating #{cmd_file}")
exec_fun(fn -> File.mkdir_p(Path.dirname(cmd_file)) end)
exec_fun(fn -> File.write(cmd_file, cmd_contents) end)
end
defp update_formatter(path) do
formatter_file = Path.join(path, ".formatter.exs")
formatter_contents = File.read!(formatter_file)
IO.puts("* updating #{formatter_file}")
formatter_contents
|> String.replace("[\n", "[\n import_deps: [:tux],\n")
|> then(&exec_fun(fn -> File.write(formatter_file, &1) end))
end
# Show how to build the command
defp show_next_steps(path) do
name = Path.basename(path)
"""
Build & run your escript:
cd #{path}
mix deps.get
mix escript.build
./#{name} ping
"""
|> IO.puts()
end
end