defmodule Mix.Tasks.Rustler.New do use Mix.Task import Mix.Generator @shortdoc "Creates a new Rustler project." @moduledoc """ Generates boilerplate for a new Rustler project. Usage: ``` mix rustler.new [--module ] [--name ] [--otp-app ] ``` """ @basic [ {:eex, "basic/.cargo/config.toml", ".cargo/config.toml"}, {:eex, "basic/README.md", "README.md"}, {:eex, "basic/Cargo.toml.eex", "Cargo.toml"}, {:eex, "basic/src/lib.rs", "src/lib.rs"}, {:text, "basic/.gitignore", ".gitignore"} ] root = Path.join(:code.priv_dir(:rustler), "templates/") for {format, source, _} <- @basic do unless format == :keep do @external_resource Path.join(root, source) defp render(unquote(source)), do: unquote(File.read!(Path.join(root, source))) end end @switches [module: :string, name: :string, otp_app: :string] def run(argv) do {opts, _argv, _} = OptionParser.parse(argv, switches: @switches) module = case opts[:module] do nil -> prompt( "This is the name of the Elixir module the NIF module will be registered to.\n" <> "Module name" ) module -> module end name = case opts[:name] do nil -> prompt_default( "This is the name used for the generated Rust crate. The default is most likely fine.\n" <> "Library name", format_module_name_as_name(module) ) name -> name end otp_app = case opts[:otp_app] do nil -> Mix.Project.config() |> Keyword.get(:app) otp_app -> otp_app end check_module_name_validity!(module) path = Path.join([File.cwd!(), "native/", name]) new(otp_app, path, module, name, opts) end defp new(otp_app, path, module, name, _opts) do module_elixir = "Elixir." <> module binding = [ otp_app: otp_app, project_name: module_elixir, native_module: module_elixir, module: module, library_name: name, rustler_version: Rustler.rustler_version() ] copy_from(path, binding, @basic) Mix.Shell.IO.info([:green, "Ready to go! See #{path}/README.md for further instructions."]) end defp check_module_name_validity!(name) do unless name =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/ do Mix.raise( "Module name must be a valid Elixir alias (for example: Foo.Bar), got: #{inspect(name)}" ) end end defp format_module_name_as_name(module_name) do String.replace(String.downcase(module_name), ".", "_") end defp copy_from(target_dir, binding, mapping) when is_list(mapping) do for {format, source, target_path} <- mapping do target = Path.join(target_dir, target_path) case format do :keep -> File.mkdir_p!(target) :text -> create_file(target, render(source)) :eex -> contents = EEx.eval_string(render(source), binding, file: source) create_file(target, contents) end end end defp prompt_default(message, default) do response = prompt([message, :white, " (", default, ")"]) case response do "" -> default _ -> response end end defp prompt(message) do Mix.Shell.IO.print_app() resp = IO.gets(IO.ANSI.format([message, :white, " > "])) ?\n = :binary.last(resp) :binary.part(resp, {0, byte_size(resp) - 1}) end end