<commit_before>defmodule Mix.Local.Installer do
  @moduledoc false

  # This module implements pieces of functionality shared by the archive- and escript-related
  # tasks.

  @typedoc """
  Installs types supported by `Mix.Local.Installer`.

    * `:project` - installs the current Mix project's artifact
    * `:local` - installs the artifact located at `path`
    * `:url` - installs the artifact retrievable at `url`
    * `:fetcher` - builds and install the artifact generated by the `dep_spec`

  """
  @type install_spec ::
    :project |
    {:local, path :: Path.t} |
    {:url, url :: binary} |
    {:fetcher, dep_spec :: tuple}

  @doc """
  Checks that the `install_spec` and `opts` are supported by the respective module.
  """
  @callback check_install_spec(install_spec, opts :: Keyword.t) :: :ok | {:error, String.t}

  @doc """
  Returns a list of already installed version of the same archive or escript.
  """
  @callback find_previous_versions(String.t, Path.t) :: [Path.t]

  @doc """
  For installs involving a `fetch`, this will be executed as the `in_package`.
  """
  @callback build(mixfile :: atom) :: :ok

  @doc """
  The installation itself.
  """
  @callback install(dest :: Path.t, contents :: binary, previous :: [Path.t]) :: :ok

  @doc """
  Common implementation of installation for archives and escripts.

  Relies on a few callbacks provided by respective callback modules
  for customizing certain steps in the installation process.
  """
  @spec install({module, atom}, OptionParser.argv, Keyword.t) :: boolean
  def install({module, name}, argv, switches) do
    {opts, args} = OptionParser.parse!(argv, strict: switches)

    install_spec =
      case parse_args(args, opts) do
        {:error, message} -> Mix.raise message <> "\n" <> usage(name)
        install_spec -> install_spec
      end

    case module.check_install_spec(install_spec, opts) do
      :ok -> :noop
      {:error, message} -> Mix.raise message <> "\n" <> usage(name)
    end

    case install_spec do
      {:fetcher, dep_spec} ->
        if opts[:sha512] do
          Mix.raise "--sha512 is not supported for #{name} from git/github/hex\n" <> usage(name)
        end

        fetch dep_spec, fn mixfile ->
          module.build(mixfile)
          argv = if opts[:force], do: ["--force"], else: []
          install({module, name}, argv, switches)
        end

      {path_or_url, src} when path_or_url in [:local, :url] ->
        do_install({module, name}, src, opts)

      :project ->
        src = Mix.Local.name_for(name, Mix.Project.config)
        if File.exists?(src) do
          do_install({module, name}, src, opts)
        else
          Mix.raise "Expected an #{name} to exist in the current directory " <>
                    "or an argument to be given.\n#{usage(name)}"
        end
    end
  end

  defp local_path?(url_or_path) do
    File.regular?(url_or_path)
  end

  defp file_url?(url_or_path) do
    URI.parse(url_or_path).scheme in ["http", "https"]
  end

  defp usage(name), do: "\nRun:\n\n    mix help #{name}.install\n\nfor more information."

  defp do_install({module, name}, src, opts) do
    src_basename = Path.basename(URI.parse(src).path)
    dst = Path.join(Mix.Local.path_for(name), src_basename)
    previous_files = module.find_previous_versions(src, dst)

    if opts[:force] || should_install?(name, src, previous_files) do
      case Mix.Utils.read_path(src, opts) do
        {:ok, binary} ->
          module.install(dst, binary, previous_files)

        :badpath ->
          Mix.raise "Expected #{inspect src} to be a URL or a local file path"

        {:local, message} ->
          Mix.raise message

        {kind, message} when kind in [:remote, :checksum] ->
          Mix.raise """
          #{message}

          Could not fetch #{name} at:

              #{src}

          Please download the #{name} above manually to your current directory and run:

              mix #{name}.install ./#{src_basename}
          """
      end

      true
    else
      false
    end
  end

  defp should_install?(name, src, previous_files) do
    message = case previous_files do
      [] ->
        "Are you sure you want to install #{name} #{inspect src}?"
      [file] ->
        "Found existing #{name}: #{file}.\n" <>
        "Are you sure you want to replace it with #{inspect src}?"
      files ->
        "Found existing #{name}s: #{Enum.map_join(files, ", ", &Path.basename/1)}.\n" <><commit_msg>Remove the trailing period in the installer message (#6007)<commit_after>defmodule Mix.Local.Installer do
  @moduledoc false

  # This module implements pieces of functionality shared by the archive- and escript-related
  # tasks.

  @typedoc """
  Installs types supported by `Mix.Local.Installer`.

    * `:project` - installs the current Mix project's artifact
    * `:local` - installs the artifact located at `path`
    * `:url` - installs the artifact retrievable at `url`
    * `:fetcher` - builds and install the artifact generated by the `dep_spec`

  """
  @type install_spec ::
    :project |
    {:local, path :: Path.t} |
    {:url, url :: binary} |
    {:fetcher, dep_spec :: tuple}

  @doc """
  Checks that the `install_spec` and `opts` are supported by the respective module.
  """
  @callback check_install_spec(install_spec, opts :: Keyword.t) :: :ok | {:error, String.t}

  @doc """
  Returns a list of already installed version of the same archive or escript.
  """
  @callback find_previous_versions(String.t, Path.t) :: [Path.t]

  @doc """
  For installs involving a `fetch`, this will be executed as the `in_package`.
  """
  @callback build(mixfile :: atom) :: :ok

  @doc """
  The installation itself.
  """
  @callback install(dest :: Path.t, contents :: binary, previous :: [Path.t]) :: :ok

  @doc """
  Common implementation of installation for archives and escripts.

  Relies on a few callbacks provided by respective callback modules
  for customizing certain steps in the installation process.
  """
  @spec install({module, atom}, OptionParser.argv, Keyword.t) :: boolean
  def install({module, name}, argv, switches) do
    {opts, args} = OptionParser.parse!(argv, strict: switches)

    install_spec =
      case parse_args(args, opts) do
        {:error, message} -> Mix.raise message <> "\n" <> usage(name)
        install_spec -> install_spec
      end

    case module.check_install_spec(install_spec, opts) do
      :ok -> :noop
      {:error, message} -> Mix.raise message <> "\n" <> usage(name)
    end

    case install_spec do
      {:fetcher, dep_spec} ->
        if opts[:sha512] do
          Mix.raise "--sha512 is not supported for #{name} from git/github/hex\n" <> usage(name)
        end

        fetch dep_spec, fn mixfile ->
          module.build(mixfile)
          argv = if opts[:force], do: ["--force"], else: []
          install({module, name}, argv, switches)
        end

      {path_or_url, src} when path_or_url in [:local, :url] ->
        do_install({module, name}, src, opts)

      :project ->
        src = Mix.Local.name_for(name, Mix.Project.config)
        if File.exists?(src) do
          do_install({module, name}, src, opts)
        else
          Mix.raise "Expected an #{name} to exist in the current directory " <>
                    "or an argument to be given.\n#{usage(name)}"
        end
    end
  end

  defp local_path?(url_or_path) do
    File.regular?(url_or_path)
  end

  defp file_url?(url_or_path) do
    URI.parse(url_or_path).scheme in ["http", "https"]
  end

  defp usage(name), do: "\nRun:\n\n    mix help #{name}.install\n\nfor more information."

  defp do_install({module, name}, src, opts) do
    src_basename = Path.basename(URI.parse(src).path)
    dst = Path.join(Mix.Local.path_for(name), src_basename)
    previous_files = module.find_previous_versions(src, dst)

    if opts[:force] || should_install?(name, src, previous_files) do
      case Mix.Utils.read_path(src, opts) do
        {:ok, binary} ->
          module.install(dst, binary, previous_files)

        :badpath ->
          Mix.raise "Expected #{inspect src} to be a URL or a local file path"

        {:local, message} ->
          Mix.raise message

        {kind, message} when kind in [:remote, :checksum] ->
          Mix.raise """
          #{message}

          Could not fetch #{name} at:

              #{src}

          Please download the #{name} above manually to your current directory and run:

              mix #{name}.install ./#{src_basename}
          """
      end

      true
    else
      false
    end
  end

  defp should_install?(name, src, previous_files) do
    message = case previous_files do
      [] ->
        "Are you sure you want to install #{name} #{inspect src}?"
      [file] ->
        "Found existing #{name}: #{file}\n" <>
        "Are you sure you want to replace it with #{inspect src}?"
      files ->
        "Found existing #{name}s: #{Enum.map_join(files, ", ", &Path.basename/1)}\n" <>
{'fraction_non_alphanumeric': 0.12946030330062444, 'fraction_numerical': 0.002007136485280999, 'mean_word_length': 2.6297045730473494, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6626775', 'n_tokens_mistral': 2818, 'n_tokens_neox': 2721, 'n_words': 864}