Skip to content

Commit

Permalink
feat: add bang variant of render - render!
Browse files Browse the repository at this point in the history
  • Loading branch information
c4710n committed Jan 14, 2024
1 parent 74ac763 commit 7377332
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 32 deletions.
91 changes: 67 additions & 24 deletions lib/cozy_svg.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
defmodule CozySVG.CompileError do
@type t :: %__MODULE__{}

defexception message: nil
end

defmodule CozySVG.RuntimeError do
@type t :: %__MODULE__{}

defexception message: nil
end

defmodule CozySVG do
@moduledoc """
A tiny and fast library to compile and render SVG files.
Expand Down Expand Up @@ -41,11 +53,17 @@ defmodule CozySVG do
def render(key, attrs \\ []) do
CozySVG.render(library(), key, attrs)
end
# Render an SVG from the library
def render!(key, attrs \\ []) do
CozySVG.render!(library(), key, attrs)
end
end
`DemoWeb.SVG.render/1` / `DemoWeb.SVG.render/2` will be ready to use.
`DemoWeb.SVG.render/1` / `DemoWeb.SVG.render/2` / `DemoWeb.SVG.render!/1` / `DemoWeb.SVG.render!/2`
will be ready to use.
> `@library` should be accessed through a function.
> #### `@library` should be accessed through a function. {: .info}
>
> The library could be very large, wrapping it with a function ensures that
> it is only stored as a term in BEAM file once.
Expand Down Expand Up @@ -76,7 +94,7 @@ defmodule CozySVG do
def svg(assigns) do
~H\"\"\"
<%= raw(CompiledSVG.render(@key, @rest)) %>
<%= raw(CompiledSVG.render!(@key, @rest)) %>
\"\"\"
end
end
Expand Down Expand Up @@ -106,8 +124,8 @@ defmodule CozySVG do
will be added to the library with a key that is relative path of the SVG file
minus the `.svg` part.
For example, if you compile the folder "assets/svg" and it finds a file with
the path "assets/svg/heroicons/calendar.svg", then the key for that SVG is
For example, if you compile the folder `"assets/svg"` and it finds a file with
the path `"assets/svg/heroicons/calendar.svg"`, then the key for that SVG is
`"heroicons/calendar"` in the library.
## Examples
Expand Down Expand Up @@ -203,11 +221,11 @@ defmodule CozySVG do
## Examples
iex> CozySVG.render(library(), "heroicons/menu")
"<svg xmlns= ... </svg>"
{:ok, "<svg xmlns= ... </svg>"}
"""
@spec render(library(), key()) :: String.t()
def render(%{} = library, key) do
@spec render(library(), key()) :: {:ok, String.t()} | {:error, CozySVG.RuntimeError.t()}
def render(library, key) when is_map(library) do
render(library, key, [])
end

Expand All @@ -224,20 +242,21 @@ defmodule CozySVG do
## Examples
iex> CozySVG.render(library(), "heroicons/menu", class: "h-5 w-5")
"<svg class=\"h-5 w-5\" xmlns= ... </svg>"
{:ok, "<svg class=\"h-5 w-5\" xmlns= ... </svg>"}
iex> CozySVG.render(library(), "heroicons/menu", %{phx_click: "action"})
"<svg phx-click=\"action\" xmlns= ... </svg>"
{:ok, "<svg phx-click=\"action\" xmlns= ... </svg>"}
"""
@spec render(library(), key(), attrs()) :: String.t()
def render(%{} = library, key, attrs) when is_list(attrs) do
@spec render(library(), key(), attrs()) ::
{:ok, String.t()} | {:error, CozySVG.RuntimeError.t()}
def render(library, key, attrs) when is_map(library) and is_list(attrs) do
case Map.fetch(library, key) do
{:ok, {open_tag, content, close_tag}} ->
open_tag <> render_attrs(attrs) <> content <> close_tag
{:ok, open_tag <> render_attrs(attrs) <> content <> close_tag}

_ ->
raise CozySVG.RuntimeError, "SVG #{inspect(key)} not found in library"
{:error, %CozySVG.RuntimeError{message: "SVG #{inspect(key)} not found in library"}}
end
end

Expand All @@ -246,6 +265,40 @@ defmodule CozySVG do
render(library, key, attrs)
end

@doc """
The bang variant of `render/2`.
## Examples
iex> CozySVG.render!(library(), "heroicons/menu")
"<svg xmlns= ... </svg>"
"""
@spec render!(library(), key()) :: {:ok, String.t()} | {:error, CozySVG.RuntimeError.t()}
def render!(library, key) when is_map(library) do
render!(library, key, [])
end

@doc """
The bang variant of `render/3`.
## Examples
iex> CozySVG.render!(library(), "heroicons/menu", class: "h-5 w-5")
"<svg class=\"h-5 w-5\" xmlns= ... </svg>"
iex> CozySVG.render!(library(), "heroicons/menu", %{phx_click: "action"})
"<svg phx-click=\"action\" xmlns= ... </svg>"
"""
@spec render!(library(), key(), attrs()) :: String.t()
def render!(library, key, attrs) when is_map(library) do
case render(library, key, attrs) do
{:ok, svg} -> svg
{:error, exception} -> raise exception
end
end

defp render_attrs(attrs), do: render_attrs(attrs, "")

defp render_attrs([], acc), do: acc
Expand All @@ -255,13 +308,3 @@ defmodule CozySVG do
render_attrs(tail, "#{acc} #{name}=#{inspect(value)}")
end
end

defmodule CozySVG.CompileError do
@moduledoc false
defexception message: nil, svg: nil
end

defmodule CozySVG.RuntimeError do
@moduledoc false
defexception message: nil
end
8 changes: 8 additions & 0 deletions lib/cozy_svg/quick_wrapper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ defmodule CozySVG.QuickWrapper do
DemoWeb.SVG.render("misc/header", class: "w-6 h-auto mr-2")
DemoWeb.SVG.render("misc/footer", class: "w-6 h-auto mr-2")
DemoWeb.SVG.render!("logo")
DemoWeb.SVG.render!("misc/header", class: "w-6 h-auto mr-2")
DemoWeb.SVG.render!("misc/footer", class: "w-6 h-auto mr-2")
"""

defmacro __using__(opts) do
Expand All @@ -52,6 +56,10 @@ defmodule CozySVG.QuickWrapper do
def render(key, attrs \\ []) do
CozySVG.render(library(), key, attrs)
end

def render!(key, attrs \\ []) do
CozySVG.render!(library(), key, attrs)
end
end
end
end
3 changes: 2 additions & 1 deletion test/cozy_svg/quick_wrapper_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ defmodule CozySVG.QuickWrapperTest do
use CozySVG.QuickWrapper, root: "../svgs"
end

assert "<svg" <> _ = SVG.render("x")
assert {:ok, "<svg" <> _} = SVG.render("x")
assert "<svg" <> _ = SVG.render!("x")
end
end
end
46 changes: 39 additions & 7 deletions test/cozy_svg_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -40,37 +40,69 @@ defmodule CozySVGTest do
end
end

describe "render/2" do
describe "render" do
test "retrieves the svg as a safe string" do
svg = CozySVG.render(library(), "x")
{:ok, svg} = CozySVG.render(library(), "x")
assert String.starts_with?(svg, "<svg xmlns=")
end

test "preserves the tailing </svg>" do
svg = CozySVG.render(library(), "x")
{:ok, svg} = CozySVG.render(library(), "x")
assert String.ends_with?(svg, "</svg>")
end

test "inserts attributes as a list" do
svg = CozySVG.render(library(), "x", class: "test_class", "@click": "action")
{:ok, svg} = CozySVG.render(library(), "x", class: "test_class", "@click": "action")
assert String.starts_with?(svg, "<svg class=\"test_class\" @click=\"action\" xmlns=")
end

test "inserts attributes as a map" do
svg = CozySVG.render(library(), "x", %{class: "test_class", "@click": "action"})
{:ok, svg} = CozySVG.render(library(), "x", %{class: "test_class", "@click": "action"})
assert String.starts_with?(svg, "<svg @click=\"action\" class=\"test_class\" xmlns=")
end

test "converts _ in attr name into -" do
svg = CozySVG.render(library(), "x", test_attr: "some_data")
{:ok, svg} = CozySVG.render(library(), "x", test_attr: "some_data")
assert String.starts_with?(svg, "<svg test-attr=\"some_data\" xmlns=")
end

test "raises an error if the svg is not in the library" do
assert {:error, %CozySVG.RuntimeError{message: "SVG \"missing\" not found in library"}} =
CozySVG.render(library(), "missing")
end
end

describe "render!" do
test "retrieves the svg as a safe string" do
svg = CozySVG.render!(library(), "x")
assert String.starts_with?(svg, "<svg xmlns=")
end

test "preserves the tailing </svg>" do
svg = CozySVG.render!(library(), "x")
assert String.ends_with?(svg, "</svg>")
end

test "inserts attributes as a list" do
svg = CozySVG.render!(library(), "x", class: "test_class", "@click": "action")
assert String.starts_with?(svg, "<svg class=\"test_class\" @click=\"action\" xmlns=")
end

test "inserts attributes as a map" do
svg = CozySVG.render!(library(), "x", %{class: "test_class", "@click": "action"})
assert String.starts_with?(svg, "<svg @click=\"action\" class=\"test_class\" xmlns=")
end

test "converts _ in attr name into -" do
svg = CozySVG.render!(library(), "x", test_attr: "some_data")
assert String.starts_with?(svg, "<svg test-attr=\"some_data\" xmlns=")
end

test "raises an error if the svg is not in the library" do
assert_raise CozySVG.RuntimeError,
"SVG \"missing\" not found in library",
fn ->
CozySVG.render(library(), "missing")
CozySVG.render!(library(), "missing")
end
end
end
Expand Down

0 comments on commit 7377332

Please sign in to comment.