lib/mix/tasks/lucide/gen.ex

defmodule Mix.Tasks.Lucide.Gen do
  use Mix.Task

  @shortdoc "Convert source SVG files into Phoenix LiveView components"
  def run(_) do
    loop_directory()

    Mix.Task.run("format")
  end

  defp loop_directory() do
    src_path = "./priv/lucide/icons/"
    dest_path = "./lib/lucide.ex"
    module_name = "Lucide"

    File.rm(dest_path)

    content =
      src_path
      |> File.ls!()
      |> Enum.filter(&(Path.extname(&1) == ".svg"))
      |> Enum.sort()
      |> Enum.map(&build_component(src_path, &1))
      |> build_module(module_name)

    File.write!(dest_path, content)
  end

  defp build_component(src_path, filename) do
    svg_filepath = Path.join(src_path, filename)
    docs = filename

    svg_content =
      File.read!(svg_filepath)
      |> String.trim()
      |> String.replace(~r/<svg[:blank]*/, "<svg {@attrs}")

    filename |> function_name() |> build_function(docs, svg_content)
  end

  defp function_name(current_filename) do
    current_filename
    |> Path.basename(".svg")
    |> String.split("-")
    |> Enum.join("_")
  end

  defp build_module(functions, module_name) do
    """
    defmodule #{module_name} do
      use Phoenix.Component

      def render(%{icon: icon} = assigns) when is_bitstring(icon) do
        # load the function names into the atom table
        __MODULE__.module_info(:functions) |> Keyword.keys()
        icon_atom = icon |> String.replace("-", "_") |> String.downcase() |> String.to_existing_atom()

        apply(__MODULE__, icon_atom, [assigns])
      end

      def render(%{icon: icon} = assigns), do: apply(__MODULE__, icon, [assigns])

      #{Enum.join(functions, "\n")}
    end
    """
  end

  defp build_function(function_name, docs, svg) do
    """
    @doc "#{docs}"
    def #{function_name}(assigns) do
      attrs = assigns_to_attributes(assigns)
      assigns = assign(assigns, :attrs, attrs)

      ~H\"\"\"
      #{svg}
      \"\"\"
    end
    """
  end
end