Continue parsing after --no-pry, closes #12072

defmodule Kernel.CLI do
  @moduledoc false

  @compile {:no_warn_undefined, [Logger, IEx]}

  @blank_config %{
    commands: [],
    output: ".",
    compile: [],
    no_halt: false,
    compiler_options: [],
    errors: [],
    pa: [],
    pz: [],
    verbose_compile: false,
    profile: nil,
    pry: false
  }

  @standalone_opts ["-h", "--help", "--short-version"]

  @doc """
  This is the API invoked by Elixir boot process.
  """
  def main(argv) do
    argv = for arg <- argv, do: IO.chardata_to_string(arg)

    {config, argv} = parse_argv(argv)
    System.argv(argv)
    System.no_halt(config.no_halt)

    if config.pry do
      Application.put_env(:elixir, :dbg_callback, {IEx.Pry, :dbg, []})
    end

    fun = fn _ ->
      errors = process_commands(config)

      if errors != [] do
        Enum.each(errors, &IO.puts(:stderr, &1))
        System.halt(1)
      end
    end

    run(fun)
  end

  @doc """
  Runs the given function by catching any failure
  and printing them to stdout. `at_exit` hooks are
  also invoked before exiting.

  This function is used by Elixir's CLI and also
  by escripts generated by Elixir.
  """
  def run(fun) do
    {ok_or_shutdown, status} = exec_fun(fun, {:ok, 0})

    if ok_or_shutdown == :shutdown or not System.no_halt() do
      {_, status} = at_exit({ok_or_shutdown, status})

      # Ensure Logger messages are flushed before halting
      case :erlang.whereis(Logger) do
        pid when is_pid(pid) -> Logger.flush()
        _ -> :ok
      end

      System.halt(status)
    end
  end

  @doc """
  Parses the CLI arguments. Made public for testing.
  """
  def parse_argv(argv) do
    parse_argv(argv, @blank_config)
  end

  @doc """
  Process CLI commands. Made public for testing.
  """
  def process_commands(config) do
    results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
    errors = for {:error, msg} <- results, do: msg
    Enum.reverse(config.errors, errors)
  end

  @doc """
  Shared helper for error formatting on CLI tools.
  """
  def format_error(kind, reason, stacktrace) do
    {blamed, stacktrace} = Exception.blame(kind, reason, stacktrace)

    iodata =
      case blamed do
        %FunctionClauseError{} ->
          formatted = Exception.format_banner(kind, reason, stacktrace)
          padded_blame = pad(FunctionClauseError.blame(blamed, &inspect/1, &blame_match/1))
          [formatted, padded_blame]

        _ ->
          Exception.format_banner(kind, blamed, stacktrace)
      end

    [iodata, ?\n, Exception.format_stacktrace(prune_stacktrace(stacktrace))]
  end

  @doc """
  Function invoked across nodes for `--rpc-eval`.
  """
  def rpc_eval(expr) do
    wrapper(fn -> Code.eval_string(expr) end)
  catch
    kind, reason -> {kind, reason, __STACKTRACE__}
  end

  ## Helpers

  defp at_exit(res) do
    hooks = :elixir_config.get_and_put(:at_exit, [])
    res = Enum.reduce(hooks, res, &exec_fun/2)
    if hooks == [], do: res, else: at_exit(res)
  end

  defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
    parent = self()

    {pid, ref} =
      spawn_monitor(fn ->
        try do
          fun.(elem(res, 1))
        catch
          :exit, {:shutdown, int} when is_integer(int) ->
            send(parent, {self(), {:shutdown, int}})
            exit({:shutdown, int})

          :exit, reason
          when reason == :normal
          when reason == :shutdown
          when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
            send(parent, {self(), {:shutdown, 0}})
            exit(reason)

          kind, reason ->
            print_error(kind, reason, __STACKTRACE__)
            send(parent, {self(), {:shutdown, 1}})
            exit(to_exit(kind, reason, __STACKTRACE__))
        else
          _ ->
            send(parent, {self(), res})
        end
      end)

    receive do
      {^pid, res} ->
        :erlang.demonitor(ref, [:flush])
        res

      {:DOWN, ^ref, _, _, other} ->
        print_error({:EXIT, pid}, other, [])
        {:shutdown, 1}
    end
  end

  defp to_exit(:throw, reason, stack), do: {{:nocatch, reason}, stack}
  defp to_exit(:error, reason, stack), do: {reason, stack}
  defp to_exit(:exit, reason, _stack), do: reason

  defp shared_option?(list, config, callback) do
    case parse_shared(list, config) do
      {[h | hs], _} when h == hd(list) ->
        new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
        callback.(hs, new_config)

      {new_list, new_config} ->
        callback.(new_list, new_config)
    end
  end

  ## Error handling

  defp print_error(kind, reason, stacktrace) do
    IO.write(:stderr, format_error(kind, reason, stacktrace))
  end

  defp blame_match(%{match?: true, node: node}), do: blame_ansi(:normal, "+", node)
  defp blame_match(%{match?: false, node: node}), do: blame_ansi(:red, "-", node)

  defp blame_ansi(color, no_ansi, node) do
    if IO.ANSI.enabled?() do
      [color | Macro.to_string(node)]
      |> IO.ANSI.format(true)
      |> IO.iodata_to_binary()
    else
      no_ansi <> Macro.to_string(node) <> no_ansi
    end
  end

  defp pad(string) do
    "    " <> String.replace(string, "\n", "\n    ")
  end

  @elixir_internals [:elixir, :elixir_aliases, :elixir_expand, :elixir_compiler, :elixir_module] ++
                      [:elixir_clauses, :elixir_lexical, :elixir_def, :elixir_map, :elixir_locals] ++
                      [:elixir_erl, :elixir_erl_clauses, :elixir_erl_compiler, :elixir_erl_pass] ++
                      [Kernel.ErrorHandler, Module.ParallelChecker]

  defp prune_stacktrace([{mod, _, _, _} | t]) when mod in @elixir_internals do
    prune_stacktrace(t)
  end

  defp prune_stacktrace([{__MODULE__, :wrapper, 1, _} | _]) do
    []
  end

  defp prune_stacktrace([h | t]) do
    [h | prune_stacktrace(t)]
  end

  defp prune_stacktrace([]) do
    []
  end

  # Parse shared options

  defp halt_standalone(opt) do
    IO.puts(:stderr, "#{opt} : Standalone options can't be combined with other options")
    System.halt(1)
  end

  defp parse_shared([opt | _], _config) when opt in @standalone_opts do
    halt_standalone(opt)
  end

  defp parse_shared([opt | t], _config) when opt in ["-v", "--version"] do
    if function_exported?(IEx, :started?, 0) and IEx.started?() do
      IO.puts("IEx " <> System.build_info()[:build])
    else
      IO.puts(:erlang.system_info(:system_version))
      IO.puts("Elixir " <> System.build_info()[:build])
    end

    if t != [] do
      halt_standalone(opt)
    else
      System.halt(0)
    end
  end

  defp parse_shared(["-pa", h | t], config) do
    paths = expand_code_path(h)
    Enum.each(paths, &:code.add_patha/1)
    parse_shared(t, %{config | pa: config.pa ++ paths})
  end

  defp parse_shared(["-pz", h | t], config) do
    paths = expand_code_path(h)
    Enum.each(paths, &:code.add_pathz/1)
    parse_shared(t, %{config | pz: config.pz ++ paths})
  end

  defp parse_shared(["--app", h | t], config) do
    parse_shared(t, %{config | commands: [{:app, h} | config.commands]})
  end

  defp parse_shared(["--no-halt" | t], config) do
    parse_shared(t, %{config | no_halt: true})
  end

  defp parse_shared(["-e", h | t], config) do
    parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
  end

  defp parse_shared(["--eval", h | t], config) do
    parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
  end

  defp parse_shared(["--rpc-eval", node, h | t], config) do
    node = append_hostname(node)
    parse_shared(t, %{config | commands: [{:rpc_eval, node, h} | config.commands]})
  end

  defp parse_shared(["--rpc-eval" | _], config) do
    new_config = %{config | errors: ["--rpc-eval : wrong number of arguments" | config.errors]}
    {[], new_config}
  end

  defp parse_shared(["-r", h | t], config) do
    parse_shared(t, %{config | commands: [{:require, h} | config.commands]})
  end

  defp parse_shared(["-pr", h | t], config) do
    parse_shared(t, %{config | commands: [{:parallel_require, h} | config.commands]})
  end

  defp parse_shared(list, config) do
    {list, config}
  end

  defp append_hostname(node) do
    with :nomatch <- :string.find(node, "@"),
         <<suffix::binary>> <- :string.find(Atom.to_string(:net_kernel.nodename()), "@") do
      node <> suffix
    else
      _ -> node
    end
  end

  defp expand_code_path(path) do
    path = Path.expand(path)

    case Path.wildcard(path) do
      [] -> [to_charlist(path)]
      list -> Enum.map(list, &to_charlist/1)
    end
  end

  # Process init options

  defp parse_argv(["--" | t], config) do
    {config, t}
  end

  defp parse_argv(["+elixirc" | t], config) do
    parse_compiler(t, config)
  end

  defp parse_argv(["+iex" | t], config) do
    parse_iex(t, %{config | pry: true})
  end

  defp parse_argv(["-S", h | t], config) do
    {%{config | commands: [{:script, h} | config.commands]}, t}
  end

  defp parse_argv([h | t] = list, config) do
    case h do
      "-" <> _ ->
        shared_option?(list, config, &parse_argv(&1, &2))

      _ ->
        if List.keymember?(config.commands, :eval, 0) do
          {config, list}
        else
          {%{config | commands: [{:file, h} | config.commands]}, t}
        end
    end
  end

  defp parse_argv([], config) do
    {config, []}
  end

  # Parse compiler options

  defp parse_compiler(["--" | t], config) do
    {config, t}
  end

  defp parse_compiler(["-o", h | t], config) do
    parse_compiler(t, %{config | output: h})
  end

  defp parse_compiler(["--no-docs" | t], config) do
    parse_compiler(t, %{config | compiler_options: [{:docs, false} | config.compiler_options]})
  end

  defp parse_compiler(["--no-debug-info" | t], config) do
    compiler_options = [{:debug_info, false} | config.compiler_options]
    parse_compiler(t, %{config | compiler_options: compiler_options})
  end

  defp parse_compiler(["--ignore-module-conflict" | t], config) do
    compiler_options = [{:ignore_module_conflict, true} | config.compiler_options]
    parse_compiler(t, %{config | compiler_options: compiler_options})
  end

  defp parse_compiler(["--warnings-as-errors" | t], config) do
    compiler_options = [{:warnings_as_errors, true} | config.compiler_options]
    parse_compiler(t, %{config | compiler_options: compiler_options})
  end

  defp parse_compiler(["--verbose" | t], config) do
    parse_compiler(t, %{config | verbose_compile: true})
  end

  # Private compiler options

  defp parse_compiler(["--profile", "time" | t], config) do
    parse_compiler(t, %{config | profile: :time})
  end

  defp parse_compiler([h | t] = list, config) do
    case h do
      "-" <> _ ->
        shared_option?(list, config, &parse_compiler(&1, &2))

      _ ->
        pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
        parse_compiler(t, %{config | compile: [pattern | config.compile]})
    end
  end

  defp parse_compiler([], config) do
    {%{config | commands: [{:compile, config.compile} | config.commands]}, []}
  end

  # Parse IEx options

  defp parse_iex(["--" | t], config) do
    {config, t}
  end

  defp parse_iex(["-S", h | t], config) do
    {%{config | commands: [{:script, h} | config.commands]}, t}
  end

  # These clauses are here so that Kernel.CLI does not error out with "unknown option"
  defp parse_iex(["--dot-iex", _ | t], config), do: parse_iex(t, config)
  defp parse_iex(["--remsh", _ | t], config), do: parse_iex(t, config)

  defp parse_iex(["--no-pry" | t], config), do: parse_iex(t, %{config | pry: false})

  defp parse_iex([h | t] = list, config) do
    case h do
      "-" <> _ -> shared_option?(list, config, &parse_iex(&1, &2))
      _ -> {%{config | commands: [{:file, h} | config.commands]}, t}
    end
  end

  defp parse_iex([], config) do
    {config, []}
  end

  # Process commands

  defp process_command({:cookie, h}, _config) do
    wrapper(fn -> Node.set_cookie(String.to_atom(h)) end)
  end

  defp process_command({:eval, expr}, _config) when is_binary(expr) do
    wrapper(fn -> Code.eval_string(expr, []) end)
  end

  defp process_command({:rpc_eval, node, expr}, _config) when is_binary(expr) do
    node = String.to_atom(node)

    # Explicitly connect the node in case the rpc node was started with --sname/--name undefined.
    _ = :net_kernel.connect_node(node)

    case :rpc.call(node, __MODULE__, :rpc_eval, [expr]) do
      :ok ->
        :ok

      {:badrpc, {:EXIT, exit}} ->
        Process.exit(self(), exit)

      {:badrpc, reason} ->
        if reason == :nodedown and :net_kernel.nodename() == :ignored do
          {:error,
           "--rpc-eval : Cannot run --rpc-eval if the node is not alive (set --name or --sname)"}
        else
          {:error, "--rpc-eval : RPC failed with reason #{inspect(reason)}"}
        end

      {kind, error, stack} ->
        :erlang.raise(kind, error, stack)
    end
  end

  defp process_command({:app, app}, _config) when is_binary(app) do
    case Application.ensure_all_started(String.to_atom(app)) do
      {:error, {app, reason}} ->
        msg = "--app : Could not start application #{app}: " <> Application.format_error(reason)
        {:error, msg}

      {:ok, _} ->
        :ok
    end
  end

  defp process_command({:script, file}, _config) when is_binary(file) do
    if exec = find_elixir_executable(file) do
      wrapper(fn -> Code.require_file(exec) end)
    else
      {:error, "-S : Could not find executable #{file}"}
    end
  end

  defp process_command({:file, file}, _config) when is_binary(file) do
    if File.regular?(file) do
      wrapper(fn -> Code.require_file(file) end)
    else
      {:error, "No file named #{file}"}
    end
  end

  defp process_command({:require, pattern}, _config) when is_binary(pattern) do
    files = filter_patterns(pattern)

    if files != [] do
      wrapper(fn -> Enum.map(files, &Code.require_file(&1)) end)
    else
      {:error, "-r : No files matched pattern #{pattern}"}
    end
  end

  defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
    files = filter_patterns(pattern)

    if files != [] do
      wrapper(fn ->
        case Kernel.ParallelCompiler.require(files) do
          {:ok, _, _} -> :ok
          {:error, _, _} -> exit({:shutdown, 1})
        end
      end)
    else
      {:error, "-pr : No files matched pattern #{pattern}"}
    end
  end

  defp process_command({:compile, patterns}, config) do
    # If ensuring the dir returns an error no files will be found.
    _ = :filelib.ensure_dir(:filename.join(config.output, "."))

    case filter_multiple_patterns(patterns) do
      {:ok, []} ->
        {:error, "No files matched provided patterns"}

      {:ok, files} ->
        wrapper(fn ->
          Code.compiler_options(config.compiler_options)

          verbose_opts =
            if config.verbose_compile do
              [each_file: &IO.puts("Compiling #{Path.relative_to_cwd(&1)}")]
            else
              [
                each_long_compilation:
                  &IO.puts("Compiling #{Path.relative_to_cwd(&1)} (it's taking more than 10s)")
              ]
            end

          profile_opts =
            if config.profile do
              [profile: config.profile]
            else
              []
            end

          opts = verbose_opts ++ profile_opts

          case Kernel.ParallelCompiler.compile_to_path(files, config.output, opts) do
            {:ok, _, _} -> :ok
            {:error, _, _} -> exit({:shutdown, 1})
          end
        end)

      {:missing, missing} ->
        {:error, "No files matched pattern(s) #{Enum.join(missing, ",")}"}
    end
  end

  defp filter_patterns(pattern) do
    pattern
    |> Path.expand()
    |> Path.wildcard()
    |> :lists.usort()
    |> Enum.filter(&File.regular?/1)
  end

  defp filter_multiple_patterns(patterns) do
    {files, missing} =
      Enum.reduce(patterns, {[], []}, fn pattern, {files, missing} ->
        case filter_patterns(pattern) do
          [] -> {files, [pattern | missing]}
          match -> {match ++ files, missing}
        end
      end)

    case missing do
      [] -> {:ok, :lists.usort(files)}
      _ -> {:missing, :lists.usort(missing)}
    end
  end

  defp wrapper(fun) do
    _ = fun.()
    :ok
  end

  defp find_elixir_executable(file) do
    if exec = System.find_executable(file) do
      # If we are on Windows, the executable is going to be
      # a .bat file that must be in the same directory as
      # the actual Elixir executable.
      case :os.type() do
        {:win32, _} ->
          base = Path.rootname(exec)
          if File.regular?(base), do: base, else: exec

        _ ->
          exec
      end
    end
  end
end

defmodule Kernel.CLI do
  @moduledoc false

  @compile {:no_warn_undefined, [Logger, IEx]}

  @blank_config %{
    commands: [],
    output: ".",
    compile: [],
    no_halt: false,
    compiler_options: [],
    errors: [],
    pa: [],
    pz: [],
    verbose_compile: false,
    profile: nil,
    pry: false
  }

  @standalone_opts ["-h", "--help", "--short-version"]

  @doc """
  This is the API invoked by Elixir boot process.
  """
  def main(argv) do
    argv = for arg <- argv, do: IO.chardata_to_string(arg)

    {config, argv} = parse_argv(argv)
    System.argv(argv)
    System.no_halt(config.no_halt)

    if config.pry do
      Application.put_env(:elixir, :dbg_callback, {IEx.Pry, :dbg, []})
    end

    fun = fn _ ->
      errors = process_commands(config)

      if errors != [] do
        Enum.each(errors, &IO.puts(:stderr, &1))
        System.halt(1)
      end
    end

    run(fun)
  end

  @doc """
  Runs the given function by catching any failure
  and printing them to stdout. `at_exit` hooks are
  also invoked before exiting.

  This function is used by Elixir's CLI and also
  by escripts generated by Elixir.
  """
  def run(fun) do
    {ok_or_shutdown, status} = exec_fun(fun, {:ok, 0})

    if ok_or_shutdown == :shutdown or not System.no_halt() do
      {_, status} = at_exit({ok_or_shutdown, status})

      # Ensure Logger messages are flushed before halting
      case :erlang.whereis(Logger) do
        pid when is_pid(pid) -> Logger.flush()
        _ -> :ok
      end

      System.halt(status)
    end
  end

  @doc """
  Parses the CLI arguments. Made public for testing.
  """
  def parse_argv(argv) do
    parse_argv(argv, @blank_config)
  end

  @doc """
  Process CLI commands. Made public for testing.
  """
  def process_commands(config) do
    results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
    errors = for {:error, msg} <- results, do: msg
    Enum.reverse(config.errors, errors)
  end

  @doc """
  Shared helper for error formatting on CLI tools.
  """
  def format_error(kind, reason, stacktrace) do
    {blamed, stacktrace} = Exception.blame(kind, reason, stacktrace)

    iodata =
      case blamed do
        %FunctionClauseError{} ->
          formatted = Exception.format_banner(kind, reason, stacktrace)
          padded_blame = pad(FunctionClauseError.blame(blamed, &inspect/1, &blame_match/1))
          [formatted, padded_blame]

        _ ->
          Exception.format_banner(kind, blamed, stacktrace)
      end

    [iodata, ?\n, Exception.format_stacktrace(prune_stacktrace(stacktrace))]
  end

  @doc """
  Function invoked across nodes for `--rpc-eval`.
  """
  def rpc_eval(expr) do
    wrapper(fn -> Code.eval_string(expr) end)
  catch
    kind, reason -> {kind, reason, __STACKTRACE__}
  end

  ## Helpers

  defp at_exit(res) do
    hooks = :elixir_config.get_and_put(:at_exit, [])
    res = Enum.reduce(hooks, res, &exec_fun/2)
    if hooks == [], do: res, else: at_exit(res)
  end

  defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
    parent = self()

    {pid, ref} =
      spawn_monitor(fn ->
        try do
          fun.(elem(res, 1))
        catch
          :exit, {:shutdown, int} when is_integer(int) ->
            send(parent, {self(), {:shutdown, int}})
            exit({:shutdown, int})

          :exit, reason
          when reason == :normal
          when reason == :shutdown
          when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
            send(parent, {self(), {:shutdown, 0}})
            exit(reason)

          kind, reason ->
            print_error(kind, reason, __STACKTRACE__)
            send(parent, {self(), {:shutdown, 1}})
            exit(to_exit(kind, reason, __STACKTRACE__))
        else
          _ ->
            send(parent, {self(), res})
        end
      end)

    receive do
      {^pid, res} ->
        :erlang.demonitor(ref, [:flush])
        res

      {:DOWN, ^ref, _, _, other} ->
        print_error({:EXIT, pid}, other, [])
        {:shutdown, 1}
    end
  end

  defp to_exit(:throw, reason, stack), do: {{:nocatch, reason}, stack}
  defp to_exit(:error, reason, stack), do: {reason, stack}
  defp to_exit(:exit, reason, _stack), do: reason

  defp shared_option?(list, config, callback) do
    case parse_shared(list, config) do
      {[h | hs], _} when h == hd(list) ->
        new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
        callback.(hs, new_config)

      {new_list, new_config} ->
        callback.(new_list, new_config)
    end
  end

  ## Error handling

  defp print_error(kind, reason, stacktrace) do
    IO.write(:stderr, format_error(kind, reason, stacktrace))
  end

  defp blame_match(%{match?: true, node: node}), do: blame_ansi(:normal, "+", node)
  defp blame_match(%{match?: false, node: node}), do: blame_ansi(:red, "-", node)

  defp blame_ansi(color, no_ansi, node) do
    if IO.ANSI.enabled?() do
      [color | Macro.to_string(node)]
      |> IO.ANSI.format(true)
      |> IO.iodata_to_binary()
    else
      no_ansi <> Macro.to_string(node) <> no_ansi
    end
  end

  defp pad(string) do
    "    " <> String.replace(string, "\n", "\n    ")
  end

  @elixir_internals [:elixir, :elixir_aliases, :elixir_expand, :elixir_compiler, :elixir_module] ++
                      [:elixir_clauses, :elixir_lexical, :elixir_def, :elixir_map, :elixir_locals] ++
                      [:elixir_erl, :elixir_erl_clauses, :elixir_erl_compiler, :elixir_erl_pass] ++
                      [Kernel.ErrorHandler, Module.ParallelChecker]

  defp prune_stacktrace([{mod, _, _, _} | t]) when mod in @elixir_internals do
    prune_stacktrace(t)
  end

  defp prune_stacktrace([{__MODULE__, :wrapper, 1, _} | _]) do
    []
  end

  defp prune_stacktrace([h | t]) do
    [h | prune_stacktrace(t)]
  end

  defp prune_stacktrace([]) do
    []
  end

  # Parse shared options

  defp halt_standalone(opt) do
    IO.puts(:stderr, "#{opt} : Standalone options can't be combined with other options")
    System.halt(1)
  end

  defp parse_shared([opt | _], _config) when opt in @standalone_opts do
    halt_standalone(opt)
  end

  defp parse_shared([opt | t], _config) when opt in ["-v", "--version"] do
    if function_exported?(IEx, :started?, 0) and IEx.started?() do
      IO.puts("IEx " <> System.build_info()[:build])
    else
      IO.puts(:erlang.system_info(:system_version))
      IO.puts("Elixir " <> System.build_info()[:build])
    end

    if t != [] do
      halt_standalone(opt)
    else
      System.halt(0)
    end
  end

  defp parse_shared(["-pa", h | t], config) do
    paths = expand_code_path(h)
    Enum.each(paths, &:code.add_patha/1)
    parse_shared(t, %{config | pa: config.pa ++ paths})
  end

  defp parse_shared(["-pz", h | t], config) do
    paths = expand_code_path(h)
    Enum.each(paths, &:code.add_pathz/1)
    parse_shared(t, %{config | pz: config.pz ++ paths})
  end

  defp parse_shared(["--app", h | t], config) do
    parse_shared(t, %{config | commands: [{:app, h} | config.commands]})
  end

  defp parse_shared(["--no-halt" | t], config) do
    parse_shared(t, %{config | no_halt: true})
  end

  defp parse_shared(["-e", h | t], config) do
    parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
  end

  defp parse_shared(["--eval", h | t], config) do
    parse_shared(t, %{config | commands: [{:eval, h} | config.commands]})
  end

  defp parse_shared(["--rpc-eval", node, h | t], config) do
    node = append_hostname(node)
    parse_shared(t, %{config | commands: [{:rpc_eval, node, h} | config.commands]})
  end

  defp parse_shared(["--rpc-eval" | _], config) do
    new_config = %{config | errors: ["--rpc-eval : wrong number of arguments" | config.errors]}
    {[], new_config}
  end

  defp parse_shared(["-r", h | t], config) do
    parse_shared(t, %{config | commands: [{:require, h} | config.commands]})
  end

  defp parse_shared(["-pr", h | t], config) do
    parse_shared(t, %{config | commands: [{:parallel_require, h} | config.commands]})
  end

  defp parse_shared(list, config) do
    {list, config}
  end

  defp append_hostname(node) do
    with :nomatch <- :string.find(node, "@"),
         <<suffix::binary>> <- :string.find(Atom.to_string(:net_kernel.nodename()), "@") do
      node <> suffix
    else
      _ -> node
    end
  end

  defp expand_code_path(path) do
    path = Path.expand(path)

    case Path.wildcard(path) do
      [] -> [to_charlist(path)]
      list -> Enum.map(list, &to_charlist/1)
    end
  end

  # Process init options

  defp parse_argv(["--" | t], config) do
    {config, t}
  end

  defp parse_argv(["+elixirc" | t], config) do
    parse_compiler(t, config)
  end

  defp parse_argv(["+iex" | t], config) do
    parse_iex(t, %{config | pry: true})
  end

  defp parse_argv(["-S", h | t], config) do
    {%{config | commands: [{:script, h} | config.commands]}, t}
  end

  defp parse_argv([h | t] = list, config) do
    case h do
      "-" <> _ ->
        shared_option?(list, config, &parse_argv(&1, &2))

      _ ->
        if List.keymember?(config.commands, :eval, 0) do
          {config, list}
        else
          {%{config | commands: [{:file, h} | config.commands]}, t}
        end
    end
  end

  defp parse_argv([], config) do
    {config, []}
  end

  # Parse compiler options

  defp parse_compiler(["--" | t], config) do
    {config, t}
  end

  defp parse_compiler(["-o", h | t], config) do
    parse_compiler(t, %{config | output: h})
  end

  defp parse_compiler(["--no-docs" | t], config) do
    parse_compiler(t, %{config | compiler_options: [{:docs, false} | config.compiler_options]})
  end

  defp parse_compiler(["--no-debug-info" | t], config) do
    compiler_options = [{:debug_info, false} | config.compiler_options]
    parse_compiler(t, %{config | compiler_options: compiler_options})
  end

  defp parse_compiler(["--ignore-module-conflict" | t], config) do
    compiler_options = [{:ignore_module_conflict, true} | config.compiler_options]
    parse_compiler(t, %{config | compiler_options: compiler_options})
  end

  defp parse_compiler(["--warnings-as-errors" | t], config) do
    compiler_options = [{:warnings_as_errors, true} | config.compiler_options]
    parse_compiler(t, %{config | compiler_options: compiler_options})
  end

  defp parse_compiler(["--verbose" | t], config) do
    parse_compiler(t, %{config | verbose_compile: true})
  end

  # Private compiler options

  defp parse_compiler(["--profile", "time" | t], config) do
    parse_compiler(t, %{config | profile: :time})
  end

  defp parse_compiler([h | t] = list, config) do
    case h do
      "-" <> _ ->
        shared_option?(list, config, &parse_compiler(&1, &2))

      _ ->
        pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
        parse_compiler(t, %{config | compile: [pattern | config.compile]})
    end
  end

  defp parse_compiler([], config) do
    {%{config | commands: [{:compile, config.compile} | config.commands]}, []}
  end

  # Parse IEx options

  defp parse_iex(["--" | t], config) do
    {config, t}
  end

  # These clauses are here so that Kernel.CLI does not error out with "unknown option"
  defp parse_iex(["--dot-iex", _ | t], config), do: parse_iex(t, config)
  defp parse_iex(["--remsh", _ | t], config), do: parse_iex(t, config)

  defp parse_iex(["--no-pry" | t], config) do
    {%{config | pry: false}, t}
  end

  defp parse_iex(["-S", h | t], config) do
    {%{config | commands: [{:script, h} | config.commands]}, t}
  end

  defp parse_iex([h | t] = list, config) do
    case h do
      "-" <> _ -> shared_option?(list, config, &parse_iex(&1, &2))
      _ -> {%{config | commands: [{:file, h} | config.commands]}, t}
    end
  end

  defp parse_iex([], config) do
    {config, []}
  end

  # Process commands

  defp process_command({:cookie, h}, _config) do
    wrapper(fn -> Node.set_cookie(String.to_atom(h)) end)
  end

  defp process_command({:eval, expr}, _config) when is_binary(expr) do
    wrapper(fn -> Code.eval_string(expr, []) end)
  end

  defp process_command({:rpc_eval, node, expr}, _config) when is_binary(expr) do
    node = String.to_atom(node)

    # Explicitly connect the node in case the rpc node was started with --sname/--name undefined.
    _ = :net_kernel.connect_node(node)

    case :rpc.call(node, __MODULE__, :rpc_eval, [expr]) do
      :ok ->
        :ok

      {:badrpc, {:EXIT, exit}} ->
        Process.exit(self(), exit)

      {:badrpc, reason} ->
        if reason == :nodedown and :net_kernel.nodename() == :ignored do
          {:error,
           "--rpc-eval : Cannot run --rpc-eval if the node is not alive (set --name or --sname)"}
        else
          {:error, "--rpc-eval : RPC failed with reason #{inspect(reason)}"}
        end

      {kind, error, stack} ->
        :erlang.raise(kind, error, stack)
    end
  end

  defp process_command({:app, app}, _config) when is_binary(app) do
    case Application.ensure_all_started(String.to_atom(app)) do
      {:error, {app, reason}} ->
        msg = "--app : Could not start application #{app}: " <> Application.format_error(reason)
        {:error, msg}

      {:ok, _} ->
        :ok
    end
  end

  defp process_command({:script, file}, _config) when is_binary(file) do
    if exec = find_elixir_executable(file) do
      wrapper(fn -> Code.require_file(exec) end)
    else
      {:error, "-S : Could not find executable #{file}"}
    end
  end

  defp process_command({:file, file}, _config) when is_binary(file) do
    if File.regular?(file) do
      wrapper(fn -> Code.require_file(file) end)
    else
      {:error, "No file named #{file}"}
    end
  end

  defp process_command({:require, pattern}, _config) when is_binary(pattern) do
    files = filter_patterns(pattern)

    if files != [] do
      wrapper(fn -> Enum.map(files, &Code.require_file(&1)) end)
    else
      {:error, "-r : No files matched pattern #{pattern}"}
    end
  end

  defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
    files = filter_patterns(pattern)

    if files != [] do
      wrapper(fn ->
        case Kernel.ParallelCompiler.require(files) do
          {:ok, _, _} -> :ok
          {:error, _, _} -> exit({:shutdown, 1})
        end
      end)
    else
      {:error, "-pr : No files matched pattern #{pattern}"}
    end
  end

  defp process_command({:compile, patterns}, config) do
    # If ensuring the dir returns an error no files will be found.
    _ = :filelib.ensure_dir(:filename.join(config.output, "."))

    case filter_multiple_patterns(patterns) do
      {:ok, []} ->
        {:error, "No files matched provided patterns"}

      {:ok, files} ->
        wrapper(fn ->
          Code.compiler_options(config.compiler_options)

          verbose_opts =
            if config.verbose_compile do
              [each_file: &IO.puts("Compiling #{Path.relative_to_cwd(&1)}")]
            else
              [
                each_long_compilation:
                  &IO.puts("Compiling #{Path.relative_to_cwd(&1)} (it's taking more than 10s)")
              ]
            end

          profile_opts =
            if config.profile do
              [profile: config.profile]
            else
              []
            end

          opts = verbose_opts ++ profile_opts

          case Kernel.ParallelCompiler.compile_to_path(files, config.output, opts) do
            {:ok, _, _} -> :ok
            {:error, _, _} -> exit({:shutdown, 1})
          end
        end)

      {:missing, missing} ->
        {:error, "No files matched pattern(s) #{Enum.join(missing, ",")}"}
    end
  end

  defp filter_patterns(pattern) do
    pattern
    |> Path.expand()
    |> Path.wildcard()
    |> :lists.usort()
    |> Enum.filter(&File.regular?/1)
  end

  defp filter_multiple_patterns(patterns) do
    {files, missing} =
      Enum.reduce(patterns, {[], []}, fn pattern, {files, missing} ->
        case filter_patterns(pattern) do
          [] -> {files, [pattern | missing]}
          match -> {match ++ files, missing}
        end
      end)

    case missing do
      [] -> {:ok, :lists.usort(files)}
      _ -> {:missing, :lists.usort(missing)}
    end
  end

  defp wrapper(fun) do
    _ = fun.()
    :ok
  end

  defp find_elixir_executable(file) do
    if exec = System.find_executable(file) do
      # If we are on Windows, the executable is going to be
      # a .bat file that must be in the same directory as
      # the actual Elixir executable.
      case :os.type() do
        {:win32, _} ->
          base = Path.rootname(exec)
          if File.regular?(base), do: base, else: exec

        _ ->
          exec
      end
    end
  end
end


Keep .gitignore ordering

Keep .gitignore ordering

defmodule Mix.Tasks.New do
  use Mix.Task

  import Mix.Generator
  import Mix.Utils, only: [camelize: 1, underscore: 1]

  @shortdoc "Create a new Elixir project"

  @moduledoc """
  Creates a new Elixir project.
  It expects the path of the project as argument.

      mix new PATH [--sup] [--module MODULE] [--app APP] [--umbrella]

  A project at the given PATH  will be created. The
  application name and module name will be retrieved
  from the path, unless `--module` or `--app` is given.

  A `--sup` option can be given to generate an OTP application
  skeleton including a supervision tree. Normally an app is
  generated without a supervisor and without the app callback.

  An `--umbrella` option can be given to generate an
  umbrella project.

  An `--app` option can be given in order to
  name the OTP application for the project.

  A `--module` option can be given in order
  to name the modules in the generated code skeleton.

  ## Examples

      mix new hello_world

  Is equivalent to:

      mix new hello_world --module HelloWorld

  To generate an app with supervisor and application callback:

      mix new hello_world --sup

  """

  @spec run(OptionParser.argv) :: :ok
  def run(argv) do
    {opts, argv, _} = OptionParser.parse(argv, switches: [sup: :boolean, umbrella: :boolean])

    case argv do
      [] ->
        Mix.raise "Expected PATH to be given, please use `mix new PATH`"
      [path|_] ->
        app = opts[:app] || Path.basename(Path.expand(path))
        check_application_name!(app, !!opts[:app])
        mod = opts[:module] || camelize(app)
        check_mod_name!(mod)
        File.mkdir_p!(path)

        File.cd! path, fn ->
          if opts[:umbrella] do
            do_generate_umbrella(app, mod, path, opts)
          else
            do_generate(app, mod, path, opts)
          end
        end
    end
  end

  defp do_generate(app, mod, path, opts) do
    assigns = [app: app, mod: mod, otp_app: otp_app(mod, !!opts[:sup]),
               version: get_version(System.version)]

    create_file "README.md",  readme_template(assigns)
    create_file ".gitignore", gitignore_text

    if in_umbrella? do
      create_file "mix.exs", mixfile_apps_template(assigns)
    else
      create_file "mix.exs", mixfile_template(assigns)
    end

    create_directory "config"
    create_file "config/config.exs", config_template(assigns)

    create_directory "lib"

    if opts[:sup] do
      create_file "lib/#{app}.ex", lib_sup_template(assigns)
    else
      create_file "lib/#{app}.ex", lib_template(assigns)
    end

    create_directory "test"
    create_file "test/test_helper.exs", test_helper_template(assigns)
    create_file "test/#{app}_test.exs", test_template(assigns)

    Mix.shell.info """

    Your mix project was created successfully.
    You can use mix to compile it, test it, and more:

        cd #{path}
        mix test

    Run `mix help` for more commands.
    """
  end

  defp otp_app(_mod, false) do
    "    [applications: [:logger]]"
  end

  defp otp_app(mod, true) do
    "    [applications: [:logger],\n     mod: {#{mod}, []}]"
  end

  defp do_generate_umbrella(_app, mod, path, _opts) do
    assigns = [mod: mod]

    create_file ".gitignore", gitignore_text
    create_file "README.md", readme_template(assigns)
    create_file "mix.exs", mixfile_umbrella_template(assigns)

    create_directory "apps"

    create_directory "config"
    create_file "config/config.exs",
      config_template(assigns) <> config_umbrella_template(assigns)

    Mix.shell.info """

    Your umbrella project was created successfully.
    Inside your project, you will find an apps/ directory
    where you can create and host many apps:

        cd #{path}
        cd apps
        mix new my_app

    Commands like `mix compile` and `mix test` when executed
    in the umbrella project root will automatically run
    for each application in the apps/ directory.
    """
  end

  defp check_application_name!(name, from_app_flag) do
    unless name =~ ~r/^[a-z][\w_]*$/ do
      Mix.raise "Application name must start with a letter and have only lowercase " <>
                "letters, numbers and underscore, got: #{inspect name}" <>
                (if !from_app_flag do
                  ". The application name is inferred from the path, if you'd like to " <>
                  "explicitly name the application then use the `--app APP` option."
                else
                  ""
                end)
    end
  end

  defp check_mod_name!(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 get_version(version) do
    {:ok, version} = Version.parse(version)
    "#{version.major}.#{version.minor}" <>
      case version.pre do
        [h|_] -> "-#{h}"
        []    -> ""
      end
  end

  defp in_umbrella? do
    apps = Path.dirname(File.cwd!)

    try do
      Mix.Project.in_project(:umbrella_check, "../..", fn _ ->
        path = Mix.Project.config[:apps_path]
        path && Path.expand(path) == apps
      end)
    catch
      _, _ -> false
    end
  end

   embed_template :readme, """
   <%= @mod %>
   <%= String.duplicate("=", String.length(@mod)) %>

   ** TODO: Add description **
   """

   embed_text :gitignore, """
   /_build
   /cover
   /deps
   erl_crash.dump
   *.ez
   """

  embed_template :mixfile, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.0.1",
       elixir: "~> <%= @version %>",
       deps: deps]
    end

    # Configuration for the OTP application
    #
    # Type `mix help compile.app` for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type `mix help deps` for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_apps, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.0.1",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",
       elixir: "~> <%= @version %>",
       deps: deps]
    end

    # Configuration for the OTP application
    #
    # Type `mix help compile.app` for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # To depend on another app inside the umbrella:
    #
    #   {:myapp, in_umbrella: true}
    #
    # Type `mix help deps` for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_umbrella, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [apps_path: "apps",
       deps: deps]
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type `mix help deps` for more examples and options.
    #
    # Dependencies listed here are available only for this project
    # and cannot be accessed from applications inside the apps folder
    defp deps do
      []
    end
  end
  """

  embed_template :config, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # This configuration is loaded before any dependency and is restricted
  # to this project. If another project depends on this project, this
  # file won't be loaded nor affect the parent project. For this reason,
  # if you want to provide default values for your application for third-
  # party users, it should be done in your mix.exs file.

  # Sample configuration:
  #
  #     config :logger,
  #       level: :info
  #
  #     config :logger, :console,
  #       format: "$date $time [$level] $metadata$message\n",
  #       metadata: [:user_id]

  # It is also possible to import configuration files, relative to this
  # directory. For example, you can emulate configuration per environment
  # by uncommenting the line below and defining dev.exs, test.exs and such.
  # Configuration from the imported file will override the ones defined
  # here (which is why it is important to import them last).
  #
  #     import_config "#{Mix.env}.exs"
  """

  embed_template :config_umbrella, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # The configuration defined here will only affect the dependencies
  # in the apps directory when commands are executed from the umbrella
  # project. For this reason, it is preferred to configure each child
  # application directly and import its configuration, as done below.
  import_config "../apps/*/config/config.exs"

  # Sample configuration (overrides the imported configuration above):
  #
  #     config :logger, :console,
  #       level: :info,
  #       format: "$date $time [$level] $metadata$message\n",
  #       metadata: [:user_id]
  """

  embed_template :lib, """
  defmodule <%= @mod %> do
  end
  """

  embed_template :lib_sup, """
  defmodule <%= @mod %> do
    use Application

    # See http://elixir-lang.org/docs/stable/elixir/Application.html
    # for more information on OTP Applications
    def start(_type, _args) do
      import Supervisor.Spec, warn: false

      children = [
        # Define workers and child supervisors to be supervised
        # worker(<%= @mod %>.Worker, [arg1, arg2, arg3])
      ]

      # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
      # for other strategies and supported options
      opts = [strategy: :one_for_one, name: <%= @mod %>.Supervisor]
      Supervisor.start_link(children, opts)
    end
  end
  """

  embed_template :test, """
  defmodule <%= @mod %>Test do
    use ExUnit.Case
    doctest <%= @mod %>

    test "the truth" do
      assert 1 + 1 == 2
    end
  end
  """

  embed_template :test_helper, """
  ExUnit.start()
  """
end

defmodule Mix.Tasks.New do
  use Mix.Task

  import Mix.Generator
  import Mix.Utils, only: [camelize: 1, underscore: 1]

  @shortdoc "Create a new Elixir project"

  @moduledoc """
  Creates a new Elixir project.
  It expects the path of the project as argument.

      mix new PATH [--sup] [--module MODULE] [--app APP] [--umbrella]

  A project at the given PATH  will be created. The
  application name and module name will be retrieved
  from the path, unless `--module` or `--app` is given.

  A `--sup` option can be given to generate an OTP application
  skeleton including a supervision tree. Normally an app is
  generated without a supervisor and without the app callback.

  An `--umbrella` option can be given to generate an
  umbrella project.

  An `--app` option can be given in order to
  name the OTP application for the project.

  A `--module` option can be given in order
  to name the modules in the generated code skeleton.

  ## Examples

      mix new hello_world

  Is equivalent to:

      mix new hello_world --module HelloWorld

  To generate an app with supervisor and application callback:

      mix new hello_world --sup

  """

  @spec run(OptionParser.argv) :: :ok
  def run(argv) do
    {opts, argv, _} = OptionParser.parse(argv, switches: [sup: :boolean, umbrella: :boolean])

    case argv do
      [] ->
        Mix.raise "Expected PATH to be given, please use `mix new PATH`"
      [path|_] ->
        app = opts[:app] || Path.basename(Path.expand(path))
        check_application_name!(app, !!opts[:app])
        mod = opts[:module] || camelize(app)
        check_mod_name!(mod)
        File.mkdir_p!(path)

        File.cd! path, fn ->
          if opts[:umbrella] do
            do_generate_umbrella(app, mod, path, opts)
          else
            do_generate(app, mod, path, opts)
          end
        end
    end
  end

  defp do_generate(app, mod, path, opts) do
    assigns = [app: app, mod: mod, otp_app: otp_app(mod, !!opts[:sup]),
               version: get_version(System.version)]

    create_file "README.md",  readme_template(assigns)
    create_file ".gitignore", gitignore_text

    if in_umbrella? do
      create_file "mix.exs", mixfile_apps_template(assigns)
    else
      create_file "mix.exs", mixfile_template(assigns)
    end

    create_directory "config"
    create_file "config/config.exs", config_template(assigns)

    create_directory "lib"

    if opts[:sup] do
      create_file "lib/#{app}.ex", lib_sup_template(assigns)
    else
      create_file "lib/#{app}.ex", lib_template(assigns)
    end

    create_directory "test"
    create_file "test/test_helper.exs", test_helper_template(assigns)
    create_file "test/#{app}_test.exs", test_template(assigns)

    Mix.shell.info """

    Your mix project was created successfully.
    You can use mix to compile it, test it, and more:

        cd #{path}
        mix test

    Run `mix help` for more commands.
    """
  end

  defp otp_app(_mod, false) do
    "    [applications: [:logger]]"
  end

  defp otp_app(mod, true) do
    "    [applications: [:logger],\n     mod: {#{mod}, []}]"
  end

  defp do_generate_umbrella(_app, mod, path, _opts) do
    assigns = [mod: mod]

    create_file ".gitignore", gitignore_text
    create_file "README.md", readme_template(assigns)
    create_file "mix.exs", mixfile_umbrella_template(assigns)

    create_directory "apps"

    create_directory "config"
    create_file "config/config.exs",
      config_template(assigns) <> config_umbrella_template(assigns)

    Mix.shell.info """

    Your umbrella project was created successfully.
    Inside your project, you will find an apps/ directory
    where you can create and host many apps:

        cd #{path}
        cd apps
        mix new my_app

    Commands like `mix compile` and `mix test` when executed
    in the umbrella project root will automatically run
    for each application in the apps/ directory.
    """
  end

  defp check_application_name!(name, from_app_flag) do
    unless name =~ ~r/^[a-z][\w_]*$/ do
      Mix.raise "Application name must start with a letter and have only lowercase " <>
                "letters, numbers and underscore, got: #{inspect name}" <>
                (if !from_app_flag do
                  ". The application name is inferred from the path, if you'd like to " <>
                  "explicitly name the application then use the `--app APP` option."
                else
                  ""
                end)
    end
  end

  defp check_mod_name!(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 get_version(version) do
    {:ok, version} = Version.parse(version)
    "#{version.major}.#{version.minor}" <>
      case version.pre do
        [h|_] -> "-#{h}"
        []    -> ""
      end
  end

  defp in_umbrella? do
    apps = Path.dirname(File.cwd!)

    try do
      Mix.Project.in_project(:umbrella_check, "../..", fn _ ->
        path = Mix.Project.config[:apps_path]
        path && Path.expand(path) == apps
      end)
    catch
      _, _ -> false
    end
  end

   embed_template :readme, """
   <%= @mod %>
   <%= String.duplicate("=", String.length(@mod)) %>

   ** TODO: Add description **
   """

   embed_text :gitignore, """
   /_build
   /deps
   erl_crash.dump
   *.ez
   /cover
   """

  embed_template :mixfile, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.0.1",
       elixir: "~> <%= @version %>",
       deps: deps]
    end

    # Configuration for the OTP application
    #
    # Type `mix help compile.app` for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type `mix help deps` for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_apps, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.0.1",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",
       elixir: "~> <%= @version %>",
       deps: deps]
    end

    # Configuration for the OTP application
    #
    # Type `mix help compile.app` for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # To depend on another app inside the umbrella:
    #
    #   {:myapp, in_umbrella: true}
    #
    # Type `mix help deps` for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_umbrella, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [apps_path: "apps",
       deps: deps]
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type `mix help deps` for more examples and options.
    #
    # Dependencies listed here are available only for this project
    # and cannot be accessed from applications inside the apps folder
    defp deps do
      []
    end
  end
  """

  embed_template :config, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # This configuration is loaded before any dependency and is restricted
  # to this project. If another project depends on this project, this
  # file won't be loaded nor affect the parent project. For this reason,
  # if you want to provide default values for your application for third-
  # party users, it should be done in your mix.exs file.

  # Sample configuration:
  #
  #     config :logger,
  #       level: :info
  #
  #     config :logger, :console,
  #       format: "$date $time [$level] $metadata$message\n",
  #       metadata: [:user_id]

  # It is also possible to import configuration files, relative to this
  # directory. For example, you can emulate configuration per environment
  # by uncommenting the line below and defining dev.exs, test.exs and such.
  # Configuration from the imported file will override the ones defined
  # here (which is why it is important to import them last).
  #
  #     import_config "#{Mix.env}.exs"
  """

  embed_template :config_umbrella, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # The configuration defined here will only affect the dependencies
  # in the apps directory when commands are executed from the umbrella
  # project. For this reason, it is preferred to configure each child
  # application directly and import its configuration, as done below.
  import_config "../apps/*/config/config.exs"

  # Sample configuration (overrides the imported configuration above):
  #
  #     config :logger, :console,
  #       level: :info,
  #       format: "$date $time [$level] $metadata$message\n",
  #       metadata: [:user_id]
  """

  embed_template :lib, """
  defmodule <%= @mod %> do
  end
  """

  embed_template :lib_sup, """
  defmodule <%= @mod %> do
    use Application

    # See http://elixir-lang.org/docs/stable/elixir/Application.html
    # for more information on OTP Applications
    def start(_type, _args) do
      import Supervisor.Spec, warn: false

      children = [
        # Define workers and child supervisors to be supervised
        # worker(<%= @mod %>.Worker, [arg1, arg2, arg3])
      ]

      # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
      # for other strategies and supported options
      opts = [strategy: :one_for_one, name: <%= @mod %>.Supervisor]
      Supervisor.start_link(children, opts)
    end
  end
  """

  embed_template :test, """
  defmodule <%= @mod %>Test do
    use ExUnit.Case
    doctest <%= @mod %>

    test "the truth" do
      assert 1 + 1 == 2
    end
  end
  """

  embed_template :test_helper, """
  ExUnit.start()
  """
end


Add Hexdocs link to generated new project README (#4906)

defmodule Mix.Tasks.New do
  use Mix.Task

  import Mix.Generator
  import Mix.Utils, only: [camelize: 1]

  @shortdoc "Creates a new Elixir project"

  @moduledoc """
  Creates a new Elixir project.
  It expects the path of the project as argument.

      mix new PATH [--sup] [--module MODULE] [--app APP] [--umbrella]

  A project at the given PATH  will be created. The
  application name and module name will be retrieved
  from the path, unless `--module` or `--app` is given.

  A `--sup` option can be given to generate an OTP application
  skeleton including a supervision tree. Normally an app is
  generated without a supervisor and without the app callback.

  An `--umbrella` option can be given to generate an
  umbrella project.

  An `--app` option can be given in order to
  name the OTP application for the project.

  A `--module` option can be given in order
  to name the modules in the generated code skeleton.

  ## Examples

      mix new hello_world

  Is equivalent to:

      mix new hello_world --module HelloWorld

  To generate an app with supervisor and application callback:

      mix new hello_world --sup

  """

  @switches [sup: :boolean, umbrella: :boolean, app: :string, module: :string]

  @spec run(OptionParser.argv) :: :ok
  def run(argv) do
    {opts, argv} = OptionParser.parse!(argv, strict: @switches)

    case argv do
      [] ->
        Mix.raise "Expected PATH to be given, please use \"mix new PATH\""
      [path | _] ->
        app = opts[:app] || Path.basename(Path.expand(path))
        check_application_name!(app, !!opts[:app])
        mod = opts[:module] || camelize(app)
        check_mod_name_validity!(mod)
        check_mod_name_availability!(mod)
        File.mkdir_p!(path)

        File.cd! path, fn ->
          if opts[:umbrella] do
            generate_umbrella(app, mod, path, opts)
          else
            generate(app, mod, path, opts)
          end
        end
    end
  end

  defp generate(app, mod, path, opts) do
    assigns = [app: app, mod: mod, otp_app: otp_app(mod, !!opts[:sup]),
               version: get_version(System.version)]

    create_file "README.md",  readme_template(assigns)
    create_file ".gitignore", gitignore_text()

    if in_umbrella?() do
      create_file "mix.exs", mixfile_apps_template(assigns)
    else
      create_file "mix.exs", mixfile_template(assigns)
    end

    create_directory "config"
    create_file "config/config.exs", config_template(assigns)

    create_directory "lib"

    if opts[:sup] do
      create_file "lib/#{app}.ex", lib_sup_template(assigns)
    else
      create_file "lib/#{app}.ex", lib_template(assigns)
    end

    create_directory "test"
    create_file "test/test_helper.exs", test_helper_template(assigns)
    create_file "test/#{app}_test.exs", test_template(assigns)

    """

    Your Mix project was created successfully.
    You can use "mix" to compile it, test it, and more:

        cd #{path}
        mix test

    Run "mix help" for more commands.
    """
    |> String.trim_trailing
    |> Mix.shell.info
  end

  defp otp_app(_mod, false) do
    "    [applications: [:logger]]"
  end

  defp otp_app(mod, true) do
    "    [applications: [:logger],\n     mod: {#{mod}, []}]"
  end

  defp generate_umbrella(_app, mod, path, _opts) do
    assigns = [app: nil, mod: mod]

    create_file ".gitignore", gitignore_text()
    create_file "README.md", readme_template(assigns)
    create_file "mix.exs", mixfile_umbrella_template(assigns)

    create_directory "apps"

    create_directory "config"
    create_file "config/config.exs", config_umbrella_template(assigns)

    """

    Your umbrella project was created successfully.
    Inside your project, you will find an apps/ directory
    where you can create and host many apps:

        cd #{path}
        cd apps
        mix new my_app

    Commands like "mix compile" and "mix test" when executed
    in the umbrella project root will automatically run
    for each application in the apps/ directory.
    """
    |> String.trim_trailing
    |> Mix.shell.info
  end

  defp check_application_name!(name, from_app_flag) do
    unless name =~ ~r/^[a-z][\w_]*$/ do
      Mix.raise "Application name must start with a letter and have only lowercase " <>
                "letters, numbers and underscore, got: #{inspect name}" <>
                (if !from_app_flag do
                  ". The application name is inferred from the path, if you'd like to " <>
                  "explicitly name the application then use the \"--app APP\" option."
                else
                  ""
                end)
    end
  end

  defp check_mod_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 check_mod_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

  defp get_version(version) do
    {:ok, version} = Version.parse(version)
    "#{version.major}.#{version.minor}" <>
      case version.pre do
        [h | _] -> "-#{h}"
        []      -> ""
      end
  end

  defp in_umbrella? do
    apps = Path.dirname(File.cwd!)

    try do
      Mix.Project.in_project(:umbrella_check, "../..", fn _ ->
        path = Mix.Project.config[:apps_path]
        path && Path.expand(path) == apps
      end)
    catch
      _, _ -> false
    end
  end

  embed_template :readme, """
  # <%= @mod %>

  **TODO: Add description**
  <%= if @app do %>
  ## Installation

  If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:

    1. Add `<%= @app %>` to your list of dependencies in `mix.exs`:

      ```elixir
      def deps do
        [{:<%= @app %>, "~> 0.1.0"}]
      end
      ```

    2. Ensure `<%= @app %>` is started before your application:

      ```elixir
      def application do
        [applications: [:<%= @app %>]]
      end
      ```

  If [published on Hexdocs](https://hex.pm/docs/tasks#hex_docs), the docs can
  be found at [https://hexdocs.pm/<%= @app %>](https://hexdocs.pm/<%= @app %>)
  <% end %>
  """

  embed_text :gitignore, """
  # The directory Mix will write compiled artifacts to.
  /_build

  # If you run "mix test --cover", coverage assets end up here.
  /cover

  # The directory Mix downloads your dependencies sources to.
  /deps

  # Where 3rd-party dependencies like ExDoc output generated docs.
  /doc

  # If the VM crashes, it generates a dump, let's ignore it too.
  erl_crash.dump

  # Also ignore archive artifacts (built via "mix archive.build").
  *.ez
  """

  embed_template :mixfile, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.1.0",
       elixir: "~> <%= @version %>",
       build_embedded: Mix.env == :prod,
       start_permanent: Mix.env == :prod,
       deps: deps()]
    end

    # Configuration for the OTP application
    #
    # Type "mix help compile.app" for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type "mix help deps" for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_apps, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.1.0",
       build_path: "../../_build",
       config_path: "../../config/config.exs",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",
       elixir: "~> <%= @version %>",
       build_embedded: Mix.env == :prod,
       start_permanent: Mix.env == :prod,
       deps: deps()]
    end

    # Configuration for the OTP application
    #
    # Type "mix help compile.app" for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # To depend on another app inside the umbrella:
    #
    #   {:myapp, in_umbrella: true}
    #
    # Type "mix help deps" for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_umbrella, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [apps_path: "apps",
       build_embedded: Mix.env == :prod,
       start_permanent: Mix.env == :prod,
       deps: deps()]
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type "mix help deps" for more examples and options.
    #
    # Dependencies listed here are available only for this project
    # and cannot be accessed from applications inside the apps folder
    defp deps do
      []
    end
  end
  """

  embed_template :config, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # This configuration is loaded before any dependency and is restricted
  # to this project. If another project depends on this project, this
  # file won't be loaded nor affect the parent project. For this reason,
  # if you want to provide default values for your application for
  # 3rd-party users, it should be done in your "mix.exs" file.

  # You can configure for your application as:
  #
  #     config :<%= @app %>, key: :value
  #
  # And access this configuration in your application as:
  #
  #     Application.get_env(:<%= @app %>, :key)
  #
  # Or configure a 3rd-party app:
  #
  #     config :logger, level: :info
  #

  # It is also possible to import configuration files, relative to this
  # directory. For example, you can emulate configuration per environment
  # by uncommenting the line below and defining dev.exs, test.exs and such.
  # Configuration from the imported file will override the ones defined
  # here (which is why it is important to import them last).
  #
  #     import_config "#{Mix.env}.exs"
  """

  embed_template :config_umbrella, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # By default, the umbrella project as well as each child
  # application will require this configuration file, ensuring
  # they all use the same configuration. While one could
  # configure all applications here, we prefer to delegate
  # back to each application for organization purposes.
  import_config "../apps/*/config/config.exs"

  # Sample configuration (overrides the imported configuration above):
  #
  #     config :logger, :console,
  #       level: :info,
  #       format: "$date $time [$level] $metadata$message\n",
  #       metadata: [:user_id]
  """

  embed_template :lib, """
  defmodule <%= @mod %> do
  end
  """

  embed_template :lib_sup, """
  defmodule <%= @mod %> do
    use Application

    # See http://elixir-lang.org/docs/stable/elixir/Application.html
    # for more information on OTP Applications
    def start(_type, _args) do
      import Supervisor.Spec, warn: false

      # Define workers and child supervisors to be supervised
      children = [
        # Starts a worker by calling: <%= @mod %>.Worker.start_link(arg1, arg2, arg3)
        # worker(<%= @mod %>.Worker, [arg1, arg2, arg3]),
      ]

      # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
      # for other strategies and supported options
      opts = [strategy: :one_for_one, name: <%= @mod %>.Supervisor]
      Supervisor.start_link(children, opts)
    end
  end
  """

  embed_template :test, """
  defmodule <%= @mod %>Test do
    use ExUnit.Case
    doctest <%= @mod %>

    test "the truth" do
      assert 1 + 1 == 2
    end
  end
  """

  embed_template :test_helper, """
  ExUnit.start()
  """
end

defmodule Mix.Tasks.New do
  use Mix.Task

  import Mix.Generator
  import Mix.Utils, only: [camelize: 1]

  @shortdoc "Creates a new Elixir project"

  @moduledoc """
  Creates a new Elixir project.
  It expects the path of the project as argument.

      mix new PATH [--sup] [--module MODULE] [--app APP] [--umbrella]

  A project at the given PATH  will be created. The
  application name and module name will be retrieved
  from the path, unless `--module` or `--app` is given.

  A `--sup` option can be given to generate an OTP application
  skeleton including a supervision tree. Normally an app is
  generated without a supervisor and without the app callback.

  An `--umbrella` option can be given to generate an
  umbrella project.

  An `--app` option can be given in order to
  name the OTP application for the project.

  A `--module` option can be given in order
  to name the modules in the generated code skeleton.

  ## Examples

      mix new hello_world

  Is equivalent to:

      mix new hello_world --module HelloWorld

  To generate an app with supervisor and application callback:

      mix new hello_world --sup

  """

  @switches [sup: :boolean, umbrella: :boolean, app: :string, module: :string]

  @spec run(OptionParser.argv) :: :ok
  def run(argv) do
    {opts, argv} = OptionParser.parse!(argv, strict: @switches)

    case argv do
      [] ->
        Mix.raise "Expected PATH to be given, please use \"mix new PATH\""
      [path | _] ->
        app = opts[:app] || Path.basename(Path.expand(path))
        check_application_name!(app, !!opts[:app])
        mod = opts[:module] || camelize(app)
        check_mod_name_validity!(mod)
        check_mod_name_availability!(mod)
        File.mkdir_p!(path)

        File.cd! path, fn ->
          if opts[:umbrella] do
            generate_umbrella(app, mod, path, opts)
          else
            generate(app, mod, path, opts)
          end
        end
    end
  end

  defp generate(app, mod, path, opts) do
    assigns = [app: app, mod: mod, otp_app: otp_app(mod, !!opts[:sup]),
               version: get_version(System.version)]

    create_file "README.md",  readme_template(assigns)
    create_file ".gitignore", gitignore_text()

    if in_umbrella?() do
      create_file "mix.exs", mixfile_apps_template(assigns)
    else
      create_file "mix.exs", mixfile_template(assigns)
    end

    create_directory "config"
    create_file "config/config.exs", config_template(assigns)

    create_directory "lib"

    if opts[:sup] do
      create_file "lib/#{app}.ex", lib_sup_template(assigns)
    else
      create_file "lib/#{app}.ex", lib_template(assigns)
    end

    create_directory "test"
    create_file "test/test_helper.exs", test_helper_template(assigns)
    create_file "test/#{app}_test.exs", test_template(assigns)

    """

    Your Mix project was created successfully.
    You can use "mix" to compile it, test it, and more:

        cd #{path}
        mix test

    Run "mix help" for more commands.
    """
    |> String.trim_trailing
    |> Mix.shell.info
  end

  defp otp_app(_mod, false) do
    "    [applications: [:logger]]"
  end

  defp otp_app(mod, true) do
    "    [applications: [:logger],\n     mod: {#{mod}, []}]"
  end

  defp generate_umbrella(_app, mod, path, _opts) do
    assigns = [app: nil, mod: mod]

    create_file ".gitignore", gitignore_text()
    create_file "README.md", readme_template(assigns)
    create_file "mix.exs", mixfile_umbrella_template(assigns)

    create_directory "apps"

    create_directory "config"
    create_file "config/config.exs", config_umbrella_template(assigns)

    """

    Your umbrella project was created successfully.
    Inside your project, you will find an apps/ directory
    where you can create and host many apps:

        cd #{path}
        cd apps
        mix new my_app

    Commands like "mix compile" and "mix test" when executed
    in the umbrella project root will automatically run
    for each application in the apps/ directory.
    """
    |> String.trim_trailing
    |> Mix.shell.info
  end

  defp check_application_name!(name, from_app_flag) do
    unless name =~ ~r/^[a-z][\w_]*$/ do
      Mix.raise "Application name must start with a letter and have only lowercase " <>
                "letters, numbers and underscore, got: #{inspect name}" <>
                (if !from_app_flag do
                  ". The application name is inferred from the path, if you'd like to " <>
                  "explicitly name the application then use the \"--app APP\" option."
                else
                  ""
                end)
    end
  end

  defp check_mod_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 check_mod_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

  defp get_version(version) do
    {:ok, version} = Version.parse(version)
    "#{version.major}.#{version.minor}" <>
      case version.pre do
        [h | _] -> "-#{h}"
        []      -> ""
      end
  end

  defp in_umbrella? do
    apps = Path.dirname(File.cwd!)

    try do
      Mix.Project.in_project(:umbrella_check, "../..", fn _ ->
        path = Mix.Project.config[:apps_path]
        path && Path.expand(path) == apps
      end)
    catch
      _, _ -> false
    end
  end

  embed_template :readme, """
  # <%= @mod %>

  **TODO: Add description**
  <%= if @app do %>
  ## Installation

  If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:

    1. Add `<%= @app %>` to your list of dependencies in `mix.exs`:

      ```elixir
      def deps do
        [{:<%= @app %>, "~> 0.1.0"}]
      end
      ```

    2. Ensure `<%= @app %>` is started before your application:

      ```elixir
      def application do
        [applications: [:<%= @app %>]]
      end
      ```
  <% end %>
  """

  embed_text :gitignore, """
  # The directory Mix will write compiled artifacts to.
  /_build

  # If you run "mix test --cover", coverage assets end up here.
  /cover

  # The directory Mix downloads your dependencies sources to.
  /deps

  # Where 3rd-party dependencies like ExDoc output generated docs.
  /doc

  # If the VM crashes, it generates a dump, let's ignore it too.
  erl_crash.dump

  # Also ignore archive artifacts (built via "mix archive.build").
  *.ez
  """

  embed_template :mixfile, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.1.0",
       elixir: "~> <%= @version %>",
       build_embedded: Mix.env == :prod,
       start_permanent: Mix.env == :prod,
       deps: deps()]
    end

    # Configuration for the OTP application
    #
    # Type "mix help compile.app" for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type "mix help deps" for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_apps, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [app: :<%= @app %>,
       version: "0.1.0",
       build_path: "../../_build",
       config_path: "../../config/config.exs",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",
       elixir: "~> <%= @version %>",
       build_embedded: Mix.env == :prod,
       start_permanent: Mix.env == :prod,
       deps: deps()]
    end

    # Configuration for the OTP application
    #
    # Type "mix help compile.app" for more information
    def application do
  <%= @otp_app %>
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # To depend on another app inside the umbrella:
    #
    #   {:myapp, in_umbrella: true}
    #
    # Type "mix help deps" for more examples and options
    defp deps do
      []
    end
  end
  """

  embed_template :mixfile_umbrella, """
  defmodule <%= @mod %>.Mixfile do
    use Mix.Project

    def project do
      [apps_path: "apps",
       build_embedded: Mix.env == :prod,
       start_permanent: Mix.env == :prod,
       deps: deps()]
    end

    # Dependencies can be Hex packages:
    #
    #   {:mydep, "~> 0.3.0"}
    #
    # Or git/path repositories:
    #
    #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
    #
    # Type "mix help deps" for more examples and options.
    #
    # Dependencies listed here are available only for this project
    # and cannot be accessed from applications inside the apps folder
    defp deps do
      []
    end
  end
  """

  embed_template :config, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # This configuration is loaded before any dependency and is restricted
  # to this project. If another project depends on this project, this
  # file won't be loaded nor affect the parent project. For this reason,
  # if you want to provide default values for your application for
  # 3rd-party users, it should be done in your "mix.exs" file.

  # You can configure for your application as:
  #
  #     config :<%= @app %>, key: :value
  #
  # And access this configuration in your application as:
  #
  #     Application.get_env(:<%= @app %>, :key)
  #
  # Or configure a 3rd-party app:
  #
  #     config :logger, level: :info
  #

  # It is also possible to import configuration files, relative to this
  # directory. For example, you can emulate configuration per environment
  # by uncommenting the line below and defining dev.exs, test.exs and such.
  # Configuration from the imported file will override the ones defined
  # here (which is why it is important to import them last).
  #
  #     import_config "#{Mix.env}.exs"
  """

  embed_template :config_umbrella, ~S"""
  # This file is responsible for configuring your application
  # and its dependencies with the aid of the Mix.Config module.
  use Mix.Config

  # By default, the umbrella project as well as each child
  # application will require this configuration file, ensuring
  # they all use the same configuration. While one could
  # configure all applications here, we prefer to delegate
  # back to each application for organization purposes.
  import_config "../apps/*/config/config.exs"

  # Sample configuration (overrides the imported configuration above):
  #
  #     config :logger, :console,
  #       level: :info,
  #       format: "$date $time [$level] $metadata$message\n",
  #       metadata: [:user_id]
  """

  embed_template :lib, """
  defmodule <%= @mod %> do
  end
  """

  embed_template :lib_sup, """
  defmodule <%= @mod %> do
    use Application

    # See http://elixir-lang.org/docs/stable/elixir/Application.html
    # for more information on OTP Applications
    def start(_type, _args) do
      import Supervisor.Spec, warn: false

      # Define workers and child supervisors to be supervised
      children = [
        # Starts a worker by calling: <%= @mod %>.Worker.start_link(arg1, arg2, arg3)
        # worker(<%= @mod %>.Worker, [arg1, arg2, arg3]),
      ]

      # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
      # for other strategies and supported options
      opts = [strategy: :one_for_one, name: <%= @mod %>.Supervisor]
      Supervisor.start_link(children, opts)
    end
  end
  """

  embed_template :test, """
  defmodule <%= @mod %>Test do
    use ExUnit.Case
    doctest <%= @mod %>

    test "the truth" do
      assert 1 + 1 == 2
    end
  end
  """

  embed_template :test_helper, """
  ExUnit.start()
  """
end


load nif smartly

load nif smartly

defmodule CCC do
  @on_load {:init, 0}

  def init do
    :code.priv_dir(:ccc)
    |> :filename.join('iconv')
    |> :erlang.load_nif(0)
  end

  def convert(_string, _from, _to), do: exit(:nif_not_loaded)
end

defmodule CCC do
  @on_load {:init, 0}

  def init do
    [__DIR__, ~w[.. priv iconv]]
    |> List.flatten
    |> Path.join
    |> String.to_char_list
    |> :erlang.load_nif(0)
  end

  def convert(_string, _from, _to), do: exit(:nif_not_loaded)
end


Proxy and API paths should start with / (#137)

defmodule Gateway.Changeset.Validator.Settings do
  @moduledoc """
  This module provides helpers to validate individual plugin settings via JSON Schema that is stored inside methods.
  """
  alias Ecto.Changeset
  import Ecto.Changeset
  import Gateway.Changeset.Validator.JsonSchema

  # JWT
  def validate_settings(%Changeset{changes: %{name: "jwt", settings: settings}} = ch) do
    {%{}, %{signature: :string}}
    |> cast(settings, [:signature])
    |> validate_required([:signature])
    |> put_changeset_errors(ch)
  end

  # ACL
  def validate_settings(%Changeset{changes: %{name: "acl"}} = ch) do
    validate_via_json_schema(ch, :settings, %{
      "type" => "object",
      "required" => ["rules"],
      "additionalProperties" => false,
      "properties" => %{
        "rules" => %{
          "type" => "array",
          "minItems" => 1,
          "uniqueItems" => true,
          "items" => %{
            "type" => "object",
            "required" => ["methods", "path", "scopes"],
            "properties" => %{
              "methods" => %{
                "type" => "array",
                "minItems" => 1,
                "items" => %{
                  "type" => "string",
                  "enum" => ["GET", "POST", "PUT", "DELETE", "PATCH"]
                }
              },
              "path" => %{
                "type" => "string",
                "pattern": "^/.*$"
              },
              "scopes" => %{
                "type" => "array",
                "minItems" => 1,
                "items" => %{
                  "type" => "string"
                }
              }
            }
          }
        }
      }
    })
  end

  # Validator
  def validate_settings(%Changeset{changes: %{name: "validator"}} = ch) do
    validate_via_json_schema(ch, :settings, %{
      "type" => "object",
      "required" => ["rules"],
      "additionalProperties" => false,
      "properties" => %{
        "rules" => %{
          "type" => "array",
          "minItems" => 1,
          "uniqueItems" => true,
          "items" => %{
            "type" => "object",
            "required" => ["methods", "path", "schema"],
            "properties" => %{
              "methods" => %{
                "type" => "array",
                "minItems" => 1,
                "items" => %{
                  "type" => "string",
                  "enum" => ["GET", "POST", "PUT", "DELETE", "PATCH"]
                }
              },
              "path" => %{
                "type" => "string"
              },
              "schema" => %{
                "type" => "object"
              }
            }
          }
        }
      }
    })
  end

  # IPRestriction
  def validate_settings(%Changeset{changes: %{name: "ip_restriction"}} = ch) do
    ch
    |> validate_via_json_schema(:settings, %{
      "type" => "object",
      "anyOf" => [
        %{"required" => ["ip_whitelist", "ip_blacklist"]},
        %{"required" => ["ip_whitelist"]},
        %{"required" => ["ip_blacklist"]}
      ],
      "additionalProperties" => false,
      "properties" => %{
        "ip_whitelist" => %{
          "type" => "array",
          "items" => %{
            "type" => "string",
            "oneOf": [
              %{"format" => "ipv4"}
            ]
          }
        },
        "ip_blacklist" => %{
          "type" => "array",
          "items" => %{
            "type" => "string",
            "oneOf": [
              %{"format" => "ipv4"}
            ]
          }
        }
      }
    })
  end

  # Proxy
  def validate_settings(%Changeset{changes: %{name: "proxy"}} = ch) do
    ch
    |> validate_via_json_schema(:settings, %{
      "type" => "object",
      "required" => ["host"],
      "additionalProperties" => false,
      "properties" => %{
        "strip_request_path" => %{
          "type" => "boolean"
        },
        "additional_headers" => %{
          "type" => "array",
          "uniqueItems" => true,
          "items" => %{
            "type" => "string"
          }
        },
        "scheme" => %{
          "enum" => ["http", "https"]
        },
        "host" => %{
          "type" => "string",
          "oneOf": [
            %{"format" => "hostname"},
            %{"format" => "ipv4"}
          ]
        },
        "port" => %{
          "type" => "integer"
        },
        "path" => %{
          "type" => "string",
          "pattern": "^/.*"
        },
        "strip_request_path" => %{
          "type" => "boolean"
        },
        "additional_headers" => %{
          "type" => "array",
          "items" => %{
            "type" => "object"
          }
        }
      },
    })
  end

  # general
  def validate_settings(ch), do: ch

  defp put_changeset_errors(%Changeset{valid?: true}, ch), do: ch
  defp put_changeset_errors(%Changeset{valid?: false, errors: errors}, ch) do
    ch
    |> Map.merge(%{errors: errors, valid?: false})
  end
end

defmodule Gateway.Changeset.Validator.Settings do
  @moduledoc """
  This module provides helpers to validate individual plugin settings via JSON Schema that is stored inside methods.
  """
  alias Ecto.Changeset
  import Ecto.Changeset
  import Gateway.Changeset.Validator.JsonSchema

  # JWT
  def validate_settings(%Changeset{changes: %{name: "jwt", settings: settings}} = ch) do
    {%{}, %{signature: :string}}
    |> cast(settings, [:signature])
    |> validate_required([:signature])
    |> put_changeset_errors(ch)
  end

  # ACL
  def validate_settings(%Changeset{changes: %{name: "acl"}} = ch) do
    validate_via_json_schema(ch, :settings, %{
      "type" => "object",
      "required" => ["rules"],
      "additionalProperties" => false,
      "properties" => %{
        "rules" => %{
          "type" => "array",
          "minItems" => 1,
          "uniqueItems" => true,
          "items" => %{
            "type" => "object",
            "required" => ["methods", "path", "scopes"],
            "properties" => %{
              "methods" => %{
                "type" => "array",
                "minItems" => 1,
                "items" => %{
                  "type" => "string",
                  "enum" => ["GET", "POST", "PUT", "DELETE", "PATCH"]
                }
              },
              "path" => %{
                "type" => "string"
              },
              "scopes" => %{
                "type" => "array",
                "minItems" => 1,
                "items" => %{
                  "type" => "string"
                }
              }
            }
          }
        }
      }
    })
  end

  # Validator
  def validate_settings(%Changeset{changes: %{name: "validator"}} = ch) do
    validate_via_json_schema(ch, :settings, %{
      "type" => "object",
      "required" => ["rules"],
      "additionalProperties" => false,
      "properties" => %{
        "rules" => %{
          "type" => "array",
          "minItems" => 1,
          "uniqueItems" => true,
          "items" => %{
            "type" => "object",
            "required" => ["methods", "path", "schema"],
            "properties" => %{
              "methods" => %{
                "type" => "array",
                "minItems" => 1,
                "items" => %{
                  "type" => "string",
                  "enum" => ["GET", "POST", "PUT", "DELETE", "PATCH"]
                }
              },
              "path" => %{
                "type" => "string"
              },
              "schema" => %{
                "type" => "object"
              }
            }
          }
        }
      }
    })
  end

  # IPRestriction
  def validate_settings(%Changeset{changes: %{name: "ip_restriction"}} = ch) do
    ch
    |> validate_via_json_schema(:settings, %{
      "type" => "object",
      "anyOf" => [
        %{"required" => ["ip_whitelist", "ip_blacklist"]},
        %{"required" => ["ip_whitelist"]},
        %{"required" => ["ip_blacklist"]}
      ],
      "additionalProperties" => false,
      "properties" => %{
        "ip_whitelist" => %{
          "type" => "array",
          "items" => %{
            "type" => "string",
            "oneOf": [
              %{"format" => "ipv4"}
            ]
          }
        },
        "ip_blacklist" => %{
          "type" => "array",
          "items" => %{
            "type" => "string",
            "oneOf": [
              %{"format" => "ipv4"}
            ]
          }
        }
      }
    })
  end

  # Proxy
  def validate_settings(%Changeset{changes: %{name: "proxy"}} = ch) do
    ch
    |> validate_via_json_schema(:settings, %{
      "type" => "object",
      "required" => ["host"],
      "additionalProperties" => false,
      "properties" => %{
        "strip_request_path" => %{
          "type" => "boolean"
        },
        "additional_headers" => %{
          "type" => "array",
          "uniqueItems" => true,
          "items" => %{
            "type" => "string"
          }
        },
        "scheme" => %{
          "enum" => ["http", "https"]
        },
        "host" => %{
          "type" => "string",
          "oneOf": [
            %{"format" => "hostname"},
            %{"format" => "ipv4"}
          ]
        },
        "port" => %{
          "type" => "integer"
        },
        "path" => %{
          "type" => "string"
        },
        "strip_request_path" => %{
          "type" => "boolean"
        },
        "additional_headers" => %{
          "type" => "array",
          "items" => %{
            "type" => "object"
          }
        }
      },
    })
  end

  # general
  def validate_settings(ch), do: ch

  defp put_changeset_errors(%Changeset{valid?: true}, ch), do: ch
  defp put_changeset_errors(%Changeset{valid?: false, errors: errors}, ch) do
    ch
    |> Map.merge(%{errors: errors, valid?: false})
  end
end