Skip to main content

lib/mix/tasks/agentix.gen.components.ex

defmodule Mix.Tasks.Agentix.Gen.Components do
  @shortdoc "Copies Agentix's chat components into your project to own and customize"

  @moduledoc """
  Copies the default `Agentix.Chat` components into your project so you can edit the
  markup directly (an alternative to `import Agentix.Components`).

      mix agentix.gen.components lib/my_app_web/components
      mix agentix.gen.components lib/my_app_web/components --module MyAppWeb.AgentixComponents

  Writes one module file under the given directory. With no `--module`, the module is
  `AgentixComponents`; otherwise the file is named after the module's last segment. An
  existing file is left untouched (you own it after copying) unless you pass `--force`.
  """

  use Mix.Task

  @template "priv/templates/components/agentix_components.ex"

  @impl Mix.Task
  def run(args) do
    {opts, paths} = OptionParser.parse!(args, strict: [module: :string, force: :boolean])
    dir = path!(paths)
    module = validate_module!(opts[:module] || "AgentixComponents")

    contents =
      :agentix
      |> Application.app_dir(@template)
      |> File.read!()
      |> rename_module(module)

    File.mkdir_p!(dir)
    target = Path.join(dir, file_name(module))

    if File.exists?(target) and not Keyword.get(opts, :force, false) do
      # The host owns and edits this file once copied — don't clobber their changes on a
      # re-run (e.g. after a library upgrade). Pass `--force` to overwrite.
      Mix.shell().info([:yellow, "* exists ", :reset, "#{target} (use --force to overwrite)"])
    else
      File.write!(target, contents)
      Mix.shell().info([:green, "* creating ", :reset, target])
    end

    target
  end

  defp path!([dir | _]), do: dir

  defp path!([]) do
    Mix.raise("expected a target directory: mix agentix.gen.components DIR [--module MODULE]")
  end

  # The module name is spliced into generated source, so reject anything that isn't a plain
  # dotted Elixir alias (no whitespace, no injected code).
  defp validate_module!(module) do
    if Regex.match?(~r/\A[A-Z][A-Za-z0-9_]*(\.[A-Z][A-Za-z0-9_]*)*\z/, module) do
      module
    else
      Mix.raise("invalid --module #{inspect(module)}: expected a name like MyAppWeb.Components")
    end
  end

  defp rename_module(contents, "AgentixComponents"), do: contents

  defp rename_module(contents, module),
    do: String.replace(contents, "defmodule AgentixComponents do", "defmodule #{module} do")

  defp file_name(module),
    do: (module |> String.split(".") |> List.last() |> Macro.underscore()) <> ".ex"
end