Skip to content

Commit e5812ec

Browse files
committed
Polish Iconify JSON handling
1 parent fa6d8c6 commit e5812ec

12 files changed

Lines changed: 338 additions & 241 deletions

File tree

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ Add `iconify` to your list of dependencies in `mix.exs`:
1111
```elixir
1212
def deps do
1313
[
14-
{:iconify, "~> 0.1.0"},
15-
{:req, "~> 0.5"} # Optional, for fetching icons
14+
{:iconify, "~> 0.2.0"}
1615
]
1716
end
1817
```
@@ -52,7 +51,7 @@ svg = Iconify.to_svg(icon, class: "w-6 h-6", id: "user-icon")
5251

5352
### Fetching from Iconify
5453

55-
If you have `req` installed, you can fetch icons directly:
54+
Iconify includes its HTTP dependency, so you can fetch icons directly:
5655

5756
```elixir
5857
# Fetch entire icon set from NPM

lib/iconify.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ defmodule Iconify do
2929
3030
"""
3131

32-
alias Iconify.{Icon, Svg}
32+
alias Iconify.{Icon, SVG}
3333

3434
@doc """
3535
Renders an icon as an SVG string.
@@ -54,7 +54,7 @@ defmodule Iconify do
5454
"""
5555
@spec to_svg(Icon.t(), keyword()) :: String.t()
5656
def to_svg(%Icon{} = icon, opts \\ []) do
57-
Svg.render(icon, opts)
57+
SVG.render(icon, opts)
5858
end
5959

6060
@doc """

lib/iconify/fetcher.ex

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ defmodule Iconify.Fetcher do
22
@moduledoc """
33
Fetches icon data from Iconify sources.
44
5-
Requires the `req` dependency to be installed.
6-
75
## Sources
86
97
* **NPM** - Fetches complete icon sets from `@iconify-json/{prefix}` packages
@@ -37,8 +35,6 @@ defmodule Iconify.Fetcher do
3735
"""
3836
@spec fetch_set(String.t()) :: {:ok, Set.t()} | {:error, term()}
3937
def fetch_set(prefix) when is_binary(prefix) do
40-
ensure_req!()
41-
4238
with {:ok, tarball_url} <- get_tarball_url(prefix),
4339
{:ok, json} <- download_and_extract_icons(tarball_url) do
4440
Set.parse(json)
@@ -59,29 +55,12 @@ defmodule Iconify.Fetcher do
5955
@spec fetch_icons(String.t(), [String.t()]) ::
6056
{:ok, %{String.t() => Icon.t()}} | {:error, term()}
6157
def fetch_icons(prefix, names) when is_binary(prefix) and is_list(names) do
62-
ensure_req!()
63-
6458
icons_param = Enum.join(names, ",")
6559
url = "#{@iconify_api}/#{prefix}.json?icons=#{icons_param}"
6660

67-
with {:ok, data} <- req_get_json(url) do
68-
default_width = data["width"] || 24
69-
default_height = data["height"] || 24
70-
71-
defaults = [
72-
width: default_width,
73-
height: default_height,
74-
left: data["left"] || 0,
75-
top: data["top"] || 0
76-
]
77-
78-
icons =
79-
data
80-
|> Map.get("icons", %{})
81-
|> Map.new(fn {name, icon_data} ->
82-
{name, Icon.new(name, icon_data, defaults)}
83-
end)
84-
61+
with {:ok, json} <- req_get_body(url),
62+
{:ok, set} <- Set.parse(json) do
63+
icons = Map.new(names, fn name -> {name, Set.get!(set, name)} end)
8564
{:ok, icons}
8665
end
8766
end
@@ -127,7 +106,7 @@ defmodule Iconify.Fetcher do
127106
end
128107

129108
defp download_and_extract_icons(tarball_url) do
130-
with {:ok, body} <- req_get_binary(tarball_url) do
109+
with {:ok, body} <- req_get_body(tarball_url) do
131110
extract_icons_json(body)
132111
end
133112
end
@@ -145,34 +124,18 @@ defmodule Iconify.Fetcher do
145124
end
146125
end
147126

148-
defp req_get_json(url) do
149-
case Req.get(url) do
150-
{:ok, %{status: 200, body: body}} when is_map(body) -> {:ok, body}
151-
{:ok, %{status: 200, body: body}} when is_binary(body) -> Jason.decode(body)
152-
{:ok, %{status: 404}} -> {:error, :not_found}
153-
{:ok, %{status: status}} -> {:error, {:http_error, status}}
154-
{:error, reason} -> {:error, reason}
127+
defp req_get_json(url, opts \\ []) do
128+
with {:ok, body} <- req_get_body(url) do
129+
Jason.decode(body, opts)
155130
end
156131
end
157132

158-
defp req_get_binary(url) do
133+
defp req_get_body(url) do
159134
case Req.get(url, decode_body: false) do
160135
{:ok, %{status: 200, body: body}} -> {:ok, body}
161136
{:ok, %{status: 404}} -> {:error, :not_found}
162137
{:ok, %{status: status}} -> {:error, {:http_error, status}}
163138
{:error, reason} -> {:error, reason}
164139
end
165140
end
166-
167-
defp ensure_req! do
168-
unless Code.ensure_loaded?(Req) do
169-
raise """
170-
The :req dependency is required for fetching icons.
171-
172-
Add it to your mix.exs:
173-
174-
{:req, "~> 0.5"}
175-
"""
176-
end
177-
end
178141
end

lib/iconify/icon.ex

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,38 @@
11
defmodule Iconify.Icon do
22
@moduledoc """
3-
Represents a single icon from an Iconify icon set.
4-
5-
## Fields
6-
7-
* `:name` - Icon name (e.g., "user", "home")
8-
* `:body` - SVG content without the `<svg>` wrapper
9-
* `:width` - Icon width (default: 24)
10-
* `:height` - Icon height (default: 24)
11-
* `:left` - Left position of viewBox (default: 0)
12-
* `:top` - Top position of viewBox (default: 0)
13-
3+
Represents a single normalized Iconify icon.
144
"""
155

6+
@derive Jason.Encoder
167
@type t :: %__MODULE__{
178
name: String.t(),
189
body: String.t(),
1910
width: pos_integer(),
2011
height: pos_integer(),
2112
left: integer(),
22-
top: integer()
13+
top: integer(),
14+
rotate: integer(),
15+
h_flip: boolean(),
16+
v_flip: boolean(),
17+
hidden: boolean()
2318
}
2419

2520
@enforce_keys [:name, :body]
2621
defstruct [
2722
:name,
2823
:body,
29-
width: 24,
30-
height: 24,
24+
width: 16,
25+
height: 16,
3126
left: 0,
32-
top: 0
27+
top: 0,
28+
rotate: 0,
29+
h_flip: false,
30+
v_flip: false,
31+
hidden: false
3332
]
3433

35-
@doc """
36-
Creates an Icon struct from a map (typically parsed from IconifyJSON).
37-
38-
## Examples
39-
40-
iex> Iconify.Icon.new("user", %{"body" => "<path/>", "width" => 24, "height" => 24})
41-
%Iconify.Icon{name: "user", body: "<path/>", width: 24, height: 24, left: 0, top: 0}
42-
43-
"""
44-
@spec new(String.t(), map(), keyword()) :: t()
45-
def new(name, data, defaults \\ []) when is_binary(name) and is_map(data) do
46-
%__MODULE__{
47-
name: name,
48-
body: Map.fetch!(data, "body"),
49-
width: data["width"] || defaults[:width] || 24,
50-
height: data["height"] || defaults[:height] || 24,
51-
left: data["left"] || defaults[:left] || 0,
52-
top: data["top"] || defaults[:top] || 0
53-
}
54-
end
55-
5634
@doc """
5735
Returns the viewBox string for this icon.
58-
59-
## Examples
60-
61-
iex> icon = %Iconify.Icon{name: "test", body: "", width: 24, height: 24, left: 0, top: 0}
62-
iex> Iconify.Icon.viewbox(icon)
63-
"0 0 24 24"
64-
6536
"""
6637
@spec viewbox(t()) :: String.t()
6738
def viewbox(%__MODULE__{left: left, top: top, width: width, height: height}) do

0 commit comments

Comments
 (0)