feat: New Component API

This commit is contained in:
Mitchell Hanberg 2021-01-02 13:21:48 -05:00
parent 271567dc8f
commit ced2f6ab66
56 changed files with 715 additions and 405 deletions

View File

@ -1,5 +1,5 @@
locals_without_parens = ~w[ locals_without_parens = ~w[
temple temple c
html head title style script html head title style script
noscript template noscript template
body section nav article aside h1 h2 h3 h4 h5 h6 body section nav article aside h1 h2 h3 h4 h5 h6

View File

@ -30,7 +30,58 @@ end
### Breaking ### Breaking
Components are now defined using modules. You can convert your existing components by configuring your component prefix and wrapping your current component files in the `Temple.Component` behaviour implementation. #### Components
Components are now a thin layer over template partials, compiling to calls to `render/3` and `render_layout/4` under the hood.
To upgrade your components the new syntax, you can copy your component markup and paste it into the `render/1` macro inside the component module and references to `@children` can be updated to `@inner_content`.
Components can are also referenced differently than before when using them. Before, one would simply call `flex` to render a component named `Flex`. Now, one must use the keyword `c` to render a component, passing the keyword the component module along with any assigns.
##### Before
```elixir
# definition
div class: "flex #{@class}" do
@children
end
# usage
flex class: "justify-between" do
for item <- @items do
div do
item.name
end
end
end
```
##### After
```elixir
# definition
defmodule MyAppWeb.Component.Flex do
use Temple.Component
render do
div class: "flex #{@class}" do
@inner_content
end
end
end
# usage
alias MyApp.Component.Flex # probably located in my_app_web.ex
c Flex, class: "justify-between" do
for item <- @items do
div do
item.name
end
end
end
```
### Bugs ### Bugs

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 Mitchell Hanberg Copyright (c) 2021 Mitchell Hanberg
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -6,7 +6,7 @@
> You are looking at the README for the master branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.5.0). > You are looking at the README for the master branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.5.0).
Temple is a DSL for writing HTML using Elixir. Temple is a DSL for writing HTML and EEx using Elixir.
You're probably here because you want to use Temple to write Phoenix templates, which is why Temple includes a [Phoenix template engine](#phoenix-templates). You're probably here because you want to use Temple to write Phoenix templates, which is why Temple includes a [Phoenix template engine](#phoenix-templates).
@ -16,7 +16,10 @@ Add `temple` to your list of dependencies in `mix.exs`:
```elixir ```elixir
def deps do def deps do
[{:temple, "~> 0.6.0-alpha.4"}] [
{:temple, "~> 0.6.0-alpha.4"},
{:phoenix, ">= 1.5.0"} # requires at least Phoenix v1.5.0
]
end end
``` ```
@ -30,9 +33,9 @@ end
## Usage ## Usage
Using Temple is a as simple as using the DSL inside of an `temple/1` block. This returns an EEx string at compile time. Using Temple is as simple as using the DSL inside of an `temple/1` block. This returns an EEx string at compile time.
See the [documentation](https://hexdocs.pm/temple/Temple.Html.html) for more details. See the [documentation](https://hexdocs.pm/temple/Temple.html) for more details.
```elixir ```elixir
use Temple use Temple
@ -66,33 +69,17 @@ end
### Components ### Components
To define a component, you can define a module that that starts with your defined components prefix. The last name in the module should be a came-cases version of the component name. Temple components are mostly a little syntax sugar over Phoenix's `render/3` and `render_layout/4` functions.
This module should implement the `Temple.Component` behaviour. For example, if I were to define a `Flex` component, I would create the following module.
```elixir
# config/config.exs
config :temple, :component_prefix, MyAppWeb.Components
# also set the path so recompiling will work in Phoenix projects
config :temple, :components_path, "./lib/my_app_web/components"
```
You can then use this component in any other temple template.
For example, if I were to define a `flex` component, I would create the following module.
```elixir ```elixir
defmodule MyAppWeb.Components.Flex do defmodule MyAppWeb.Components.Flex do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do div class: "flex #\{@class}" do
quote do @inner_content
div class: "flex #{@temple[:class]}", id: @id do
@children
end
end end
end end
end end
@ -101,7 +88,9 @@ end
And we could use the component like so And we could use the component like so
```elixir ```elixir
flex class: "justify-between items-center", id: "arnold" do alias MyAppWeb.Components.Flex
c Flex, class: "justify-between items-center", id: "arnold" do
div do: "Hi" div do: "Hi"
div do: "I'm" div do: "I'm"
div do: "Arnold" div do: "Arnold"
@ -109,29 +98,9 @@ flex class: "justify-between items-center", id: "arnold" do
end end
``` ```
We've demonstrated several features to components in this example.
We can pass assigns to our component, and access them just like we would in a normal phoenix template. If they don't match up with any assigns we passed to our component, they will be rendered as-is, and will become a normal Phoenix assign.
You can also access a special `@temple` assign. This allows you do optionally pass an assign, and not have the `@my_assign` pass through. If you didn't pass it to your component, it will evaluate to nil.
The block passed to your component can be accessed as `@children`. This allows your components to wrap a body of markup from the call site.
In order for components to trigger a recompile when they are changed, you can call `use Temple.Recompiler` in your `lib/my_app_web.ex` file, in the `view`, `live_view`, and `live_component` functions
```elixir
def view do
quote do
# ...
use Temple.Recompiler
# ...
end
end
```
### Phoenix templates ### Phoenix templates
Add the templating engine to your Phoenix configuration. Add the template engine to your Phoenix configuration.
```elixir ```elixir
# config.exs # config.exs

View File

@ -1,4 +1,3 @@
use Mix.Config use Mix.Config
config :temple, :component_prefix, Temple.Components
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View File

@ -1,3 +1,5 @@
use Mix.Config use Mix.Config
config :temple, components_path: "./test/support/components" # this is to make the warning go away,
# Temple does not use a json_library
config :phoenix, json_library: Temple

View File

@ -35,8 +35,7 @@ config :temple,
label: :_label, label: :_label,
link: :_link, link: :_link,
textarea: :_textarea textarea: :_textarea
], ]
component_prefix: TempleDemoWeb.Component
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

View File

@ -52,7 +52,7 @@ config :temple_demo, TempleDemoWeb.Endpoint,
patterns: [ patterns: [
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$", ~r"priv/gettext/.*(po)$",
~r"lib/temple_demo_web/(live|views)/.*(ex)$", ~r"lib/temple_demo_web/(live|views|components)/.*(ex)$",
~r"lib/temple_demo_web/templates/.*(eex|exs)$" ~r"lib/temple_demo_web/templates/.*(eex|exs)$"
] ]
] ]

View File

@ -21,6 +21,10 @@ config :temple_demo, TempleDemoWeb.Endpoint,
config :temple_demo, :sql_sandbox, true config :temple_demo, :sql_sandbox, true
config :wallaby, config :wallaby,
chromedriver: [
# headless: false,
binary: System.get_env("CHROME_BROWSER")
],
base_url: "http://localhost:4002", base_url: "http://localhost:4002",
otp_app: :temple_demo, otp_app: :temple_demo,
screenshot_on_failure: true screenshot_on_failure: true

View File

@ -36,6 +36,9 @@ defmodule TempleDemoWeb do
# Import convenience functions from controllers # Import convenience functions from controllers
import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1] import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
alias TempleDemoWeb.Component.Outer
alias TempleDemoWeb.Component.Flash
# Include shared imports and aliases for views # Include shared imports and aliases for views
unquote(view_helpers()) unquote(view_helpers())
end end

View File

@ -0,0 +1,9 @@
defmodule TempleDemoWeb.Component.Flash do
use Temple.Component
render do
div class: "alert alert-#{@type}", style: "border: solid 5px pink" do
@inner_content
end
end
end

View File

@ -0,0 +1,9 @@
defmodule TempleDemoWeb.Component.Inner do
use Temple.Component
render do
div id: "inner", outer_id: @outer_id do
@inner_content
end
end
end

View File

@ -0,0 +1,10 @@
defmodule TempleDemoWeb.Component.Outer do
use Temple.Component
alias TempleDemoWeb.Component.Inner
render do
c Inner, outer_id: "from-outer" do
@inner_content
end
end
end

View File

@ -3,6 +3,10 @@ section class: "phx-hero" do
gettext("Welcome to %{name}!", name: "Phoenix") gettext("Welcome to %{name}!", name: "Phoenix")
end end
c Outer, outer_id: "hello" do
"inner content of outer"
end
case @text do case @text do
"staging" -> "staging" ->
p do p do

View File

@ -1,25 +1,25 @@
form_for @changeset, @action, fn f -> form_for @changeset, @action, fn f ->
if @changeset.action do if @changeset.action do
div class: "alert alert-danger" do c Flash, type: :info do
p do: "Oops, something went wrong! Please check the errors below." p do: "Oops, something went wrong! Please check the errors below."
end end
end end
label f, :title label f, :title
text_input f, :title text_input f, :title
error_tag(f, :title) error_tag(f, :title)
label f, :body label f, :body
textarea f, :body textarea f, :body
error_tag(f, :body) error_tag(f, :body)
label f, :published_at label f, :published_at
datetime_select f, :published_at datetime_select f, :published_at
error_tag(f, :published_at) error_tag(f, :published_at)
label f, :author label f, :author
text_input f, :author text_input f, :author
error_tag(f, :author) error_tag(f, :author)
div do div do
submit "Save" submit "Save"

View File

@ -1,27 +1,26 @@
h1 do: "Listing Posts" h1 do: "Listing Posts"
table do table do
thead do c Headers do
tr do th do: "Title"
th do: "Title" th do: "Body"
th do: "Body" th do: "Published at"
th do: "Published at" th do: "Author"
th do: "Author" th do: "BOB"
th() end
end
tbody do tbody do
for post <- @posts do for post <- @posts do
tr do tr do
td do: post.title td do: post.title
td do: post.body td do: post.body
td do: post.published_at td do: post.published_at
td do: post.author td do: post.author
td do td do
link "Show", to: Routes.post_path(@conn, :show, post) link "Show", to: Routes.post_path(@conn, :show, post)
link "Edit", to: Routes.post_path(@conn, :edit, post) link "Edit", to: Routes.post_path(@conn, :edit, post)
link "Delete", to: Routes.post_path(@conn, :delete, post), link "Delete", to: Routes.post_path(@conn, :delete, post),
method: :delete, data: [confirm: "Are you sure?"] method: :delete, data: [confirm: "Are you sure?"]
end
end end
end end
end end

View File

@ -1,3 +1,14 @@
defmodule TempleDemoWeb.PostView do defmodule TempleDemoWeb.PostView do
use TempleDemoWeb, :view use TempleDemoWeb, :view
import Temple.Component, only: [defcomp: 2]
def thing(), do: "foobar"
defcomp Headers do
thead id: PostView.thing() do
tr do
@inner_content
end
end
end
end end

View File

@ -45,7 +45,7 @@ defmodule TempleDemo.MixProject do
{:gettext, "~> 0.11"}, {:gettext, "~> 0.11"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:wallaby, "~> 0.26.0", only: :test}, {:wallaby, "~> 0.28.0", only: :test},
{:tzdata, "~> 1.0.3"}, {:tzdata, "~> 1.0.3"},
{:temple, path: "../../"} {:temple, path: "../../"}
] ]

View File

@ -1,42 +1,42 @@
%{ %{
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, "db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, "ecto": {:hex, :ecto, "3.5.5", "48219a991bb86daba6e38a1e64f8cea540cded58950ff38fbc8163e062281a07", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98dd0e5e1de7f45beca6130d13116eae675db59adfa055fb79612406acf6f6f1"},
"ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"}, "ecto_sql": {:hex, :ecto_sql, "3.5.3", "1964df0305538364b97cc4661a2bd2b6c89d803e66e5655e4e55ff1571943efd", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2f53592432ce17d3978feb8f43e8dc0705e288b0890caf06d449785f018061c"},
"file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
"httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"},
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"phoenix": {:hex, :phoenix, "1.5.2", "7ba05d6cb0024eefd3cb08b176e6f041a9edff094912de2f6a49e3ba67140fb3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3047022367d415a935dceda1176e67d9c7f2d41cd52a0419b53cfca66fc4c64e"}, "phoenix": {:hex, :phoenix, "1.5.2", "7ba05d6cb0024eefd3cb08b176e6f041a9edff094912de2f6a49e3ba67140fb3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3047022367d415a935dceda1176e67d9c7f2d41cd52a0419b53cfca66fc4c64e"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.4", "3080e8a89bab3ec08d4dd9a6858dfa24af9334464aae78c83e58a2db37c6f983", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.12.0 or ~> 0.13.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1c89595ef60f1b76ac07705e73f001823af451491792a4b0d5b2b2a3789b0a00"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.4", "3080e8a89bab3ec08d4dd9a6858dfa24af9334464aae78c83e58a2db37c6f983", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.12.0 or ~> 0.13.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1c89595ef60f1b76ac07705e73f001823af451491792a4b0d5b2b2a3789b0a00"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.0", "dec006b3da4ab164283d5bebe960724eb4d19cd0ed553e05fb99b260233e200f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "bd6f13b666fa9bfeca88b013db20414c693d5a5e6d19b1fc2602c282d626ed8e"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.13.0", "dec006b3da4ab164283d5bebe960724eb4d19cd0ed553e05fb99b260233e200f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "bd6f13b666fa9bfeca88b013db20414c693d5a5e6d19b1fc2602c282d626ed8e"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"}, "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"},
"plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"}, "plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, "postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.0", "4770888ef85599ead39c7f51d6b4b62306e602d96c69b2625d54dea3d9a5204b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69e4e8e65b0ae077c9e14cd5f42c7cc486de0e07ac6e3409e6f0e52699a7872c"}, "telemetry_poller": {:hex, :telemetry_poller, "0.5.0", "4770888ef85599ead39c7f51d6b4b62306e602d96c69b2625d54dea3d9a5204b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69e4e8e65b0ae077c9e14cd5f42c7cc486de0e07ac6e3409e6f0e52699a7872c"},
"tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"}, "tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"},
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
"wallaby": {:hex, :wallaby, "0.26.0", "170b05b2fe572ec38071dbe45a908123959d5245f389f657e9a79eb463dc0431", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "07a437e75c9276900288e4fe5c1814a5486f10a54940aa524ea65ce22b40c182"}, "wallaby": {:hex, :wallaby, "0.28.0", "2ff217c0f245cadb3e5d91748ebcf0102873ceb9ef8a3507717c8bdd73915668", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "e58112650d0b51e81714a626eab7d486d7a77342c9bbc2ba262b6653f9b22558"},
"web_driver_client": {:hex, :web_driver_client, "0.1.0", "19466a989c76b7ec803c796cec0fec4611a64f445fd5120ce50c9e3817e09c2c", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c9c031ca915e8fc75b5e24ac93503244f3cc406dd7f53047087a45aa62d60e9e"}, "web_driver_client": {:hex, :web_driver_client, "0.1.0", "19466a989c76b7ec803c796cec0fec4611a64f445fd5120ce50c9e3817e09c2c", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c9c031ca915e8fc75b5e24ac93503244f3cc406dd7f53047087a45aa62d60e9e"},
} }

View File

@ -2,33 +2,28 @@ defmodule TempleDemoWeb.TempleFeatureTest do
use ExUnit.Case, async: false use ExUnit.Case, async: false
use Wallaby.Feature use Wallaby.Feature
alias TempleDemoWeb.Router.Helpers, as: Routes alias TempleDemoWeb.Router.Helpers, as: Routes
alias TempleDemoWeb.Endpoint, as: E @endpoint TempleDemoWeb.Endpoint
feature "renders the homepage", %{session: session} do feature "renders the homepage", %{session: session} do
session session
|> visit("/") |> visit("/")
|> assert_text("Welcome to Phoenix!") |> assert_text("Welcome to Phoenix!")
|> assert_text("inner content of outer")
end end
feature "case statements work", %{session: session} do feature "case statements work", %{session: session} do
session = session
session |> visit("/?text=staging")
|> visit("/?text=staging") |> assert_text("Welcome to Phoenix!")
|> assert_text("Peace-of-mind from prototype to staging")
session |> assert_text("Welcome to Phoenix!") |> visit("/?text=foobar")
session |> assert_text("Peace-of-mind from prototype to staging") |> assert_text("Welcome to Phoenix!")
|> assert_text("Peace-of-mind from prototype to production")
session =
session
|> visit("/?text=foobar")
session |> assert_text("Welcome to Phoenix!")
session |> assert_text("Peace-of-mind from prototype to production")
end end
feature "can create a new post", %{session: session} do feature "can create a new post", %{session: session} do
session session
|> visit(Routes.post_path(E, :index)) |> visit(Routes.post_path(@endpoint, :index))
|> click(Query.link("New Post")) |> click(Query.link("New Post"))
|> fill_in(Query.text_field("Title"), with: "Temple is awesome!") |> fill_in(Query.text_field("Title"), with: "Temple is awesome!")
|> fill_in(Query.text_field("Body"), with: "In this post I will show you how to use Temple") |> fill_in(Query.text_field("Body"), with: "In this post I will show you how to use Temple")

View File

@ -1,5 +1,8 @@
defmodule Temple.Buffer do defmodule Temple.Buffer do
@moduledoc false @moduledoc false
use Agent
def start_link(state \\ []) do def start_link(state \\ []) do
Agent.start_link(fn -> state end) Agent.start_link(fn -> state end)
end end

View File

@ -2,9 +2,7 @@ defmodule Temple do
alias Temple.Parser alias Temple.Parser
@moduledoc """ @moduledoc """
> Warning: Docs are WIP Temple syntax is available inside the `temple`, and is compiled into EEx at build time.
Temple syntax is available inside the `temple` and `live_temple` macros, and is compiled into EEx at build time.
### Usage ### Usage
@ -47,7 +45,7 @@ defmodule Temple do
text_input f, :name text_input f, :name
end end
# You can explicitly call a tag by prefixing with the Temple module # You can explicitly emit a tag by prefixing with the Temple module
Temple.div do Temple.div do
"Foo" "Foo"
end end
@ -59,11 +57,11 @@ defmodule Temple do
### Reserved keywords ### Reserved keywords
You can pass a keyword list to an element as element attributes, but there are several reserved keywords. You can pass a keyword list to an element as element attributes, but there is currently a reserved keyword.
#### Compact #### Compact
Passing `compact: true` will not rendering new lines from within the element. This is useful if you are trying to use the `:empty` psuedo selector. Passing `compact: true` will not emit a new line between the opening tag, the content, and the closing tag. This is useful if you are trying to use the `:empty` psuedo selector.
```elixir ```elixir
temple do temple do
@ -122,6 +120,27 @@ defmodule Temple do
end end
end end
@doc """
Context for temple markup.
Returns an EEx string.
## Usage
```elixir
import Temple
temple do
div class: @class do
"Hello, world!"
end
end
# <div class="<%= @class %>">
# Hello, world!
# </div>
```
"""
defmacro temple([do: block] = _block) do defmacro temple([do: block] = _block) do
markup = Parser.parse(block) markup = Parser.parse(block)
@ -136,9 +155,26 @@ defmodule Temple do
end end
end end
defmacro live_temple([do: block] = _block) do @doc """
Compiles temple markup into a quoted expression using the given EEx Engine.
## Usage
```elixir
require Temple
Temple.compile Phoenix.HTML.Engine do
div class: @class do
"Hello, world!"
end
end
# Returns the same output that Phoenix templates output into the `render/1` function of their view modules.
```
"""
defmacro compile(engine, [do: block] = _block) do
markup = Parser.parse(block) markup = Parser.parse(block)
EEx.compile_string(markup, engine: Phoenix.LiveView.Engine) EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file)
end end
end end

View File

@ -1,23 +1,161 @@
defmodule Temple.Component do defmodule Temple.Component do
@moduledoc """ @moduledoc """
Behaviour for defining temple components. API for defining components.
"""
@doc """ Component modules are basically normal Phoenix View modules. The contents of the `render` macro are compiled into a `render/2` function. This means that you can define functions in your component module and use them in your component markup.
The render callback must return AST to be inserted into the markup.
Since you need to return AST, it is typical to wrap the contents of the function in a `quote` block like: Since component modules are view modules, the assigns you pass to the component are accessible via the `@` macro and the `assigns` variable.
## Usage
```elixir
defmodule MyAppWeb.Components.Flash do
use Temple.Component
def border_class(:info), do: "border-blue-500"
def border_class(:warning), do: "border-yellow-500"
def border_class(:error), do: "border-red-500"
def border_class(:success), do: "border-green-500"
render do
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
@inner_content
end
end
end
```
Components are used by calling the `c` keyword, followed by the component module and any assigns you need to pass to the template.
`c` is a _**compile time keyword**_, not a function or a macro, so you won't see it in the generated documention.
``` ```
@impl Temple.Component c MyAppWeb.Components.Flash, class: "font-bold", message_type: :info do
def render() do ul do
quote do for info <- infos do
div do li class: "p-4" do
@children info.message
end
end
end
end
```
Since components are just modules, if you alias your module, you can use them more ergonomically.
```
alias MyAppWeb.Components.Flex
c Flex, class: "justify-between items center" do
for item <- items do
div class: "p-4" do
item.name
end end
end end
end end
``` ```
""" """
@callback render() :: Macro.t()
defmacro __using__(_) do
quote do
import Temple.Component, only: [render: 1]
end
end
@doc """
Defines a component template.
## Usage
```elixir
defmodule MyAppWeb.Components.Flash do
use Temple.Component
def border_class(:info), do: "border-blue-500"
def border_class(:warning), do: "border-yellow-500"
def border_class(:error), do: "border-red-500"
def border_class(:success), do: "border-green-500"
render do
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
@inner_content
end
end
end
```
"""
defmacro render(block) do
quote do
def render(assigns), do: render(:self, assigns)
def render(:self, var!(assigns)) do
require Temple
_ = var!(assigns)
Temple.compile(unquote(Temple.Component.engine()), unquote(block))
end
end
end
@doc """
Defines a component module.
This macro makes it easy to define components without creating a separate file. It literally inlines a component module.
Since it defines a module inside of the current module, local function calls from the outer module won't be available. For convenience, the outer module is aliased for you, so you can call remote functions with a shorter module name.
## Usage
```elixir
def MyAppWeb.SomeView do
use MyAppWeb.SomeView, :view
import Temple.Component, only: [defcomp: 2]
# define a function in outer module
def foobar(), do: "foobar"
# define a component
defcomp Button do
button id: SomeView.foobar(), # `MyAppWeb.SomeView` is aliased for you.
class: "text-sm px-3 py-2 rounded #\{assigns[:extra_classes]}",
type: "submit" do
@inner_content
end
end
end
# use the component in a SomeView template. Or else, you must alias `MyAppWeb.SomeView.Button`
c Button, extra_classes: "border-2 border-red-500" do
"Submit!"
end
```
"""
defmacro defcomp(module, [do: block] = _block) do
quote location: :keep do
defmodule unquote(module) do
use Temple.Component
alias unquote(__CALLER__.module)
render do
unquote(block)
end
end
end
end
@doc false
def engine() do
cond do
Code.ensure_loaded?(Phoenix.LiveView.Engine) ->
Phoenix.LiveView.Engine
Code.ensure_loaded?(Phoenix.HTML.Engine) ->
Phoenix.HTML.Engine
true ->
nil
end
end
end end

View File

@ -1,19 +1,44 @@
defmodule Temple.Engine do defmodule Temple.Engine do
@behaviour Phoenix.Template.Engine @behaviour Phoenix.Template.Engine
@moduledoc false @moduledoc """
The Temple HTML engine makes it possible to use Temple with Phoenix controllers.
To get started, you will configure Phoenix to use this module for `.exs` files.
```elixir
# config.exs
config :phoenix, :template_engines,
# this will work for files named like `index.html.exs`
exs: Temple.Engine
# config/dev.exs
config :your_app, YourAppWeb.Endpoint,
live_reload: [
patterns: [
~r"lib/myapp_web/(live|views)/.*(ex|exs|lexs)$",
~r"lib/myapp_web/templates/.*(eex|exs|lexs)$"
]
]
# my_app/
# lib/
# my_app/
# my_app_web/
# templates/
# posts/
# show.html.exs
```
Now you can get started by writing `exs` files in the templates directory and they will be compiled as you would expect.
"""
def compile(path, _name) do def compile(path, _name) do
require Temple require Temple
template = path |> File.read!() |> Code.string_to_quoted!(file: path) template = path |> File.read!() |> Code.string_to_quoted!(file: path)
ast = Temple.temple(template)
quote do
unquote(template)
end
Temple.temple(ast)
|> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1) |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
end end
end end

View File

@ -1,14 +1,46 @@
defmodule Temple.LiveViewEngine do defmodule Temple.LiveViewEngine do
@behaviour Phoenix.Template.Engine @behaviour Phoenix.Template.Engine
@moduledoc false @moduledoc """
The Temple LiveView engine makes it possible to use Temple with Phoenix LiveView.
To get started, you will configure Phoenix to use this module for `.lexs` files.
```elixir
# config.exs
config :phoenix, :template_engines,
# this will work for files named like `index.html.lexs`
# you can enable Elixir syntax highlighting in your editor for this extension
lexs: Temple.LiveViewEngine
# config/dev.exs
config :your_app, YourAppWeb.Endpoint,
live_reload: [
patterns: [
~r"lib/myapp_web/(live|views)/.*(ex|exs|lexs)$",
~r"lib/myapp_web/templates/.*(eex|exs|lexs)$"
]
]
# my_app/
# lib/
# my_app/
# my_app_web/
# live/
# posts_live/
# show.ex
# show.html.lexs
```
Now you can get started by writing `lexs` files co-located with your live views and they will be compiled as you would expect.
"""
def compile(path, _name) do def compile(path, _name) do
require Temple require Temple
ast = path |> File.read!() |> Code.string_to_quoted!(file: path) template = path |> File.read!() |> Code.string_to_quoted!(file: path)
Temple.temple(ast) Temple.temple(template)
|> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1) |> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1)
end end
end end

View File

@ -1,4 +1,6 @@
defmodule Temple.Parser do defmodule Temple.Parser do
@moduledoc false
@doc """ @doc """
Should return true if the parser should apply for the given AST. Should return true if the parser should apply for the given AST.
""" """

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.AnonymousFunctions do defmodule Temple.Parser.AnonymousFunctions do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,21 +1,18 @@
defmodule Temple.Parser.Components do defmodule Temple.Parser.Components do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
@component_prefix Application.fetch_env!(:temple, :component_prefix)
alias Temple.Parser alias Temple.Buffer
def applicable?({name, meta, _}) when is_atom(name) do def applicable?({:c, _, _}) do
!meta[:temple_component_applied] && true
match?({:module, _}, name |> component_module() |> Code.ensure_compiled())
end end
def applicable?(_), do: false def applicable?(_), do: false
defp component_module(name) do def run({:c, _meta, [component_module | args]}, buffer) do
Module.concat([@component_prefix, Macro.camelize(to_string(name))]) import Temple.Parser.Private
end
def run({name, _meta, args}, _buffer) do
{assigns, children} = {assigns, children} =
case args do case args do
[assigns, [do: block]] -> [assigns, [do: block]] ->
@ -31,45 +28,26 @@ defmodule Temple.Parser.Components do
{[], nil} {[], nil}
end end
component_module = Module.concat([@component_prefix, Macro.camelize(to_string(name))]) if children do
Buffer.put(
buffer,
"<%= Phoenix.View.render_layout #{Macro.to_string(component_module)}, :self, #{
Macro.to_string(assigns)
} do %>"
)
ast = apply(component_module, :render, []) traverse(buffer, children)
{name, meta, args} = Buffer.put(buffer, "<% end %>")
ast else
|> Macro.prewalk(fn Buffer.put(
{:@, _, [{:children, _, _}]} -> buffer,
children "<%= Phoenix.View.render #{Macro.to_string(component_module)}, :self, #{
Macro.to_string(assigns)
} %>"
)
end
{:@, _, [{:temple, _, _}]} -> :ok
assigns
{:@, _, [{name, _, _}]} = node ->
if name in Keyword.keys(assigns) do
Keyword.get(assigns, name, nil)
else
node
end
node ->
node
end)
ast =
if Enum.any?(
[
Parser.nonvoid_elements(),
Parser.nonvoid_elements_aliases(),
Parser.void_elements(),
Parser.void_elements_aliases()
],
fn elements -> name in elements end
) do
{name, Keyword.put(meta, :temple_component_applied, true), args}
else
{name, meta, args}
end
{:component_applied, ast}
end end
end end

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.Default do defmodule Temple.Parser.Default do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.DoExpressions do defmodule Temple.Parser.DoExpressions do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.Empty do defmodule Temple.Parser.Empty do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.Match do defmodule Temple.Parser.Match do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.NonvoidElementsAliases do defmodule Temple.Parser.NonvoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.RightArrow do defmodule Temple.Parser.RightArrow do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.TempleNamespaceNonvoid do defmodule Temple.Parser.TempleNamespaceNonvoid do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.TempleNamespaceVoid do defmodule Temple.Parser.TempleNamespaceVoid do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.Text do defmodule Temple.Parser.Text do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Buffer alias Temple.Buffer

View File

@ -1,4 +1,5 @@
defmodule Temple.Parser.VoidElementsAliases do defmodule Temple.Parser.VoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser @behaviour Temple.Parser
alias Temple.Parser alias Temple.Parser

View File

@ -1,15 +0,0 @@
defmodule Temple.Recompiler do
defmacro __using__(_) do
quote do
component_path = Application.get_env(:temple, :components_path)
for f <- File.ls!(component_path),
do:
Module.put_attribute(
__MODULE__,
:external_resource,
Path.join(component_path, f)
)
end
end
end

View File

@ -1,8 +0,0 @@
defmodule Temple.Utils do
@moduledoc false
def puts(binary) do
IO.puts(binary)
binary
end
end

View File

@ -15,10 +15,7 @@ defmodule Temple.MixProject do
source_url: "https://github.com/mhanberg/temple", source_url: "https://github.com/mhanberg/temple",
docs: [ docs: [
main: "Temple", main: "Temple",
extras: ["README.md"], extras: ["README.md"]
deps: [
phoenix_html: "https://hexdocs.pm/phoenix_html/"
]
] ]
] ]
end end
@ -48,7 +45,8 @@ defmodule Temple.MixProject do
[ [
{:ex_doc, "~> 0.22.0", only: :dev, runtime: false}, {:ex_doc, "~> 0.22.0", only: :dev, runtime: false},
{:phoenix, ">= 0.0.0", optional: true}, {:phoenix, ">= 0.0.0", optional: true},
{:phoenix_html, ">= 0.0.0", only: :test} {:phoenix_html, ">= 0.0.0", only: :test},
{:phoenix_live_view, ">= 0.0.0", only: :test}
] ]
end end
end end

View File

@ -8,6 +8,7 @@
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"}, "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"},
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.3", "2186c55cc7c54ca45b97c6f28cfd267d1c61b5f205f3c83533704cd991bdfdec", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.2", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "c6309a7da2e779cb9cdf2fb603d75f38f49ef324bedc7a81825998bd1744ff8a"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"}, "plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},

124
test/component_test.exs Normal file
View File

@ -0,0 +1,124 @@
defmodule Temple.ComponentTest do
use ExUnit.Case, async: true
use Temple
use Temple.Support.Utils
# `Phoenix.View.render_layout/4` is a phoenix function used for rendering partials that contain inner_content.
# These are usually layouts, but components that contain children are basically the same thing
test "renders components using Phoenix.View.render_layout" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
c Temple.Components.Component do
aside class: "foobar" do
"I'm a component!"
end
end
end
assert result ==
~s{<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component, :self, [] do %><aside class="foobar">I'm a component!</aside><% end %>}
assert evaluate_template(result) ==
~s{<div class="font-bold">Hello, world</div><div><aside class="foobar">I'm a component!</aside></div>}
end
test "function components can accept local assigns" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
c Temple.Components.Component2, class: "bg-red" do
"I'm a component!"
end
end
assert result ==
~s{<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [class: "bg-red"] do %>I'm a component!<% end %>}
assert evaluate_template(result) ==
~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>}
end
test "function components can accept local assigns that are variables" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
class = "bg-red"
c Temple.Components.Component2, class: class do
"I'm a component!"
end
end
assert result ==
~s{<div class="font-bold">Hello, world</div><% class = "bg-red" %><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [class: class] do %>I'm a component!<% end %>}
end
test "function components can use other components" do
result =
temple do
c Temple.Components.Outer do
"outer!"
end
c Temple.Components.Inner, outer_id: "set by root inner" do
"inner!"
end
end
assert result ==
~s{<%= Phoenix.View.render_layout Temple.Components.Outer, :self, [] do %>outer!\n<% end %><%= Phoenix.View.render_layout Temple.Components.Inner, :self, [outer_id: "set by root inner"] do %>inner!\n<% end %>}
assert evaluate_template(result) == ~s"""
<div id="inner" outer-id="from-outer">outer!</div>
<div id="inner" outer-id="set by root inner">inner!</div>
"""
end
test "normal functions with blocks should be treated like if expressions" do
result =
temple do
leenk to: "/route", class: "foo" do
div class: "hi"
end
end
assert result ==
~s{<%= leenk(to: "/route", class: "foo") do %><div class="hi"></div><% end %>}
end
test "components can use functions from their modules" do
result =
temple do
c Temple.Components.WithFuncs, foo: :bar do
"doo doo"
end
end
assert result ==
~s{<%= Phoenix.View.render_layout Temple.Components.WithFuncs, :self, [foo: :bar] do %>doo doo<% end %>}
assert evaluate_template(result) == ~s{<div class="barbarbar">doo doo</div>}
end
test "components can be void elements" do
result =
temple do
c Temple.Components.VoidComponent, foo: :bar
end
assert result ==
~s{<%= Phoenix.View.render Temple.Components.VoidComponent, :self, [foo: :bar] %>}
assert evaluate_template(result) == ~s{<div class="void!!">bar</div>}
end
end

View File

@ -0,0 +1,51 @@
defmodule Temple.Parser.ComponentsTest do
use ExUnit.Case, async: false
alias Temple.Parser.Components
use Temple.Support.Utils
describe "applicable?/1" do
test "runs when using the `c` ast with a block" do
ast =
quote do
c SomeModule, foo: :bar do
div do
"hello"
end
end
end
assert Components.applicable?(ast)
end
test "runs when using the `c` ast without a block" do
ast =
quote do
c(SomeModule, foo: :bar)
end
assert Components.applicable?(ast)
end
end
describe "run/2" do
test "is correct" do
buf = start_supervised!(Temple.Buffer)
ast =
quote do
c SomeModule, foo: :bar do
aside class: "foobar" do
"I'm a component!"
end
end
end
Temple.Parser.Components.run(ast, buf)
result = Temple.Buffer.get(buf)
assert result ==
~s{<%= Phoenix.View.render_layout SomeModule, :self, [foo: :bar] do %><aside class="foobar">I'm a component!</aside><% end %>}
end
end
end

View File

@ -1,16 +0,0 @@
defmodule PartialTest do
use ExUnit.Case, async: true
use Temple
use Temple.Support.Utils
test "can correctly redefine elements" do
result =
temple do
section do
"Howdy!"
end
end
assert result == ~s{<section class="foo!">Howdy!</section>}
end
end

View File

@ -1,12 +1,9 @@
defmodule Temple.Components.Component do defmodule Temple.Components.Component do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do div do
quote do @inner_content
div class: @assign do
@children
end
end end
end end
end end

View File

@ -1,12 +1,9 @@
defmodule Temple.Components.Component2 do defmodule Temple.Components.Component2 do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do div class: @class do
quote do @inner_content
div class: @class do
@children
end
end end
end end
end end

View File

@ -1,12 +1,9 @@
defmodule Temple.Components.HasTemple do defmodule Temple.Components.HasTemple do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do div class: @temple[:class] do
quote do @inner_content
div class: @temple[:class] do
@children
end
end end
end end
end end

View File

@ -1,12 +0,0 @@
defmodule Temple.Components.HasTempleFunctionAssign do
@behaviour Temple.Component
@impl Temple.Component
def render do
quote do
div Keyword.put(@temple, :class, "flex #{@temple[:class]}") do
@children
end
end
end
end

View File

@ -1,12 +1,9 @@
defmodule Temple.Components.Inner do defmodule Temple.Components.Inner do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do div id: "inner", outer_id: @outer_id do
quote do @inner_content
div id: "inner", outer_id: @outer_id do
@children
end
end end
end end
end end

View File

@ -1,12 +1,9 @@
defmodule Temple.Components.Outer do defmodule Temple.Components.Outer do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do c Temple.Components.Inner, outer_id: "from-outer" do
quote do @inner_content
inner outer_id: "from-outer" do
@children
end
end end
end end
end end

View File

@ -1,12 +1,9 @@
defmodule Temple.Components.Section do defmodule Temple.Components.Section do
@behaviour Temple.Component use Temple.Component
@impl Temple.Component render do
def render do section class: "foo!" do
quote do @inner_content
section class: "foo!" do
@children
end
end end
end end
end end

View File

@ -0,0 +1,9 @@
defmodule Temple.Components.VoidComponent do
use Temple.Component
render do
div class: "void!!" do
"bar"
end
end
end

View File

@ -0,0 +1,17 @@
defmodule Temple.Components.WithFuncs do
use Temple.Component
def get_class(:bar) do
"barbarbar"
end
def get_class(_) do
"foofoofoo"
end
render do
div class: get_class(@foo) do
@inner_content
end
end
end

View File

@ -19,4 +19,12 @@ defmodule Temple.Support.Utils do
Kernel.=~(a, b) Kernel.=~(a, b)
end end
def evaluate_template(template) do
template
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|> Code.eval_quoted([])
|> elem(0)
|> Phoenix.HTML.safe_to_string()
end
end end

View File

@ -269,7 +269,7 @@ defmodule TempleTest do
~s{<div class="font-bold">Hello, world</div><div class="font-bold">Hello, world</div><div>Hello, world</div>} ~s{<div class="font-bold">Hello, world</div><div class="font-bold">Hello, world</div><div>Hello, world</div>}
end end
test "passing 'compact: true' will not insert new lines" do test "`do` passed as keyword will compile compacted markup" do
import Temple.Support.Utils, only: [] import Temple.Support.Utils, only: []
import Kernel import Kernel
@ -287,95 +287,6 @@ defmodule TempleTest do
assert result == ~s{<p>Bob</p>\n<p><%= foo %></p>} assert result == ~s{<p>Bob</p>\n<p><%= foo %></p>}
end end
test "inlines function components" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
component do
"I'm a component!"
end
end
assert result ==
~s{<div class="font-bold">Hello, world</div><div class="<%= @assign %>">I'm a component!</div>}
end
test "function components can accept local assigns" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
component2 class: "bg-red" do
"I'm a component!"
end
end
assert result ==
~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>}
end
test "function components can accept local assigns that are variables" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
class = "bg-red"
component2 class: class do
"I'm a component!"
end
end
assert result ==
~s{<div class="font-bold">Hello, world</div><% class = "bg-red" %><div class="<%= class %>">I'm a component!</div>}
end
test "function components can use other components" do
result =
temple do
outer do
"outer!"
end
inner do
"inner!"
end
end
assert result ==
~s{<div id="inner" outer-id="from-outer">outer!</div><div id="inner" outer-id="<%= @outer_id %>">inner!</div>}
end
test "@temple should be available in any component" do
result =
temple do
has_temple class: "boom" do
"yay!"
end
end
assert result == ~s{<div class="<%= [class: "boom"][:class] %>">yay!</div>}
end
test "normal functions with blocks should be treated like if expressions" do
result =
temple do
leenk to: "/route", class: "foo" do
div class: "hi"
end
end
assert result ==
~s{<%= leenk(to: "/route", class: "foo") do %><div class="hi"></div><% end %>}
end
test "for with 2 generators" do test "for with 2 generators" do
result = result =
temple do temple do
@ -424,33 +335,4 @@ defmodule TempleTest do
assert result == assert result ==
~s{<fieldset<%= Temple.Parser.Private.runtime_attrs(Foo.foo_bar()) %>><input type="text"></fieldset>} ~s{<fieldset<%= Temple.Parser.Private.runtime_attrs(Foo.foo_bar()) %>><input type="text"></fieldset>}
end end
test "can pass a function as assigns that has @temple" do
result =
temple do
has_temple_function_assign class: "justify-end", style: "color: pink" do
input type: "text"
end
end
expected =
~S"""
<div<%= Temple.Parser.Private.runtime_attrs(Keyword.put([class: "justify-end", style: "color: pink"], :class, "flex #{[class: "justify-end", style: "color: pink"][:class]}")) %>>
<input type="text">
</div>
"""
|> String.trim()
assert result == expected
assert evaluate_template(result) == evaluate_template(expected)
end
defp evaluate_template(template) do
template
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|> Code.eval_quoted()
|> elem(0)
|> Phoenix.HTML.safe_to_string()
end
end end