Last active
March 10, 2023 15:36
-
-
Save aiwaiwa/8e9d54d2cb929370f41a9715292fac6d to your computer and use it in GitHub Desktop.
heroicons_gen for Phoenix >=1.7.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | | |
# | This is designed for Phoenix >= 1.7.1 to add Heroicons components | |
# | from the list of .svg files in any of: | |
# | | |
possible_sources = [ | |
# As introduced in =1.7.1 | |
"priv/hero_icons/optimized/20/solid", | |
# Since >=1.7.2 with a maybe variant | |
"assets/vendor/hero_icons/optimized/20/solid/", | |
"assets/vendor/heroicons/optimized/20/solid/" | |
] | |
class_name_prefix = "heroicon-" | |
# | |
# Add this script to lib/components or lib/components/heroicons folder, according to your taste. | |
# | |
# Call this script with from any location, as long as the path to .exs file is reachable. | |
# The script will discover the closest mix.exs to the script. | |
# | |
# $ eilixir heroicons_gen.exs | |
# | |
# This will generate heroicons.ex in the folder where heroicons_gen.exs has been dropped in. | |
# | |
defmodule Traverse do | |
def locate(path, where) do | |
cond do | |
File.regular?(path) -> | |
case where.(path) do | |
false -> | |
[] | |
true -> | |
[path] | |
end | |
File.dir?(path) -> | |
File.ls!(path) | |
|> Enum.map(&Path.join(path, &1)) | |
|> Enum.map(&locate(&1, where)) | |
|> Enum.concat() | |
true -> | |
[] | |
end | |
end | |
def find_up(path, match) when is_function(match, 1) do | |
path = Path.absname(path) | |
false = File.regular?(path) | |
case path do | |
"" -> | |
"" | |
path -> | |
case File.ls!(path) |> Enum.filter(&match.(&1)) do | |
[_ | _] -> path | |
[] -> find_up(Path.dirname(path), match) | |
end | |
end | |
end | |
end | |
path = __DIR__ | |
mix_root = Traverse.find_up(path, fn v -> v == "mix.exs" end) | |
mix_root = | |
case mix_root do | |
"" -> raise("mix.exs folder not located from #{Path.absolute(path)}") | |
_ -> mix_root | |
end | |
IO.inspect(mix_root, label: "mix.exs") | |
possible_sources_from_mix_root = Enum.map(possible_sources, &Path.join(mix_root, &1)) | |
# Detect ModuleWeb | |
w = | |
Traverse.locate(mix_root, fn f -> | |
String.contains?(f, "/core_components.ex") and | |
!String.starts_with?(f, Path.join(mix_root, "_build")) and | |
!String.starts_with?(f, Path.join(mix_root, ".elixir_ls")) and | |
!String.starts_with?(f, Path.join(mix_root, "deps")) | |
end) | |
1 = length(w) | |
[core_components | _] = w | |
IO.inspect(core_components, label: "core_components") | |
r = File.read!(core_components) | |
r = String.split(r, "\n") | |
[first_line | _] = r | |
web_module = | |
String.trim_trailing(String.trim_leading(first_line, "defmodule "), ".CoreComponents do") | |
IO.inspect(web_module, label: "web module name") | |
true = web_module != "" | |
contains_any = fn search, enum -> | |
Enum.any?(enum, &String.contains?(search, &1)) | |
end | |
icons = | |
Traverse.locate(mix_root, fn f -> | |
contains_any.(f, possible_sources_from_mix_root) and Path.extname(f) == ".svg" | |
end) | |
|> Enum.map(fn f -> Path.basename(f) end) | |
|> Enum.uniq() | |
case length(icons) do | |
0 -> raise("This doesn't look like a Phoenix >=1.7.1 folder structure") | |
_ -> nil | |
end | |
f = Path.join(__DIR__, "heroicons.ex") | |
stream = File.stream!(f, [:write, :utf8]) | |
_icon_def = fn f -> | |
basename = Path.basename(f) | |
trimmed = String.trim_trailing(basename, Path.extname(f)) | |
trimmed_underlined = String.replace(trimmed, "-", "_") | |
""" | |
attr :rest, :global, | |
doc: "the arbitrary HTML attributes for the svg container", | |
include: ~w(fill stroke stroke-width) | |
attr :outline, :boolean, default: true | |
attr :solid, :boolean, default: false | |
attr :mini, :boolean, default: false | |
@doc \"\"\" | |
#{basename} | |
\"\"\" | |
def #{trimmed_underlined}(%{solid: true} = assigns) do | |
~H\"\"\" | |
<.icon name={"#{class_name_prefix}#{trimmed}-solid"} {@rest}/> | |
\"\"\" | |
end | |
def #{trimmed_underlined}(%{mini: true} = assigns) do | |
~H\"\"\" | |
<.icon name={"#{class_name_prefix}#{trimmed}-mini"} {@rest}/> | |
\"\"\" | |
end | |
def #{trimmed_underlined}(assigns) do | |
~H\"\"\" | |
<.icon name={"#{class_name_prefix}#{trimmed}"} {@rest}/> | |
\"\"\" | |
end | |
""" | |
end | |
span_def = fn f -> | |
basename = Path.basename(f) | |
trimmed = String.trim_trailing(basename, Path.extname(f)) | |
trimmed_underlined = String.replace(trimmed, "-", "_") | |
""" | |
attr :class, :string, default: "w-5 h-5" | |
attr :rest, :global, | |
doc: "the arbitrary HTML attributes for the svg container", | |
include: ~w(fill stroke stroke-width) | |
attr :outline, :boolean, default: true | |
attr :solid, :boolean, default: false | |
attr :mini, :boolean, default: false | |
@doc \"\"\" | |
#{basename} | |
\"\"\" | |
def #{trimmed_underlined}(%{solid: true} = assigns) do | |
~H\"\"\" | |
<span class={["#{class_name_prefix}#{trimmed}-solid", @class]} {@rest}/> | |
\"\"\" | |
end | |
def #{trimmed_underlined}(%{mini: true} = assigns) do | |
~H\"\"\" | |
<span class={["#{class_name_prefix}#{trimmed}-mini", @class]} {@rest}/> | |
\"\"\" | |
end | |
def #{trimmed_underlined}(assigns) do | |
~H\"\"\" | |
<span class={["#{class_name_prefix}#{trimmed}", @class]} {@rest}/> | |
\"\"\" | |
end | |
""" | |
end | |
[ | |
""" | |
defmodule #{web_module}.Heroicons do | |
use Phoenix.Component | |
import #{web_module}.CoreComponents, warn: false | |
""" | |
| [Stream.map(icons, &span_def.(&1)) |> Enum.to_list() | ["end"]] | |
] | |
|> Stream.into(stream) | |
|> Stream.run() | |
IO.inspect(length(icons), label: "icons") | |
IO.inspect(f, label: "output") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment